Skip to content

CNTRLPLANE-3820: add cleanleaked tool for AWS leaked resource cleanup#8964

Open
jparrill wants to merge 1 commit into
openshift:mainfrom
jparrill:aws-clean-leaked-resources
Open

CNTRLPLANE-3820: add cleanleaked tool for AWS leaked resource cleanup#8964
jparrill wants to merge 1 commit into
openshift:mainfrom
jparrill:aws-clean-leaked-resources

Conversation

@jparrill

@jparrill jparrill commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Go tool (contrib/aws-leaked-resources/cleanleaked) that scans AWS VPCs, classifies them through safety gates, and deletes leaked CI resources with full cascade support
  • Claude Code skill (triage-leaked-infra) for interactive assessment of individual infra sets
  • Protection tags applied to all root CI resources (ci-2, ci-3, ci-metrics)

Context

On 2026-07-06, ad-hoc bash cleanup scripts accidentally deleted NAT gateways belonging to ci-2/ci-3 management clusters due to a bash IFS field-parsing bug, causing a multi-hour CI outage. This tool addresses the root causes with testable classification logic, protection tags, and safety invariants verified by unit tests.

Classification Gates

Gate Check Verdict
0 do-not-delete tag, protected VPC name, protected username PROTECTED
1 expirationDate tag or VPCE/ENI timestamp < min-age TOO_YOUNG
2 S3 OIDC discovery document exists ACTIVE
3 EC2 instances tagged with infraID ACTIVE
4 infraID does not match CI patterns UNCERTAIN
5 All gates pass LEAKED

Features

  • 3 delete modes: prompt-once, --confirm (CI-safe), --interactive (per infra set with full inventory)
  • Full cascade: ELBs → VPCE Services → VPCE → NAT → EIPs → IGW → RTB → ENI → Subnets → SG → VPC → Zones → OIDC → IAM roles
  • Retries 0/10 with 2s delay, Ctrl+C support (context propagation)
  • EIPs scoped to NAT gateways of target VPC only
  • Default SG never deleted, main RTB never deleted
  • --target for single infra set operations, --limit for batch control, --dry-run
  • Prow correlation: test type inference, HC namespace from service.ci zone, Prow deck link
  • Output: table/JSON, timestamped file reports

Cleanup Results (2026-07-08)

Metric Value
VPCs 150 → 90
VPCs deleted by cleanleaked 48
HostedZones 339 → 210
Zones deleted by cleanleaked 87
IAM roles deleted 11
EIPs released 18
LEAKED remaining 0
Quota errors resolved VPCLimitExceeded, TooManyHostedZones

Test plan

  • go build ./contrib/aws-leaked-resources/ compiles
  • go test ./contrib/aws-leaked-resources/... — all unit tests pass
  • go vet ./contrib/aws-leaked-resources/... — no issues
  • Protected VPCs (ci-2, ci-3, ci-metrics) classified as PROTECTED in all scenarios
  • do-not-delete=true tag always results in PROTECTED verdict
  • Default SG never deleted (tested)
  • EIPs scoped to NAT gateways only — never releases unrelated EIPs (tested with real-world ci-2 scenario)
  • API errors in DescribeInstances result in UNCERTAIN, not false LEAKED (tested)
  • Ctrl+C interrupts immediately during retries and NAT wait
  • --dry-run verified against real AWS — no resources modified
  • --interactive mode shows full resource inventory before prompting
  • Ran 3 delete sessions against real AWS: 48 infra sets cleaned, 0 incidents

Fixes CNTRLPLANE-3820

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a new cleanleaked command-line tool to scan and (optionally) delete leaked AWS HyperShift CI infrastructure using verdict-based gating, ordered cleanup, and dry-run/confirmation modes.
    • Added enriched output (sorted table + JSON) and Prow/namespace correlation, plus detailed reporting of related AWS resources.
  • Bug Fixes
    • Improved deletion safety with dependency-aware cascade logic, scoped ELB/VPC targeting, and resilient retry behavior.
  • Documentation
    • Added comprehensive README and a triage guide for assessing whether CI VPC “infra sets” are safe to delete.
  • Tests
    • Added extensive unit tests covering scanning, safety rules, and deletion/release ordering.
  • Chores
    • Added a Makefile and updated .gitignore for build artifacts.

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

openshift-ci-robot commented Jul 8, 2026

Copy link
Copy Markdown

@jparrill: This pull request references CNTRLPLANE-3820 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

  • Go tool (contrib/aws-leaked-resources/cleanleaked) that scans AWS VPCs, classifies them through safety gates, and deletes leaked CI resources with full cascade support
  • Claude Code skill (triage-leaked-infra) for interactive assessment of individual infra sets
  • Protection tags applied to all root CI resources (ci-2, ci-3, ci-metrics)

Context

On 2026-07-06, ad-hoc bash cleanup scripts accidentally deleted NAT gateways belonging to ci-2/ci-3 management clusters due to a bash IFS field-parsing bug, causing a multi-hour CI outage. This tool addresses the root causes with testable classification logic, protection tags, and safety invariants verified by unit tests.

Classification Gates

Gate Check Verdict
0 do-not-delete tag, protected VPC name, protected username PROTECTED
1 expirationDate tag or VPCE/ENI timestamp < min-age TOO_YOUNG
2 S3 OIDC discovery document exists ACTIVE
3 EC2 instances tagged with infraID ACTIVE
4 infraID does not match CI patterns UNCERTAIN
5 All gates pass LEAKED

Features

  • 3 delete modes: prompt-once, --confirm (CI-safe), --interactive (per infra set with full inventory)
  • Full cascade: ELBs → VPCE Services → VPCE → NAT → EIPs → IGW → RTB → ENI → Subnets → SG → VPC → Zones → OIDC → IAM roles
  • Retries 0/10 with 2s delay, Ctrl+C support (context propagation)
  • EIPs scoped to NAT gateways of target VPC only
  • Default SG never deleted, main RTB never deleted
  • --target for single infra set operations, --limit for batch control, --dry-run
  • Prow correlation: test type inference, HC namespace from service.ci zone, Prow deck link
  • Output: table/JSON, timestamped file reports

Cleanup Results (2026-07-08)

Metric Value
VPCs 150 → 90
VPCs deleted by cleanleaked 48
HostedZones 339 → 210
Zones deleted by cleanleaked 87
IAM roles deleted 11
EIPs released 18
LEAKED remaining 0
Quota errors resolved VPCLimitExceeded, TooManyHostedZones

Test plan

  • go build ./contrib/aws-leaked-resources/ compiles
  • go test ./contrib/aws-leaked-resources/... — all unit tests pass
  • go vet ./contrib/aws-leaked-resources/... — no issues
  • Protected VPCs (ci-2, ci-3, ci-metrics) classified as PROTECTED in all scenarios
  • do-not-delete=true tag always results in PROTECTED verdict
  • Default SG never deleted (tested)
  • EIPs scoped to NAT gateways only — never releases unrelated EIPs (tested with real-world ci-2 scenario)
  • API errors in DescribeInstances result in UNCERTAIN, not false LEAKED (tested)
  • Ctrl+C interrupts immediately during retries and NAT wait
  • --dry-run verified against real AWS — no resources modified
  • --interactive mode shows full resource inventory before prompting
  • Ran 3 delete sessions against real AWS: 48 infra sets cleaned, 0 incidents

Fixes CNTRLPLANE-3820

🤖 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.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds the cleanleaked Go CLI for scanning and classifying leaked AWS HyperShift CI infrastructure, correlating results with Route53 and Prow metadata, reporting findings, and deleting leaked resources through an ordered VPC cleanup cascade. It also adds AWS client interfaces, shared models, tests, build automation, documentation, and a manual triage skill.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as cleanleaked CLI
  participant Scanner
  participant AWS as AWS APIs
  participant Prow as CorrelateWithProw
  participant Deleter

  CLI->>Scanner: Scan(ctx)
  Scanner->>AWS: Describe VPCs, OIDC providers, instances, and S3 objects
  AWS-->>Scanner: Resource data and activity results
  Scanner-->>CLI: InfraSet verdicts and inventories
  CLI->>Prow: CorrelateWithProw(sets)
  Prow->>AWS: Inspect Route53 zones and TXT records
  AWS-->>Prow: Namespace and zone metadata
  Prow-->>CLI: TestType, Namespace, and ProwLink
  CLI->>CLI: Print report
  alt delete requested
    CLI->>Deleter: DeleteAll(leaked, mode)
    Deleter->>AWS: Delete zones, IAM resources, and VPC dependencies
    AWS-->>Deleter: Deletion results
    Deleter-->>CLI: Aggregate summary
  end
Loading

Suggested reviewers: bryan-cox


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error New stdout/stderr inventory logs print infra IDs, VPC/zone names, OIDC ARNs, role names, IPs, and namespaces—internal/customer data exposure. Redact or omit resource identifiers in default logs/reports, and gate detailed inventory behind an explicit verbose/redacted-safe mode.
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding the cleanleaked AWS leaked resource cleanup tool.
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 test titles or runtime-formatted names were added; all test names are static string literals and deterministic.
Test Structure And Quality ✅ Passed No Ginkgo constructs were added; these are isolated table-driven Go unit tests with in-memory mocks, so the Ginkgo-specific quality checks don’t apply.
Topology-Aware Scheduling Compatibility ✅ Passed This PR only adds an AWS cleanup CLI, docs, tests, and a Claude skill; no deployment manifests, controllers, or pod scheduling constraints were introduced.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo/e2e tests were added; the changed tests are plain Go unit tests with mocks and no external connectivity.
No-Weak-Crypto ✅ Passed No weak-crypto APIs or custom cryptography appear in the new tool; comparisons are ordinary tag/ID checks, not secret/token comparisons.
Container-Privileges ✅ Passed PR adds only docs/Go code; no changed file contains privileged, hostPID/Network/IPC, SYS_ADMIN, or allowPrivilegeEscalation settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci openshift-ci Bot requested review from bryan-cox and cblecker July 8, 2026 16:29
@openshift-ci openshift-ci Bot added area/ai Indicates the PR includes changes related to AI - Claude agents, Cursor rules, etc. and removed do-not-merge/needs-area labels Jul 8, 2026
@openshift-ci

openshift-ci Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: 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-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 8, 2026
@jparrill

jparrill commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Triage Skill Output Example

The triage-leaked-infra skill (.claude/skills/triage-leaked-infra/) provides interactive assessment of individual infra sets. Here's an example run against a leaked VPC:

/triage-leaked-infra 03a5c3112e6a5524d1b6-vpc

Output:

Triage: 03a5c3112e6a5524d1b6

┌─────┬────────────────────┬────────┬─────────────────────────────────────────────────────────────┐
│  #  │       Check        │ Result │                           Detail                            │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 1   │ Protection tags    │ PASS   │ No do-not-delete or ci-cluster tag                          │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 2   │ Protected VPC name │ PASS   │ Name is 03a5c3112e6a5524d1b6-vpc                            │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 3   │ Protected user     │ PASS   │ No username match in infraID                                │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 4   │ Age                │ PASS   │ IAM role created 2026-05-01 (68 days ago), well over 24h    │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 5   │ CI pattern match   │ PASS   │ 20-char hex infraID → CI generic e2e                        │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 6   │ OIDC S3 liveness   │ PASS   │ Not found in any bucket (all 3 returned 404)                │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 7   │ EC2 instances      │ PASS   │ 0 instances                                                 │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 8   │ red-hat-managed    │ PASS   │ No managed instances                                        │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 9   │ Sub-resources      │ INFO   │ 1 SG (default), 2 RTBs (03a5c3112e6a5524d1b6-public + main) │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 10  │ Route53 zones      │ INFO   │ 0 zones found                                               │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 11  │ OIDC IAM provider  │ INFO   │ 0 orphaned providers                                        │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 12  │ IAM roles          │ INFO   │ 1 orphaned role: 03a5c3112e6a5524d1b6 (created 2026-05-01)  │
└─────┴────────────────────┴────────┴─────────────────────────────────────────────────────────────┘

Verdict: SAFE TO DELETE

This is a leaked CI e2e infra set — 68 days old, hex infraID pattern, no protection
tags, no running instances, no OIDC document, no Route53 zones. The VPC is nearly
empty (just default SG and route tables). There's one orphaned IAM role
(03a5c3112e6a5524d1b6) to clean up alongside it.

@openshift-ci

openshift-ci Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

@jparrill: The label(s) triage/03a5c3112e6a5524d1b6-vpc cannot be applied, because the repository doesn't have them.

Details

In response to this:

Triage Skill Output Example

The triage-leaked-infra skill (.claude/skills/triage-leaked-infra/) provides interactive assessment of individual infra sets. Here's an example run against a leaked VPC:

/triage-leaked-infra 03a5c3112e6a5524d1b6-vpc

Output:

Triage: 03a5c3112e6a5524d1b6

┌─────┬────────────────────┬────────┬─────────────────────────────────────────────────────────────┐
│  #  │       Check        │ Result │                           Detail                            │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 1   │ Protection tags    │ PASS   │ No do-not-delete or ci-cluster tag                          │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 2   │ Protected VPC name │ PASS   │ Name is 03a5c3112e6a5524d1b6-vpc                            │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 3   │ Protected user     │ PASS   │ No username match in infraID                                │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 4   │ Age                │ PASS   │ IAM role created 2026-05-01 (68 days ago), well over 24h    │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 5   │ CI pattern match   │ PASS   │ 20-char hex infraID → CI generic e2e                        │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 6   │ OIDC S3 liveness   │ PASS   │ Not found in any bucket (all 3 returned 404)                │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 7   │ EC2 instances      │ PASS   │ 0 instances                                                 │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 8   │ red-hat-managed    │ PASS   │ No managed instances                                        │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 9   │ Sub-resources      │ INFO   │ 1 SG (default), 2 RTBs (03a5c3112e6a5524d1b6-public + main) │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 10  │ Route53 zones      │ INFO   │ 0 zones found                                               │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 11  │ OIDC IAM provider  │ INFO   │ 0 orphaned providers                                        │
├─────┼────────────────────┼────────┼─────────────────────────────────────────────────────────────┤
│ 12  │ IAM roles          │ INFO   │ 1 orphaned role: 03a5c3112e6a5524d1b6 (created 2026-05-01)  │
└─────┴────────────────────┴────────┴─────────────────────────────────────────────────────────────┘

Verdict: SAFE TO DELETE

This is a leaked CI e2e infra set — 68 days old, hex infraID pattern, no protection
tags, no running instances, no OIDC document, no Route53 zones. The VPC is nearly
empty (just default SG and route tables). There's one orphaned IAM role
(03a5c3112e6a5524d1b6) to clean up alongside it.

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.

@jparrill jparrill force-pushed the aws-clean-leaked-resources branch from 716d5da to 2172c30 Compare July 8, 2026 16:46

@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: 7

🧹 Nitpick comments (9)
contrib/aws-leaked-resources/tags.go (1)

57-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Dead/misleading format entry in parseExpirationDate.

"2006-01-02T15:04+00:00" is not a valid Go time layout for a numeric offset — the stdlib only recognizes the "-0700"/"-07:00" family (which is sign-agnostic and already matches +hh:mm inputs) or the "Z..." family. Since "+00:00" isn't a special reference token, it's treated as literal text, so this format only ever matches inputs containing the literal substring "+00:00". In practice, the preceding format ("2006-01-02T15:04-07:00") already parses any ±hh:mm offset, so this entry is unreachable/dead — confirmed by TestParseExpirationDate's "short ISO with offset" case succeeding purely via the prior format. Consider removing this entry to avoid confusion.

🤖 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 `@contrib/aws-leaked-resources/tags.go` around lines 57 - 73, The
parseExpirationDate helper includes a misleading dead layout entry that can
never match numeric offsets as intended. Remove the redundant
"2006-01-02T15:04+00:00" format from the formats list in parseExpirationDate,
since the preceding "2006-01-02T15:04-07:00" already handles ±hh:mm offsets and
this extra entry only adds confusion.
contrib/aws-leaked-resources/scan.go (2)

398-444: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Duplicate AWS lookups for VPC creation time between resolveVPCCreationTime and inventorySubResources.

Both functions independently call DescribeVpcEndpoints and DescribeNetworkInterfaces and run the same "earliest timestamp wins" logic to backfill infra.VPCs[i].CreatedAt. For any VPC lacking a creation_date tag that ultimately gets classified LEAKED, these two API calls are issued twice. Consider extracting a shared helper (e.g. resolveCreatedAtFromEndpoint/resolveCreatedAtFromENI) that both call sites reuse, or have inventorySubResources reuse the already-fetched endpoint/ENI results from resolveVPCCreationTime instead of re-querying.

Also applies to: 498-511

🤖 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 `@contrib/aws-leaked-resources/scan.go` around lines 398 - 444, The VPC
creation-time backfill logic is duplicated between resolveVPCCreationTime and
inventorySubResources, causing repeated DescribeVpcEndpoints and
DescribeNetworkInterfaces calls for the same VPC. Refactor the shared “earliest
timestamp wins” logic into a helper that both resolveVPCCreationTime and
inventorySubResources reuse, or pass the already-fetched endpoint/ENI data
through so CreatedAt is resolved once. Keep the behavior centered around
infra.VPCs[i].CreatedAt and the existing EC2 lookup paths.

567-651: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

AWS API errors are silently swallowed in the inventory functions.

inventorySubResources, inventoryHostedZones, and inventoryIAMRoles all ignore errors from Describe*/List* calls without logging (e.g. inventoryHostedZones returns immediately on the first ListHostedZones error, leaving HostedZones empty for that infra set). Contrast this with checkEC2Instances (lines 381-384), which logs a WARN and reports an explicit Uncertain reason on failure. For a tool whose output drives real deletion decisions, silently under-reporting inventory on transient AWS errors (throttling, permissions) could mask resources that should have been flagged, without any signal to the operator. Consider applying the same WARN-and-note pattern 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 `@contrib/aws-leaked-resources/scan.go` around lines 567 - 651, AWS API
failures in the inventory helpers are currently swallowed, causing partial or
empty results without any operator signal. Update inventorySubResources,
inventoryHostedZones, and inventoryIAMRoles to handle Describe*/List* errors
like checkEC2Instances does: log a WARN with the AWS call context and error, and
record an uncertain/incomplete inventory reason on the InfraSet instead of just
returning. Use the existing helper symbols inventoryHostedZones,
inventoryIAMRoles, and inventorySubResources to add the same failure-reporting
pattern consistently.
contrib/aws-leaked-resources/clients.go (1)

13-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Large, producer-side interfaces conflict with the stated Go guidelines.

EC2API (~20 methods), IAMAPI (~11 methods), and friends are defined here as producer-side interfaces, whereas the repo guidelines call for small (1–2 method), consumer-defined interfaces. This is a common/pragmatic pattern for wrapping AWS SDK clients for mocking, so I'm not blocking on it, but be aware it's a guideline deviation if scrutinized later. As per coding guidelines, "Keep interfaces small and focused (generally 1–2 methods)" and "Define interfaces on the consumer side, not the producer side."

🤖 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 `@contrib/aws-leaked-resources/clients.go` around lines 13 - 72, The AWS client
interfaces in EC2API, IAMAPI, Route53API, ELBv2API, and S3API are large
producer-side mocks and should be refactored to follow the repo’s
consumer-defined, small-interface guideline. Split the methods into narrower
interfaces at the call sites that need them, and move the interface definitions
next to the consumers that use them instead of keeping one broad wrapper in
clients.go. Preserve the same method signatures, but only expose the minimal
methods each consumer actually requires.

Source: Coding guidelines

contrib/aws-leaked-resources/prow.go (1)

45-64: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefix ordering is fragile but currently correct.

InferTestType relies on knownTestPrefixes being ordered so more-specific prefixes (karpenter-upgrade-control-plane) precede shorter ones that are also prefixes (karpenter). It works today, but a future addition placed in the wrong position would silently misclassify test types with no compiler/test signal.

Consider adding a short comment above knownTestPrefixes documenting the "longer/more-specific prefix must come first" invariant, or sorting by descending length at init to make it order-independent.

🤖 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 `@contrib/aws-leaked-resources/prow.go` around lines 45 - 64, InferTestType
depends on knownTestPrefixes being ordered from more-specific to less-specific
values, so add a clear comment or similar guidance near that symbol documenting
the “longer prefix first” invariant. If you want to remove the ordering
fragility entirely, update the initialization of knownTestPrefixes so it is
sorted by descending length before InferTestType iterates over it, keeping the
existing matching behavior in isHexInfraID unchanged.
contrib/aws-leaked-resources/prow_test.go (1)

1-173: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No test coverage for LookupNamespaceFromServiceZone, FindServiceCIZoneID, or CorrelateWithProw.

These are the only functions in prow.go that talk to Route53 and contain pagination logic — and, per the companion comment on prow.go (lines 80-111), the pagination handling there is currently missing. A table-driven test using a mock Route53API that returns a truncated first page (IsTruncated: true) followed by a second page containing the matching TXT record would have caught that gap.

🤖 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 `@contrib/aws-leaked-resources/prow_test.go` around lines 1 - 173, Add
table-driven coverage in prow_test.go for LookupNamespaceFromServiceZone,
FindServiceCIZoneID, and CorrelateWithProw using a mocked Route53API. Make the
mock return a truncated first page (IsTruncated true) and a second page with the
matching TXT record so the pagination path in prow.go is exercised. Assert the
namespace/zone ID/prow correlation are found only after the second page, and
keep the tests centered around the Route53API and the three function names so
they’re easy to locate.
contrib/aws-leaked-resources/report.go (1)

12-72: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

PrintTable mixes sorting, header rendering, and detailed sub-resource dumping in one function.

The function handles sort ordering, the summary row, and then four different detail-block loops (VPCs, hosted zones, OIDC providers, IAM roles) for leaked/uncertain sets. Extracting the "print details for a leaked/uncertain set" block (lines 41-70) into a helper (e.g. printInfraDetails(w, s)) would make PrintTable easier to scan and test independently.

🤖 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 `@contrib/aws-leaked-resources/report.go` around lines 12 - 72, The PrintTable
function is doing too much by combining table sorting, summary-row rendering,
and the leaked/uncertain detail output in one block. Extract the detail-printing
section for VerdictLeaked and VerdictUncertain into a dedicated helper such as
printInfraDetails(w, s), and have PrintTable call it after writing the summary
row; keep the existing VPC, HostedZones, OIDCProviders, and IAMRoles loops
inside that helper so the main function is easier to scan and test.
contrib/aws-leaked-resources/main.go (1)

116-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate flag registration between scanCmd and deleteCmd.

--region, --min-age, --target, --output, and --output-dir are registered identically on both subcommands. Since they bind to the same shared cfg struct, these could be declared once as rootCmd.PersistentFlags() instead.

🤖 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 `@contrib/aws-leaked-resources/main.go` around lines 116 - 130, The flags
registered in both scanCmd and deleteCmd are duplicated even though they all
bind to the shared cfg struct. Move the common options like region, min-age,
target, output, and output-dir to rootCmd.PersistentFlags() in main.go so both
subcommands inherit them, and keep only command-specific flags on scanCmd and
deleteCmd.
contrib/aws-leaked-resources/delete.go (1)

587-754: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

printDetailedInventory duplicates the resource-listing logic already implemented in the dedicated delete* helpers.

This function re-implements its own Describe+iterate loops for ELBs, VPCEs, NATs, IGWs, subnets, RTBs, SGs, and ENIs, instead of reusing deleteLoadBalancers, deleteVPCEndpoints, etc. (in list-only mode). Keeping two independent implementations of "what belongs to this VPC" risks the two falling out of sync (e.g., a future filter added to one path but not the other).

Extract small list*(ctx, vpcID) helpers used by both the deletion cascade and this inventory printer.

🤖 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 `@contrib/aws-leaked-resources/delete.go` around lines 587 - 754,
printDetailedInventory currently duplicates the “what belongs to this VPC”
listing logic that already exists in the delete* flow, so the two paths can
drift. Refactor the resource discovery in Deleter by extracting shared list
helpers (for example listLoadBalancers, listVPCEndpoints, listNatGateways,
listInternetGateways, listSubnets, listRouteTables, listSecurityGroups, and
listNetworkInterfaces) and have both printDetailedInventory and the existing
deleteLoadBalancers/deleteVPCEndpoints/etc. reuse them. Keep the VPC-scoped
filtering and formatting centralized in those shared helpers.
🤖 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 @.claude/skills/triage-leaked-infra/SKILL.md:
- Around line 89-97: The shell examples in the triage guide are broken by inline
comments after a pipe, so they cannot be copied and run verbatim. Update the
command examples in the skill documentation so the AWS CLI snippets stand alone
as valid shell, and move explanatory notes about filtering or cross-checking
outside the command text; check the affected snippet blocks around the
describe-vpc-endpoint-service-configurations and describe-addresses examples for
consistency.

In `@contrib/aws-leaked-resources/delete.go`:
- Around line 108-120: DeleteAll currently reports deletion counts but always
returns nil, so failures in failedVPCs and failedZones never reach
runDelete/main and the CLI can exit successfully despite leaks. Update DeleteAll
to surface an error when any VPC or Zone deletions fail, using the existing
failure counters and the deletion summary logic near the end of the function, so
runDelete and main() can propagate a non-zero exit in CI-safe delete mode.
Ensure the success path still returns nil only when all tracked deletions
complete without failures.
- Around line 227-287: The regional list calls in deleteLoadBalancers and
deleteVPCEndpointServices only fetch the first page, so leaked resources can be
missed when there are multiple pages. Update these helpers to page through
DescribeLoadBalancers and DescribeVpcEndpointServiceConfigurations once per
cleanup run, collect all results, and then filter locally by VpcId or cluster
tag before calling retryDelete. Use the existing deleteLoadBalancers and
deleteVPCEndpointServices methods as the main touchpoints.

In `@contrib/aws-leaked-resources/prow.go`:
- Around line 151-178: CorrelateWithProw currently skips VerdictUncertain sets
even though PrintTable in report.go renders the TestType/Namespace/ProwLink
details for both leaked and uncertain infra. Update CorrelateWithProw to enrich
both VerdictLeaked and VerdictUncertain entries by changing the verdict filter
near the start of the loop, while keeping the existing ProwJobURL and
LookupNamespaceFromServiceZone behavior unchanged. Use the CorrelateWithProw and
PrintTable symbols to verify the correlation now feeds the report path for
uncertain sets too.
- Around line 80-111: LookupNamespaceFromServiceZone only inspects the first
ListResourceRecordSets response, so it can miss matching TXT records in larger
hosted zones. Update the Route53 scan logic in LookupNamespaceFromServiceZone to
iterate through all pages using route53.NewListResourceRecordSetsPaginator (or
equivalent manual pagination with
StartRecordName/StartRecordType/StartRecordIdentifier until IsTruncated is
false), while preserving the existing TXT filtering and extractNamespace
matching.

In `@contrib/aws-leaked-resources/report.go`:
- Around line 74-92: PrintSummary is missing the VerdictUntagged bucket from the
summary breakdown. Update PrintSummary to include an Untagged line using
counts[VerdictUntagged] and countVPCs(sets, VerdictUntagged), alongside the
existing Protected, Active, Too young, Uncertain, and Leaked entries. Keep the
output aligned with the other verdict rows so the totals remain consistent with
len(sets) and the VPC counts.

In `@contrib/aws-leaked-resources/scan.go`:
- Around line 274-293: The age gate in checkAge currently treats an undetermined
VPC age as a pass by returning empty verdicts when oldestVPC(vpcs) cannot infer
a creation time. Update checkAge to fail safe by returning VerdictUncertain with
a clear reason whenever oldestVPC is zero/unknown, matching the existing
uncertain handling used in checkEC2Instances and other ambiguous checks. Add or
update tests around checkAge/oldestVPC to cover VPC sets with no creation_date,
VPCE, or ENI attach time.

---

Nitpick comments:
In `@contrib/aws-leaked-resources/clients.go`:
- Around line 13-72: The AWS client interfaces in EC2API, IAMAPI, Route53API,
ELBv2API, and S3API are large producer-side mocks and should be refactored to
follow the repo’s consumer-defined, small-interface guideline. Split the methods
into narrower interfaces at the call sites that need them, and move the
interface definitions next to the consumers that use them instead of keeping one
broad wrapper in clients.go. Preserve the same method signatures, but only
expose the minimal methods each consumer actually requires.

In `@contrib/aws-leaked-resources/delete.go`:
- Around line 587-754: printDetailedInventory currently duplicates the “what
belongs to this VPC” listing logic that already exists in the delete* flow, so
the two paths can drift. Refactor the resource discovery in Deleter by
extracting shared list helpers (for example listLoadBalancers, listVPCEndpoints,
listNatGateways, listInternetGateways, listSubnets, listRouteTables,
listSecurityGroups, and listNetworkInterfaces) and have both
printDetailedInventory and the existing
deleteLoadBalancers/deleteVPCEndpoints/etc. reuse them. Keep the VPC-scoped
filtering and formatting centralized in those shared helpers.

In `@contrib/aws-leaked-resources/main.go`:
- Around line 116-130: The flags registered in both scanCmd and deleteCmd are
duplicated even though they all bind to the shared cfg struct. Move the common
options like region, min-age, target, output, and output-dir to
rootCmd.PersistentFlags() in main.go so both subcommands inherit them, and keep
only command-specific flags on scanCmd and deleteCmd.

In `@contrib/aws-leaked-resources/prow_test.go`:
- Around line 1-173: Add table-driven coverage in prow_test.go for
LookupNamespaceFromServiceZone, FindServiceCIZoneID, and CorrelateWithProw using
a mocked Route53API. Make the mock return a truncated first page (IsTruncated
true) and a second page with the matching TXT record so the pagination path in
prow.go is exercised. Assert the namespace/zone ID/prow correlation are found
only after the second page, and keep the tests centered around the Route53API
and the three function names so they’re easy to locate.

In `@contrib/aws-leaked-resources/prow.go`:
- Around line 45-64: InferTestType depends on knownTestPrefixes being ordered
from more-specific to less-specific values, so add a clear comment or similar
guidance near that symbol documenting the “longer prefix first” invariant. If
you want to remove the ordering fragility entirely, update the initialization of
knownTestPrefixes so it is sorted by descending length before InferTestType
iterates over it, keeping the existing matching behavior in isHexInfraID
unchanged.

In `@contrib/aws-leaked-resources/report.go`:
- Around line 12-72: The PrintTable function is doing too much by combining
table sorting, summary-row rendering, and the leaked/uncertain detail output in
one block. Extract the detail-printing section for VerdictLeaked and
VerdictUncertain into a dedicated helper such as printInfraDetails(w, s), and
have PrintTable call it after writing the summary row; keep the existing VPC,
HostedZones, OIDCProviders, and IAMRoles loops inside that helper so the main
function is easier to scan and test.

In `@contrib/aws-leaked-resources/scan.go`:
- Around line 398-444: The VPC creation-time backfill logic is duplicated
between resolveVPCCreationTime and inventorySubResources, causing repeated
DescribeVpcEndpoints and DescribeNetworkInterfaces calls for the same VPC.
Refactor the shared “earliest timestamp wins” logic into a helper that both
resolveVPCCreationTime and inventorySubResources reuse, or pass the
already-fetched endpoint/ENI data through so CreatedAt is resolved once. Keep
the behavior centered around infra.VPCs[i].CreatedAt and the existing EC2 lookup
paths.
- Around line 567-651: AWS API failures in the inventory helpers are currently
swallowed, causing partial or empty results without any operator signal. Update
inventorySubResources, inventoryHostedZones, and inventoryIAMRoles to handle
Describe*/List* errors like checkEC2Instances does: log a WARN with the AWS call
context and error, and record an uncertain/incomplete inventory reason on the
InfraSet instead of just returning. Use the existing helper symbols
inventoryHostedZones, inventoryIAMRoles, and inventorySubResources to add the
same failure-reporting pattern consistently.

In `@contrib/aws-leaked-resources/tags.go`:
- Around line 57-73: The parseExpirationDate helper includes a misleading dead
layout entry that can never match numeric offsets as intended. Remove the
redundant "2006-01-02T15:04+00:00" format from the formats list in
parseExpirationDate, since the preceding "2006-01-02T15:04-07:00" already
handles ±hh:mm offsets and this extra entry only adds confusion.
🪄 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: 10d6af41-62b0-4df5-a271-a3aa5ef6ac90

📥 Commits

Reviewing files that changed from the base of the PR and between 8b5103a and 716d5da.

📒 Files selected for processing (15)
  • .claude/skills/triage-leaked-infra/SKILL.md
  • contrib/aws-leaked-resources/.gitignore
  • contrib/aws-leaked-resources/Makefile
  • contrib/aws-leaked-resources/README.md
  • contrib/aws-leaked-resources/clients.go
  • contrib/aws-leaked-resources/delete.go
  • contrib/aws-leaked-resources/delete_test.go
  • contrib/aws-leaked-resources/main.go
  • contrib/aws-leaked-resources/prow.go
  • contrib/aws-leaked-resources/prow_test.go
  • contrib/aws-leaked-resources/report.go
  • contrib/aws-leaked-resources/scan.go
  • contrib/aws-leaked-resources/scan_test.go
  • contrib/aws-leaked-resources/tags.go
  • contrib/aws-leaked-resources/types.go

Comment thread .claude/skills/triage-leaked-infra/SKILL.md
Comment thread contrib/aws-leaked-resources/delete.go
Comment thread contrib/aws-leaked-resources/delete.go
Comment thread contrib/aws-leaked-resources/prow.go
Comment thread contrib/aws-leaked-resources/prow.go
Comment thread contrib/aws-leaked-resources/report.go
Comment thread contrib/aws-leaked-resources/scan.go
@jparrill jparrill force-pushed the aws-clean-leaked-resources branch from 2172c30 to 2649352 Compare July 8, 2026 16:49

@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.

♻️ Duplicate comments (1)
.claude/skills/triage-leaked-infra/SKILL.md (1)

89-97: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Broken shell snippets still present despite prior "addressed" note.

Lines 89 and 96 still pipe into a trailing # comment, which makes the pipeline truncate at the comment and produces invalid/incomplete shell (the AWS CLI output is piped into nothing runnable). This matches a previously raised concern that was marked as addressed, but the current file content shows the same issue unresolved.

🔧 Proposed fix
-aws ec2 describe-vpc-endpoint-service-configurations --region us-east-1 --output json | # filter by kubernetes.io/cluster/<infraID> tag
+aws ec2 describe-vpc-endpoint-service-configurations --region us-east-1 --output json
+# filter by kubernetes.io/cluster/<infraID> tag
@@
-aws ec2 describe-addresses --region us-east-1 --filters "Name=domain,Values=vpc" --output text | # cross-check with NAT gateway EIPs
+aws ec2 describe-addresses --region us-east-1 --filters "Name=domain,Values=vpc" --output text
+# cross-check with NAT gateway EIPs

As per path instructions, .claude files are high risk and should be reviewed with a security and malware lens; although this is a documentation-only issue, it should be fixed so the snippets remain copy/paste-safe.

🤖 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 @.claude/skills/triage-leaked-infra/SKILL.md around lines 89 - 97, The shell
snippets in the triage guide still contain trailing inline comments after a
pipe, which breaks the command flow and leaves incomplete copy/pasteable AWS CLI
examples. Update the affected snippets in SKILL.md so the comment is moved onto
its own line or removed, preserving valid pipelines in the examples around the
VPC endpoint, NAT gateway, and address checks. Keep the commands in the same
section and verify the snippets remain runnable as written.

Source: Path instructions

🧹 Nitpick comments (1)
.claude/skills/triage-leaked-infra/SKILL.md (1)

36-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded username allowlist will go stale.

The protected-user list is a hardcoded set of individual usernames embedded directly in the skill doc. As team membership changes, this list requires manual doc edits to stay accurate, and it's easy to forget. Consider sourcing this from a shared, more discoverable location (e.g., a small config file or a well-known Slack/JIRA group) referenced by the skill instead of inlining names in markdown.

🤖 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 @.claude/skills/triage-leaked-infra/SKILL.md around lines 36 - 38, The
protected-user check in the triage skill is using a hardcoded username allowlist
that will drift over time. Update the skill guidance where it currently
enumerates usernames to reference a shared source of truth instead, such as a
small config file or an approved group identifier, and make the infraID check in
the skill point to that external list. Keep the existing FAIL behavior for
matches, but ensure the usernames themselves are no longer embedded directly in
the markdown.
🤖 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.

Duplicate comments:
In @.claude/skills/triage-leaked-infra/SKILL.md:
- Around line 89-97: The shell snippets in the triage guide still contain
trailing inline comments after a pipe, which breaks the command flow and leaves
incomplete copy/pasteable AWS CLI examples. Update the affected snippets in
SKILL.md so the comment is moved onto its own line or removed, preserving valid
pipelines in the examples around the VPC endpoint, NAT gateway, and address
checks. Keep the commands in the same section and verify the snippets remain
runnable as written.

---

Nitpick comments:
In @.claude/skills/triage-leaked-infra/SKILL.md:
- Around line 36-38: The protected-user check in the triage skill is using a
hardcoded username allowlist that will drift over time. Update the skill
guidance where it currently enumerates usernames to reference a shared source of
truth instead, such as a small config file or an approved group identifier, and
make the infraID check in the skill point to that external list. Keep the
existing FAIL behavior for matches, but ensure the usernames themselves are no
longer embedded directly in the markdown.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 83375943-372c-4040-8e02-8beeb707e0f3

📥 Commits

Reviewing files that changed from the base of the PR and between 716d5da and 2649352.

📒 Files selected for processing (15)
  • .claude/skills/triage-leaked-infra/SKILL.md
  • contrib/aws-leaked-resources/.gitignore
  • contrib/aws-leaked-resources/Makefile
  • contrib/aws-leaked-resources/README.md
  • contrib/aws-leaked-resources/clients.go
  • contrib/aws-leaked-resources/delete.go
  • contrib/aws-leaked-resources/delete_test.go
  • contrib/aws-leaked-resources/main.go
  • contrib/aws-leaked-resources/prow.go
  • contrib/aws-leaked-resources/prow_test.go
  • contrib/aws-leaked-resources/report.go
  • contrib/aws-leaked-resources/scan.go
  • contrib/aws-leaked-resources/scan_test.go
  • contrib/aws-leaked-resources/tags.go
  • contrib/aws-leaked-resources/types.go
✅ Files skipped from review due to trivial changes (2)
  • contrib/aws-leaked-resources/.gitignore
  • contrib/aws-leaked-resources/README.md
🚧 Files skipped from review as they are similar to previous changes (11)
  • contrib/aws-leaked-resources/prow_test.go
  • contrib/aws-leaked-resources/report.go
  • contrib/aws-leaked-resources/clients.go
  • contrib/aws-leaked-resources/prow.go
  • contrib/aws-leaked-resources/tags.go
  • contrib/aws-leaked-resources/types.go
  • contrib/aws-leaked-resources/main.go
  • contrib/aws-leaked-resources/scan.go
  • contrib/aws-leaked-resources/scan_test.go
  • contrib/aws-leaked-resources/delete.go
  • contrib/aws-leaked-resources/delete_test.go

@cblecker

cblecker commented Jul 9, 2026

Copy link
Copy Markdown
Member

/uncc

@openshift-ci openshift-ci Bot removed the request for review from cblecker July 9, 2026 00:30
@sdminonne

Copy link
Copy Markdown
Contributor

/cc @sdminonne

@openshift-ci openshift-ci Bot requested a review from sdminonne July 13, 2026 08:48

@sdminonne sdminonne 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.

@jparrill may you have a look and let me know?

Comment thread contrib/aws-leaked-resources/delete.go Outdated
return
}

out, err := d.ELBv2.DescribeLoadBalancers(ctx, &elbv2.DescribeLoadBalancersInput{})

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.

Hmm, if I'm understanding correctly we may need to add a paginator here

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.

Added — both deleteLoadBalancers and inventorySubResources now paginate through all pages. Also added a per-scan cache in Scanner so the scan path fetches LBs once per run.

Comment thread contrib/aws-leaked-resources/scan.go Outdated
}

if s.ELBv2 != nil {
if out, err := s.ELBv2.DescribeLoadBalancers(ctx, &elbv2.DescribeLoadBalancersInput{}); err == nil {

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.

Paginator here?

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.

Fixed — paginated DescribeVpcEndpointServiceConfigurations here and in delete.go.

Comment thread contrib/aws-leaked-resources/prow.go Outdated
// Input: "heritage=external-dns,...,external-dns/resource=route/e2e-clusters-7c84g-node-pool-78vcg/kube-apiserver"
// Output: "e2e-clusters-7c84g-node-pool-78vcg"
func extractNamespace(txtValue string) string {
re := regexp.MustCompile(`resource=route/([^/]+)/`)

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.

Can we put in a var this regexp? I know it won't be executed many times though :)

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 — moved to a package-level var namespaceFromRouteRe = regexp.MustCompile(...).

Comment thread contrib/aws-leaked-resources/prow.go
Comment thread contrib/aws-leaked-resources/scan.go
} else {
fmt.Fprintf(os.Stderr, " LEAKED: %s — inventorying sub-resources...\n", infraID)
}
s.inventorySubResources(ctx, &infraSet)

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.

This is called for one infraID, right?

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.

Yes, inventorySubResources is called once per InfraSet (= one infraID). It iterates over the VPCs in that set and counts their sub-resources.

VerdictUncertain Verdict = "UNCERTAIN"
)

type InfraSet struct {

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.

To be honest the InfraSet name is a little bit misleading, since it can contain only one infraID but it's `nit

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.

Acknowledged — InfraSet groups all AWS resources (VPCs, hosted zones, OIDC providers, IAM roles) that share the same infraID. Think of it as the set of infrastructure belonging to one cluster deployment. The name is accurate since it's a set of resources, not a set of infraIDs. That said, if it causes confusion during reviews I'm open to renaming in a follow-up.

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.

InfraInfo? InfraData? It may come from my past but a Set is an ensemble of things. Anyway. A follow-up is good too.

Comment thread contrib/aws-leaked-resources/scan.go Outdated
}

if s.ELBv2 != nil {
if out, err := s.ELBv2.DescribeLoadBalancers(ctx, &elbv2.DescribeLoadBalancersInput{}); err == nil {

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.

Here we describe all account LB, right so for each potentially leaked infraID we goes over all the LBs... May we cache it somhow?

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 catch — fixed. Added a per-scan LB cache to Scanner that fetches all LBs once (with pagination) and filters locally per VPC. No more N×DescribeLoadBalancers calls.

Comment thread contrib/aws-leaked-resources/delete.go Outdated
natEIPs := d.collectNATGatewayEIPs(ctx, vpcID)
if natDeleted := d.deleteNATGateways(ctx, vpcID); natDeleted > 0 {
fmt.Fprintf(os.Stderr, " Waiting for NAT gateways to release ENIs...\n")
if err := sleepWithContext(ctx, 15*time.Second); err != nil {

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.

Not sure 15secs. couldn't we implement retry wit exponential backoff? I won't block on this since I'm not totally sure neither. Just a question

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 question. The 15s sleep was a grace period for NAT gateways to release their ENIs (async AWS behavior), not a retry. That said, a blind sleep is fragile. I've replaced it with a polling loop: check every 5s whether NATs are still in deleting state, up to 12 retries (60s max). Fixed interval, no exponential backoff — this isn't a k8s controller and these AWS calls are cheap and on-demand.

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.

SGTM it was just to think about this and sharing rationale. TY!

RoleName: aws.String(roleName),
}); err == nil {
for _, prof := range profOut.InstanceProfiles {
_, _ = d.IAM.RemoveRoleFromInstanceProfile(ctx, &iam.RemoveRoleFromInstanceProfileInput{

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.

OK but we should delete the instance profile too, in case of no error we should invoke DeleteInstanceProfile.

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 catch — added DeleteInstanceProfile call right after RemoveRoleFromInstanceProfile so the orphaned profile doesn't linger.

@jparrill jparrill force-pushed the aws-clean-leaked-resources branch from 2649352 to 4f1efd0 Compare July 13, 2026 12:48
@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.83285% with 1023 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.74%. Comparing base (3e6ecb5) to head (ee69d33).
⚠️ Report is 59 commits behind head on main.

Files with missing lines Patch % Lines
contrib/aws-leaked-resources/delete.go 41.04% 319 Missing and 53 partials ⚠️
contrib/aws-leaked-resources/main.go 0.00% 253 Missing ⚠️
contrib/aws-leaked-resources/scan.go 57.90% 171 Missing and 34 partials ⚠️
contrib/aws-leaked-resources/report.go 0.00% 135 Missing ⚠️
contrib/aws-leaked-resources/prow.go 54.54% 43 Missing and 2 partials ⚠️
contrib/aws-leaked-resources/tags.go 86.31% 11 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8964      +/-   ##
==========================================
+ Coverage   43.37%   43.74%   +0.36%     
==========================================
  Files         771      779       +8     
  Lines       95718    97766    +2048     
==========================================
+ Hits        41520    42767    +1247     
- Misses      51313    51993     +680     
- Partials     2885     3006     +121     
Files with missing lines Coverage Δ
contrib/aws-leaked-resources/types.go 100.00% <100.00%> (ø)
contrib/aws-leaked-resources/tags.go 86.31% <86.31%> (ø)
contrib/aws-leaked-resources/prow.go 54.54% <54.54%> (ø)
contrib/aws-leaked-resources/report.go 0.00% <0.00%> (ø)
contrib/aws-leaked-resources/scan.go 57.90% <57.90%> (ø)
contrib/aws-leaked-resources/main.go 0.00% <0.00%> (ø)
contrib/aws-leaked-resources/delete.go 41.04% <41.04%> (ø)

... and 29 files with indirect coverage changes

Flag Coverage Δ
cmd-support 37.43% <ø> (+0.55%) ⬆️
cpo-hostedcontrolplane 45.91% <ø> (+0.69%) ⬆️
cpo-other 45.11% <ø> (+0.01%) ⬆️
hypershift-operator 54.07% <ø> (+0.41%) ⬆️
other 33.38% <40.83%> (+1.29%) ⬆️

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.

…leanup

- Go tool (contrib/aws-leaked-resources/cleanleaked) that scans AWS VPCs,
  classifies them through safety gates, and deletes leaked CI resources
  with full cascade support
- Claude Code skill (triage-leaked-infra) for interactive assessment of
  individual infra sets
- Protection tags applied to all root CI resources (ci-2, ci-3, ci-metrics)

Classification gates: protection tags, age check, OIDC/S3 verification,
EC2 instance detection, CI pattern matching.

Deletion cascade: ELBs → VPCE services → VPCE → NAT (poll drain) → EIPs →
IGW → RTB → ENI → Subnets → SG → VPC, with retries and context cancellation.

Signed-off-by: Juan Manuel Parrilla Madrid <jparrill@redhat.com>
@jparrill jparrill force-pushed the aws-clean-leaked-resources branch from 4f1efd0 to ee69d33 Compare July 13, 2026 13:39

@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: 3

🧹 Nitpick comments (2)
contrib/aws-leaked-resources/scan.go (2)

74-105: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

--limit selects a nondeterministic subset of leaked sets.

Scan ranges over grouped (a map), so iteration order is random. With Config.Limit > 0, the first Limit leaked sets encountered are inventoried and the rest are dropped (Line 103-105), meaning repeated runs report/act on different infra sets. For a repeatable cleanup tool, consider iterating over sorted infra IDs so --limit is deterministic.

🤖 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 `@contrib/aws-leaked-resources/scan.go` around lines 74 - 105, Make the
iteration that drives Scan’s limit handling deterministic by extracting the
infra IDs from grouped, sorting them, and iterating in that sorted order instead
of ranging over the map directly. Preserve the existing per-set processing and
--limit behavior while ensuring the same infra IDs are selected on every run.

630-662: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Cache account-wide IAM role and hosted-zone listings like ELBs. Both functions run once per leaked infra set (Lines 147-148), and each paginates the entire account (ListRoles / ListHostedZones) from scratch every time. With many leaked sets this is O(leaked × total-roles) and O(leaked × total-zones) API calls — the exact N× fan-out that was already fixed for load balancers via lbCache/fetchAllLoadBalancers. Fetch each collection once and filter locally per infra set.

  • contrib/aws-leaked-resources/scan.go#L630-L662: add a rolesCache (fetched once, paginated) to Scanner and filter by suffix/infraID locally instead of calling ListRoles per infra set.
  • contrib/aws-leaked-resources/scan.go#L578-L611: add a zonesCache (fetched once, paginated) to Scanner and run ZoneMatchesInfra locally instead of calling ListHostedZones per infra set.
🤖 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 `@contrib/aws-leaked-resources/scan.go` around lines 630 - 662, Update
contrib/aws-leaked-resources/scan.go:630-662 around Scanner.inventoryIAMRoles to
add a Scanner-level rolesCache, fetch and paginate ListRoles once, then filter
cached roles by suffix and infraID locally for each infra set instead of calling
ListRoles per set. Also update contrib/aws-leaked-resources/scan.go:578-611
around the hosted-zone inventory function to add and reuse a zonesCache
populated once through paginated ListHostedZones calls, applying
ZoneMatchesInfra locally for each infra set.
🤖 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 @.claude/skills/triage-leaked-infra/SKILL.md:
- Around line 17-19: Update the identifier extraction flow to fully validate
user-provided VPC IDs and infraIDs against their required formats before
constructing AWS CLI commands. Store validated values in shell variables and
quote those variables in every command interpolation, including both
VPC-to-infraID and infraID-to-VPC lookups.
- Around line 61-65: Update the OIDC liveness check instructions for the aws
s3api head-object commands so only an explicit 404 is classified as PASS;
classify 200 as FAIL and all other errors, including AccessDenied, throttling,
wrong-region, or transport failures, as UNKNOWN. Ensure UNKNOWN blocks deletion
rather than being treated as evidence that the object is absent.

In `@contrib/aws-leaked-resources/Makefile`:
- Around line 7-10: Define one root-relative BINDIR for the aws-leaked-resources
artifact directory, then update the build and clean targets to create, write to,
echo, and remove that same directory. Replace the separate ./bin creation and
hard-coded $(REPO_ROOT)/contrib/aws-leaked-resources/bin references while
preserving the existing binary name.

---

Nitpick comments:
In `@contrib/aws-leaked-resources/scan.go`:
- Around line 74-105: Make the iteration that drives Scan’s limit handling
deterministic by extracting the infra IDs from grouped, sorting them, and
iterating in that sorted order instead of ranging over the map directly.
Preserve the existing per-set processing and --limit behavior while ensuring the
same infra IDs are selected on every run.
- Around line 630-662: Update contrib/aws-leaked-resources/scan.go:630-662
around Scanner.inventoryIAMRoles to add a Scanner-level rolesCache, fetch and
paginate ListRoles once, then filter cached roles by suffix and infraID locally
for each infra set instead of calling ListRoles per set. Also update
contrib/aws-leaked-resources/scan.go:578-611 around the hosted-zone inventory
function to add and reuse a zonesCache populated once through paginated
ListHostedZones calls, applying ZoneMatchesInfra locally for each infra set.
🪄 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: 8094bf45-ae9e-4139-90f5-1ea285ffeeb7

📥 Commits

Reviewing files that changed from the base of the PR and between 2649352 and ee69d33.

📒 Files selected for processing (15)
  • .claude/skills/triage-leaked-infra/SKILL.md
  • contrib/aws-leaked-resources/.gitignore
  • contrib/aws-leaked-resources/Makefile
  • contrib/aws-leaked-resources/README.md
  • contrib/aws-leaked-resources/clients.go
  • contrib/aws-leaked-resources/delete.go
  • contrib/aws-leaked-resources/delete_test.go
  • contrib/aws-leaked-resources/main.go
  • contrib/aws-leaked-resources/prow.go
  • contrib/aws-leaked-resources/prow_test.go
  • contrib/aws-leaked-resources/report.go
  • contrib/aws-leaked-resources/scan.go
  • contrib/aws-leaked-resources/scan_test.go
  • contrib/aws-leaked-resources/tags.go
  • contrib/aws-leaked-resources/types.go
🚧 Files skipped from review as they are similar to previous changes (10)
  • contrib/aws-leaked-resources/.gitignore
  • contrib/aws-leaked-resources/types.go
  • contrib/aws-leaked-resources/clients.go
  • contrib/aws-leaked-resources/report.go
  • contrib/aws-leaked-resources/main.go
  • contrib/aws-leaked-resources/prow.go
  • contrib/aws-leaked-resources/delete.go
  • contrib/aws-leaked-resources/scan_test.go
  • contrib/aws-leaked-resources/delete_test.go
  • contrib/aws-leaked-resources/README.md

Comment on lines +17 to +19
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

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.

🔒 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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/triage-leaked-infra/SKILL.md around lines 17 - 19, Update the
identifier extraction flow to fully validate user-provided VPC IDs and infraIDs
against their required formats before constructing AWS CLI commands. Store
validated values in shell variables and quote those variables in every command
interpolation, including both VPC-to-infraID and infraID-to-VPC lookups.

Source: Path instructions

Comment on lines +61 to +65
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**.

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.

🔒 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-1

Repository: 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]}")
PY

Repository: openshift/hypershift

Length of output: 725


Treat only 404 as PASS in the OIDC liveness check. head-object failures like AccessDenied, throttling, wrong-region, or transport errors can mask an active cluster; classify them as UNKNOWN and block deletion unless the bucket returns a real 404.

🤖 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 @.claude/skills/triage-leaked-infra/SKILL.md around lines 61 - 65, Update the
OIDC liveness check instructions for the aws s3api head-object commands so only
an explicit 404 is classified as PASS; classify 200 as FAIL and all other
errors, including AccessDenied, throttling, wrong-region, or transport failures,
as UNKNOWN. Ensure UNKNOWN blocks deletion rather than being treated as evidence
that the object is absent.

Source: Path instructions

Comment on lines +7 to +10
build:
@mkdir -p bin
cd $(REPO_ROOT) && go build -o $(PKG)/bin/$(BINARY) $(PKG)
@echo "Built: bin/$(BINARY)"

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 | 🟠 Major | ⚡ Quick win

Use one absolute artifact directory for build and clean.

Lines 8–10 create ./bin but build to $(REPO_ROOT)/contrib/aws-leaked-resources/bin/cleanleaked; from the repository root, that parent directory does not exist, so make build fails. Line 19 also removes the wrong directory. Define a single root-relative BINDIR and use it consistently.

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
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contrib/aws-leaked-resources/Makefile` around lines 7 - 10, Define one
root-relative BINDIR for the aws-leaked-resources artifact directory, then
update the build and clean targets to create, write to, echo, and remove that
same directory. Replace the separate ./bin creation and hard-coded
$(REPO_ROOT)/contrib/aws-leaked-resources/bin references while preserving the
existing binary name.

@openshift-ci

openshift-ci Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

@jparrill: 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.

@hypershift-jira-solve-ci

hypershift-jira-solve-ci Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Interesting — the imports look correctly grouped already. The gci error is actually about gofmt-level formatting: when gci reports "File is not properly formatted", it means the file needs to be run through gci which also applies its own formatting. The gci linter is detecting formatting inconsistencies beyond just import order. The key issue is that make fmt (which runs gci as part of formatting) modifies these files. The root cause is that the PR author didn't run make fmt before committing.

Now I have complete evidence for all three failures. Here is the final report:

Test Failure Analysis Complete

Job Information

  • Prow Job: codecov/patch (Codecov GitHub check)
  • Build ID: Check run 86836430894 on commit ee69d33
  • PR: #8964CNTRLPLANE-3820: add cleanleaked tool for AWS leaked resource cleanup
  • Branch: aws-clean-leaked-resources
  • Related Failures: verify / Verify (run 29254639171), lint / Lint (run 29254637811) — all share the same root cause family

Test Failure Analysis

Error

codecov/patch: 40.83% of diff hit (target 43.37%)
  → 1,023 lines in the diff are missing test coverage

verify / Verify: "contrib/aws-leaked-resources/prow.go: needs update"
                 "contrib/aws-leaked-resources/prow_test.go: needs update"
  → Process completed with exit code 1

lint / Lint: "contrib/aws-leaked-resources/prow.go:22:1: File is not properly formatted (gci)"
             "contrib/aws-leaked-resources/prow_test.go:182:1: File is not properly formatted (gci)"
  → 2 issues (gci: 2), exit code 2

Summary

The codecov/patch check failed because only 40.83% of the 1,729 new executable lines added by this PR are covered by tests, falling short of the 43.37% project target. Two files — main.go (253 lines, 0% covered) and report.go (135 lines, 0% covered) — have zero test coverage, and delete.go (41% covered, 372 lines missing) drags the overall diff coverage below the threshold. Additionally, two related CI checks (verify / Verify and lint / Lint) fail because prow.go and prow_test.go were not formatted with the project's gci import ordering tool before committing.

Root Cause

codecov/patch — Insufficient diff test coverage (40.83% < 43.37%)

The PR introduces an entirely new standalone tool under contrib/aws-leaked-resources/ comprising 8 new .go source files (+2,048 lines). The existing tests (scan_test.go, delete_test.go, prow_test.go) exercise many code paths but leave significant portions untested:

File Patch Coverage Missing Lines Why Uncovered
main.go 0.00% 253 lines CLI entrypoint: cobra command setup, flag parsing, AWS session init — no tests at all
report.go 0.00% 135 lines Report formatting (table, JSON, file output) — pure logic but completely untested
delete.go 41.04% 319 missing + 53 partials AWS resource deletion functions — only mock-path branches covered, real API call sequences and error-handling paths untested
scan.go 57.90% 171 missing + 34 partials AWS scanning — mock-based tests cover happy paths but many AWS API interaction branches are not exercised
prow.go 54.54% 43 missing + 2 partials Route53 hosted zone scanning — some branches untested
tags.go 86.31% 11 missing + 2 partials Tag parsing — mostly covered, edge cases missing
clients.go N/A N/A Interface definitions only — no executable lines
types.go 100% 0 Fully covered

The aggregate: 706 lines hit / 1,729 executable lines = 40.83%, which is 2.54 percentage points below the 43.37% project target. The gap is ~44 additional lines needing coverage.

verify / Verify and lint / Lint — gci import formatting

The project uses the gci linter with custom-order: true and a specific section ordering defined in .golangci.yml:

gci:
  sections:
    - standard
    - dot
    - prefix(github.com/openshift/hypershift)
    - prefix(github.com/openshift)
    - prefix(github.com/aws)
    - prefix(github.com/Azure)
    - prefix(k8s.io)
    - prefix(sigs.k8s.io)
    - default
  custom-order: true

The make fmt step runs gci which rewrites the import blocks and also normalizes surrounding code formatting. The prow.go and prow_test.go files were committed without running make fmt, so:

  1. make fmt reformats both files (modifying whitespace/import ordering)
  2. git update-index --refresh detects the tree is dirty → verify fails (exit code 1)
  3. golangci-lint independently detects gci formatting violations → lint fails (exit code 2)

Specifically in prow.go, the knownTestPrefixes slice literal starting at line 22 has its string elements indented with a single tab (aligned with the var keyword) instead of a double tab (indented inside the var() + slice literal). gci/gofmt corrects this indentation.

Recommendations
  1. Fix formatting (resolves both verify and lint failures):

    cd contrib/aws-leaked-resources
    make fmt   # or: gofmt -w . && gci write --custom-order -s standard -s dot \
               #   -s 'prefix(github.com/openshift/hypershift)' \
               #   -s 'prefix(github.com/openshift)' \
               #   -s 'prefix(github.com/aws)' .

    Then commit the reformatted prow.go and prow_test.go.

  2. Increase test coverage (resolves codecov/patch failure) — pick the easiest path:

    • Option A (recommended): Add unit tests for report.go. Its functions (printTable, printJSON, writeReport) are pure formatting logic with no AWS dependencies — straightforward to test. Covering ~67 of the 135 uncovered lines would push diff coverage above 43.37%.
    • Option B: Add a few more table-driven test cases to delete_test.go to cover error-handling branches (e.g., deletion retry failures, partial failures). Even 44 more lines hit would cross the threshold.
    • Option C: Add basic smoke tests for main.go command construction (e.g., verify cobra command tree structure, flag defaults) — easy to write, covers many lines.
  3. Pre-push verification: Run make fmt verify lint locally before pushing to catch these issues early.

Evidence
Evidence Detail
codecov/patch result 40.83% of diff hit (target 43.37%) — 1,023 lines missing coverage across 6 files
Largest coverage gap main.go at 0% (253 lines) and report.go at 0% (135 lines) — together account for 388 of 1,023 uncovered lines
Coverage shortfall 2.54 percentage points below target; ~44 additional lines needed
verify failure step Run git update-index --refresh — detected prow.go and prow_test.go modified by make fmt
verify failure output contrib/aws-leaked-resources/prow.go: needs update / contrib/aws-leaked-resources/prow_test.go: needs update
lint failure output prow.go:22:1: File is not properly formatted (gci) / prow_test.go:182:1: File is not properly formatted (gci)
Formatting root cause prow.go line 22: knownTestPrefixes slice elements use single-tab indent instead of double-tab; gci/gofmt corrects this
gci config .golangci.yml defines 9 import sections with custom-order: true — files must match this exact grouping
PR commit ee69d33 on branch aws-clean-leaked-resources — all 15 changed files under contrib/aws-leaked-resources/

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/ai Indicates the PR includes changes related to AI - Claude agents, Cursor rules, etc. 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.

4 participants