Skip to content

feat(ci): update-app-env workflow + Secrets Manager IAM#268

Open
arielr-lt wants to merge 1 commit into
mainfrom
feat/update-app-env-workflow
Open

feat(ci): update-app-env workflow + Secrets Manager IAM#268
arielr-lt wants to merge 1 commit into
mainfrom
feat/update-app-env-workflow

Conversation

@arielr-lt

@arielr-lt arielr-lt commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a workflow_dispatch workflow that updates the AWS Secrets Manager secret feeding the ctdl-xtra-app-env k8s Secret (via External Secrets Operator), then rolls ctdl-xtra-api and ctdl-xtra-worker so they pick up the new values. Sandbox and prod runs are gated through GitHub Environments (same pattern as promote-*).

Why

Today ctdl-xtra/{env}/app in Secrets Manager can only be changed from the AWS console. No run log, no Slack notice, no automatic pod refresh — and the 5-minute ESO refresh window means people sometimes "redeploy" just to pick up env changes. This closes that gap with an audited, scriptable path.

What the workflow does

Inputs:

  • environment — test / sandbox / prod.
  • changes — JSON map, e.g. {"LOG_LEVEL":"debug","OLD_FLAG":null}. null removes the key.
  • restart — bool, default true.
  • dry_run — bool, default false.

Pipeline:

  1. Validate changes is a {string: string|null} object and keys match [A-Za-z_][A-Za-z0-9_]*.
  2. ::add-mask:: every value (incoming and existing) before anything else runs.
  3. Fetch the current secret, merge with jq (null deletes), write the next JSON to a tmp file.
  4. Emit a key-level diff (added / changed / removed — never values) to the run summary.
  5. If dry_run or no-op → exit cleanly.
  6. aws secretsmanager put-secret-value → new VersionId (previous versions remain available for one-step revert).
  7. Annotate externalsecret/ctdl-xtra-app with force-sync=<ts> and poll the target ctdl-xtra-app-env Secret's resourceVersion (up to 60s) to confirm ESO actually picked up the new version — avoids the "pods bounced but values still old" failure mode.
  8. If restart=true: kubectl rollout restart deploy/ctdl-xtra-api deploy/ctdl-xtra-worker then rollout status (5m timeout each).
  9. Slack notify (always — success or failure) with key diff, new VersionId, and outcome (applied / applied + restarted / dry-run / no-op).

Only ctdl-xtra-api and ctdl-xtra-worker are restarted; db-migrate-job and other consumers are out of scope per the design ask.

IAM (prerequisite — must terraform apply before the workflow can succeed)

infra/terraform/github-ci-oidc/main.tf adds aws_iam_policy.github_actions_secretsmanager with Describe/Get/Put/ListSecretVersionIds, scoped strictly to ctdl-xtra/{test,sandbox,prod}/app-*. Attached to the existing ctdl-xtra-github-actions-ci role. No other resources touched.

Adds a workflow_dispatch job that mutates the AWS Secrets Manager
secret feeding the ctdl-xtra-app-env k8s Secret (via External Secrets
Operator), then rolls the api and worker Deployments so they pick up
the new values. Sandbox/prod are gated through GitHub Environments,
matching promote-* workflows.

Why
- ESO already syncs ctdl-xtra/{env}/app from Secrets Manager into the
  ctdl-xtra-app-env k8s Secret (5m refresh). The api/worker Deployments
  consume it via envFrom. Today there is no audited path to add/change/
  remove keys other than the AWS console; result is no run log, no
  Slack notice, and no automatic pod refresh.
- The workflow closes that gap with a controlled, scriptable surface
  that never echoes values to logs and produces a key-level diff in
  the run summary and Slack.

How
- Inputs: environment, changes (JSON {"KEY":"value" | null}), restart,
  dry_run. null deletes the key; non-string values are rejected; keys
  must match [A-Za-z_][A-Za-z0-9_]*.
- All values (both incoming and existing) are passed through
  ::add-mask:: before any further step runs.
- jq merge yields the next secret JSON, written to a tmp file; diff is
  emitted as added/changed/removed key lists only (never values).
- aws secretsmanager put-secret-value creates a new version; previous
  versions remain available for one-step revert via the AWS console.
- ESO is force-refreshed with a force-sync annotation on the
  ExternalSecret; we poll the target Secret's resourceVersion (up to
  60s) to confirm it actually picked up the new version before any
  rollout. Avoids the silent "values still old, pods bounced for
  nothing" failure mode.
- Rollout: kubectl rollout restart deploy/ctdl-xtra-api and
  deploy/ctdl-xtra-worker, then rollout status with a 5m timeout each.
- Slack always notifies (success or failure) with the key-level diff,
  new VersionId, and outcome (applied / applied+restarted / dry-run /
  no-op).

IAM
- New aws_iam_policy.github_actions_secretsmanager grants
  Describe/Get/Put/ListSecretVersionIds scoped to
  ctdl-xtra/{test,sandbox,prod}/app-* only. Attached to the existing
  ctdl-xtra-github-actions-ci role used by every other workflow.
- Must be applied with `terraform apply` in
  infra/terraform/github-ci-oidc/ before the workflow can succeed.

Test plan
- Dispatch on test with dry_run=true and changes={"LOG_LEVEL":"debug"}
  -> step summary shows changed: ["LOG_LEVEL"], no put-secret-value.
- Dispatch on test for real with the same input -> new VersionId,
  ctdl-xtra-app-env resourceVersion bumps, api/worker rollout restart
  + rollout status OK, pods come up with new LOG_LEVEL.
- Dispatch on test with changes={"LOG_LEVEL":null} -> key removed.
- Dispatch on test with changes={"LOG_LEVEL":"info"} and restart=false
  -> secret updated, pods not bounced.
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.

1 participant