Expand description
Substrate primitives over kube::Error — the semantic layer
every controller’s match … { Err(kube::Error::Api(e)) if e.code == <N> => … } guard AND every consumer’s
.map_err(|e| anyhow::anyhow!("<ctx>: {e}"))? error-wrap restates
by hand pre-lift.
Owns two closed-set predicates over the kube::Error::Api
sub-variant’s HTTP status code:
is_conflict— HTTP 409 (Conflict) — the K8s API server refused acreatebecause a resource with the same key already exists, or refused apatchbecause of an optimistic-concurrency generation mismatch. Every controller’s create-branch reads this arm as “someone else got here first; treat the intended write as already-done” or “refresh via PATCH”.is_not_found— HTTP 404 (Not Found) — the K8s API server has no resource with the given key. Every controller’s delete-branch reads this arm as “already deleted / never existed; the intended state (absence) is already true”.
Plus the KubeResultExt extension trait on Result<T, kube::Error>
that owns the display-prefix wrap the phase-machine / signal-effect
/ pool-reconciler consumers thread every K8s round-trip through into
their anyhow-returning handler:
KubeResultExt::kube_ctx— attach a&'static strcontext slug to aResult<_, kube::Error>and get ananyhow::ResultwhoseDisplayreads"<ctx>: <kube::Error display>"(byte-identical to the pre-lift.map_err(|e| anyhow!("<ctx>: {e}"))chain).KubeResultExt::kube_ctx_with— owned-Stringpeer for consumers that compose the slug viaformat!at runtime.
See the trait’s own docstring for the full consumer inventory + the
naming rationale (why kube_ctx and not anyhow::Context::context).
Both predicates lift the 2-link matches!(err, kube::Error::Api(e) if e.code == <N>) shape past the ★★ PRIME-DIRECTIVE ≥ 2
duplication trigger. Pre-lift the SAME chain was hand-authored at
FIVE workspace-wide sites, each interpreting the same HTTP status
code with the same semantic:
tatara-closed-loop-probe::write_receipt_configmap— 409 arm onapi.create(...)→ falls through to a merge-patch of thedatafield so the receipt payload lands idempotently.tatara-github-watcher::handler::handle_pr_event— 404 arm onapi.delete(...)→ returns200 OK "allocation already gone"so a closed-PR event is a no-op past the first delivery.tatara-github-watcher::handler::handle_pr_event— 409 arm onapi.create(...)→ returns200 OK "allocation already exists (synchronize)"so a re-delivery of anopenedPR event maps onto the existing allocation.tatara-pool-reconciler::controller_pool(spawn branch, spawn loop) — 409 arm onprocess_api.create(...)→ treats the race as a successful spawn, incrementingspawnedpast the arm.tatara-pool-reconciler::controller_pool(desired-loop branch) — 409 arm onprocess_api.create(...)→ treats the race as a no-op so the next reconcile picks up the existing Process.
All FIVE sites walked the SAME two-link shape — destructure the
kube::Error::Api sub-variant, guard on e.code == <N> — and
interpret the code identically (“write already succeeded” / “delete
already succeeded”). The e: ErrorResponse binding is bound but
unused at every callsite; the body reads the SEMANTIC (conflict /
not-found) rather than the specific fields (e.reason, e.message).
Post-lift each callsite reads
Err(ref e) if kube_error::is_conflict(e) => { ... } (or
is_not_found), and the two-link shape lives at ONE substrate
owner.
§Semantic axis (why predicates, not raw codes)
The K8s API server sends the same HTTP status code for a set of
semantically identical outcomes (a 404 on get and a 404 on
delete both mean “the resource is not present”); it also
occasionally sends the same code for OTHER outcomes with subtly
different meanings (a 404 on a subresource whose parent exists,
for instance). Lifting the raw-code check to a NAMED predicate
moves every consumer onto the semantic axis, so a future
normalization (a version of is_not_found that also matches
kube::Error::Api(ErrorResponse { reason: "NotFound", .. }) for
servers that stamp the reason but not the code, or a version of
is_conflict that folds the AlreadyExists, Conflict, and
generation-mismatch reasons together) lands at THIS ONE substrate
owner and every downstream idempotent-write consumer inherits the
upgrade mechanically — no per-site edit at any of the FIVE listed
callers or at future consumers (an allocation delete-branch, a
pool-owned Process reap idempotent gate, a table-controller stale-
claim strip that must survive a race with cluster-side GC).
§#[must_use]
Every consumer either drives a match-arm guard on the returned bool or short-circuits a fallthrough branch on it. Dropping the return means the predicate was computed for no observable reason — the attribute surfaces that as a warning at every call site.
Traits§
- Kube
Result Ext - Substrate extension trait over
Result<T, kube::Error>— the ONE substrate owner of the.map_err(|e| anyhow::anyhow!("<ctx>: {e}"))wrap-shape every reconciler consumer restates by hand at the K8s round-trip → anyhow error boundary.
Functions§
- is_
conflict - The kube error names an HTTP 409 Conflict response — a
createrefused because the resource already exists, or apatchrefused because of an optimistic-concurrency generation mismatch. - is_
not_ found - The kube error names an HTTP 404 Not Found response — the K8s API server has no resource with the given key.