Skip to content

CNTRLPLANE-3793: enhance HyperShift teardown to prevent AWS resource leaks - #81788

Merged
openshift-merge-bot[bot] merged 2 commits into
openshift:mainfrom
jparrill:improve-hypershift-teardown
Jul 22, 2026
Merged

CNTRLPLANE-3793: enhance HyperShift teardown to prevent AWS resource leaks#81788
openshift-merge-bot[bot] merged 2 commits into
openshift:mainfrom
jparrill:improve-hypershift-teardown

Conversation

@jparrill

@jparrill jparrill commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add force-cleanup fallback to the HyperShift pruner in core-services/ipi-deprovision/aws.sh to prevent AWS resource leaks when hypershift destroy cluster aws hangs on stuck finalizers
  • Add timeout 30m + --cluster-grace-period 15m to the graceful destroy path (currently has neither)
  • Replace exit $had_failure with return-code tracking so the VPC expiration cleanup always runs

Fixes

Problem

When hypershift destroy cluster aws hangs (stuck finalizers, degraded controller), the script exits early and the VPC expiration-date cleanup section never runs — leaving orphaned VPCs, EC2 instances, NAT gateways, IAM roles, and OIDC providers in AWS.

Changes

Three new functions added:

  • strip_finalizers() — clears all finalizers from resources of a given type in a namespace
  • hypershift_force_cleanup() — for each stuck HC: extracts metadata, strips finalizers from 9 resource types (machine, awsmachine, machineset, machinedeployment, cluster, awsendpointservice, hostedcontrolplane, nodepool, hostedcluster), terminates orphaned EC2 instances, runs hypershift destroy infra/iam aws directly via AWS API
  • hypershift_pruner() — replaces the inline block: graceful destroy with timeout, falls back to force-cleanup on failure, returns rc instead of calling exit

The OIDC deadlock concern from the original code is resolved because force-cleanup strips finalizers before deletion (no controller reconciliation needed) and infra/IAM cleanup uses the AWS API directly.

Finalizer list verified against a live management cluster and the HyperShift source code.

Test plan

  • bash -n aws.sh — syntax validation passes
  • Tested locally against a real management cluster (jparrill-dev) with HC jparrill-hosted (us-west-1)
  • All AWS resources cleaned up: S3 bucket, ELB, security groups, VPC endpoints, DNS hosted zone, route tables, NAT gateway, subnets, VPC, EIP, DHCP options, OIDC provider, 7 IAM roles, worker instance profile
  • Zero orphaned EC2 instances after cleanup
  • CI rehearsal on this PR

/cc @openshift/hypershift-team

🤖 Generated with Claude Code

Summary by CodeRabbit

Updates the OpenShift CI AWS deprovisioning teardown for HyperShift in core-services/ipi-deprovision/aws.sh to prevent AWS resource leaks when hypershift destroy cluster aws stalls on stuck finalizers or degraded HostedCluster controllers.

In practice, the script now:

  • Adds a tiered destruction strategy for HostedClusters that fail graceful teardown:
    • Graceful phase: retries teardown with hypershift destroy cluster aws capped at 30 minutes and --cluster-grace-period 15m.
    • If it fails, hypershift_force_cleanup escalates based on HostedCluster deletion age (minutes since metadata.deletionTimestamp is set), running only the highest applicable tier per invocation:
      • >= 30m: delete/strip awsmachine and Machine finalizers; terminate matching EC2 instances tagged kubernetes.io/cluster/${infraID}=owned.
      • >= 1h: delete/strip finalizers for HCP/CAPI-related resources (e.g., hostedcontrolplane, cluster, machinedeployment, etc.).
      • >= 1.5h: delete the hosted control-plane namespace (when not already deleting).
      • >= 2h (last resort): delete nodepools and strip finalizers on nodepool + the HostedCluster.
    • Finalizers are removed best-effort only from deletion-timestamped objects via oc patch ... -p '{"metadata":{"finalizers":null}}'.
  • Ensures AWS infra and IAM cleanup runs on every force-cleanup attempt (not only when Kubernetes objects disappear):
    • Always executes hypershift destroy infra aws and hypershift destroy iam aws (each 30m timeout) using the derived infraID and AWS region (and HYPERSHIFT_BASE_DOMAIN default).
  • Reworks the pruning workflow to target “stale” HostedClusters (older than 4 hours):
    • Enumerates stale HostedCluster objects either cluster-wide (HYPERSHIFT_PRUNER_ALL_NAMESPACES) or within the clusters namespace.
    • Attempts graceful destroy per stale HostedCluster and records only failures for subsequent force cleanup.
  • Tracks return codes so broader AWS expiration cleanup continues even if pruning fails:
    • Captures the pruner’s exit status (hypershift_pruner_rc) and sets the script’s final_rc at the end, after the VPC deprovisioning work and other cleanup steps, instead of exiting early.

The change includes syntax validation and local testing; CI rehearsal remains pending.

@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 13, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

@jparrill: This pull request references CNTRLPLANE-3793 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 story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Summary

  • Add force-cleanup fallback to the HyperShift pruner in core-services/ipi-deprovision/aws.sh to prevent AWS resource leaks when hypershift destroy cluster aws hangs on stuck finalizers
  • Add timeout 30m + --cluster-grace-period 15m to the graceful destroy path (currently has neither)
  • Replace exit $had_failure with return-code tracking so the VPC expiration cleanup always runs

Fixes

Problem

When hypershift destroy cluster aws hangs (stuck finalizers, degraded controller), the script exits early and the VPC expiration-date cleanup section never runs — leaving orphaned VPCs, EC2 instances, NAT gateways, IAM roles, and OIDC providers in AWS.

Changes

Three new functions added:

  • strip_finalizers() — clears all finalizers from resources of a given type in a namespace
  • hypershift_force_cleanup() — for each stuck HC: extracts metadata, strips finalizers from 9 resource types (machine, awsmachine, machineset, machinedeployment, cluster, awsendpointservice, hostedcontrolplane, nodepool, hostedcluster), terminates orphaned EC2 instances, runs hypershift destroy infra/iam aws directly via AWS API
  • hypershift_pruner() — replaces the inline block: graceful destroy with timeout, falls back to force-cleanup on failure, returns rc instead of calling exit

The OIDC deadlock concern from the original code is resolved because force-cleanup strips finalizers before deletion (no controller reconciliation needed) and infra/IAM cleanup uses the AWS API directly.

Finalizer list verified against a live management cluster and the HyperShift source code.

Test plan

  • bash -n aws.sh — syntax validation passes
  • Tested locally against a real management cluster (jparrill-dev) with HC jparrill-hosted (us-west-1)
  • All AWS resources cleaned up: S3 bucket, ELB, security groups, VPC endpoints, DNS hosted zone, route tables, NAT gateway, subnets, VPC, EIP, DHCP options, OIDC provider, 7 IAM roles, worker instance profile
  • Zero orphaned EC2 instances after cleanup
  • CI rehearsal on this PR

/cc @openshift/hypershift-team

🤖 Generated with Claude Code

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 13, 2026

Copy link
Copy Markdown
Contributor

@jparrill: GitHub didn't allow me to request PR reviews from the following users: openshift/hypershift-team.

Note that only openshift members and repo collaborators can review this PR, and authors cannot review their own PRs.

Details

In response to this:

Summary

  • Add force-cleanup fallback to the HyperShift pruner in core-services/ipi-deprovision/aws.sh to prevent AWS resource leaks when hypershift destroy cluster aws hangs on stuck finalizers
  • Add timeout 30m + --cluster-grace-period 15m to the graceful destroy path (currently has neither)
  • Replace exit $had_failure with return-code tracking so the VPC expiration cleanup always runs

Fixes

Problem

When hypershift destroy cluster aws hangs (stuck finalizers, degraded controller), the script exits early and the VPC expiration-date cleanup section never runs — leaving orphaned VPCs, EC2 instances, NAT gateways, IAM roles, and OIDC providers in AWS.

Changes

Three new functions added:

  • strip_finalizers() — clears all finalizers from resources of a given type in a namespace
  • hypershift_force_cleanup() — for each stuck HC: extracts metadata, strips finalizers from 9 resource types (machine, awsmachine, machineset, machinedeployment, cluster, awsendpointservice, hostedcontrolplane, nodepool, hostedcluster), terminates orphaned EC2 instances, runs hypershift destroy infra/iam aws directly via AWS API
  • hypershift_pruner() — replaces the inline block: graceful destroy with timeout, falls back to force-cleanup on failure, returns rc instead of calling exit

The OIDC deadlock concern from the original code is resolved because force-cleanup strips finalizers before deletion (no controller reconciliation needed) and infra/IAM cleanup uses the AWS API directly.

Finalizer list verified against a live management cluster and the HyperShift source code.

Test plan

  • bash -n aws.sh — syntax validation passes
  • Tested locally against a real management cluster (jparrill-dev) with HC jparrill-hosted (us-west-1)
  • All AWS resources cleaned up: S3 bucket, ELB, security groups, VPC endpoints, DNS hosted zone, route tables, NAT gateway, subnets, VPC, EIP, DHCP options, OIDC provider, 7 IAM roles, worker instance profile
  • Zero orphaned EC2 instances after cleanup
  • CI rehearsal on this PR

/cc @openshift/hypershift-team

🤖 Generated with Claude Code

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.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

HyperShift pruning now attempts graceful destruction before forced Kubernetes and AWS cleanup. The script records pruner failures and combines them with deprovisioning failures to determine its final exit status.

Changes

HyperShift pruning

Layer / File(s) Summary
Tiered HyperShift cleanup
core-services/ipi-deprovision/aws.sh
Adds finalizer removal, staged HostedCluster resource cleanup, optional tagged EC2 termination, and AWS infrastructure and IAM teardown.
Pruner orchestration and exit status
core-services/ipi-deprovision/aws.sh
Attempts graceful destruction for stale HostedClusters, force-cleans failed attempts, captures the pruner result, and incorporates it into the final exit status.

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

Sequence Diagram(s)

sequenceDiagram
  participant Deprovisioner
  participant KubernetesAPI
  participant AWS
  participant HyperShiftCLI
  Deprovisioner->>KubernetesAPI: Enumerate stale HostedClusters
  Deprovisioner->>HyperShiftCLI: Attempt graceful cluster destroy
  Deprovisioner->>KubernetesAPI: Delete resources and strip finalizers
  Deprovisioner->>AWS: Terminate infraID-tagged EC2 instances
  Deprovisioner->>HyperShiftCLI: Destroy AWS infrastructure and IAM
  Deprovisioner->>Deprovisioner: Combine cleanup and deprovisioning status
Loading

Suggested reviewers: smg247, prucek

🚥 Pre-merge checks | ✅ 14 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (14 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: strengthening HyperShift teardown to prevent AWS resource leaks.
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 Only core-services/ipi-deprovision/aws.sh changed; no Ginkgo/spec test titles were added or modified.
Test Structure And Quality ✅ Passed No Ginkgo test files were changed; the PR only modifies a shell script, so this test-structure check is not applicable.
Microshift Test Compatibility ✅ Passed PR only changes shell teardown logic in aws.sh; no Ginkgo e2e tests were added or modified, so this MicroShift check is not applicable.
Single Node Openshift (Sno) Test Compatibility ✅ Passed Only core-services/ipi-deprovision/aws.sh changed; it contains no Ginkgo tests or SNO-specific assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed Only teardown logic changed in aws.sh; no affinity, nodeSelector, spread, PDB, or topology-based scheduling was introduced.
Ote Binary Stdout Contract ✅ Passed Only core-services/ipi-deprovision/aws.sh changed; no OTE binary main/init/TestMain/suite setup stdout writes were introduced.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed Only core-services/ipi-deprovision/aws.sh changed; no Ginkgo tests or network assumptions were added, so the IPv6/disconnected test check is not applicable.
No-Weak-Crypto ✅ Passed The changed aws.sh logic is cleanup-only; diff/searches show no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or secret/token comparisons.
Container-Privileges ✅ Passed Only core-services/ipi-deprovision/aws.sh changed; no container/K8s manifests or privileged settings were added.
No-Sensitive-Data-In-Logs ✅ Passed New logs only print cluster/resource IDs, age, and status; no added output includes secrets, tokens, PII, hostnames, or customer data.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-merge-bot openshift-merge-bot Bot added the rehearsals-ack Signifies that rehearsal jobs have been acknowledged label Jul 13, 2026

@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)
core-services/ipi-deprovision/aws.sh (1)

103-126: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Sequential processing could make the pruner run very long with multiple stale clusters.

Each iteration of the graceful-destroy loop can take up to 30 minutes, and each force-cleanup iteration can take up to ~60+ minutes (2m wait + 30m infra destroy + 30m iam destroy). Both loops (lines 103-121, 123-126) process clusters sequentially rather than using the queue() helper already established in this file (lines 16-24) for concurrency. With several stale/stuck HostedClusters accumulating, this could make the periodic pruning job run for many hours, risking job-level timeouts.

Consider parallelizing cluster processing similar to the queue/file-flag pattern used by deprovision() (e.g., signal per-cluster success/failure via temp files under a scratch dir, then aggregate).

🤖 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 `@core-services/ipi-deprovision/aws.sh` around lines 103 - 126, Parallelize
both the graceful-destroy loop and the subsequent hypershift_force_cleanup loop
using the existing queue() helper and scratch-file success/failure signaling
pattern from deprovision(). Preserve per-cluster namespace/name handling,
aggregate failed clusters after queued destruction completes, and continue
updating had_failure for force-cleanup failures without processing clusters
sequentially.
🤖 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 `@core-services/ipi-deprovision/aws.sh`:
- Line 257: Quote the $FAILED expansion in the xargs command within the
deprovisioning flow to satisfy SC2086 and prevent unintended glob expansion,
while preserving xargs’ existing whitespace-based splitting behavior.
- Around line 81-85: Update hypershift_force_cleanup so failures from the
timeout-wrapped hypershift destroy infra and destroy iam commands are preserved
instead of being suppressed by || true. Capture and propagate a nonzero status
from either cleanup command while still attempting both operations, so callers
such as hypershift_pruner can increment had_failure and surface the final
failure status.

---

Nitpick comments:
In `@core-services/ipi-deprovision/aws.sh`:
- Around line 103-126: Parallelize both the graceful-destroy loop and the
subsequent hypershift_force_cleanup loop using the existing queue() helper and
scratch-file success/failure signaling pattern from deprovision(). Preserve
per-cluster namespace/name handling, aggregate failed clusters after queued
destruction completes, and continue updating had_failure for force-cleanup
failures without processing clusters sequentially.
🪄 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: 0310a592-ea41-45d1-ac2c-cf74496b8c50

📥 Commits

Reviewing files that changed from the base of the PR and between d526949 and eb2c1ae.

📒 Files selected for processing (1)
  • core-services/ipi-deprovision/aws.sh

Comment thread core-services/ipi-deprovision/aws.sh Outdated
Comment thread core-services/ipi-deprovision/aws.sh Outdated
@deepsm007

Copy link
Copy Markdown
Contributor

/approve

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 13, 2026
@jparrill
jparrill force-pushed the improve-hypershift-teardown branch from eb2c1ae to f620bf0 Compare July 13, 2026 10:21

@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)
core-services/ipi-deprovision/aws.sh (1)

71-79: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider waiting for EC2 termination before running infra destroy.

aws ec2 terminate-instances is asynchronous; proceeding straight to hypershift destroy infra aws (line 84) without confirming instances are gone can cause spurious failures if VPC/subnet/security-group teardown requires instances to be fully terminated.

♻️ Proposed fix
 	if [[ -n "${instance_ids}" ]]; then
 		echo "  Terminating EC2 instances: ${instance_ids}"
 		aws ec2 terminate-instances --region "${region}" --instance-ids ${instance_ids} || true
+		aws ec2 wait instance-terminated --region "${region}" --instance-ids ${instance_ids} || true
 	fi

[reliability]

🤖 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 `@core-services/ipi-deprovision/aws.sh` around lines 71 - 79, Update the
orphaned EC2 cleanup flow around the terminate-instances call to wait until
every instance in instance_ids reaches the terminated state before proceeding to
hypershift destroy infra aws. Reuse the existing region and instance ID values,
and ensure the destroy command is not reached while termination is still
pending.
🤖 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 `@core-services/ipi-deprovision/aws.sh`:
- Around line 39-44: Wrap every remaining oc invocation in strip_finalizers and
hypershift_force_cleanup with the existing timeout mechanism, including oc get,
oc patch, oc delete, and oc wait calls. Preserve the current arguments, cleanup
flow, and tolerated-failure behavior while ensuring stalled management-cluster
API calls cannot block indefinitely.
- Around line 52-57: Remove the guessed defaults for infra_id and region in the
deprovisioning flow. Make the HostedCluster lookups fail explicitly when either
value is missing or the oc command fails, before the AWS tag filtering and
hypershift destroy operations use them; preserve the existing values from the
HostedCluster for successful lookups.

---

Nitpick comments:
In `@core-services/ipi-deprovision/aws.sh`:
- Around line 71-79: Update the orphaned EC2 cleanup flow around the
terminate-instances call to wait until every instance in instance_ids reaches
the terminated state before proceeding to hypershift destroy infra aws. Reuse
the existing region and instance ID values, and ensure the destroy command is
not reached while termination is still pending.
🪄 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: 35b9e01a-e900-4ee1-936d-0e4e04c8fb9c

📥 Commits

Reviewing files that changed from the base of the PR and between eb2c1ae and f620bf0.

📒 Files selected for processing (1)
  • core-services/ipi-deprovision/aws.sh

Comment on lines +39 to +44
function strip_finalizers() {
local resource="${1}" ns="${2}"
oc get "${resource}" -n "${ns}" --no-headers -o custom-columns=NAME:.metadata.name 2>/dev/null | while read -r obj; do
[[ -n "${obj}" ]] && oc patch "${resource}" "${obj}" -n "${ns}" -p '{"metadata":{"finalizers":null}}' --type=merge 2>/dev/null || true
done
}

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='core-services/ipi-deprovision/aws.sh'

echo '--- map structure ---'
ast-grep outline "$file" --view expanded

echo '--- relevant line ranges ---'
sed -n '1,170p' "$file" | cat -n

echo '--- search for timeout usage ---'
rg -n 'timeout|oc get hostedcluster|oc delete hostedcluster|oc patch|oc wait|strip_finalizers|hypershift_force_cleanup' "$file"

Repository: openshift/release

Length of output: 10703


Add timeouts around the remaining oc calls in core-services/ipi-deprovision/aws.sh:39-69. strip_finalizers and the oc get/oc delete/oc wait calls in hypershift_force_cleanup are still unbounded, while the surrounding hypershift destroy calls already use timeout. If the management-cluster API stalls, cleanup can hang indefinitely 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 `@core-services/ipi-deprovision/aws.sh` around lines 39 - 44, Wrap every
remaining oc invocation in strip_finalizers and hypershift_force_cleanup with
the existing timeout mechanism, including oc get, oc patch, oc delete, and oc
wait calls. Preserve the current arguments, cleanup flow, and tolerated-failure
behavior while ensuring stalled management-cluster API calls cannot block
indefinitely.

Comment thread core-services/ipi-deprovision/aws.sh Outdated
Comment on lines +52 to +57
local infra_id region
infra_id="$(oc get hostedcluster -n "${hc_ns}" "${hc_name}" -o jsonpath='{.spec.infraID}' 2>/dev/null || echo "")"
region="$(oc get hostedcluster -n "${hc_ns}" "${hc_name}" -o jsonpath='{.spec.platform.aws.region}' 2>/dev/null || echo "")"
: "${infra_id:=${hc_name}}"
: "${region:=us-east-1}"
echo " infraID=${infra_id} region=${region} hcp_ns=${hcp_ns}"

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guessed infra_id/region fallback can silently no-op the cleanup instead of failing.

If the oc get hostedcluster lookups fail or return empty, infra_id falls back to hc_name and region falls back to us-east-1. Since infra_id/region drive both the EC2 tag filter (line 74) and the direct hypershift destroy infra/iam calls (84-85), a wrong guess makes those AWS calls simply find nothing and return success — leaking exactly the resources this PR is meant to catch, without force_rc ever reflecting the failure.

🐛 Proposed fix
 	local infra_id region
 	infra_id="$(oc get hostedcluster -n "${hc_ns}" "${hc_name}" -o jsonpath='{.spec.infraID}' 2>/dev/null || echo "")"
 	region="$(oc get hostedcluster -n "${hc_ns}" "${hc_name}" -o jsonpath='{.spec.platform.aws.region}' 2>/dev/null || echo "")"
-	: "${infra_id:=${hc_name}}"
-	: "${region:=us-east-1}"
+	if [[ -z "${infra_id}" || -z "${region}" ]]; then
+		echo "  ERROR: could not determine infraID/region for ${hc_ns}/${hc_name}; skipping direct AWS cleanup to avoid targeting wrong resources" >&2
+		return 1
+	fi
 	echo "  infraID=${infra_id} region=${region} hcp_ns=${hcp_ns}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
local infra_id region
infra_id="$(oc get hostedcluster -n "${hc_ns}" "${hc_name}" -o jsonpath='{.spec.infraID}' 2>/dev/null || echo "")"
region="$(oc get hostedcluster -n "${hc_ns}" "${hc_name}" -o jsonpath='{.spec.platform.aws.region}' 2>/dev/null || echo "")"
: "${infra_id:=${hc_name}}"
: "${region:=us-east-1}"
echo " infraID=${infra_id} region=${region} hcp_ns=${hcp_ns}"
local infra_id region
infra_id="$(oc get hostedcluster -n "${hc_ns}" "${hc_name}" -o jsonpath='{.spec.infraID}' 2>/dev/null || echo "")"
region="$(oc get hostedcluster -n "${hc_ns}" "${hc_name}" -o jsonpath='{.spec.platform.aws.region}' 2>/dev/null || echo "")"
if [[ -z "${infra_id}" || -z "${region}" ]]; then
echo " ERROR: could not determine infraID/region for ${hc_ns}/${hc_name}; skipping direct AWS cleanup to avoid targeting wrong resources" >&2
return 1
fi
echo " infraID=${infra_id} region=${region} hcp_ns=${hcp_ns}"
🤖 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 `@core-services/ipi-deprovision/aws.sh` around lines 52 - 57, Remove the
guessed defaults for infra_id and region in the deprovisioning flow. Make the
HostedCluster lookups fail explicitly when either value is missing or the oc
command fails, before the AWS tag filtering and hypershift destroy operations
use them; preserve the existing values from the HostedCluster for successful
lookups.

@ironcladlou

Copy link
Copy Markdown
Contributor

This one makes me a little hesitant, lots of hard to test logic in the CI scripts which has a large blast radius if it goes wrong, and is leaking knowledge of state that hypershift owns the tracking of (e.g. should this be incorporated into the CLI somehow?)

This class of tooling might be safer with openshift/hypershift#8909?

@ironcladlou

Copy link
Copy Markdown
Contributor

cc @csrwng

@jparrill

Copy link
Copy Markdown
Contributor Author

/auto-cc

@openshift-ci
openshift-ci Bot requested review from Prucek and smg247 July 15, 2026 07:43
Comment thread core-services/ipi-deprovision/aws.sh Outdated

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.

If you strip finalizers, then some cleanup will not happen (like deleting the control plane namespace), I think we need to be a little smarter with this fast deletion. One option would be to simply remove the finalizer from any awsmachine resource once they have a deletion timestamp (and only after they have a deletion timestamp).
As a next step, I would remove the finalizer from the hcp resource only after it has a deletion timestamp.
For this last part, we need to ensure that any left over volumes, load balancers, s3 buckets are properly cleaned up.

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.

Good call. Changed strip_finalizers to only act on resources that already have a deletionTimestamp — it now filters with jq before patching:

oc get "${resource}" -n "${ns}" -o json | \
    jq -r '.items[] | select(.metadata.deletionTimestamp != null) | .metadata.name' | \
    while read -r obj; do ...

Additionally, the force cleanup now explicitly deletes HCP namespace resources (--wait=false) before stripping finalizers, so they always have a deletionTimestamp set. This preserves normal cleanup ordering — finalizers are only removed to unblock stuck deletions, not to bypass cleanup.

@jparrill
jparrill force-pushed the improve-hypershift-teardown branch from f620bf0 to d3426b3 Compare July 20, 2026 07:44
Comment thread core-services/ipi-deprovision/aws.sh Outdated
Comment on lines 72 to 79

@celebdor celebdor Jul 20, 2026

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.

I think that just as with the rest of the script, using json output would be more appropriate. Maybe something like (I also changed instance_ids to be a shell array to be safer (and shellcheck compliant) with word splitting.

Suggested change
local -a instance_ids
readarray -t instance_ids < <(aws ec2 describe-instances --region "${region}" \
--filters "Name=tag:kubernetes.io/cluster/${infra_id},Values=owned" "Name=instance-state-name,Values=running,pending,stopping,stopped" \
--query 'Reservations[].Instances[].InstanceId' --output json 2>/dev/null | jq -r '.[]' 2>/dev/null || true)
if [[ ${#instance_ids[@]} -gt 0 && -n "${instance_ids[0]}" ]]; then
echo " Terminating EC2 instances: ${instance_ids[*]}"
aws ec2 terminate-instances --region "${region}" --instance-ids "${instance_ids[@]}" || true

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.

Done — switched to --output json with jq and a shell array (readarray) for safe word splitting. Thanks for the suggestion.

@jparrill
jparrill force-pushed the improve-hypershift-teardown branch 2 times, most recently from 262ffd3 to a82772c Compare July 21, 2026 14:17
@jparrill

Copy link
Copy Markdown
Contributor Author

Reworked per your feedback — force_cleanup now uses tiered finalizer removal based on the HC deletion age. The pruner cron runs every 15min, so each invocation evaluates how long the HC has been deleting and escalates one tier:

  • Tier 1 (>=30min): strip awsmachine finalizers + terminate EC2 instances directly
  • Tier 2 (>=1hr): strip HCP + CAPI resource finalizers (machineset, machinedeployment, cluster, awsendpointservice, hostedcontrolplane)
  • Tier 3 (>=1.5hr): delete CP namespace if it has no deletionTimestamp
  • Tier 4 (>=2hr): strip HC and NodePool finalizers (last resort)

Finalizers are only removed from resources that already have a deletionTimestamp (unchanged from previous push). AWS infra/IAM cleanup runs on every invocation regardless of tier.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

@jparrill: pj-rehearse could not automatically process this event because the request waited in queue for longer than 5 minutes. Use /pj-rehearse to trigger rehearsals manually.

@jparrill
jparrill force-pushed the improve-hypershift-teardown branch from a82772c to 0ca96df Compare July 21, 2026 20:15
jparrill and others added 2 commits July 22, 2026 17:46
…er removal

Restructure the HyperShift pruner in the ipi-deprovision script to
prevent AWS resource leaks when graceful destroy times out.

When hypershift destroy cluster fails, fall back to
hypershift_force_cleanup which uses tiered finalizer removal based on
how long the HC has been in deletion. The pruner cron runs every 15min,
so each invocation evaluates the deletion age and runs only the highest
applicable tier, avoiding wasted time on lower-level cleanup:

- Tier 1 (>=1hr): strip awsmachine finalizers + terminate EC2 instances
- Tier 2 (>=2hr): strip HCP + CAPI resource finalizers
- Tier 3 (>=3hr): delete CP namespace if not already deleting
- Tier 4 (>=4hr): strip HC and NodePool finalizers (last resort)

Finalizers are only removed from resources that already have a
deletionTimestamp set (not blindly). AWS infra/IAM cleanup runs on
every invocation regardless of tier.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>
…region

Three bugs found during live testing of the tiered teardown:

1. strip_finalizers namespace-wide blast radius: At Tier 4,
   strip_finalizers stripped finalizers from ALL HCs and NodePools in
   the namespace, not just the target cluster. This caused other HCs
   to be garbage-collected before their own force-cleanup could run.
   Fix: add optional 3rd name parameter to strip_finalizers, and scope
   T4 to only the target HC and its NodePools (filtered by
   spec.clusterName).

2. infraID/region loss: When an HC disappears between graceful destroy
   and force-cleanup, the script fell back to HC name as infraID and
   us-east-1 as region, causing infra/IAM cleanup to target the wrong
   resources. Fix: pre-capture infraID and region before graceful
   destroy, pack them into the failed_clusters array, and pass them
   through to hypershift_force_cleanup.

3. hypershift_force_cleanup now accepts optional $3/$4 for
   pre-captured infraID/region, only querying the HC if they are not
   provided.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>
@jparrill
jparrill force-pushed the improve-hypershift-teardown branch from 30533e1 to cfc1861 Compare July 22, 2026 15:47
@jparrill

Copy link
Copy Markdown
Contributor Author

/label acknowledge-critical-fixes-only

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

[REHEARSALNOTIFIER]
@jparrill: no rehearsable tests are affected by this change

Note: If this PR includes changes to step registry files (ci-operator/step-registry/) and you expected jobs to be found, try rebasing your PR onto the base branch. This helps pj-rehearse accurately detect changes when the base branch has moved forward.

Interacting with pj-rehearse

Comment: /pj-rehearse to run up to 5 rehearsals
Comment: /pj-rehearse skip to opt-out of rehearsals
Comment: /pj-rehearse {test-name}, with each test separated by a space, to run one or more specific rehearsals
Comment: /pj-rehearse more to run up to 10 rehearsals
Comment: /pj-rehearse max to run up to 25 rehearsals
Comment: /pj-rehearse auto-ack to run up to 5 rehearsals, and add the rehearsals-ack label on success
Comment: /pj-rehearse list to get an up-to-date list of affected jobs
Comment: /pj-rehearse abort to abort all active rehearsals
Comment: /pj-rehearse network-access-allowed to allow rehearsals of tests that have the restrict_network_access field set to false. This must be executed by an openshift org member who is not the PR author

Once you are satisfied with the results of the rehearsals, comment: /pj-rehearse ack to unblock merge. When the rehearsals-ack label is present on your PR, merge will no longer be blocked by rehearsals.
If you would like the rehearsals-ack label removed, comment: /pj-rehearse reject to re-block merging.

@openshift-ci openshift-ci Bot added the acknowledge-critical-fixes-only Indicates if the issuer of the label is OK with the policy. label Jul 22, 2026
@csrwng

csrwng commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Jul 22, 2026
@openshift-ci

openshift-ci Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: csrwng, deepsm007, jparrill

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-merge-bot
openshift-merge-bot Bot merged commit b6629ac into openshift:main Jul 22, 2026
5 of 6 checks passed
apahim pushed a commit to apahim/release that referenced this pull request Jul 24, 2026
…leaks (openshift#81788)

* fix(ipi-deprovision): enhance HyperShift teardown with tiered finalizer removal

Restructure the HyperShift pruner in the ipi-deprovision script to
prevent AWS resource leaks when graceful destroy times out.

When hypershift destroy cluster fails, fall back to
hypershift_force_cleanup which uses tiered finalizer removal based on
how long the HC has been in deletion. The pruner cron runs every 15min,
so each invocation evaluates the deletion age and runs only the highest
applicable tier, avoiding wasted time on lower-level cleanup:

- Tier 1 (>=1hr): strip awsmachine finalizers + terminate EC2 instances
- Tier 2 (>=2hr): strip HCP + CAPI resource finalizers
- Tier 3 (>=3hr): delete CP namespace if not already deleting
- Tier 4 (>=4hr): strip HC and NodePool finalizers (last resort)

Finalizers are only removed from resources that already have a
deletionTimestamp set (not blindly). AWS infra/IAM cleanup runs on
every invocation regardless of tier.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>

* fix(ipi-deprovision): scope finalizer stripping and preserve infraID/region

Three bugs found during live testing of the tiered teardown:

1. strip_finalizers namespace-wide blast radius: At Tier 4,
   strip_finalizers stripped finalizers from ALL HCs and NodePools in
   the namespace, not just the target cluster. This caused other HCs
   to be garbage-collected before their own force-cleanup could run.
   Fix: add optional 3rd name parameter to strip_finalizers, and scope
   T4 to only the target HC and its NodePools (filtered by
   spec.clusterName).

2. infraID/region loss: When an HC disappears between graceful destroy
   and force-cleanup, the script fell back to HC name as infraID and
   us-east-1 as region, causing infra/IAM cleanup to target the wrong
   resources. Fix: pre-capture infraID and region before graceful
   destroy, pack them into the failed_clusters array, and pass them
   through to hypershift_force_cleanup.

3. hypershift_force_cleanup now accepts optional $3/$4 for
   pre-captured infraID/region, only querying the HC if they are not
   provided.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>

---------

Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cesar Wong <cewong@redhat.com>
mrniranjan pushed a commit to mrniranjan/release that referenced this pull request Jul 28, 2026
…leaks (openshift#81788)

* fix(ipi-deprovision): enhance HyperShift teardown with tiered finalizer removal

Restructure the HyperShift pruner in the ipi-deprovision script to
prevent AWS resource leaks when graceful destroy times out.

When hypershift destroy cluster fails, fall back to
hypershift_force_cleanup which uses tiered finalizer removal based on
how long the HC has been in deletion. The pruner cron runs every 15min,
so each invocation evaluates the deletion age and runs only the highest
applicable tier, avoiding wasted time on lower-level cleanup:

- Tier 1 (>=1hr): strip awsmachine finalizers + terminate EC2 instances
- Tier 2 (>=2hr): strip HCP + CAPI resource finalizers
- Tier 3 (>=3hr): delete CP namespace if not already deleting
- Tier 4 (>=4hr): strip HC and NodePool finalizers (last resort)

Finalizers are only removed from resources that already have a
deletionTimestamp set (not blindly). AWS infra/IAM cleanup runs on
every invocation regardless of tier.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>

* fix(ipi-deprovision): scope finalizer stripping and preserve infraID/region

Three bugs found during live testing of the tiered teardown:

1. strip_finalizers namespace-wide blast radius: At Tier 4,
   strip_finalizers stripped finalizers from ALL HCs and NodePools in
   the namespace, not just the target cluster. This caused other HCs
   to be garbage-collected before their own force-cleanup could run.
   Fix: add optional 3rd name parameter to strip_finalizers, and scope
   T4 to only the target HC and its NodePools (filtered by
   spec.clusterName).

2. infraID/region loss: When an HC disappears between graceful destroy
   and force-cleanup, the script fell back to HC name as infraID and
   us-east-1 as region, causing infra/IAM cleanup to target the wrong
   resources. Fix: pre-capture infraID and region before graceful
   destroy, pack them into the failed_clusters array, and pass them
   through to hypershift_force_cleanup.

3. hypershift_force_cleanup now accepts optional $3/$4 for
   pre-captured infraID/region, only querying the HC if they are not
   provided.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>

---------

Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cesar Wong <cewong@redhat.com>
amogh-redhat pushed a commit to amogh-redhat/release that referenced this pull request Aug 5, 2026
…leaks (openshift#81788)

* fix(ipi-deprovision): enhance HyperShift teardown with tiered finalizer removal

Restructure the HyperShift pruner in the ipi-deprovision script to
prevent AWS resource leaks when graceful destroy times out.

When hypershift destroy cluster fails, fall back to
hypershift_force_cleanup which uses tiered finalizer removal based on
how long the HC has been in deletion. The pruner cron runs every 15min,
so each invocation evaluates the deletion age and runs only the highest
applicable tier, avoiding wasted time on lower-level cleanup:

- Tier 1 (>=1hr): strip awsmachine finalizers + terminate EC2 instances
- Tier 2 (>=2hr): strip HCP + CAPI resource finalizers
- Tier 3 (>=3hr): delete CP namespace if not already deleting
- Tier 4 (>=4hr): strip HC and NodePool finalizers (last resort)

Finalizers are only removed from resources that already have a
deletionTimestamp set (not blindly). AWS infra/IAM cleanup runs on
every invocation regardless of tier.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>

* fix(ipi-deprovision): scope finalizer stripping and preserve infraID/region

Three bugs found during live testing of the tiered teardown:

1. strip_finalizers namespace-wide blast radius: At Tier 4,
   strip_finalizers stripped finalizers from ALL HCs and NodePools in
   the namespace, not just the target cluster. This caused other HCs
   to be garbage-collected before their own force-cleanup could run.
   Fix: add optional 3rd name parameter to strip_finalizers, and scope
   T4 to only the target HC and its NodePools (filtered by
   spec.clusterName).

2. infraID/region loss: When an HC disappears between graceful destroy
   and force-cleanup, the script fell back to HC name as infraID and
   us-east-1 as region, causing infra/IAM cleanup to target the wrong
   resources. Fix: pre-capture infraID and region before graceful
   destroy, pack them into the failed_clusters array, and pass them
   through to hypershift_force_cleanup.

3. hypershift_force_cleanup now accepts optional $3/$4 for
   pre-captured infraID/region, only querying the HC if they are not
   provided.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>

---------

Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cesar Wong <cewong@redhat.com>
TimurMP pushed a commit to TimurMP/release that referenced this pull request Sep 5, 2026
…leaks (openshift#81788)

* fix(ipi-deprovision): enhance HyperShift teardown with tiered finalizer removal

Restructure the HyperShift pruner in the ipi-deprovision script to
prevent AWS resource leaks when graceful destroy times out.

When hypershift destroy cluster fails, fall back to
hypershift_force_cleanup which uses tiered finalizer removal based on
how long the HC has been in deletion. The pruner cron runs every 15min,
so each invocation evaluates the deletion age and runs only the highest
applicable tier, avoiding wasted time on lower-level cleanup:

- Tier 1 (>=1hr): strip awsmachine finalizers + terminate EC2 instances
- Tier 2 (>=2hr): strip HCP + CAPI resource finalizers
- Tier 3 (>=3hr): delete CP namespace if not already deleting
- Tier 4 (>=4hr): strip HC and NodePool finalizers (last resort)

Finalizers are only removed from resources that already have a
deletionTimestamp set (not blindly). AWS infra/IAM cleanup runs on
every invocation regardless of tier.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>

* fix(ipi-deprovision): scope finalizer stripping and preserve infraID/region

Three bugs found during live testing of the tiered teardown:

1. strip_finalizers namespace-wide blast radius: At Tier 4,
   strip_finalizers stripped finalizers from ALL HCs and NodePools in
   the namespace, not just the target cluster. This caused other HCs
   to be garbage-collected before their own force-cleanup could run.
   Fix: add optional 3rd name parameter to strip_finalizers, and scope
   T4 to only the target HC and its NodePools (filtered by
   spec.clusterName).

2. infraID/region loss: When an HC disappears between graceful destroy
   and force-cleanup, the script fell back to HC name as infraID and
   us-east-1 as region, causing infra/IAM cleanup to target the wrong
   resources. Fix: pre-capture infraID and region before graceful
   destroy, pack them into the failed_clusters array, and pass them
   through to hypershift_force_cleanup.

3. hypershift_force_cleanup now accepts optional $3/$4 for
   pre-captured infraID/region, only querying the HC if they are not
   provided.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>

---------

Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Cesar Wong <cewong@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

acknowledge-critical-fixes-only Indicates if the issuer of the label is OK with the policy. approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. rehearsals-ack Signifies that rehearsal jobs have been acknowledged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants