Step 1 of 2 to port the Cloudflare rotators. Adds the cloudflare-roll binary mode; the manifest port follows once CI publishes the image (the CronJobs pin it by digest, which doesn't exist until this merges).
What it does
Rolls a Cloudflare API token's value in place via PUT /user/tokens/{id}/value — same id, scope and policy, new value. No make-before-break, no destructive window.
Replaces the two identical python rotators, which differ only in two parameters:
bootstrap token-id field
target
cloudflare (cert-manager)
token-id
infrastructure/cloudflare#api-token
external-dns
external-dns-token-id
apps/external-dns#api-token
Both read the admin token from the same shared bootstrap, so it lives in one place; only the token-id pin differs, keeping the two rolling independently.
Safety carried over from the earlier lessons
assert_fields_exist guards the target path — a typo strands the real token instead of adding a stray field.
The API value is used verbatim from serde_json with nothing appended: a stray newline is what once crash-looped external-dns (invalid header field value for Authorization).
result is accepted as a bare string or{value: ...}, so a future API shape change fails loud rather than storing "null".
Validated against real Cloudflare (throwaway token, zero blast radius)
PUT /user/tokens/{id}/value {} -> success: true, result is a bare string
new != old: YES, no trailing newline: YES
new value verifies: active
old value verifies: Invalid API Token <- roll invalidates old immediately (no window)
Throwaway token deleted after. clippy -D warnings clean; 12 tests pass; the in-cluster pieces (login, read_field, assert_fields_exist, patch_field) are the same code already proven live by the other modes.
Step 1 of 2 to port the Cloudflare rotators. Adds the `cloudflare-roll` binary mode; the manifest port follows once CI publishes the image (the CronJobs pin it by digest, which doesn't exist until this merges).
## What it does
Rolls a Cloudflare API token's value in place via `PUT /user/tokens/{id}/value` — same id, scope and policy, new value. **No make-before-break, no destructive window.**
Replaces the two identical python rotators, which differ only in two parameters:
| | bootstrap token-id field | target |
|---|---|---|
| cloudflare (cert-manager) | `token-id` | `infrastructure/cloudflare#api-token` |
| external-dns | `external-dns-token-id` | `apps/external-dns#api-token` |
Both read the admin token from the **same** shared bootstrap, so it lives in one place; only the token-id pin differs, keeping the two rolling independently.
## Safety carried over from the earlier lessons
- `assert_fields_exist` guards the target path — a typo strands the real token instead of adding a stray field.
- The API value is used **verbatim** from `serde_json` with nothing appended: a stray newline is what once crash-looped external-dns (`invalid header field value for Authorization`).
- `result` is accepted as a bare string **or** `{value: ...}`, so a future API shape change fails loud rather than storing "null".
## Validated against real Cloudflare (throwaway token, zero blast radius)
```
PUT /user/tokens/{id}/value {} -> success: true, result is a bare string
new != old: YES, no trailing newline: YES
new value verifies: active
old value verifies: Invalid API Token <- roll invalidates old immediately (no window)
```
Throwaway token deleted after. `clippy -D warnings` clean; 12 tests pass; the in-cluster pieces (login, read_field, assert_fields_exist, patch_field) are the same code already proven live by the other modes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds the mode that will replace the two identical Cloudflare python rotators
(cert-manager's token and external-dns's). They differ only in which bootstrap
field holds the token id and which OpenBao path receives the value:
cloudflare bootstrap#token-id -> infrastructure/cloudflare#api-token
external-dns bootstrap#external-dns-token-id -> apps/external-dns#api-token
Both read the admin token from the same shared bootstrap, so it lives in one
place; only the token-id pin differs, keeping the two tokens rolling
independently.
Rolls the token value in place via PUT /user/tokens/{id}/value — same id,
scope and policy, new value — so there is no make-before-break and no
destructive window. assert_fields_exist guards the target path (a typo would
strand the real token), and the API value is used verbatim: a stray newline
would surface as "invalid header field value for Authorization" and crash the
consumer (the bug that hit external-dns once), so the string comes straight
from serde_json with nothing appended.
Handles result as either a bare string or {value: ...} so a future API shape
change fails loud rather than storing "null".
Validated against the real Cloudflare API on a throwaway token (zero blast
radius): PUT with empty body returns success + a bare-string value, new != old
with no trailing newline, new verifies active while the old immediately reads
"Invalid API Token" (confirming roll-in-place has no window). clippy
-D warnings clean; 12 tests pass.
No rotator uses it yet — the manifest port follows once CI publishes the image.
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 1 of 2 to port the Cloudflare rotators. Adds the
cloudflare-rollbinary mode; the manifest port follows once CI publishes the image (the CronJobs pin it by digest, which doesn't exist until this merges).What it does
Rolls a Cloudflare API token's value in place via
PUT /user/tokens/{id}/value— same id, scope and policy, new value. No make-before-break, no destructive window.Replaces the two identical python rotators, which differ only in two parameters:
token-idinfrastructure/cloudflare#api-tokenexternal-dns-token-idapps/external-dns#api-tokenBoth read the admin token from the same shared bootstrap, so it lives in one place; only the token-id pin differs, keeping the two rolling independently.
Safety carried over from the earlier lessons
assert_fields_existguards the target path — a typo strands the real token instead of adding a stray field.serde_jsonwith nothing appended: a stray newline is what once crash-looped external-dns (invalid header field value for Authorization).resultis accepted as a bare string or{value: ...}, so a future API shape change fails loud rather than storing "null".Validated against real Cloudflare (throwaway token, zero blast radius)
Throwaway token deleted after.
clippy -D warningsclean; 12 tests pass; the in-cluster pieces (login, read_field, assert_fields_exist, patch_field) are the same code already proven live by the other modes.🤖 Generated with Claude Code
Adds the mode that will replace the two identical Cloudflare python rotators (cert-manager's token and external-dns's). They differ only in which bootstrap field holds the token id and which OpenBao path receives the value: cloudflare bootstrap#token-id -> infrastructure/cloudflare#api-token external-dns bootstrap#external-dns-token-id -> apps/external-dns#api-token Both read the admin token from the same shared bootstrap, so it lives in one place; only the token-id pin differs, keeping the two tokens rolling independently. Rolls the token value in place via PUT /user/tokens/{id}/value — same id, scope and policy, new value — so there is no make-before-break and no destructive window. assert_fields_exist guards the target path (a typo would strand the real token), and the API value is used verbatim: a stray newline would surface as "invalid header field value for Authorization" and crash the consumer (the bug that hit external-dns once), so the string comes straight from serde_json with nothing appended. Handles result as either a bare string or {value: ...} so a future API shape change fails loud rather than storing "null". Validated against the real Cloudflare API on a throwaway token (zero blast radius): PUT with empty body returns success + a bare-string value, new != old with no trailing newline, new verifies active while the old immediately reads "Invalid API Token" (confirming roll-in-place has no window). clippy -D warnings clean; 12 tests pass. No rotator uses it yet — the manifest port follows once CI publishes the image. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>