@@ -37,19 +37,37 @@ func main() {
3737
3838 doClient := digitalocean .NewGodoClient (* token )
3939
40- logger .Info ("Starting droplet cleanup" ,
40+ logger .Info ("Starting resource cleanup" ,
4141 zap .String ("prefix" , * namePrefix ),
4242 zap .String ("long_running_tag" , * longRunning ),
4343 zap .Bool ("dry_run" , * dryRun ))
4444
45- if err := cleanupDroplets (ctx , doClient , logger ); err != nil {
45+ skippedPrefixes , err := getSkippedDropletPrefixes (ctx , doClient , logger )
46+ if err != nil {
47+ logger .Fatal ("Failed to get skipped droplet prefixes" , zap .Error (err ))
48+ }
49+
50+ if err := cleanupDroplets (ctx , doClient , logger , skippedPrefixes ); err != nil {
4651 logger .Fatal ("Failed to cleanup droplets" , zap .Error (err ))
4752 }
4853
49- logger .Info ("Droplet cleanup completed successfully" )
54+ if err := cleanupFirewalls (ctx , doClient , logger , skippedPrefixes ); err != nil {
55+ logger .Fatal ("Failed to cleanup firewalls" , zap .Error (err ))
56+ }
57+
58+ logger .Info ("Resource cleanup completed successfully" )
59+ }
60+
61+ func extractPrefix (dropletName string ) string {
62+ // Extract prefix pattern like "petri-ib-XXXXXX" from "petri-ib-XXXXXX-validator-0"
63+ parts := strings .Split (dropletName , "-" )
64+ if len (parts ) >= 3 {
65+ return strings .Join (parts [:3 ], "-" )
66+ }
67+ return dropletName
5068}
5169
52- func cleanupDroplets (ctx context.Context , client digitalocean.DoClient , logger * zap.Logger ) error {
70+ func getSkippedDropletPrefixes (ctx context.Context , client digitalocean.DoClient , logger * zap.Logger ) ( map [ string ] bool , error ) {
5371 opts := & godo.ListOptions {
5472 Page : 1 ,
5573 PerPage : 200 ,
@@ -59,7 +77,7 @@ func cleanupDroplets(ctx context.Context, client digitalocean.DoClient, logger *
5977 for {
6078 droplets , err := client .ListDroplets (ctx , opts )
6179 if err != nil {
62- return fmt .Errorf ("failed to list droplets: %w" , err )
80+ return nil , fmt .Errorf ("failed to list droplets: %w" , err )
6381 }
6482
6583 allDroplets = append (allDroplets , droplets ... )
@@ -71,15 +89,21 @@ func cleanupDroplets(ctx context.Context, client digitalocean.DoClient, logger *
7189 opts .Page ++
7290 }
7391
74- logger .Info ("Retrieved droplets" , zap .Int ("total_count" , len (allDroplets )))
75-
76- var dropletsToDelete []godo.Droplet
92+ skippedPrefixes := make (map [string ]bool )
7793 now := time .Now ()
94+
7895 for _ , droplet := range allDroplets {
96+ // Skip non-petri droplets
7997 if ! strings .HasPrefix (droplet .Name , * namePrefix ) {
8098 continue
8199 }
82100
101+ prefix := extractPrefix (droplet .Name )
102+ if skippedPrefixes [prefix ] {
103+ continue
104+ }
105+
106+ // Skip droplets with the LONG_RUNNING tag
83107 hasLongRunningTag := false
84108 for _ , tag := range droplet .Tags {
85109 if tag == * longRunning {
@@ -89,29 +113,69 @@ func cleanupDroplets(ctx context.Context, client digitalocean.DoClient, logger *
89113 }
90114
91115 if hasLongRunningTag {
92- logger .Info ("Skipping droplet with long-running tag" ,
93- zap .String ("name" , droplet .Name ),
94- zap .Int ("id" , droplet .ID ))
116+ prefix := extractPrefix (droplet .Name )
117+ skippedPrefixes [prefix ] = true
118+ logger .Info ("Marking droplet as skipped (long-running)" ,
119+ zap .String ("droplet_name" , droplet .Name ))
95120 continue
96121 }
97122
98- // Parse the creation time and skip if created in the last 30 minutes
123+ // Skip if droplet was created in the last 30 minutes
99124 createdAt , err := time .Parse (time .RFC3339 , droplet .Created )
100125 if err != nil {
101- logger .Warn ("Failed to parse droplet creation time, skipping " ,
126+ logger .Error ("Failed to parse droplet creation time, marking as skipped " ,
102127 zap .String ("name" , droplet .Name ),
103- zap .Int ("id" , droplet .ID ),
104128 zap .String ("created_at" , droplet .Created ),
105129 zap .Error (err ))
130+ prefix := extractPrefix (droplet .Name )
131+ skippedPrefixes [prefix ] = true
106132 continue
107133 }
108134
109135 if now .Sub (createdAt ) < 30 * time .Minute {
110- logger .Info ("Skipping recently created droplet" ,
111- zap .String ("name" , droplet .Name ),
112- zap .Int ("id" , droplet .ID ),
113- zap .String ("created_at" , droplet .Created ),
114- zap .Duration ("age" , now .Sub (createdAt )))
136+ prefix := extractPrefix (droplet .Name )
137+ skippedPrefixes [prefix ] = true
138+ logger .Info ("Marking droplet as skipped (too recent)" ,
139+ zap .String ("droplet_name" , droplet .Name ),
140+ zap .String ("created_at" , droplet .Created ))
141+ }
142+ }
143+
144+ return skippedPrefixes , nil
145+ }
146+
147+ func cleanupDroplets (ctx context.Context , client digitalocean.DoClient , logger * zap.Logger , skippedPrefixes map [string ]bool ) error {
148+ opts := & godo.ListOptions {
149+ Page : 1 ,
150+ PerPage : 200 ,
151+ }
152+
153+ var allDroplets []godo.Droplet
154+ for {
155+ droplets , err := client .ListDroplets (ctx , opts )
156+ if err != nil {
157+ return fmt .Errorf ("failed to list droplets: %w" , err )
158+ }
159+
160+ allDroplets = append (allDroplets , droplets ... )
161+
162+ if len (droplets ) < opts .PerPage {
163+ break
164+ }
165+
166+ opts .Page ++
167+ }
168+
169+ logger .Info ("Retrieved droplets" , zap .Int ("total_count" , len (allDroplets )))
170+
171+ var dropletsToDelete []godo.Droplet
172+ for _ , droplet := range allDroplets {
173+ if ! strings .HasPrefix (droplet .Name , * namePrefix ) {
174+ continue
175+ }
176+
177+ if shouldSkipResource (droplet .Name , skippedPrefixes ) {
178+ logger .Info ("Skipping droplet" , zap .String ("name" , droplet .Name ))
115179 continue
116180 }
117181
@@ -121,14 +185,9 @@ func cleanupDroplets(ctx context.Context, client digitalocean.DoClient, logger *
121185 logger .Info ("Found droplets to delete" , zap .Int ("count" , len (dropletsToDelete )))
122186
123187 if * dryRun {
124- logger .Info ("Dry run mode - would delete the following droplets:" )
125- for _ , droplet := range dropletsToDelete {
126- logger .Info ("Would delete droplet" ,
127- zap .String ("name" , droplet .Name ),
128- zap .Int ("id" , droplet .ID ),
129- zap .Strings ("tags" , droplet .Tags ),
130- zap .String ("created_at" , droplet .Created ))
131- }
188+ logger .Info ("Dry run mode - would delete droplets" ,
189+ zap .Int ("count" , len (dropletsToDelete )),
190+ zap .Any ("droplets" , dropletsToDelete ))
132191 return nil
133192 }
134193
@@ -150,8 +209,6 @@ func cleanupDroplets(ctx context.Context, client digitalocean.DoClient, logger *
150209 logger .Info ("Successfully deleted droplet" ,
151210 zap .String ("name" , droplet .Name ),
152211 zap .Int ("id" , droplet .ID ))
153-
154- time .Sleep (100 * time .Millisecond )
155212 }
156213
157214 logger .Info ("Cleanup completed" ,
@@ -160,3 +217,78 @@ func cleanupDroplets(ctx context.Context, client digitalocean.DoClient, logger *
160217
161218 return nil
162219}
220+
221+ func shouldSkipResource (resourceName string , skippedPrefixes map [string ]bool ) bool {
222+ prefix := extractPrefix (resourceName )
223+ return skippedPrefixes [prefix ]
224+ }
225+
226+ func cleanupFirewalls (ctx context.Context , client digitalocean.DoClient , logger * zap.Logger , skippedPrefixes map [string ]bool ) error {
227+ opts := & godo.ListOptions {
228+ Page : 1 ,
229+ PerPage : 200 ,
230+ }
231+
232+ var allFirewalls []godo.Firewall
233+ for {
234+ firewalls , err := client .ListFirewalls (ctx , opts )
235+ if err != nil {
236+ return fmt .Errorf ("failed to list firewalls: %w" , err )
237+ }
238+
239+ allFirewalls = append (allFirewalls , firewalls ... )
240+
241+ if len (firewalls ) < opts .PerPage {
242+ break
243+ }
244+
245+ opts .Page ++
246+ }
247+
248+ logger .Info ("Retrieved firewalls" , zap .Int ("total_count" , len (allFirewalls )))
249+
250+ var firewallsToDelete []godo.Firewall
251+ for _ , firewall := range allFirewalls {
252+ if ! strings .HasPrefix (firewall .Name , * namePrefix ) {
253+ continue
254+ }
255+
256+ if shouldSkipResource (firewall .Name , skippedPrefixes ) {
257+ logger .Info ("Skipping firewall " ,
258+ zap .String ("name" , firewall .Name ))
259+ continue
260+ }
261+
262+ firewallsToDelete = append (firewallsToDelete , firewall )
263+ }
264+
265+ logger .Info ("Found firewalls to delete" , zap .Int ("count" , len (firewallsToDelete )))
266+
267+ if * dryRun {
268+ logger .Info ("Dry run mode - would delete firewalls" ,
269+ zap .Int ("count" , len (firewallsToDelete )),
270+ zap .Any ("firewalls" , firewallsToDelete ))
271+ return nil
272+ }
273+
274+ var deletedCount int
275+ for _ , firewall := range firewallsToDelete {
276+ logger .Info ("Deleting firewall" , zap .String ("name" , firewall .Name ))
277+
278+ if err := client .DeleteFirewall (ctx , firewall .ID ); err != nil {
279+ logger .Error ("Failed to delete firewall" ,
280+ zap .String ("name" , firewall .Name ),
281+ zap .Error (err ))
282+ continue
283+ }
284+
285+ deletedCount ++
286+ logger .Info ("Successfully deleted firewall" ,
287+ zap .String ("name" , firewall .Name ))
288+ }
289+
290+ logger .Info ("Firewall cleanup completed" ,
291+ zap .Int ("deleted_count" , deletedCount ))
292+
293+ return nil
294+ }
0 commit comments