Skip to content

feat: deployable Helm chart + analytics-agent bootstrap CLI#12

Merged
shirshanka merged 22 commits into
mainfrom
craig/make-deployable
May 2, 2026
Merged

feat: deployable Helm chart + analytics-agent bootstrap CLI#12
shirshanka merged 22 commits into
mainfrom
craig/make-deployable

Conversation

@craig-rueda

Copy link
Copy Markdown
Contributor

Summary

  • Adds an initial Helm chart (helm/analytics-agent/) with deployment, service, ingress, secret-env, serviceaccount, and a new bootstrap-job.yaml that runs as a pre-install/pre-upgrade hook.
  • Introduces an analytics-agent CLI (Click-based) with subcommands migrate, seed-integrations, seed-context-platforms, seed-defaults, and an umbrella bootstrap. The umbrella is what the Helm hook invokes.
  • Refactors the FastAPI lifespan to read-only init: encryption-key check, register_engines_from_db, propagate_datahub_env, LLM-config load, MCP discovery bg task. All DB-mutating bootstrap work (Alembic + yaml seeds + first-run defaults) now lives in analytics_agent.bootstrap and runs out-of-band via the CLI.

Why

Running migrations and yaml→DB seeding inside the lifespan was wrong for Kubernetes: every replica racing to migrate on scale-out, slower pod boot, and shared-state mutations that should happen once per release. The CLI + Helm hook model fixes both.

What changed

Area Files
CLI + bootstrap module backend/src/analytics_agent/cli.py, backend/src/analytics_agent/bootstrap.py
Slimmed lifespan backend/src/analytics_agent/main.py (540 → 355 lines)
Click dep + entrypoint pyproject.toml, uv.lock
Helm chart helm/analytics-agent/** (chart + bootstrap-job hook + values default bootstrap.enabled=true)
Docs README.md, AGENTS.md, CLAUDE.md, helm/analytics-agent/README.md
Tests tests/unit/test_bootstrap.py (8 tests), tests/unit/test_cli.py (3 tests)

Behavior change

Dev / pip / docker users must run analytics-agent bootstrap once before the first uvicorn start (and after any release that adds migrations). README documents this. Helm users get it automatically via the pre-install/pre-upgrade hook.

If a pod boots without the bootstrap having run, the lifespan crashes loudly with a SQLAlchemy "no such table" error — verified end-to-end during implementation.

Verification

  • uv run pytest tests/unit/ — 90 passed (was 79, +11 new bootstrap/cli tests)
  • uv run ruff check backend/ tests/ — clean (pre-existing errors in scripts/ unaffected)
  • helm lint helm/analytics-agent — 0 chart(s) failed
  • helm template ... --set bootstrap.enabled=false — Job omitted as expected
  • Smoke test: bootstrap → uvicorn → /health returns ok
  • Negative smoke test: skip bootstrap → uvicorn crashloops with no such table: context_platforms

shirshanka added a commit that referenced this pull request May 2, 2026
- CLAUDE.md: /path/to/talkster → /path/to/analytics-agent
- skills/*/SKILL.md: author: talkster → author: analytics-agent
- frontend/package.json: datahub-talk-to-data-ui → datahub-analytics-agent-ui
- tests/unit/test_config.py: test_talkster_* → test_yaml_*

Runtime renames (docker-compose.yml, quickstart.sh) are intentionally
deferred — they require a one-time migration path which will be added
on top of the bootstrap CLI (PR #12).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
shirshanka added a commit that referenced this pull request May 2, 2026
- CLAUDE.md: /path/to/talkster → /path/to/analytics-agent
- skills/*/SKILL.md: author: talkster → author: analytics-agent
- frontend/package.json: datahub-talk-to-data-ui → datahub-analytics-agent-ui
- tests/unit/test_config.py: test_talkster_* → test_yaml_*

Runtime renames (docker-compose.yml, quickstart.sh) are intentionally
deferred — they require a one-time migration path which will be added
on top of the bootstrap CLI (PR #12).

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@shirshanka shirshanka force-pushed the craig/make-deployable branch from 531b50b to 1472356 Compare May 2, 2026 22:17
shirshanka and others added 8 commits May 2, 2026 15:18
Dockerfile binds uvicorn to 8100; values.yaml had the legacy default
of 8000 which would have caused liveness/readiness probe failures.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- docker/Dockerfile: add .venv/bin to PATH so analytics-agent CLI is
  reachable by name in the container (Helm job command was exit 127)
- pyproject.toml: add psycopg2-binary so Alembic's sync engine can
  connect to PostgreSQL (asyncpg is async-only; Alembic needs psycopg2)
- helm/templates: make ServiceAccount and Secret pre-install/pre-upgrade
  hooks at weight -1 so they exist before the bootstrap Job (weight 0)
  fires; without this the Job pod fails with SA/Secret not found
- cli.py: consolidate bootstrap seeds into one asyncio.run() call;
  multiple sequential asyncio.run() calls in the same process cause
  "Future attached to a different loop" with asyncpg because the cached
  SQLAlchemy engine is bound to the first (now-closed) event loop

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Three checks that would have caught the bugs found during local deploy:
- helm lint: template syntax validation
- port check: asserts Dockerfile EXPOSE == values.yaml targetPort
- hook ordering: asserts SA and Secret weight < bootstrap Job weight

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Triggers only on changes to helm/** or docker/Dockerfile — not on every
Python or frontend change. Removes the helm-chart job from ci.yml.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Spins up a kind cluster, builds the image, starts Postgres, runs
helm install, and verifies the /health endpoint responds 200. Runs
after the static lint job passes (needs: helm-chart). Docker layer
caching keeps repeat runs fast.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Direct curl+chmod to /usr/local/bin fails on GitHub-hosted runners
(permission denied). helm/kind-action handles installation correctly.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
GHA cache export is unsupported with the docker buildx driver, which
is required for kind load docker-image to work. Plain docker build
is simpler and sufficient for a local kind-based integration test.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The action defaults to cluster name "chart-testing"; kind load
docker-image expects "kind". Explicitly set the name to match.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

@shirshanka shirshanka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@shirshanka shirshanka merged commit 7de4ddc into main May 2, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants