Skip to content

CNTRLPLANE-3943: feat(destroy): add --force flag to strip finalizers on grace period expiry - #9134

Open
bryan-cox wants to merge 1 commit into
openshift:mainfrom
bryan-cox:force-destroy-finalizers
Open

CNTRLPLANE-3943: feat(destroy): add --force flag to strip finalizers on grace period expiry#9134
bryan-cox wants to merge 1 commit into
openshift:mainfrom
bryan-cox:force-destroy-finalizers

Conversation

@bryan-cox

@bryan-cox bryan-cox commented Jul 28, 2026

Copy link
Copy Markdown
Member

What this PR does / why we need it:

Adds a --force flag to hypershift destroy cluster that, when the grace period expires, force-removes all finalizers from child resources and continues with best-effort infrastructure cleanup. This fixes the Azure CI deprovisioner which fails to clean up stuck HostedClusters when the 7-layer finalizer chain gets blocked by Azure API failures.

Without this fix, orphaned HCs accumulate on the CI cluster (37 were found stuck with deletionTimestamp set but finalizers never removed), consuming cluster resources until manual intervention.

Key behaviors:

  • When --force is set and the grace period expires, finalizers are stripped bottom-up: AzureMachines → AzureClusters → CAPI Machines → CAPI Clusters → HostedControlPlane → Deployments → NodePools → HostedCluster (preserving the destroy finalizer) → Namespace
  • Platform-specific cleanup errors are logged but don't abort the destroy
  • CRDs not installed on the cluster (e.g., CAPZ types on non-Azure clusters) are handled gracefully via meta.IsNoMatchError
  • The destroy finalizer is preserved on the HC so removeFinalizer() can run after platform cleanup

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3943

Special notes for your reviewer:

  • The returnOrForceLog helper reduces the repeated if !o.ForceDestroy { return err } pattern across DestroyCluster
  • forceRemoveAllFinalizers currently hardcodes Azure CAPZ types — this is intentional since the feature targets the Azure CI deprovisioner. Other platform types can be added later if needed.
  • Namespace finalizers are stripped before deletion to handle terminating namespaces with stuck finalizers

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Summary by CodeRabbit

  • New Features
    • Added a --force option to cluster destruction.
    • In force mode, finalizers can be removed after the grace period so cleanup can continue.
  • Bug Fixes
    • Deletion now proceeds in best-effort mode when finalizer removal and platform cleanup partially fail, reporting combined errors when multiple steps encounter issues.
  • Tests
    • Expanded unit test coverage for forced finalizer removal, list-handling tolerance, and error aggregation behavior.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 28, 2026
@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 28, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 28, 2026

Copy link
Copy Markdown

@bryan-cox: This pull request references CNTRLPLANE-3943 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the bug to target the "5.0.0" version, but no target version was set.

Details

In response to this:

What this PR does / why we need it:

Adds a --force flag to hypershift destroy cluster that, when the grace period expires, force-removes all finalizers from child resources and continues with best-effort infrastructure cleanup. This fixes the Azure CI deprovisioner which fails to clean up stuck HostedClusters when the 7-layer finalizer chain gets blocked by Azure API failures.

Without this fix, orphaned HCs accumulate on the CI cluster (37 were found stuck with deletionTimestamp set but finalizers never removed), consuming cluster resources until manual intervention.

Key behaviors:

  • When --force is set and the grace period expires, finalizers are stripped bottom-up: AzureMachines → AzureClusters → CAPI Machines → CAPI Clusters → HostedControlPlane → Deployments → NodePools → HostedCluster (preserving the destroy finalizer) → Namespace
  • Platform-specific cleanup errors are logged but don't abort the destroy
  • CRDs not installed on the cluster (e.g., CAPZ types on non-Azure clusters) are handled gracefully via meta.IsNoMatchError
  • The destroy finalizer is preserved on the HC so removeFinalizer() can run after platform cleanup

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3943

Special notes for your reviewer:

  • The returnOrForceLog helper reduces the repeated if !o.ForceDestroy { return err } pattern across DestroyCluster
  • forceRemoveAllFinalizers currently hardcodes Azure CAPZ types — this is intentional since the feature targets the Azure CI deprovisioner. Other platform types can be added later if needed.
  • Namespace finalizers are stripped before deletion to handle terminating namespaces with stuck finalizers

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci

openshift-ci Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bryan-cox

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

The pull request process is described 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

@openshift-ci openshift-ci Bot added approved Indicates a PR has been approved by an approver from all required OWNERS files. area/cli Indicates the PR includes changes for CLI and removed do-not-merge/needs-area labels Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The cluster destroy command adds a --force option. Forced destruction strips finalizers from child resources, preserves the HostedCluster destroy finalizer, removes control-plane namespace finalizers, deletes the namespace, and continues cleanup while logging or aggregating errors. Tests cover successful removal, missing CRDs, patch failures, and aggregated errors.

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant DestroyCluster
  participant KubernetesAPI
  participant HostedClusterResources
  Operator->>DestroyCluster: Run destroy --force
  DestroyCluster->>KubernetesAPI: Wait for remaining finalizers
  KubernetesAPI-->>DestroyCluster: Wait fails
  DestroyCluster->>HostedClusterResources: Strip child-resource finalizers
  HostedClusterResources->>KubernetesAPI: Patch resources
  DestroyCluster->>KubernetesAPI: Delete control-plane namespace
  KubernetesAPI-->>DestroyCluster: Cleanup result
Loading
🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test Structure And Quality ⚠️ Warning The new tests are granular, but they contain many bare Gomega assertions (15 HaveOccurred and 8 BeEmpty checks) without contextual failure messages. Add brief failure messages to the new assertions, especially error and state checks; the fake-client unit tests otherwise need no cleanup or timeouts.
✅ Passed checks (10 passed)
Check name Status Explanation
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.
Stable And Deterministic Test Names ✅ Passed No Ginkgo It/Describe/Context/When titles were added; the new t.Run names are static and deterministic, with no dynamic identifiers.
Topology-Aware Scheduling Compatibility ✅ Passed Changes are limited to destroy CLI/finalizer cleanup; no deployments, controllers, pod templates, affinity, topology spread, node selectors, or replica logic were added.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests were added; the new tests are unit tests using fake clients and no IPv4 or external connectivity assumptions were found.
No-Weak-Crypto ✅ Passed No MD5/SHA1/DES/RC4/ECB, custom crypto, or secret comparisons were added; touched files only add finalizer cleanup and tests.
Container-Privileges ✅ Passed PR only changes Go destroy logic/tests and adds a CLI flag; no container/K8s manifests or privilege settings were added or modified.
No-Sensitive-Data-In-Logs ✅ Passed New logs only emit namespace/name/infraID and generic status/errors; no passwords, tokens, API keys, PII, or customer data are logged.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a --force destroy flag to strip finalizers after grace period expiry.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
cmd/cluster/core/destroy_test.go (1)

314-331: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the ForceDestroy control-flow and list-error paths.

Only the no-match tolerance case is exercised. Consider adding: (1) a genuine List failure asserting the error is returned, and (2) a DestroyCluster/returnOrForceLog case verifying that platform-cleanup and destroy-finalizer failures are logged-and-continued with ForceDestroy: true but returned when false — that conditional is the core of this PR and is currently untested.

🤖 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 `@cmd/cluster/core/destroy_test.go` around lines 314 - 331, Expand
TestStripFinalizersFromList with a genuine List failure case that asserts the
error is returned, while preserving the existing NoKindMatchError success case.
Add coverage around DestroyCluster and returnOrForceLog to verify
platform-cleanup and destroy-finalizer failures are logged and execution
continues with ForceDestroy true, but are returned when ForceDestroy is false.

Source: Coding guidelines

🤖 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 `@cmd/cluster/core/destroy.go`:
- Around line 295-318: Make forceRemoveAllFinalizers platform-agnostic by
stripping finalizers from the provider-specific infrastructure machine and
cluster resources for the selected platform, not only capzv1.AzureMachineList
and AzureClusterList. Reuse the existing platform/provider selection mechanism
so AWS, OpenStack, KubeVirt, PowerVS, and GCP force-destroy paths unblock their
control-plane namespaces while preserving the current bottom-up processing
order.
- Around line 344-359: Update the control-plane namespace cleanup in the destroy
flow to clear namespace finalizers through the namespace finalize subresource,
including spec.finalizers, rather than relying only on stripFinalizers. Preserve
error aggregation, and log “Deleted control plane namespace” only when c.Delete
succeeds; treat IsNotFound as a no-op without logging success.

---

Nitpick comments:
In `@cmd/cluster/core/destroy_test.go`:
- Around line 314-331: Expand TestStripFinalizersFromList with a genuine List
failure case that asserts the error is returned, while preserving the existing
NoKindMatchError success case. Add coverage around DestroyCluster and
returnOrForceLog to verify platform-cleanup and destroy-finalizer failures are
logged and execution continues with ForceDestroy true, but are returned when
ForceDestroy is false.
🪄 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: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 27098d98-1b5b-46c7-833d-f93a75de49da

📥 Commits

Reviewing files that changed from the base of the PR and between 5e4dd58 and bbf06af.

📒 Files selected for processing (3)
  • cmd/cluster/cluster.go
  • cmd/cluster/core/destroy.go
  • cmd/cluster/core/destroy_test.go

Comment thread cmd/cluster/core/destroy.go
Comment thread cmd/cluster/core/destroy.go Outdated
@bryan-cox
bryan-cox force-pushed the force-destroy-finalizers branch from bbf06af to 3030955 Compare July 28, 2026 01:32
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 54.83871% with 56 lines in your changes missing coverage. Please review.
✅ Project coverage is 44.57%. Comparing base (d166b4f) to head (a47428c).
⚠️ Report is 20 commits behind head on main.

Files with missing lines Patch % Lines
cmd/cluster/core/destroy.go 55.28% 47 Missing and 8 partials ⚠️
cmd/cluster/cluster.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9134      +/-   ##
==========================================
+ Coverage   44.51%   44.57%   +0.06%     
==========================================
  Files         774      774              
  Lines       96997    97120     +123     
==========================================
+ Hits        43179    43295     +116     
+ Misses      50830    50825       -5     
- Partials     2988     3000      +12     
Files with missing lines Coverage Δ
cmd/cluster/cluster.go 0.00% <0.00%> (ø)
cmd/cluster/core/destroy.go 31.50% <55.28%> (+19.39%) ⬆️

... and 6 files with indirect coverage changes

Flag Coverage Δ
cmd-support 38.46% <54.83%> (+0.07%) ⬆️
cpo-hostedcontrolplane 47.22% <ø> (ø)
cpo-other 45.25% <ø> (ø)
hypershift-operator 54.46% <ø> (+0.01%) ⬆️
other 33.07% <ø> (+0.43%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…xpiry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@bryan-cox
bryan-cox force-pushed the force-destroy-finalizers branch from 3030955 to a47428c Compare July 28, 2026 01:37
@bryan-cox
bryan-cox marked this pull request as ready for review July 28, 2026 10:30
@openshift-ci openshift-ci Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 28, 2026
@openshift-ci
openshift-ci Bot requested review from clebs and jparrill July 28, 2026 10:31
@openshift-ci

openshift-ci Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@bryan-cox: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. area/cli Indicates the PR includes changes for CLI jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants