Repository navigation
Name events explicitly instead of with an invalid generateName - #58
Conversation
EventRecorder created every event with a generateName of `<name>.`. The API server validates generateName as a DNS subdomain, tolerating only a trailing `-`, so it rejected every create with a 422 and no event was ever published. Name events `<name>.<hex nanoseconds>` instead, as client-go does, truncating the regarding name so the result stays a valid subdomain. The mock API server accepted any name, which is how this went unnoticed. It now validates created names the way the API server does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
RFC 1123 label-length validation is missing, allowing generated names that the real API server rejects.
2 open findings
What changed in this PR
Fixes event publication failures caused by invalid generateName values.
Changes:
- Generates explicit timestamp-based event names.
- Adds API-style name validation to the mock server.
- Documents the fix in the changelog.
| File | Description |
|---|---|
src/events.rs |
Generates and tests explicit event names. |
src/test_util.rs |
Validates names in mock create requests. |
CHANGELOG.md |
Records the event publication fix. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
alex-hunt-materialize
left a comment
There was a problem hiding this comment.
The copilot review comments seem valid to me. I'm not sure why they are marked as resolved.
This is strictly better than we had before, but I think it may still be possible to make things rejected by the api server.
I'm not sure how much value we get from the mock server. We might want to eventually make the tests run a real K8S cluster in Kind.
bosconi
left a comment
There was a problem hiding this comment.
Looks good, modulo one possible small tweak.
| } | ||
| format!("{prefix}{suffix}") |
There was a problem hiding this comment.
client-go's events/v1 recorder, which the description cites, now validates this name and falls back when it is invalid (GenerateEventName, kubernetes/kubernetes#127594). Some kinds allow names that are not DNS subdomains: RBAC names only need to be path segments, so for example system:controller:foo is valid. A Controller over such a kind gets a 422 on every failure event, same as before this PR. Truncation can't fix that, but a fallback prefix can. This needs is_dns_subdomain moved out of test_util into events.rs (pub(crate), imported by test_util), plus a test case like event_name(Some("system:controller:foo"), time) == "event.123456789abcdef0".
| } | |
| format!("{prefix}{suffix}") | |
| } | |
| let name = format!("{prefix}{suffix}"); | |
| // Some kinds allow names that are not DNS subdomains (RBAC names need | |
| // only be path segments), which no suffix can make valid. | |
| if is_dns_subdomain(&name) { | |
| name | |
| } else { | |
| format!("event{suffix}") | |
| } |
Some kinds only require names to be path segments, so an RBAC role named `system:foo` or `Foo` produced an event name the API server rejects. Truncating long names was a partial fix for one way the name could be invalid. Instead, check the whole name and fall back to `event.<hex>` for any name that isn't a valid subdomain, whatever the reason. The subdomain check now also limits labels to 63 bytes. The API server doesn't enforce that for event names, but enforcing it keeps the check, which the mock API server shares, at least as strict as the API server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b594f26
cargo-deny started failing on the newly published advisory that unsafe-libyaml is unmaintained. It comes in through kube-client 3's serde_yaml and can't be dropped without moving to kube 4, which replaces serde_yaml with serde-saphyr but is a breaking upgrade for this crate's users. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| if let Some(name) = metadata["name"].as_str() { | ||
| return if is_dns_subdomain(name) { | ||
| Ok(name.to_owned()) | ||
| } else { | ||
| Err(format!("metadata.name: Invalid value: {name:?}")) | ||
| }; | ||
| } | ||
| let Some(prefix) = metadata["generateName"].as_str() else { | ||
| return Err("metadata.name: Required value".to_owned()); | ||
| }; | ||
| let masked = match prefix.strip_suffix('-') { | ||
| Some(rest) => format!("{rest}a"), | ||
| None => prefix.to_owned(), | ||
| }; | ||
| if !is_dns_subdomain(&masked) { | ||
| return Err(format!("metadata.generateName: Invalid value: {prefix:?}")); | ||
| } | ||
| Ok(format!("{prefix}{suffix}")) |

Problem
EventRecordercreates every event withgenerateName: "<regarding name>.". The API server validatesgenerateNameas a DNS subdomain on its own, tolerating only a trailing-, so every create fails with a 422 and no event is ever published:The unit tests didn't catch this because
MockApiServeraccepted any name.Changes
metadata.nameof<regarding name>.<hex nanoseconds>, the scheme client-go's events/v1 recorder uses. When that isn't a valid DNS subdomain, the name falls back toevent.<hex nanoseconds>. This happens for names that only meet path-segment rules (RBAC objects may be namedsystem:fooorFoo), for names too long to fit the suffix, and for objects without a name. The event'sregardingreference still identifies the object. The old fallback, the reporter's controller name, always contains a/and so was never a valid name.MockApiServernow validates created names.namemust be a DNS subdomain, andgenerateNamemust be one apart from a trailing-. Invalid names get a 422. The check also limits each label to 63 bytes, as RFC 1123 does. The API server doesn't enforce that for event names (itsIsDNS1123Subdomainchecks only the 253-byte total and the character pattern), but it keeps the mock at least as strict as the real server. Run against the oldevents.rs, 10 existing tests fail with this.Verification
cargo test --all-featurespasses andcargo clippy --all-features --all-targets -- -D warningsis clean.ReconcileFailedevent was created, retries were counted on one event (x4inkubectl describe), and a change in the error started a new event.Notes
CHANGELOG.mdgets a hand-written## Unreleasedentry, following the existing hand-written entries. With release-plz now generating release PRs (Add release-plz workflow #56), that entry may need folding into whatever release-plz writes.🤖 Generated with Claude Code