Grant deployer Job rights, for one-off commands against an app

toolshed can now run a one-off command against an app — a migration, a
seed, a backfill — in a throwaway Job built from the app's own image with
its own configuration (internal/deploy.Client.RunCommand). It exists
because a user with a provisioned database otherwise has no way to reach
it: the operator can exec into the Postgres pod, a user cannot, and the
premise is that nobody needs kubectl.

Without this the deployer gets "forbidden" the moment anyone runs one —
the two-repositories drift internal/deploy/kubernetes.go's own package doc
warns about, and the third feature in a row to need both copies changed
together.

Delete is included because the Job is removed once its output has been
captured into the run record; without it they would accumulate one per
command forever. Notably absent is pods/exec, which an exec-into-the-
running-pod design would have required — a far more dangerous grant on the
one credential here whose compromise means the cluster, and one reason the
Job approach was chosen over it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wajog7nELA3i8JWTjxYGHF
This commit is contained in:
Mukul Sharma
2026-09-10 07:38:20 +05:30
co-authored by Claude Opus 5
parent 6d3f1643db
commit bf814854fd
@@ -57,6 +57,13 @@ rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# A one-off command run against an app executes in a throwaway Job built
# from the app's own image — see internal/deploy.Client.RunCommand. Delete
# is needed because the Job is removed once its output has been captured;
# without it they accumulate one per command forever.
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get", "list", "watch", "create", "delete"]
# An app with persistent storage is a StatefulSet instead of a Deployment
# (internal/deploy.Client.ensureStatefulSet). Delete is needed on both:
# switching an app between stateless and stateful must remove whichever