Step 2 of 2. Ports both Cloudflare rotators from inline python to the binary's cloudflare-roll mode (merged in #331). Net -65 lines; the duplicated login+PUT python and the python:3.13-alpine image go away.
Two rotators, one mode
They differ only in two parameterised env values:
BOOTSTRAP_TOKENID_FIELD
target
cloudflare (cert-manager)
token-id
infrastructure/cloudflare
external-dns
external-dns-token-id
apps/external-dns
Admin token from the same shared bootstrap; roll-in-place keeps each token's id/scope, so they roll independently with no window.
Tested live (cert-manager, via the real binary image)
rolled Cloudflare token af708481... -> infrastructure/cloudflare#api-token
OpenBao fp: d6a4d73b -> 9bd47674
new token: status=active, id=af708481 (SAME id — roll-in-place)
no trailing newline
ESO synced; certs: 27/27 Ready
external-dns is the identical code path (only the token-id field + target differ) and was deliberately not rolled — doing so would restart external-dns for no reason. Its PUT/parse was proven against real Cloudflare on a throwaway token in #331.
One footgun worth flagging
Changing the container from python command+args to args-only (relying on the image ENTRYPOINT) means the old command must be dropped. Flux server-side apply drops it — verified against the already-ported Class-A CronJobs, which show command=<none> live. A manual kubectl apply does not (it merges and keeps command: ["python3","-c"] → exec: python3 not found on the scratch image). I hit exactly this while testing; the fix is to apply via Flux, which this PR does. Flagging so nobody hand-applies these with kubectl apply.
Step 2 of 2. Ports both Cloudflare rotators from inline python to the binary's `cloudflare-roll` mode (merged in #331). **Net -65 lines**; the duplicated login+PUT python and the python:3.13-alpine image go away.
## Two rotators, one mode
They differ only in two parameterised env values:
| | `BOOTSTRAP_TOKENID_FIELD` | target |
|---|---|---|
| cloudflare (cert-manager) | `token-id` | `infrastructure/cloudflare` |
| external-dns | `external-dns-token-id` | `apps/external-dns` |
Admin token from the same shared bootstrap; roll-in-place keeps each token's id/scope, so they roll independently with no window.
## Tested live (cert-manager, via the real binary image)
```
rolled Cloudflare token af708481... -> infrastructure/cloudflare#api-token
OpenBao fp: d6a4d73b -> 9bd47674
new token: status=active, id=af708481 (SAME id — roll-in-place)
no trailing newline
ESO synced; certs: 27/27 Ready
```
external-dns is the **identical code path** (only the token-id field + target differ) and was deliberately **not** rolled — doing so would restart external-dns for no reason. Its PUT/parse was proven against real Cloudflare on a throwaway token in #331.
## One footgun worth flagging
Changing the container from python `command+args` to `args`-only (relying on the image ENTRYPOINT) means the old `command` must be dropped. **Flux server-side apply drops it** — verified against the already-ported Class-A CronJobs, which show `command=<none>` live. A manual `kubectl apply` does **not** (it merges and keeps `command: ["python3","-c"]` → `exec: python3 not found` on the scratch image). I hit exactly this while testing; the fix is to apply via Flux, which this PR does. Flagging so nobody hand-applies these with `kubectl apply`.
Image digest `sha256:978fe53c…`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Replaces the two identical Cloudflare python rotators with the secret-rotator
binary's cloudflare-roll mode (added in the prior PR). Net -65 lines; the
duplicated OpenBao-login + PUT-and-parse python and the python:3.13-alpine
runtime image go away.
Both roll their token value in place (PUT /user/tokens/{id}/value) and patch
OpenBao. They differ only in two env values, both parameterised:
cloudflare BOOTSTRAP_TOKENID_FIELD=token-id -> infrastructure/cloudflare
external-dns BOOTSTRAP_TOKENID_FIELD=external-dns-token-id -> apps/external-dns
The admin token comes from the same shared bootstrap, so it stays in one place.
Behaviour preserved: roll-in-place keeps the token id/scope (no window), the
API value is stored verbatim (no trailing newline), and assert_fields_exist
guards the target path. cert-manager needs no reload (reads fresh per DNS-01
challenge); external-dns keeps its Reloader annotation (startup env var).
Tested: the cert-manager rotator was rolled live end-to-end via the real
binary image — OpenBao rolled (d6a4d73b -> 9bd47674), new token verifies active
with the SAME id af708481 (roll-in-place), no trailing newline, ESO synced,
27/27 certs still Ready. external-dns is the identical code path (only the
token-id field + target path differ) and was not rolled to avoid a needless
restart; its PUT/parse was additionally proven against the real Cloudflare API
on a throwaway token in the mode PR.
Note: Flux server-side apply strips the old python `command` when it applies
these (verified against the already-ported Class-A CronJobs, which show
command=<none> live). A manual `kubectl apply` would NOT — it merges and keeps
the stale command — so port these via Flux, not kubectl apply.
Image digest sha256:978fe53c…
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Step 2 of 2. Ports both Cloudflare rotators from inline python to the binary's
cloudflare-rollmode (merged in #331). Net -65 lines; the duplicated login+PUT python and the python:3.13-alpine image go away.Two rotators, one mode
They differ only in two parameterised env values:
BOOTSTRAP_TOKENID_FIELDtoken-idinfrastructure/cloudflareexternal-dns-token-idapps/external-dnsAdmin token from the same shared bootstrap; roll-in-place keeps each token's id/scope, so they roll independently with no window.
Tested live (cert-manager, via the real binary image)
external-dns is the identical code path (only the token-id field + target differ) and was deliberately not rolled — doing so would restart external-dns for no reason. Its PUT/parse was proven against real Cloudflare on a throwaway token in #331.
One footgun worth flagging
Changing the container from python
command+argstoargs-only (relying on the image ENTRYPOINT) means the oldcommandmust be dropped. Flux server-side apply drops it — verified against the already-ported Class-A CronJobs, which showcommand=<none>live. A manualkubectl applydoes not (it merges and keepscommand: ["python3","-c"]→exec: python3 not foundon the scratch image). I hit exactly this while testing; the fix is to apply via Flux, which this PR does. Flagging so nobody hand-applies these withkubectl apply.Image digest
sha256:978fe53c….🤖 Generated with Claude Code
Replaces the two identical Cloudflare python rotators with the secret-rotator binary's cloudflare-roll mode (added in the prior PR). Net -65 lines; the duplicated OpenBao-login + PUT-and-parse python and the python:3.13-alpine runtime image go away. Both roll their token value in place (PUT /user/tokens/{id}/value) and patch OpenBao. They differ only in two env values, both parameterised: cloudflare BOOTSTRAP_TOKENID_FIELD=token-id -> infrastructure/cloudflare external-dns BOOTSTRAP_TOKENID_FIELD=external-dns-token-id -> apps/external-dns The admin token comes from the same shared bootstrap, so it stays in one place. Behaviour preserved: roll-in-place keeps the token id/scope (no window), the API value is stored verbatim (no trailing newline), and assert_fields_exist guards the target path. cert-manager needs no reload (reads fresh per DNS-01 challenge); external-dns keeps its Reloader annotation (startup env var). Tested: the cert-manager rotator was rolled live end-to-end via the real binary image — OpenBao rolled (d6a4d73b -> 9bd47674), new token verifies active with the SAME id af708481 (roll-in-place), no trailing newline, ESO synced, 27/27 certs still Ready. external-dns is the identical code path (only the token-id field + target path differ) and was not rolled to avoid a needless restart; its PUT/parse was additionally proven against the real Cloudflare API on a throwaway token in the mode PR. Note: Flux server-side apply strips the old python `command` when it applies these (verified against the already-ported Class-A CronJobs, which show command=<none> live). A manual `kubectl apply` would NOT — it merges and keeps the stale command — so port these via Flux, not kubectl apply. Image digest sha256:978fe53c… Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>