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: 367fd135-afe3-4f2f-856e-30caa83acc8e

📥 Commits

Reviewing files that changed from the base of the PR and between 1c2f75c and 9a28571.

📒 Files selected for processing (7)
  • pkg/handlers/resource_handler.go
  • pkg/handlers/resource_handler_test.go
  • pkg/services/resource.go
  • pkg/services/resource_test.go
  • plugins/entities/plugin.go
  • test/integration/resource_force_delete_test.go
  • test/integration/resource_helpers.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 due to trivial changes (1)
  • plugins/entities/plugin.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/handlers/resource_handler.go
  • pkg/handlers/resource_handler_test.go
  • pkg/services/resource_test.go
  • pkg/services/resource.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added force-delete actions for resources, including direct and owner-scoped endpoints.
    • Support now cascades through related child resources when a resource is in a finalizing state.
  • Bug Fixes

    • Enforced reason validation for force-delete requests.
    • Added safeguards for invalid states, missing resources, and restricted resources.
    • Improved handling to prevent partial deletion when a dependent resource blocks the operation.
  • Tests

    • Expanded automated coverage with new handler, service, and end-to-end tests for force-delete flows.

Walkthrough

Adds a ForceDelete capability to hard-delete resources bypassing normal delete restrictions. ResourceService.ForceDelete validates the resource is in a finalizing state, blocks deletion when RequiredAdapters exist, and recursively cascades to children via forceDeleteResourceTree. New ForceDelete/ForceDeleteByOwner HTTP handlers validate a reason field and delegate to the service; routes are registered for top-level and nested entities. Unit and integration tests cover success, cascade, conflict, not-found, and adapter-blocking scenarios.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Handler as ResourceHandler
  participant Service as ResourceService
  participant DAO as resourceDao

  Client->>Handler: POST /{plural}/{id}/force-delete {reason}
  Handler->>Handler: validate reason length
  Handler->>Service: ForceDelete(kind, id, reason)
  Service->>DAO: GetForUpdate(kind, id)
  Service->>Service: verify DeletedTime != nil
  Service->>Service: forceDeleteResourceTree (recursive on children)
  Service->>DAO: Delete(resource)
  Service-->>Handler: nil or *ServiceError
  Handler-->>Client: 204 No Content or error status
Loading

Related PRs: No related PRs identified from provided information.

Suggested labels: enhancement, needs-security-review

Suggested reviewers: No reviewer information provided.

CWE-284 (Improper Access Control): ForceDeleteByOwner performs a GetByOwner check as an access-control gate before calling ForceDelete — verify this cannot be bypassed via a mismatched parent_id/id pair or race condition between the check and the delete (TOCTOU).

CWE-863 (Incorrect Authorization): No RBAC/role gating visible in the handler code for force-delete routes — confirm authorization middleware is applied upstream of these new routes; force-deletion is destructive and irreversible (hard delete, not soft delete).

CWE-362 (Race Condition): GetForUpdate/FindByKindAndOwnerForUpdate row locks are used, but recursive cascade across multiple lock acquisitions in forceDeleteResourceTree should be reviewed for deadlock/lock-ordering issues under concurrent force-deletes.

No dependency, CI/CD, IDE config, or .gitattributes changes present in this diff — supply chain surface unaffected by this PR.

🚥 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 CWE-532: forceDeleteResourceTree logs raw reason and caller; actorFromContext can carry email/username, so sensitive data can hit logs. Remove reason from info logs, avoid logging caller identity verbatim, or redact/hash both fields before emission; keep only opaque IDs.
✅ Passed checks (9 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the new force-delete endpoint for generic resources.
Description check ✅ Passed The description matches the implemented force-delete endpoints, 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 non-test log statements in changed code include token/password/credential/secret fields or interpolated strings; force-delete logs only use reason/caller/IDs (CWE-532/200).
No Hardcoded Secrets ✅ Passed No hardcoded creds or secret literals found in touched files; only test fixtures and runtime token retrieval, with no CWE-798 pattern present.
No Weak Cryptography ✅ Passed PASS: Touched files contain no crypto/md5, crypto/des, crypto/rc4, SHA1-for-security, ECB, or non-constant-time secret compares (CWE-327/328/697/916).
No Injection Vectors ✅ Passed PASS: No new CWE-89/78/79/502 sinks; ForceDelete validates input and uses DAO calls, and route strings are literals/trusted registry values.
No Privileged Containers ✅ Passed Scanned changed YAML/templates (.tekton, charts); no privileged:true, hostPID/Network/IPC, allowPrivilegeEscalation:true, SYS_ADMIN, or root USER/runAsUser:0 found.
✨ 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 816 lines (>500) +2
Sensitive paths none +0
Test coverage Missing tests for: plugins/entities +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 thread pkg/services/resource_test.go
Comment thread test/integration/resource_force_delete_test.go
Comment thread test/integration/resource_force_delete_test.go
@kuudori kuudori force-pushed the HYPERFLEET-1155-feat-force-delete-generic-resources branch from 8779683 to c0d96a5 Compare June 30, 2026 21:10
Comment thread test/integration/resource_force_delete_test.go
Comment thread pkg/services/resource.go
Comment thread pkg/services/resource.go
Comment thread test/integration/resource_force_delete_test.go Outdated
@pnguyen44

Copy link
Copy Markdown
Contributor

JIRA description says "soft-deleting" but per ADR-0013 the intent is hard-delete. Worth updating the ticket wording to avoid future confusion.

@kuudori kuudori force-pushed the HYPERFLEET-1155-feat-force-delete-generic-resources branch from c0d96a5 to 1c2f75c Compare July 1, 2026 20:22
Comment on lines +310 to +316
if _, err := h.service.GetByOwner(r.Context(), h.descriptor.Kind, id, parentID); err != nil {
return nil, err
}

if err := h.service.ForceDelete(r.Context(), h.descriptor.Kind, id, req.Reason); err != nil {
return nil, err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: Wdyt about creating a ForceDeleteByOwner(ctx, kind, id, parentID, reason) for this one?
The two-call approach works correctly (no TOCTOU — ForceDelete re-locks via GetForUpdate), but breaks the handler's "validate request, delegate once" pattern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'd stick with the current implementation. The handler just calls the service layer, no additional logic 🙂
A dedicated service method would also create drift if the delete flow changes later...

Comment thread test/integration/resource_force_delete_test.go
Comment thread plugins/wifconfigs/plugin.go Outdated
@kuudori kuudori force-pushed the HYPERFLEET-1155-feat-force-delete-generic-resources branch from 1c2f75c to 8da9e50 Compare July 2, 2026 16:14
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 8da9e50 to 9a28571 Compare July 2, 2026 16:17
@kuudori

kuudori commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

/test unit

@kuudori

kuudori commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

/test presubmits-integration

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.

3 participants