Skip to content

HYPERFLEET-1155 - feat: add force-delete endpoint for generic resources#266

Open
kuudori wants to merge 1 commit into
openshift-hyperfleet:mainfrom
kuudori:HYPERFLEET-1155-feat-force-delete-generic-resources
Open

HYPERFLEET-1155 - feat: add force-delete endpoint for generic resources#266
kuudori wants to merge 1 commit into
openshift-hyperfleet:mainfrom
kuudori:HYPERFLEET-1155-feat-force-delete-generic-resources

Conversation

@kuudori

@kuudori kuudori commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add POST /{plural}/{id}/force-delete to ResourceHandler for all generic entity types (channels, versions, wifconfigs)
  • Bypasses OnParentDelete policies, recursively hard-deletes entire ownership tree for resources in Finalizing state
  • Fail-loud guard + tripwire test for RequiredAdapters invariant (HYPERFLEET-1154)

Design

Per ADR-0013 (DB-only force-delete) and ADR-0012 (bottom-up deletion ordering):

  • Finalizing gate: resource must have deleted_time IS NOT NULL (409 otherwise)
  • Reason required: validated via validateNotEmpty + validateMaxLength
  • Recursive cascade: forceDeleteResourceTree walks all children regardless of OnParentDelete policy, hard-deletes bottom-up
  • Atomicity: transaction middleware wraps POST; MarkForRollback on any error prevents partial commits
  • Audit: structured Info log per resource with kind, id, caller, reason, child IDs
  • RequiredAdapters tripwire: runtime error if non-empty + TestAllGenericDescriptors_HaveNoRequiredAdapters breaks CI when assumption changes

Test plan

  • Unit tests: handler (ForceDelete, ForceDeleteByOwner — 204/400/404/409/500), service (happy path, cascade, restrict bypass, grandchildren, RequiredAdapters guard, invalid kind)
  • Tripwire test: TestAllGenericDescriptors_HaveNoRequiredAdapters
  • Integration tests: cascade with restricted children, no-children channel, 409 not-finalizing, 404 not-found, nested version force-delete
  • make verify-all passes (1322 tests)
  • make test-integration passes (force-delete scenarios)

@openshift-ci openshift-ci Bot requested review from Mischulee and rafabene June 30, 2026 20:52
@openshift-ci

openshift-ci Bot commented Jun 30, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign tirthct for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 38f6d153-8e61-4603-8b46-a5d1dfe950ce

📥 Commits

Reviewing files that changed from the base of the PR and between 8779683 and c0d96a5.

📒 Files selected for processing (8)
  • pkg/handlers/resource_handler.go
  • pkg/handlers/resource_handler_test.go
  • pkg/services/resource.go
  • pkg/services/resource_test.go
  • plugins/channels/plugin.go
  • plugins/versions/plugin.go
  • plugins/wifconfigs/plugin.go
  • test/integration/resource_force_delete_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)
🚧 Files skipped from review as they are similar to previous changes (8)
  • test/integration/resource_force_delete_test.go
  • plugins/versions/plugin.go
  • plugins/channels/plugin.go
  • plugins/wifconfigs/plugin.go
  • pkg/handlers/resource_handler.go
  • pkg/handlers/resource_handler_test.go
  • pkg/services/resource.go
  • pkg/services/resource_test.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added force-delete API endpoints for resources, including direct and nested paths (e.g., channels and versions), to permanently remove eligible items with a required reason.
  • Bug Fixes
    • Enforced stricter request validation (non-empty, bounded reason).
    • Force-delete is now limited to resources in the proper finalizing state, cascades to allowed children, and blocks cases requiring adapter status cleanup.
  • Tests
    • Added unit and integration coverage for force-delete success and failure scenarios, including descriptor validation for adapter requirements.

Walkthrough

Adds force-delete endpoints for resource handlers and registers them for Channel, Version, and WifConfig. The service layer adds ForceDelete with kind validation, finalizing-state checks, recursive child deletion, required-adapter rejection, and hard delete behavior. Tests cover handler responses, service outcomes, and integration behavior. CWE-284 and CWE-862 apply to the exposed destructive flow.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ResourceHandler
  participant ResourceService
  participant resourceDao

  Client->>ResourceHandler: POST /{id}/force-delete
  ResourceHandler->>ResourceHandler: validate reason
  ResourceHandler->>ResourceService: ForceDelete(ctx, kind, id, reason)
  ResourceService->>resourceDao: lock and load resource
  ResourceService->>resourceDao: delete resource tree
  ResourceHandler-->>Client: 204 or mapped error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 9 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
No Pii Or Sensitive Data In Logs ⚠️ Warning FAIL: forceDeleteResourceTree logs unredacted request reason and caller via logger.With(...).Info(...), risking customer-data disclosure (CWE-532). Remove reason from info logs or redact/hash it; keep only non-sensitive IDs, or move to debug with explicit redaction.
✅ Passed checks (9 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the new force-delete endpoint for generic resources.
Description check ✅ Passed The description is directly related to the force-delete endpoint, its validation, recursion, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Sec-02: Secrets In Log Output ✅ Passed No token/password/credential/secret fields or interpolations found; the only new log is resource metadata plus reason/children, not credentials (CWE-532 not triggered).
No Hardcoded Secrets ✅ Passed No hardcoded credentials found in touched files; no apiKey/secret/token/password literals, embedded creds, or long base64 strings (CWE-798/CWE-259).
No Weak Cryptography ✅ Passed PASS: No CWE-327/CWE-328/CWE-208 indicators in touched files; no crypto/md5/des/rc4, ECB, SHA1-for-security, or secret compares.
No Injection Vectors ✅ Passed No new CWE-89/78/79/502 sink: force-delete paths use parameterized DAO calls, no exec/template/yaml, and kind is registry-trusted.
No Privileged Containers ✅ Passed No CWE-266/276 issue in PR-touched manifests; only existing Dockerfile briefly uses root to install make, then drops to non-root.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Comment @coderabbitai help to get the list of available commands.

@hyperfleet-ci-bot

hyperfleet-ci-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

Risk Score: 3 — risk/medium

Signal Detail Points
PR size 653 lines (>500) +2
Sensitive paths none +0
Test coverage Missing tests for: plugins/channels plugins/versions plugins/wifconfigs +1

Computed by hyperfleet-risk-scorer

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/services/resource_test.go`:
- Around line 1318-1328: The tripwire test is only validating local fixtures
because setupTestDescriptors() rebuilds the registry, so it never checks real
plugin descriptors. Move the RequiredAdapters assertion out of
TestAllGenericDescriptors_HaveNoRequiredAdapters and into a test that loads the
actual generic-resource plugins, then iterate registry.All() to verify the
registered descriptors from the real registry. Keep the existing Expect check
and failure message, but ensure it runs against the production plugin
registration path rather than the test fixture registry.

In `@test/integration/resource_force_delete_test.go`:
- Around line 110-139: The NestedVersionForceDelete subtest in the resource
force-delete integration test only verifies that the Version identified by
versionID is removed, but it does not confirm that the parent channel remains
intact. Update this test around the existing ForceDelete and checkResourceCount
assertions to also assert that channel.ID still exists after the nested version
delete, using the existing helpers in setupResourceTest, createChannel, and
checkResourceCount so the parent scope is explicitly validated.
- Around line 22-141: The current `TestResourceForceDelete` suite only calls
`svc.ForceDelete`, so it misses regressions in
`pkg/handlers/resource_handler.go` and the route/plugin wiring for `POST
/{plural}/{id}/force-delete` and the nested owner-scoped force-delete path.
Update these subtests to use the integration HTTP setup from
`test.RegisterIntegration(t)` (helper, client) with Resty and Gomega, send real
requests through the handler routes, and assert on response status/body plus
`reason` validation instead of only checking `ServiceError.HTTPCode`. Keep the
DB cleanup assertions, but locate the tests by `TestResourceForceDelete`,
`setupResourceTest`, and `ForceDelete` when refactoring.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: cd5c2875-03f9-424c-a4fe-6dd27eb0ccdd

📥 Commits

Reviewing files that changed from the base of the PR and between 8f20481 and 8779683.

📒 Files selected for processing (8)
  • pkg/handlers/resource_handler.go
  • pkg/handlers/resource_handler_test.go
  • pkg/services/resource.go
  • pkg/services/resource_test.go
  • plugins/channels/plugin.go
  • plugins/versions/plugin.go
  • plugins/wifconfigs/plugin.go
  • test/integration/resource_force_delete_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)

Comment on lines +1318 to +1328
func TestAllGenericDescriptors_HaveNoRequiredAdapters(t *testing.T) {
RegisterTestingT(t)
setupTestDescriptors()

for _, d := range registry.All() {
Expect(d.RequiredAdapters).To(BeEmpty(),
"Descriptor %q has RequiredAdapters=%v. "+
"ForceDelete does not yet handle adapter_status cleanup. "+
"See HYPERFLEET-1154.", d.Kind, d.RequiredAdapters)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

This tripwire never checks the real descriptor registry.

setupTestDescriptors() wipes the registry and repopulates only local fixtures, so this test will still pass if an actual plugin descriptor later adds RequiredAdapters. That misses the PR’s fail-loud CI invariant entirely. Move this assertion to a test that imports the real generic-resource plugins and inspects their registered descriptors instead of rebuilding the registry here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/services/resource_test.go` around lines 1318 - 1328, The tripwire test is
only validating local fixtures because setupTestDescriptors() rebuilds the
registry, so it never checks real plugin descriptors. Move the RequiredAdapters
assertion out of TestAllGenericDescriptors_HaveNoRequiredAdapters and into a
test that loads the actual generic-resource plugins, then iterate registry.All()
to verify the registered descriptors from the real registry. Keep the existing
Expect check and failure message, but ensure it runs against the production
plugin registration path rather than the test fixture registry.

Comment thread test/integration/resource_force_delete_test.go
Comment thread test/integration/resource_force_delete_test.go
Add POST /{plural}/{id}/force-delete to ResourceHandler for all generic
entity types (channels, versions, wifconfigs). The endpoint bypasses
OnParentDelete policies and recursively hard-deletes the entire ownership
tree for resources in Finalizing state (deleted_time IS NOT NULL).

Key design decisions per ADR-0013 (DB-only force-delete):
- Requires Finalizing state and a reason field in the request body
- Bottom-up hard-delete ordering per ADR-0012
- Fail-loud guard if RequiredAdapters non-empty (tripwire for HYPERFLEET-1154)
- MarkForRollback on error prevents partial tree deletion from committing
- Audit log with structured fields before each resource deletion

Includes unit tests (handler + service + tripwire) and integration tests
confirming force-delete succeeds where normal DELETE returns 409 due to
Restrict policy.
@kuudori kuudori force-pushed the HYPERFLEET-1155-feat-force-delete-generic-resources branch from 8779683 to c0d96a5 Compare June 30, 2026 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant