-
Notifications
You must be signed in to change notification settings - Fork 516
CNTRLPLANE-3820: add cleanleaked tool for AWS leaked resource cleanup #8964
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| --- | ||
| name: triage-leaked-infra | ||
| description: "Assess whether an AWS VPC or infra set from HyperShift CI is safe to delete. Use when the user pastes cleanleaked output and asks 'can I delete this?', 'is this safe to remove?', 'triage this infra', asks about a LEAKED or UNCERTAIN verdict, provides a VPC ID or infraID and wants to know if it's orphaned, or says 'check this VPC'. Also use when the user asks 'should I delete this?' about any AWS resource in the HyperShift CI account." | ||
| --- | ||
|
|
||
| # Triage Leaked Infrastructure | ||
|
|
||
| Assess whether an AWS infrastructure set (VPC + associated resources) from HyperShift CI is safe to delete. Every claim must be backed by an empirical AWS query — never assume, always verify. | ||
|
|
||
| ## Input | ||
|
|
||
| The user provides one of: | ||
| - cleanleaked tool output (an infra set block) | ||
| - A VPC ID (e.g., `vpc-0abc123...`) | ||
| - An infraID (e.g., `00ab3695c5f73d4354b9` or `node-pool-78vcg`) | ||
|
|
||
| Extract the **infraID** and **VPC ID** from the input. If only one is given, derive the other: | ||
| - VPC ID → `aws ec2 describe-vpcs --vpc-ids <VPC> --query 'Vpcs[0].Tags'` → extract infraID from `kubernetes.io/cluster/<infraID>` tag or `Name` tag (strip `-vpc` suffix) | ||
| - infraID → `aws ec2 describe-vpcs --filters "Name=tag:Name,Values=<infraID>-vpc"` → get VPC ID | ||
|
|
||
| ## Checks | ||
|
|
||
| Run these in order. For each, report **PASS** (safe signal), **FAIL** (do NOT delete), or **UNKNOWN** (could not determine). Use `--region us-east-1` for all commands. | ||
|
|
||
| ### 1. Protection tags | ||
| ```bash | ||
| aws ec2 describe-vpcs --vpc-ids <VPC> --query 'Vpcs[0].Tags' --output json | ||
| ``` | ||
| - Check for `hypershift.openshift.io/do-not-delete=true` → if present: **FAIL** | ||
| - Check for `hypershift.openshift.io/ci-cluster` → if present: **FAIL** (this is a management cluster) | ||
|
|
||
| ### 2. Protected VPC name | ||
| From the `Name` tag: if it is `hypershift-ci-2-vpc`, `hypershift-ci-3-vpc`, or `hypershift-ci-metrics-vpc` → **FAIL** | ||
|
|
||
| ### 3. Protected user | ||
| Check if the infraID contains any of these usernames: `aabdelre`, `agarcial`, `ahmed`, `alamela`, `alesross`, `bclement`, `brcox`, `celebdor`, `cewong`, `dario`, `dari2o`, `dmace`, `glipceanu`, `jiezhao`, `jparrill`, `mbhalodi`, `mbrown`, `meha`, `mgencur`, `mulham`, `mraee`, `rkshirsa`, `sdminonne`, `sjenning`, `tsegura`, `vismishr` | ||
|
|
||
| If match → **FAIL** (developer cluster, contact the owner) | ||
|
|
||
| ### 4. Expiration date / Age | ||
| From the VPC tags, check `expirationDate`: | ||
| - If present and **not yet expired** → **FAIL** | ||
| - If present and **expired** → **PASS** | ||
| - If absent → check resource age instead (the `expirationDate` tag only exists on resources created in the last ~2 weeks) | ||
|
|
||
| When `expirationDate` is absent, determine age from the earliest timestamped sub-resource: | ||
| ```bash | ||
| aws ec2 describe-vpc-endpoints --region us-east-1 --filters "Name=vpc-id,Values=<VPC>" --query 'VpcEndpoints[*].CreationTimestamp' --output text | ||
| aws ec2 describe-network-interfaces --region us-east-1 --filters "Name=vpc-id,Values=<VPC>" --query 'NetworkInterfaces[*].Attachment.AttachTime' --output text | ||
| ``` | ||
| Take the earliest timestamp found. If the resource is **older than 24 hours** AND the CI pattern check (step 5) is **PASS** → **PASS**. If younger than 24 hours → **FAIL**. If no timestamp found at all → **UNKNOWN**. | ||
|
|
||
| ### 5. CI pattern match | ||
| Does the infraID match a known CI test pattern? | ||
| - **Hex ID** (20+ lowercase hex chars, e.g., `00ab3695c5f73d4354b9`) → CI generic e2e → **PASS** | ||
| - **Named test**: `create-cluster-*`, `node-pool-*`, `control-plane-upgrade-*`, `autoscaling-*`, `karpenter-*`, `karpenter-upgrade-control-plane-*`, `scale-from-zero-*`, `kms-verify-*`, `request-serving-*`, `private-*`, `proxy-*`, `spot-demo-*`, `ha-break-glass-creds-*`, `custom-config-*`, `ho-upgrade-*`, `multi-hop-upgrade-*` → **PASS** | ||
| - **None of the above** (e.g., `hc1-*`, `clust-*`, `dev-*`, `test-dev-*`) → **UNKNOWN** (might be a developer cluster) | ||
|
|
||
| ### 6. OIDC S3 liveness | ||
| ```bash | ||
| aws s3api head-object --bucket hypershift-ci-oidc --key "<infraID>/.well-known/openid-configuration" --region us-east-1 | ||
| aws s3api head-object --bucket hypershift-ci-2-oidc --key "<infraID>/.well-known/openid-configuration" --region us-east-1 | ||
| aws s3api head-object --bucket hypershift-ci-3-oidc --key "<infraID>/.well-known/openid-configuration" --region us-east-1 | ||
| ``` | ||
| If any returns 200 → **FAIL** (cluster still active). If all return 404/error → **PASS**. | ||
|
Comment on lines
+61
to
+65
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
# Compare a known existing OIDC key and a known-absent key. Both commands must
# have their AWS error code captured; a non-zero exit alone is not "not found".
aws s3api head-object --bucket hypershift-ci-oidc \
--key "<known-existing-infraID>/.well-known/openid-configuration" \
--region us-east-1
aws s3api head-object --bucket hypershift-ci-oidc \
--key "__known-absent-key__" \
--region us-east-1Repository: openshift/hypershift Length of output: 236 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
# Inspect the target file around the cited lines.
file=".claude/skills/triage-leaked-infra/SKILL.md"
wc -l "$file"
sed -n '45,85p' "$file"Repository: openshift/hypershift Length of output: 2793 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
file=".claude/skills/triage-leaked-infra/SKILL.md"
nl -ba "$file" | sed -n '58,68p'Repository: openshift/hypershift Length of output: 198 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('.claude/skills/triage-leaked-infra/SKILL.md')
lines = p.read_text().splitlines()
for i in range(58, 69):
if 1 <= i <= len(lines):
print(f"{i}: {lines[i-1]}")
PYRepository: openshift/hypershift Length of output: 725 Treat only 404 as PASS in the OIDC liveness check. 🤖 Prompt for AI AgentsSource: Path instructions |
||
|
|
||
| ### 7. EC2 instances | ||
| ```bash | ||
| aws ec2 describe-instances --region us-east-1 \ | ||
| --filters "Name=tag:kubernetes.io/cluster/<infraID>,Values=owned" \ | ||
| "Name=instance-state-name,Values=pending,running,stopping,stopped" \ | ||
| --query 'Reservations[*].Instances[*].[InstanceId,State.Name,Tags[?Key==`Name`].Value|[0]]' --output text | ||
| ``` | ||
| If any instances returned → **FAIL**. If none → **PASS**. | ||
|
|
||
| ### 8. red-hat-managed check | ||
| ```bash | ||
| aws ec2 describe-instances --region us-east-1 \ | ||
| --filters "Name=vpc-id,Values=<VPC>" "Name=tag:red-hat-managed,Values=true" \ | ||
| --query 'Reservations[*].Instances[*].InstanceId' --output text | ||
| ``` | ||
| If any instances returned → **FAIL** (ROSA managed infrastructure). If none → **PASS**. | ||
|
|
||
| ### 9. Sub-resources inventory (VPC-scoped) | ||
| Query each and report **ID + Name** for every resource, not just counts: | ||
| ```bash | ||
| aws elbv2 describe-load-balancers --region us-east-1 --query "LoadBalancers[?VpcId=='<VPC>'].[LoadBalancerName,Type,Scheme,DNSName]" --output text | ||
| aws ec2 describe-vpc-endpoints --region us-east-1 --filters "Name=vpc-id,Values=<VPC>" --query 'VpcEndpoints[*].[VpcEndpointId,VpcEndpointType,ServiceName,State]' --output text | ||
| aws ec2 describe-vpc-endpoint-service-configurations --region us-east-1 --output json | # filter by kubernetes.io/cluster/<infraID> tag | ||
| aws ec2 describe-nat-gateways --region us-east-1 --filter "Name=vpc-id,Values=<VPC>" --query 'NatGateways[*].[NatGatewayId,State,Tags[?Key==`Name`].Value|[0],NatGatewayAddresses[0].PublicIp]' --output text | ||
| aws ec2 describe-internet-gateways --region us-east-1 --filters "Name=attachment.vpc-id,Values=<VPC>" --query 'InternetGateways[*].[InternetGatewayId,Tags[?Key==`Name`].Value|[0]]' --output text | ||
| aws ec2 describe-subnets --region us-east-1 --filters "Name=vpc-id,Values=<VPC>" --query 'Subnets[*].[SubnetId,CidrBlock,AvailabilityZone,Tags[?Key==`Name`].Value|[0]]' --output text | ||
| aws ec2 describe-security-groups --region us-east-1 --filters "Name=vpc-id,Values=<VPC>" --query 'SecurityGroups[*].[GroupId,GroupName]' --output text | ||
| aws ec2 describe-network-interfaces --region us-east-1 --filters "Name=vpc-id,Values=<VPC>" --query 'NetworkInterfaces[*].[NetworkInterfaceId,InterfaceType,Description]' --output text | ||
| aws ec2 describe-route-tables --region us-east-1 --filters "Name=vpc-id,Values=<VPC>" --query 'RouteTables[*].[RouteTableId,Associations[0].Main,Tags[?Key==`Name`].Value|[0]]' --output text | ||
| aws ec2 describe-addresses --region us-east-1 --filters "Name=domain,Values=vpc" --output text | # cross-check with NAT gateway EIPs | ||
| ``` | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| This check is informational — always **PASS** but list every resource with its ID and name. | ||
|
|
||
| ### 10. Route53 zones | ||
| ```bash | ||
| aws route53 list-hosted-zones --output text --query 'HostedZones[*].[Id,Name,Config.PrivateZone,ResourceRecordSetCount]' | ||
| ``` | ||
| Search for zones matching `<infraID>.ci.hypershift.devcluster.openshift.com` and `<infraID>.hypershift.local`. For each found zone, list its records: | ||
| ```bash | ||
| aws route53 list-resource-record-sets --hosted-zone-id <ZONE_ID> --query 'ResourceRecordSets[*].[Name,Type]' --output text | ||
| ``` | ||
| Report zone IDs, names, private/public, and record count. Informational — always **PASS**. | ||
|
|
||
| ### 11. OIDC IAM provider | ||
| ```bash | ||
| aws iam list-open-id-connect-providers --output json | ||
| ``` | ||
| Search for providers whose ARN contains the infraID (pattern: `oidc-provider/hypershift-ci-*-oidc.s3.*.amazonaws.com/<infraID>`). Report if found — this is an orphaned IAM resource that should be cleaned up with the infra set. | ||
|
|
||
| ### 12. IAM roles (by infraID prefix) | ||
| ```bash | ||
| aws iam list-roles --query "Roles[?starts_with(RoleName, '<infraID>')].[RoleName,CreateDate]" --output text | ||
| ``` | ||
| Report any IAM roles whose name starts with the infraID. These are orphaned OIDC-type roles (cloud-controller, ebs-csi, ingress, etc.) that belong to this infra set. | ||
|
|
||
| ## Output | ||
|
|
||
| Present the report in this format: | ||
|
|
||
| ``` | ||
| ## Triage: <infraID> | ||
|
|
||
| | # | Check | Result | Detail | | ||
| |---|-------|--------|--------| | ||
| | 1 | Protection tags | PASS | No do-not-delete or ci-cluster tag | | ||
| | 2 | Protected VPC name | PASS | Name is "abcdef1234-vpc" | | ||
| | 3 | Protected user | PASS | No username match | | ||
| | 4 | Expiration date | PASS | Expired 2026-07-03 (5 days ago) | | ||
| | 5 | CI pattern match | PASS | Hex infraID (e2e-generic) | | ||
| | 6 | OIDC S3 liveness | PASS | Not found in any bucket | | ||
| | 7 | EC2 instances | PASS | 0 instances | | ||
| | 8 | red-hat-managed | PASS | No managed instances | | ||
| | 9 | Sub-resources | INFO | 1 IGW, 1 subnet, 1 RTB | | ||
| | 10 | Route53 zones | INFO | 2 zones found | | ||
| | 11 | OIDC IAM provider | INFO | 1 orphaned provider found | | ||
| | 12 | IAM roles | INFO | 0 orphaned roles | | ||
|
|
||
| ### Verdict: SAFE TO DELETE | ||
| This infra set has no protection tags, no running instances, no OIDC document, | ||
| and the infraID matches a CI hex pattern. All deletion checks pass. | ||
| ``` | ||
|
|
||
| ## Verdict rules | ||
|
|
||
| These are non-negotiable: | ||
|
|
||
| - Any **FAIL** → verdict is **DO NOT DELETE** (explain which check failed and why) | ||
| - All checks **PASS** and CI pattern matches → **SAFE TO DELETE** | ||
| - All checks **PASS** but CI pattern is **UNKNOWN** → **UNCERTAIN** (could be a developer cluster) | ||
| - If CI pattern is **PASS** and age/expiration is **PASS** (older than 24h or expired), remaining UNKNOWN checks do not block → **SAFE TO DELETE** (a confirmed CI resource older than 24h with no OIDC, no instances, and no protection tags is leaked) | ||
| - If CI pattern is **UNKNOWN** and ANY check is **UNKNOWN** → **UNCERTAIN — REQUIRES HUMAN DECISION** | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| output/ | ||
| bin/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| BINARY := cleanleaked | ||
| REPO_ROOT := $(shell git rev-parse --show-toplevel) | ||
| PKG := ./contrib/aws-leaked-resources | ||
|
|
||
| .PHONY: build test vet clean scan dry-run | ||
|
|
||
| build: | ||
| @mkdir -p bin | ||
| cd $(REPO_ROOT) && go build -o $(PKG)/bin/$(BINARY) $(PKG) | ||
| @echo "Built: bin/$(BINARY)" | ||
|
Comment on lines
+7
to
+10
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Use one absolute artifact directory for build and clean. Lines 8–10 create Proposed fix BINARY := cleanleaked
REPO_ROOT := $(shell git rev-parse --show-toplevel)
PKG := ./contrib/aws-leaked-resources
+BINDIR := $(REPO_ROOT)/bin
build:
- `@mkdir` -p bin
- cd $(REPO_ROOT) && go build -o $(PKG)/bin/$(BINARY) $(PKG)
- `@echo` "Built: bin/$(BINARY)"
+ `@mkdir` -p $(BINDIR)
+ cd $(REPO_ROOT) && go build -o $(BINDIR)/$(BINARY) $(PKG)
+ `@echo` "Built: $(BINDIR)/$(BINARY)"
clean:
- rm -rf bin
+ rm -rf $(BINDIR)Also applies to: 18-19 🤖 Prompt for AI Agents |
||
|
|
||
| test: | ||
| cd $(REPO_ROOT) && go test $(PKG)/... -count=1 -timeout 120s -v | ||
|
|
||
| vet: | ||
| cd $(REPO_ROOT) && go vet $(PKG)/... | ||
|
|
||
| clean: | ||
| rm -rf bin | ||
|
|
||
| scan: | ||
| cd $(REPO_ROOT) && go run $(PKG) scan | ||
|
|
||
| dry-run: | ||
| cd $(REPO_ROOT) && go run $(PKG) delete --dry-run | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| # cleanleaked | ||
|
|
||
| Detect and clean leaked AWS resources left behind by failed HyperShift CI test runs. | ||
|
|
||
| Replaces the previous bash scripts (`detect-leaked-vpcs.sh`, `detect-leaked-hosted-zones.sh`, etc.) with a single Go tool that is testable, safe by design, and consolidates all detection logic. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ```bash | ||
| # From the repo root | ||
| make -C contrib/aws-leaked-resources build | ||
|
|
||
| # Scan — report only, no deletions | ||
| ./contrib/aws-leaked-resources/cleanleaked scan | ||
|
|
||
| # Preview what would be deleted | ||
| ./contrib/aws-leaked-resources/cleanleaked delete --dry-run | ||
|
|
||
| # Delete interactively, 5 at a time | ||
| ./contrib/aws-leaked-resources/cleanleaked delete --limit 5 --interactive | ||
| ``` | ||
|
|
||
| Or run directly with `go run`: | ||
|
|
||
| ```bash | ||
| go run ./contrib/aws-leaked-resources/ scan | ||
| go run ./contrib/aws-leaked-resources/ delete --dry-run | ||
| ``` | ||
|
|
||
| ## Classification Gates | ||
|
|
||
| Each VPC passes through a series of gates in order. The first gate that matches determines the verdict. | ||
|
|
||
| | Gate | Check | Verdict if matched | | ||
| |------|-------|--------------------| | ||
| | **0. Protection** | `do-not-delete=true` tag, protected VPC name, protected username in infraID | **PROTECTED** | | ||
| | **1. Age** | `expirationDate` tag not expired, or VPCE/ENI creation < `--min-age` | **TOO_YOUNG** | | ||
| | **2. OIDC/S3** | S3 discovery document exists in any allowed OIDC bucket | **ACTIVE** | | ||
| | **3. EC2 Instances** | Running/pending/stopped instances tagged with this infraID | **ACTIVE** | | ||
| | **4. CI Pattern** | infraID does not match any known CI test pattern | **UNCERTAIN** | | ||
| | **5. (all gates pass)** | No OIDC, no instances, matches CI pattern, old enough | **LEAKED** | | ||
|
|
||
| ### Protection (Gate 0) | ||
|
|
||
| Three layers of protection, checked in order: | ||
|
|
||
| 1. **Tag-based**: VPC has `hypershift.openshift.io/do-not-delete=true` | ||
| 2. **Name-based**: VPC name is `hypershift-ci-2-vpc`, `hypershift-ci-3-vpc`, or `hypershift-ci-metrics-vpc` | ||
| 3. **User-based**: infraID contains a protected team member username | ||
|
|
||
| Protected resources are **never** deleted regardless of other signals. | ||
|
|
||
| ### Age (Gate 1) | ||
|
|
||
| Two sources of age information: | ||
|
|
||
| - **`expirationDate` tag** (preferred): Set by newer CI jobs. If present and not expired, the VPC is `TOO_YOUNG`. | ||
| - **VPCE/ENI timestamps** (fallback): For older resources without the tag, the earliest `CreationTimestamp` from VPC Endpoints or `AttachTime` from ENIs is used. Must be older than `--min-age` (default 24h). | ||
|
|
||
| ### CI Pattern Match (Gate 4) | ||
|
|
||
| Known CI test patterns: | ||
| - Hex infraIDs (20+ lowercase hex chars): `00ab3695c5f73d4354b9` | ||
| - Named test prefixes: `create-cluster-*`, `node-pool-*`, `control-plane-upgrade-*`, `autoscaling-*`, `karpenter-*`, `scale-from-zero-*`, `kms-verify-*`, `request-serving-*`, `private-*`, `proxy-*`, `spot-demo-*`, `ha-break-glass-creds-*`, `custom-config-*`, `ho-upgrade-*`, `multi-hop-upgrade-*` | ||
|
|
||
| InfraIDs that don't match (e.g., `hc1-*`, `clust-*`, `dev-*`) are classified as `UNCERTAIN`. | ||
|
|
||
| ## Verdicts | ||
|
|
||
| | Verdict | Meaning | Deletable? | | ||
| |---------|---------|------------| | ||
| | `PROTECTED` | Management cluster, developer resource, or `do-not-delete` tag | Never | | ||
| | `TOO_YOUNG` | Created less than `--min-age` ago or `expirationDate` not reached | No | | ||
| | `ACTIVE` | OIDC S3 doc exists or EC2 instances running | No | | ||
| | `UNCERTAIN` | No CI pattern match — may belong to a developer | Only manually with `--interactive` | | ||
| | `LEAKED` | No OIDC, no instances, matches CI pattern, old enough | Yes | | ||
|
|
||
| ## Delete Modes | ||
|
|
||
| | Command | Behavior | | ||
| |---------|----------| | ||
| | `delete` | Prompt once at the start, then delete all LEAKED | | ||
| | `delete --confirm` | No prompts, delete all LEAKED immediately | | ||
| | `delete --interactive` | Show full resource inventory per infra set, prompt for each | | ||
| | `delete --dry-run` | Show what would be deleted, delete nothing | | ||
|
|
||
| ### Cascade Order | ||
|
|
||
| When deleting a VPC, sub-resources are removed in this order: | ||
|
|
||
| 1. ELBs/NLBs | ||
| 2. VPC Endpoint Services (by infraID tag) | ||
| 3. VPC Endpoints | ||
| 4. NAT Gateways → wait 15s for ENI release | ||
| 5. Elastic IPs (only those from the NAT gateways) | ||
| 6. Internet Gateways | ||
| 7. Route Tables (non-main only) | ||
| 8. Network Interfaces | ||
| 9. Subnets | ||
| 10. Security Groups (non-default only, revoke rules first) | ||
| 11. VPC | ||
|
|
||
| Additionally per infra set: Route53 zones, OIDC IAM providers, IAM roles. | ||
|
|
||
| Each delete operation retries up to 10 times with 2s delay. Ctrl+C interrupts immediately. | ||
|
|
||
| ## Flags | ||
|
|
||
| ### `scan` command | ||
|
|
||
| | Flag | Default | Description | | ||
| |------|---------|-------------| | ||
| | `--region` | `us-east-1` | AWS region | | ||
| | `--min-age` | `24h` | Minimum VPC age to consider for deletion | | ||
| | `--target` | *(none)* | Scan a single infraID or VPC ID instead of all VPCs | | ||
| | `--output` | `table` | Output format: `table` or `json` | | ||
| | `--output-dir` | `output` | Directory for timestamped report files (empty to disable) | | ||
|
|
||
| ### `delete` command | ||
|
|
||
| | Flag | Default | Description | | ||
| |------|---------|-------------| | ||
| | `--region` | `us-east-1` | AWS region | | ||
| | `--min-age` | `24h` | Minimum VPC age to consider for deletion | | ||
| | `--target` | *(none)* | Delete a single infraID or VPC ID instead of all leaked | | ||
| | `--dry-run` | `false` | Show what would be deleted without deleting | | ||
| | `--confirm` | `false` | No prompts, delete all LEAKED immediately | | ||
| | `--interactive`, `-i` | `false` | Prompt before each infra set with full inventory | | ||
| | `--limit` | `0` | Max infra sets to delete (0 = no limit) | | ||
| | `--output` | `table` | Output format: `table` or `json` | | ||
| | `--output-dir` | `output` | Directory for timestamped report files (empty to disable) | | ||
|
|
||
| ### `--target` usage | ||
|
|
||
| Accepts a VPC ID (`vpc-*`) or an infraID. If an infraID is given, it searches for a VPC with the Name tag `<infraID>-vpc`. | ||
|
|
||
| ```bash | ||
| # By VPC ID | ||
| cleanleaked scan --target vpc-066231b8dc5a4400f | ||
|
|
||
| # By infraID | ||
| cleanleaked scan --target node-pool-78vcg | ||
|
|
||
| # Also accepts the -vpc suffix (stripped automatically) | ||
| cleanleaked scan --target node-pool-78vcg-vpc | ||
|
|
||
| # Delete a specific target interactively | ||
| cleanleaked delete --target 00ab3695c5f73d4354b9 --interactive | ||
| ``` | ||
|
|
||
| ## Prow Correlation | ||
|
|
||
| For each LEAKED infra set, the tool infers: | ||
| - **Test type**: from the infraID pattern (e.g., `node-pool`, `create-cluster`, `e2e-generic`) | ||
| - **HC namespace**: from Route53 `service.ci` zone TXT records | ||
| - **Prow link**: direct job URL if `hypershift.openshift.io/prow-job-id` tag exists (PR #8909), otherwise a Prow deck search URL | ||
|
|
||
| ## Safety | ||
|
|
||
| - **Default SG** (`GroupName=default`) is never deleted | ||
| - **Main route table** is never deleted (auto-deleted with VPC) | ||
| - **EIPs** are scoped: only EIPs from NAT gateways of the target VPC are released | ||
| - **ELBs** are filtered by VPC ID before deletion | ||
| - All delete operations check `ctx.Done()` between steps — Ctrl+C works immediately | ||
|
|
||
| ## Development | ||
|
|
||
| ```bash | ||
| make build # Build binary | ||
| make test # Run unit tests | ||
| make vet # Run go vet | ||
| make scan # Run scan against real AWS | ||
| make dry-run # Run delete --dry-run against real AWS | ||
| make clean # Remove binary | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Validate identifiers before interpolating them into shell commands.
Lines 17–19 accept user-provided values that are then copied into AWS CLI snippets. A crafted value can inject shell syntax and execute commands under the operator’s credentials. Require full-format allow-list validation for VPC IDs and infraIDs, then use quoted shell variables in every command.
🤖 Prompt for AI Agents
Source: Path instructions