Skip to content

fix: remove panic paths from patch, ip_allocator, label_selector, cronjob#35

Open
indyjonesnl wants to merge 5 commits into
calfonso:mainfrom
indyjonesnl:upstream/panic-removal
Open

fix: remove panic paths from patch, ip_allocator, label_selector, cronjob#35
indyjonesnl wants to merge 5 commits into
calfonso:mainfrom
indyjonesnl:upstream/panic-removal

Conversation

@indyjonesnl
Copy link
Copy Markdown

Problem

Five panic sites reachable from runtime inputs. Each is a process-killing failure for what should be a request-level error.

Fix

  • `crates/api-server/src/patch.rs`: ~13 production-code `.unwrap()` calls on JSON values — `as_object_mut().unwrap()`, `op.value.as_ref().unwrap()` for JSON-Patch Add/Replace/Test, array unwraps in strategic merge, `path.rfind('/').unwrap()` in split_path. Convert each to `PatchError::InvalidPatch(...)` via `ok_or_else`. Malformed PATCH bodies now return 400 instead of crashing the handler. Includes regression tests for JSON Patch missing-value panics.
  • `crates/api-server/src/ip_allocator.rs`: 5 `lock().unwrap()` on `std::sync::Mutex`. A poison in any thread would lock out all future ClusterIP allocation. Route every caller through a `lock_allocated()` helper that recovers via `PoisonError::into_inner()` with a single warn-level log.
  • `crates/common/src/label_selector.rs`: `Display` impl indexed `req.values.as_ref().unwrap()[0]` for Equals/NotEquals/In/NotIn. A hand-constructed `LabelRequirement` (one that bypassed the parser) would panic when formatted. Use safe accessors (`.first()`, `.join()`) with empty-string fallback. Adds a regression test.
  • `crates/controller-manager/src/controllers/cronjob.rs`: `successful_jobs_history_limit` / `failed_jobs_history_limit` are `i32`. A negative value cast via `as usize` wraps to ~4 billion, which made the size comparison always pass and the subtraction also wrap, preventing any history cleanup. Use `usize::try_from(...).unwrap_or(0)` and drop the casts.

Verification

`cargo build --workspace --locked` clean. New regression tests pass. No new test failures vs the baseline.

Dependency

Depends on #32 for the test suite to show green.

indyjonesnl and others added 5 commits May 14, 2026 17:34
Malformed PATCH bodies could panic the handler at several places:
- as_object_mut/as_object unwraps on merge-patch + strategic-merge-patch
  bases and patches
- op.value.as_ref().unwrap() for Add/Replace/Test JSON Patch operations
- result_obj[key].as_array().unwrap() in strategic merge array branches
- path.rfind('/').unwrap() in split_path

Convert each to a descriptive PatchError::InvalidPatch via ok_or_else.
Tighten the named-array detection with is_some_and. Tests in #[cfg(test)]
are unchanged — their .unwrap() calls operate on Result values returned
by the patch functions themselves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add three tests covering the panics fixed in this branch: JSON Patch
add/replace/test ops that omit `value`. Before the fix these called
`op.value.as_ref().unwrap()` on None and crashed the handler.
The tests now assert apply_patch returns PatchError::InvalidPatch
instead of unwinding, so reverting the production fix re-introduces
test failures.
A panic in any thread that held the allocator lock would cause every
subsequent lock().unwrap() to panic too, locking out all ClusterIP
allocation. Route all 5 callers through a lock_allocated() helper that
recovers via PoisonError::into_inner() with a single warn-level log.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Display impl indexed req.values.as_ref().unwrap()[0] for Equals,
NotEquals, In, and NotIn. A LabelRequirement built without values (a
hand-constructed one, not from the parser) would panic when formatted.
Fall back to '' for the first-value case and an empty list for joined
values, and add a regression test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
successful_jobs_history_limit and failed_jobs_history_limit are i32. A
negative value cast via 'as usize' wraps to ~4 billion, which made the
size comparison pass and the subtraction also wrap, preventing any
history cleanup. Switch to checked usize::try_from(...).unwrap_or(0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant