@@ -64,6 +64,11 @@ Removes:
6464 log .Printf ("Warning: Failed to destroy Cloud Deploy releases: %v" , err )
6565 }
6666
67+ // Destroy AlloyDB Omni DBClusters first (if any)
68+ if err := destroyPRDBClusters (ctx , appConfig , projectID , region , prNumber , dryRun ); err != nil {
69+ log .Printf ("Warning: Failed to destroy AlloyDB Omni DBClusters: %v" , err )
70+ }
71+
6772 // Destroy Kubernetes namespace
6873 if err := destroyPRNamespace (ctx , appConfig , projectID , region , prNumber , dryRun ); err != nil {
6974 log .Printf ("Warning: Failed to destroy Kubernetes namespace: %v" , err )
@@ -153,6 +158,86 @@ func destroyPRReleases(ctx context.Context, appConfig *config.AppConfig, project
153158 return nil
154159}
155160
161+ func destroyPRDBClusters (ctx context.Context , appConfig * config.AppConfig , projectID , region , prNumber string , dryRun bool ) error {
162+ log .Printf ("🗄️ Destroying AlloyDB Omni DBClusters for PR #%s..." , prNumber )
163+
164+ // Get cluster credentials
165+ credsCmd := exec .CommandContext (ctx , "gcloud" , "container" , "clusters" , "get-credentials" ,
166+ fmt .Sprintf ("%s-cluster" , appConfig .App ),
167+ "--region=" + region ,
168+ "--project=" + projectID ,
169+ )
170+ if err := credsCmd .Run (); err != nil {
171+ // If we can't get credentials, skip DBCluster cleanup
172+ log .Printf (" Warning: Could not get cluster credentials: %v" , err )
173+ return nil
174+ }
175+
176+ namespaceName := fmt .Sprintf ("%s-preview-pr%s" , appConfig .App , prNumber )
177+
178+ // Check if namespace exists
179+ checkCmd := exec .CommandContext (ctx , "kubectl" , "get" , "namespace" , namespaceName )
180+ if err := checkCmd .Run (); err != nil {
181+ // Namespace doesn't exist, nothing to do
182+ return nil
183+ }
184+
185+ // List DBClusters in the namespace
186+ listCmd := exec .CommandContext (ctx , "kubectl" , "get" , "dbclusters.alloydbomni.dbadmin.goog" ,
187+ "-n" , namespaceName ,
188+ "-o" , "jsonpath={.items[*].metadata.name}" ,
189+ )
190+
191+ output , err := listCmd .Output ()
192+ if err != nil {
193+ // DBClusters CRD might not exist or no DBClusters in namespace
194+ if strings .Contains (err .Error (), "error validating data" ) ||
195+ strings .Contains (err .Error (), "the server doesn't have a resource type" ) {
196+ log .Printf (" No DBClusters CRD or no DBClusters found" )
197+ return nil
198+ }
199+ log .Printf (" Warning: Could not list DBClusters: %v" , err )
200+ return nil
201+ }
202+
203+ dbClusters := strings .TrimSpace (string (output ))
204+ if dbClusters == "" {
205+ log .Printf (" No DBClusters found in namespace" )
206+ return nil
207+ }
208+
209+ // Mark each DBCluster for deletion
210+ for _ , dbCluster := range strings .Fields (dbClusters ) {
211+ log .Printf (" 🗑️ Marking DBCluster '%s' for deletion" , dbCluster )
212+
213+ if ! dryRun {
214+ // Set isDeleted=true to trigger graceful deletion
215+ patchCmd := exec .CommandContext (ctx , "kubectl" , "patch" ,
216+ "dbclusters.alloydbomni.dbadmin.goog" , dbCluster ,
217+ "-n" , namespaceName ,
218+ "--type=merge" ,
219+ "-p" , `{"spec":{"isDeleted":true}}` ,
220+ )
221+
222+ if err := patchCmd .Run (); err != nil {
223+ log .Printf (" ⚠️ Failed to mark DBCluster %s for deletion: %v" , dbCluster , err )
224+ } else {
225+ log .Printf (" ✓ DBCluster %s marked for deletion" , dbCluster )
226+ }
227+ }
228+ }
229+
230+ // Wait a bit for the operator to start processing the deletion
231+ if ! dryRun && dbClusters != "" {
232+ log .Printf (" ⏳ Waiting for AlloyDB Omni operator to process deletions..." )
233+ // Sleep for 10 seconds to give the operator time to start cleanup
234+ // This helps avoid namespace getting stuck in Terminating state
235+ exec .CommandContext (ctx , "sleep" , "10" ).Run ()
236+ }
237+
238+ return nil
239+ }
240+
156241func destroyPRNamespace (ctx context.Context , appConfig * config.AppConfig , projectID , region , prNumber string , dryRun bool ) error {
157242 log .Printf ("☸️ Destroying Kubernetes namespace for PR #%s..." , prNumber )
158243
0 commit comments