feat(database): provision admin_app Postgres role on basketball-api (#302) #304
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/pal-e-platform!304
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "302-admin-postgres-job"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a one-shot, idempotent k8s Job (
admin-app-user-provision) and companionadmin-app-db-urlSecret to thedatabaseterraform module. Provides westside-admin a least-privilege DML user on the basketball-api Postgres database.Closes #302.
Labels:
story:admin-row-crud,arch:postgres.Changes
terraform/modules/database/main.tf— newdata.kubernetes_namespace_v1.basketball_api,kubernetes_job_v1.admin_app_user_provision,kubernetes_secret_v1.admin_app_db_url. Job runspsqlagainstpostgres.basketball-api.svc.cluster.localas the basketball superuser, creates/rotatesadmin_approle, applies DML grants andALTER DEFAULT PRIVILEGESfor forward grants.terraform/modules/database/variables.tf— newadmin_app_db_password(sensitive).terraform/main.tf,terraform/variables.tf— wire the new variable through to the module.terraform/secrets.auto.tfvars.example— document the new secret withopenssl rand -hex 32generation hint.Makefile— addadmin_app_db_passwordtoTF_SECRET_VARSsotofu-validate-secretsrequires it.Design Decisions
review-1097-2026-04-25flagged thatcyrilgdn/postgresqlis not registered in this repo and adding it mid-flight expands provider blast radius. The Job approach reuses already-installedhashicorp/kubernetesand runs alongside the existingkubernetes_cron_job_v1.cnpg_backup_verifyprecedent in the same module.ALTER ROLE.DO $$ ... IF NOT EXISTS $$for CREATE ROLE; GRANT and ALTER DEFAULT PRIVILEGES are no-ops when already in place. Re-running the apply is safe.FOR ROLE basketball. Drizzle migrations run as the basketball superuser; this guarantees future tables/sequences they create automatically grant DML to admin_app without a re-run.pal-e-deployments#133) is responsible for surfacing it to the westside-admin pod (cross-namespace consumption is out of scope here, per dispatch boundary).tofu plan Output
tofu validatepasses.tofu plannot included because the secrets pillar is required to rendersecrets.auto.tfvars; will be regenerated by the operator runningmake tofu-planafter addingadmin_app_db_passwordto the Salt pillar (salt/pillar/secrets/platform.sls).Test Plan
After
make tofu-applylands and ArgoCD reconciles:admin-app-user-provision-<hash>-*inbasketball-apinamespace reachesCompleted.kubectl -n basketball-api get secret admin-app-db-url -o jsonpath='{.data.DATABASE_URL}' | base64 -dreturns the expected URL.psql "$DATABASE_URL" -c "SELECT 1"succeeds.psql "$DATABASE_URL" -c "INSERT INTO <some_test_table> ..."succeeds.psql "$DATABASE_URL" -c "CREATE TABLE foo (id int)"fails withpermission denied.CREATE TABLE bar (id int); verify admin_app immediately has SELECT/INSERT/UPDATE/DELETE onbar(proves ALTER DEFAULT PRIVILEGES works).Discovered Scope
admin_app_db_passwordmust be added tosalt/pillar/secrets/platform.sls(encrypted via existing GPG flow). Out of scope for this PR; operator step beforemake tofu-apply.pal-e-deployments#133) needs to either re-emit the URL via a kustomize secretGenerator in the westside-admin overlay or stand up a small replicator. Documented in the inline module comment.sop-admin-app-password-rotation— pal-e-docs note creation belongs to main session (agents do not write to pal-e-docs). Surfacing for follow-up.Review Checklist
tofu fmtclean (verified)tofu validatepasses (verified)CHANGEMEplaceholders in example file)TF_SECRET_VARSupdated in MakefileRelated Notes
review-1097-2026-04-25— scope investigation that drove path A (k8s Job over cyrilgdn provider)feedback_never_write_prod_db— admin_app is the legitimized DML write userproject-westside-admin— consuming projectarch-deployment-westside-admin— architecture anchorRelated
forgejo_admin/westside-admin#1(Drizzle),forgejo_admin/pal-e-deployments#133(overlay secrets)PR #304 Review
DOMAIN REVIEW
Stack: OpenTofu +
hashicorp/kubernetesprovider, in-cluster postgres provisioning via a one-shot k8s Job (postgres:16-alpinerunningpsql). Plus root variable plumbing and Makefile secret-vars wiring.Pivot from ticket spec (kustomize Job in pal-e-deployments → terraform-managed
kubernetes_job_v1+kubernetes_secret_v1in pal-e-platform/modules/database): Verdict — sound and aligns with existing convention.Sibling pattern in the same module (
paledocs_db_url, lines 67-78 ofterraform/modules/database/main.tf):data "kubernetes_namespace_v1"to land the secret in a foreign namespace (pal-e-app)var.paledocs_db_password<service>-db-urlwithDATABASE_URLkeyPR #304 mirrors this shape exactly (
admin-app-db-urlSecret,DATABASE_URLkey,data.kubernetes_namespace_v1.basketball_api,var.admin_app_db_passwordsensitive). The added Job is only required because basketball-api postgres is a plain Deployment (not CNPG operator-managed), so role provisioning can't piggyback on a CNPGCluster.spec.bootstrap. The lineage inreview-1097-2026-04-25(avoid adding cyrilgdn/postgresql provider mid-flight) is the right call — provider expansion has higher blast radius than a one-shot Job using the already-installed kubernetes provider, and there is in-module precedent (kubernetes_cron_job_v1.cnpg_backup_verify).Job/SQL quality:
DO $$ ... IF NOT EXISTS $$for CREATE ROLE,ALTER ROLEon rotation, GRANT and ALTER DEFAULT PRIVILEGES are no-op on re-apply. Correct.ALTER DEFAULT PRIVILEGES FOR ROLE basketballcorrectly handles forward grants for Drizzle migrations run as superuser.psql -v admin_pw=...withformat(... %L ...)is the safe quoting pattern (no SQL injection vector via password contents).wait_for_completion = true+backoff_limit = 4+ttl_seconds_after_finished = 3600+ resource requests/limits — all good k8s hygiene.basketball-api-secrets/postgres-passwordfor PGPASSWORD viasecret_key_refinstead of duplicating the superuser password into terraform state. Correct.Secret namespace (basketball-api vs westside-admin): Intentional and correct. Secret lives where the database lives. Cross-namespace surfacing is correctly out of scope and tracked at
pal-e-deployments#133(note: PR body mentions#133, dispatch context says#135— minor doc reconciliation but not blocking).BLOCKERS
None.
NITS
ADMIN_APP_PASSWORDis passed as a literal env var (value = var.admin_app_db_password) rather than via a Secret +value_from. The PGPASSWORD pattern in the same Job usessecret_key_ref— would be more consistent (and avoid the value showing inkubectl describe podfor anyone with namespace read). Not a blocker since it's already in theadmin-app-db-urlSecret which exists right next door, but the symmetry would be nice.tofu plandeferred until the Salt pillar is populated — fine, but the eventual apply must include-lock=false(perfeedback_tofu_lock_false). Worth a one-line callout in the apply runbook.pal-e-deployments#133; dispatch context says#135. Reconcile in the cross-repo issue link.arch:postgreslabel on Job/Secret is good — but the PR body listsstory:admin-row-crud, arch:postgreswhile the actual Forgejo PR labels weren't reported in the diff. Verify labels are set on the PR itself.SOP COMPLIANCE
302-admin-postgres-job)Closes #302story:admin-row-crud,arch:postgres)CHANGEMEplaceholders)TF_SECRET_VARSupdated in Makefiletofu validateclean (per PR body);tofu fmtclaimed clean — re-verify on rebase if anytofu planoutput not in PR body — acceptable here because pillar must land first; documentedPROCESS OBSERVATIONS
review-1097-2026-04-25) before the agent committed. Good DORA hygiene: scope-revision lineage is in the module comment so a future reader won't re-litigate the choice.make tofu-applyor the run aborts on the missing required variable. Track as an apply gate, not a merge gate.pal-e-deployments#133) is downstream consumer; ensure its overlay referencesadmin-app-db-url/DATABASE_URLexactly (matches what this PR creates).APPLY GATES (do NOT apply until all green)
admin_app_db_passwordadded tosalt/pillar/secrets/platform.slsmake tofu-plan(with-lock=false) reviewed by operatorpal-e-deployments#133) ready or sequenced so westside-admin can consume the Secret post-applyVERDICT: APPROVED
Merge is fine. Apply is gated on #306. Recommend Lucas merge, then drive #306 to completion before any
make tofu-applyagainst this module.