Repository navigation
fix(cd): repair the deploy pipeline, which had never run green - #32
Closed
Badri-Rajendran wants to merge 1 commit into
Closed
Badri-Rajendran wants to merge 1 commit into
Badri-Rajendran wants to merge 1 commit into
Conversation
CD had zero successful runs since it was added. Five independent defects: 1. OIDC login failed on every run with AADSTS700213. The Entra federated identity credential carries the legacy subject `repo:Badri-Rajendran/Trueup:environment:production`, but GitHub now presents the immutable owner-ID/repo-ID form (`repo:Badri-Rajendran@79736071/Trueup@1357531178:...`), confirmed via `gh api .../actions/oidc/customization/sub`. A matching credential was added in Entra; the legacy one is kept as a fallback. 2. The `production` environment's required_reviewers rule left runs queued for up to 16h. Removed (by request). `environment: production` stays on every job deliberately -- it is what scopes the OIDC subject claim, so dropping it would break Azure login. 3. The worker readiness check could never pass. It compared `runningState` to "Running", but an app pinned to min=max=1 replicas settles on "RunningAtMaxScale" -- verified live against backend, frontend and worker. It also indexed `revision list [0]`, which has no ordering guarantee and can return a deprovisioning revision. Now resolves the revision by `latestRevisionName` and accepts both running spellings. 4. mcp-server would have crash-looped instantly. `python app/mcp/entrypoint.py` puts the script's own directory on sys.path rather than /app, so the first import raises `ModuleNotFoundError: No module named 'app'` -- reproduced directly against the built image. Now `python -m app.mcp.entrypoint`, which is what docker-compose.yml already used. 5. The post-deploy health check could report a false green. It curled the ingress straight after `containerapp update`, which returns once the new revision is created, not once it serves -- so the previous revision could answer. It now waits for the new revision to be provisioned, active and running before curling. Also restructures the worker/mcp-server steps from "exists -> update image only" to "bootstrap once, reconcile image + secrets + env vars every run". The old shortcut meant a half-bootstrapped app stayed broken forever, which is what had happened to worker: hand-created without secrets or env vars, it has been crash-looping on missing database_url/redis_url with only FLASK_APP set, and no CD run would ever have repaired it. Role assignment stays bootstrap-only and now fails with an actionable operator message, since CD's service principal holds Contributor, which cannot create role assignments -- granting it that right would let anyone who can merge to main hand out Key Vault roles. Adds a cd-production concurrency group so two merges cannot race and leave the older commit deployed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
CD has never completed a successful run since it was added. Every run either failed, was skipped because CI was red, or sat queued on the approval gate for up to 16 hours. Every deploy so far has been done by hand.
34030331323client-id/tenant-idempty — ran 23 min before the repo secrets existed. Self-resolved.34067982447,34099158004AADSTS700213at Azure loginwaiting6–16 h onrequired_reviewers, never approvedskipped— CI was red onmain(fixed by #31)Five defects, each independently fatal
1. OIDC login (blocked every run). The Entra federated credential carries the legacy subject
repo:Badri-Rajendran/Trueup:environment:production. GitHub actually presents the immutable owner-ID/repo-ID form — confirmed at source:gh api repos/.../actions/oidc/customization/subreturnssub_claim_prefix: "repo:Badri-Rajendran@79736071/Trueup@1357531178".2. Approval gate.
required_reviewerson theproductionenvironment. Removed by request.environment: productiondeliberately stays on every job — it is what scopes the OIDC subject claim, so removing it would change the subject to:ref:refs/heads/mainand re-break login.3. Worker readiness check could never pass. It compared
runningStatetoRunning, but an app pinned tomin=max=1settles onRunningAtMaxScale. Verified live —backend,frontendandworkerall reportRunningAtMaxScale, neverRunning. It also indexedrevision list [0], which has no ordering guarantee and can return a deprovisioning revision.4. mcp-server would have crash-looped instantly.
python app/mcp/entrypoint.pyputs the script's own directory onsys.pathinstead of/app. Reproduced against the built image:docker-compose.yml:107already used the-mform;cd.ymldidn't.5. Health check could report a false green. It curled the ingress immediately after
containerapp update, which returns once the new revision is created, not once it serves — so the previous revision could answer while the new image was still starting or already crash-looping.Also fixed: a live production outage this surfaced
worker— the ADR 13job_outboxdrain — has never run. It was hand-created with onlyFLASK_APP=wsgi, no secrets and no other env vars, and has been crash-looping on missingdatabase_url/redis_url:Critically, no CD run would ever have repaired it — the old
exists -> update image onlyshortcut skips config entirely. So the worker/mcp-server steps are restructured to bootstrap once, reconcile image + secrets + env vars every run. Bothsecret setand--set-env-varsare idempotent and need only Contributor, so reconciling every run costs nothing and makes this workflow the actual source of truth for their config.Role assignment stays bootstrap-only and now fails with an actionable operator message instead of a raw
AuthorizationFailed. CD's service principal holds Contributor, which cannot create role assignments — and it should stay that way: granting the pipelineroleAssignments/writewould let anyone able to merge tomainhand out Key Vault roles, exactly the escalation CLAUDE.md's least-privilege rule exists to prevent.Plus a
cd-productionconcurrency group (cancel-in-progress: false) so two merges landing together cannot race and leave the older commit deployed, and a run is never cancelled mid-migration with the database ahead of the deployed image.Validation
cd.ymlparses; all 11run:scripts passbash -nwith GitHub expressions stubbed--query "[a,b,c]" -o tsvreturns values on separate lines, not tab-separated, so the shell-built status string could never match acasepattern. Now joined in JMESPath (join('/', [...])) and re-verified:backendandfrontend→provisioned/runningatmaxscale/true(pass);worker→provisioned/activating/true(correctly keeps waiting)pythoninvocation forms tested against the real image (above)Security / DB / API impact
keyvaultref/secretref.migratejob is untouched.workeris still down. Its managed identity is missingKey Vault Secrets UserandKey Vault Crypto User, so itskeyvaultrefsecrets cannot resolve. That grant needs an operator withroleAssignments/write— commands in the PR discussion.cd.ymlrecords that this is deliberate.