Skip to content

Commit 9364aea

Browse files
committed
Add orphaned test-VPC reaper and standardize dry-run across cleaners
clean_eks: after the cluster pass, reap dedicated EFA test VPCs (tag:Name efa-test-vpc-*) left behind by failed terraform destroys. Tears the stack down in dependency order — VPC endpoints, NAT gateways, Elastic IPs, leftover ENIs, internet gateways, subnets, route tables, security groups (revoke-then-delete), then the VPC — behind three safety gates: not backing a live EKS cluster (prod + best-effort beta), no active EC2 instances (re-checked before subnet deletion), and an age gate from the newest NAT gateway/VPC endpoint. A cleaner:reaping tag lets a partially-torn-down VPC resume on a later run instead of being stranded once its age signals are gone. Shared dry-run: add tool/clean/clean_flags.go (clean.DryRun, RegisterCommonFlags, Skip) and gate every mutating call across all 12 cleaners. Add a -dry-run flag to each tool and a workflow_dispatch dry_run input (DRY_RUN env: scheduled runs stay live, manual defaults to dry-run). Normalize --tags=clean to build-flag position and move ecs/host region args to flag.Arg(0). Also fix pre-existing bugs surfaced while touching these files: - clean_ecs: inverted-sign expiration deleted clusters with active tasks - clean_security_group: break-in-select spun pagination to timeout - clean_ebs / clean_file_system: age no-op and stale-error under-deletion
1 parent 2aab35f commit 9364aea

14 files changed

Lines changed: 926 additions & 95 deletions

File tree

.github/workflows/clean-aws-resources.yml

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ on:
77
schedule:
88
- cron: "0 0 * * *" # Run Every Day At Midnight
99
workflow_dispatch:
10+
inputs:
11+
dry_run:
12+
description: "Dry run: log the resources that would be deleted without deleting them"
13+
type: boolean
14+
required: false
15+
default: true
16+
17+
env:
18+
# Scheduled (cron) runs perform real deletions. Any manual (workflow_dispatch)
19+
# run defaults to a dry run unless dry_run=false is explicitly passed — so a
20+
# manual trigger, including an API dispatch that omits the input, never deletes
21+
# by accident. (github.event.inputs does not receive the input's UI default,
22+
# hence the explicit event_name gate.)
23+
DRY_RUN: ${{ github.event_name == 'schedule' && 'false' || (github.event.inputs.dry_run || 'true') }}
1024

1125
jobs:
1226
clean-opensource-ami:
@@ -26,7 +40,7 @@ jobs:
2640

2741
- name: Clean old ami
2842
working-directory: tool/clean
29-
run: go run ./clean_ami/clean_ami.go --tags=clean
43+
run: go run --tags=clean ./clean_ami/clean_ami.go -dry-run=${DRY_RUN}
3044

3145
clean-old-file-systems:
3246
runs-on: ubuntu-latest
@@ -45,7 +59,7 @@ jobs:
4559

4660
- name: Clean old file system
4761
working-directory: tool/clean
48-
run: go run ./clean_file_system/clean_file_system.go --tags=clean
62+
run: go run --tags=clean ./clean_file_system/clean_file_system.go -dry-run=${DRY_RUN}
4963

5064
clean-opensource-dedicated-hosts:
5165
runs-on: ubuntu-latest
@@ -64,7 +78,7 @@ jobs:
6478

6579
- name: Clean old dedicated host
6680
working-directory: tool/clean
67-
run: go run ./clean_dedicated_host/clean_dedicated_host.go --tags=clean
81+
run: go run --tags=clean ./clean_dedicated_host/clean_dedicated_host.go -dry-run=${DRY_RUN}
6882

6983
clean-internal-dedicated-hosts:
7084
runs-on: ubuntu-latest
@@ -98,7 +112,7 @@ jobs:
98112

99113
- name: Clean old dedicated host
100114
working-directory: tool/clean
101-
run: go run ./clean_dedicated_host/clean_dedicated_host.go --tags=clean
115+
run: go run --tags=clean ./clean_dedicated_host/clean_dedicated_host.go -dry-run=${DRY_RUN}
102116

103117
clean-hosts:
104118
runs-on: ubuntu-latest
@@ -138,7 +152,7 @@ jobs:
138152

139153
- name: Clean old host
140154
working-directory: tool/clean
141-
run: go run ./clean_host/clean_host.go ${{ matrix.region }}
155+
run: go run ./clean_host/clean_host.go -dry-run=${DRY_RUN} ${{ matrix.region }}
142156

143157
clean-hosts-china:
144158
runs-on: ubuntu-latest
@@ -157,7 +171,7 @@ jobs:
157171

158172
- name: Clean old hosts
159173
working-directory: tool/clean
160-
run: go run ./clean_host/clean_host.go cn-north-1
174+
run: go run ./clean_host/clean_host.go -dry-run=${DRY_RUN} cn-north-1
161175

162176
clean-ecs-resources:
163177
runs-on: ubuntu-latest
@@ -176,7 +190,7 @@ jobs:
176190

177191
- name: Clean old ecs resources
178192
working-directory: tool/clean
179-
run: go run --tags=clean ./clean_ecs/clean_ecs.go us-west-2
193+
run: go run --tags=clean ./clean_ecs/clean_ecs.go -dry-run=${DRY_RUN} us-west-2
180194

181195
clean-eks-clusters:
182196
runs-on: ubuntu-latest
@@ -195,7 +209,7 @@ jobs:
195209

196210
- name: Clean old eks cluster
197211
working-directory: tool/clean
198-
run: go run ./clean_eks/clean_eks.go --tags=clean
212+
run: go run ./clean_eks/clean_eks.go -dry-run=${DRY_RUN}
199213
clean-ebs-volumes:
200214
runs-on: ubuntu-latest
201215
permissions:
@@ -213,7 +227,7 @@ jobs:
213227

214228
- name: Clean old unused ebs volumes
215229
working-directory: tool/clean
216-
run: go run ./clean_ebs/clean_ebs.go --tags=clean
230+
run: go run ./clean_ebs/clean_ebs.go -dry-run=${DRY_RUN}
217231

218232
clean-asg:
219233
runs-on: ubuntu-latest
@@ -232,7 +246,7 @@ jobs:
232246

233247
- name: Clean old asg
234248
working-directory: tool/clean
235-
run: go run ./clean_auto_scaling_groups/clean_auto_scaling_groups.go --tags=clean
249+
run: go run ./clean_auto_scaling_groups/clean_auto_scaling_groups.go -dry-run=${DRY_RUN}
236250

237251
clean-launch-configs:
238252
runs-on: ubuntu-latest
@@ -251,7 +265,7 @@ jobs:
251265

252266
- name: Clean old launch configuration
253267
working-directory: tool/clean
254-
run: go run ./clean_launch_configuration/clean_launch_configuration.go --tags=clean
268+
run: go run ./clean_launch_configuration/clean_launch_configuration.go -dry-run=${DRY_RUN}
255269
clean-iam-roles:
256270
runs-on: ubuntu-latest
257271
permissions:
@@ -269,7 +283,7 @@ jobs:
269283

270284
- name: Clean old IAM roles
271285
working-directory: tool/clean
272-
run: go run ./clean_iam_roles/clean_iam_roles.go --tags=clean
286+
run: go run ./clean_iam_roles/clean_iam_roles.go -dry-run=${DRY_RUN}
273287
clean-log-groups:
274288
runs-on: ubuntu-latest
275289
permissions:
@@ -287,7 +301,7 @@ jobs:
287301

288302
- name: Clean old Log Groups
289303
working-directory: tool/clean
290-
run: go run ./clean_log_group/clean_log_group.go
304+
run: go run ./clean_log_group/clean_log_group.go -dry-run=${DRY_RUN}
291305
clean-security-groups:
292306
runs-on: ubuntu-latest
293307
permissions:
@@ -307,5 +321,5 @@ jobs:
307321
working-directory: tool/clean
308322
run: |
309323
set -e
310-
go run ./clean_security_group/clean_security_group.go || { echo "Failed to clean security groups"; exit 1; }
324+
go run ./clean_security_group/clean_security_group.go -dry-run=${DRY_RUN} || { echo "Failed to clean security groups"; exit 1; }
311325

tool/clean/clean_ami/clean_ami.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package main
99
import (
1010
"context"
1111
"errors"
12+
"flag"
1213
"fmt"
1314
"log"
1415
"sort"
@@ -56,6 +57,8 @@ var imagePrefixes = []string{
5657
}
5758

5859
func main() {
60+
clean.RegisterCommonFlags()
61+
flag.Parse()
5962
err := cleanAMIs()
6063
if err != nil {
6164
log.Fatalf("errors cleaning %v", err)
@@ -88,6 +91,9 @@ func deregisterAMIs(ctx context.Context, ec2client *ec2.Client, images []types.I
8891
for _, image := range images {
8992
if image.Name != nil && image.ImageId != nil && image.CreationDate != nil {
9093
log.Printf("Try to delete ami %v tags %v image id %v image creation date raw %v", *image.Name, image.Tags, *image.ImageId, *image.CreationDate)
94+
if clean.Skip("deregister ami %v (%v)", *image.Name, *image.ImageId) {
95+
continue
96+
}
9197
deregisterImageInput := &ec2.DeregisterImageInput{ImageId: image.ImageId}
9298
_, err := ec2client.DeregisterImage(ctx, deregisterImageInput)
9399

tool/clean/clean_auto_scaling_groups/clean_auto_scaling_groups.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package main
55

66
import (
77
"context"
8+
"flag"
89
"log"
910
"time"
1011

@@ -18,6 +19,8 @@ import (
1819

1920
// Clean eks clusters if they have been open longer than 7 day
2021
func main() {
22+
clean.RegisterCommonFlags()
23+
flag.Parse()
2124
err := cleanAutoScalingGroups()
2225
if err != nil {
2326
log.Fatalf("errors cleaning %v", err)
@@ -60,6 +63,9 @@ func terminateAutoScaling(ctx context.Context, client *autoscaling.Client, filte
6063
for _, group := range describeAutoScalingGroupsOutput.AutoScalingGroups {
6164
if expirationDateCluster.After(*group.CreatedTime) {
6265
log.Printf("try to delete auto scaling group %s", *group.AutoScalingGroupName)
66+
if clean.Skip("delete auto scaling group %s", *group.AutoScalingGroupName) {
67+
continue
68+
}
6369
deleteAutoScalingGroupInput := autoscaling.DeleteAutoScalingGroupInput{
6470
AutoScalingGroupName: group.AutoScalingGroupName,
6571
ForceDelete: aws.Bool(true),

tool/clean/clean_dedicated_host/clean_dedicated_host.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package main
88

99
import (
1010
"context"
11+
"flag"
1112
"log"
1213
"time"
1314

@@ -24,6 +25,8 @@ const tagName = "tag:Name"
2425
const tagValue = "IntegrationTestMacDedicatedHost"
2526

2627
func main() {
28+
clean.RegisterCommonFlags()
29+
flag.Parse()
2730
err := cleanDedicatedHost()
2831
if err != nil {
2932
log.Fatalf("errors cleaning %v", err)
@@ -62,6 +65,9 @@ func cleanDedicatedHost() error {
6265
}
6366

6467
log.Printf("Dedicated hosts to release %v", dedicatedHostIds)
68+
if clean.Skip("release dedicated hosts %v", dedicatedHostIds) {
69+
return nil
70+
}
6571
releaseDedicatedHost := ec2.ReleaseHostsInput{HostIds: dedicatedHostIds}
6672
_, err = ec2client.ReleaseHosts(cxt, &releaseDedicatedHost)
6773
return err

tool/clean/clean_ebs/clean_ebs.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package main
55

66
import (
77
"context"
8+
"flag"
89
"log"
910
"time"
1011

@@ -18,6 +19,8 @@ import (
1819

1920
// Clean ebs volumes if they have been open longer than 7 day and unused
2021
func main() {
22+
clean.RegisterCommonFlags()
23+
flag.Parse()
2124
err := cleanVolumes()
2225
if err != nil {
2326
log.Fatalf("errors cleaning %v", err)
@@ -39,6 +42,10 @@ func cleanVolumes() error {
3942

4043
func deleteUnusedVolumes(ctx context.Context, client *ec2.Client) error {
4144

45+
// KeepDurationOneWeek is negative, so expirationDate is "now minus one week".
46+
// A volume created before that is older than the retention window.
47+
expirationDate := time.Now().UTC().Add(clean.KeepDurationOneWeek)
48+
4249
input := &ec2.DescribeVolumesInput{
4350
Filters: []types.Filter{
4451
{
@@ -54,14 +61,17 @@ func deleteUnusedVolumes(ctx context.Context, client *ec2.Client) error {
5461
return err
5562
}
5663
for _, volume := range volumes.Volumes {
57-
if time.Since(*volume.CreateTime) > clean.KeepDurationOneWeek && len(volume.Attachments) == 0 {
64+
if expirationDate.After(*volume.CreateTime) && len(volume.Attachments) == 0 {
65+
if clean.Skip("delete unused volume %s", *volume.VolumeId) {
66+
continue
67+
}
5868
log.Printf("Deleting unused volume %s", *volume.VolumeId)
5969
_, err = client.DeleteVolume(ctx, &ec2.DeleteVolumeInput{
6070
VolumeId: volume.VolumeId,
6171
})
62-
}
63-
if err != nil {
64-
log.Printf("Error deleting volume %s: %v", *volume.VolumeId, err)
72+
if err != nil {
73+
log.Printf("Error deleting volume %s: %v", *volume.VolumeId, err)
74+
}
6575
}
6676
}
6777
return nil

tool/clean/clean_ecs/clean_ecs.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ package main
88

99
import (
1010
"context"
11+
"flag"
1112
"log"
12-
"os"
1313
"strings"
1414
"time"
1515

@@ -23,15 +23,23 @@ import (
2323

2424
// Clean ECS clusters if they have been running longer than 7 days
2525

26-
var expirationTimeOneWeek = time.Now().UTC().Add(-clean.KeepDurationOneWeek)
26+
// KeepDurationOneWeek is negative, so this is "now minus one week"; a task that
27+
// started after this cutoff is younger than a week and keeps its cluster alive.
28+
var expirationTimeOneWeek = time.Now().UTC().Add(clean.KeepDurationOneWeek)
2729

2830
const clusterPrefix = "cwagent-integ-test-cluster-"
2931

3032
var taskdefPrefixes = []string{"cwagent-integ-test-", "extra-apps-family-", "cwagent-task-family-"}
3133

3234
func main() {
35+
clean.RegisterCommonFlags()
36+
flag.Parse()
37+
region := flag.Arg(0)
38+
if region == "" {
39+
log.Fatal("Region argument is required, e.g. clean_ecs.go <region>")
40+
}
3341
ctx := context.Background()
34-
defaultConfig, err := config.LoadDefaultConfig(ctx, config.WithRegion(os.Args[1]))
42+
defaultConfig, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
3543
if err != nil {
3644
log.Fatalf("Error loading AWS config for ECS cleanup: %v", err)
3745
}
@@ -98,6 +106,9 @@ func terminateClusters(ctx context.Context, client *ecs.Client) {
98106
// Deletion Logic
99107
for _, clusterId := range clusterIds {
100108
log.Printf("Cluster to terminate: %s", *clusterId)
109+
if clean.Skip("delete ECS cluster %s (scale down and delete its services, deregister container instances)", *clusterId) {
110+
continue
111+
}
101112

102113
// Delete cluster services
103114
serviceInput := ecs.ListServicesInput{Cluster: clusterId}
@@ -190,6 +201,10 @@ func deleteInactiveTaskDefinitions(ctx context.Context, client *ecs.Client) {
190201

191202
log.Printf("Found %d inactive task definitions to delete", len(taskDefsToDelete))
192203

204+
if clean.Skip("delete %d inactive task definitions", len(taskDefsToDelete)) {
205+
return
206+
}
207+
193208
// Batch delete task definitions (API supports up to 10 at a time)
194209
const batchSize = 10
195210
totalDeleted := 0

0 commit comments

Comments
 (0)