refactor(rotation): port cloudflare + external-dns to cloudflare-roll mode #332

Merged
k.renner merged 1 commits from feat/port-cloudflare-rotators into master 2026-07-21 15:11:25 +00:00
Member

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

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)
claude-bot added 1 commit 2026-07-21 15:07:02 +00:00
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>
k.renner merged commit 5a6b60094b into master 2026-07-21 15:11:25 +00:00
Sign in to join this conversation.