@@ -3,6 +3,7 @@ package resources
33import (
44 "context"
55 "fmt"
6+ "time"
67
78 "github.com/sirupsen/logrus"
89
@@ -32,13 +33,15 @@ func init() {
3233//
3334// Endpoints used:
3435// - GET /v2/projects/{projectId}/regions/{region}/instances
36+ // - GET /v2/projects/{projectId}/regions/{region}/instances/{instanceId}
3537// - DELETE /v2/projects/{projectId}/regions/{region}/instances/{instanceId}
3638// - DELETE /v2/projects/{projectId}/regions/{region}/instances/{instanceId}/force
3739//
3840// Deleting an instance is a soft delete: it stays listed with status
39- // "Deleted" during the retention window. Remove therefore force-deletes
40- // instances already in that state so a nuke run converges instead of
41- // re-deleting the same instance forever.
41+ // "Deleted" during the retention window, so libnuke would wait on it
42+ // forever (Remove is only invoked once per item per run). Remove therefore
43+ // soft-deletes, polls until the instance reaches "Deleted", then
44+ // force-deletes to purge it within the same run.
4245type PostgresFlexInstance struct {
4346 * BaseResource `property:",inline"`
4447
@@ -49,14 +52,48 @@ type PostgresFlexInstance struct {
4952 Status string
5053}
5154
55+ const postgresFlexStatusDeleted = "Deleted"
56+
5257func (r * PostgresFlexInstance ) Remove (ctx context.Context ) error {
5358 if r .api == nil {
5459 return fmt .Errorf ("PostgresFlexInstance.Remove: api client not set" )
5560 }
56- if r .Status == "Deleted" {
57- return r .api .ForceDeleteInstance (ctx , r .ProjectID , r .Region , r .ID ).Execute ()
61+ if r .Status != postgresFlexStatusDeleted {
62+ if err := r .api .DeleteInstance (ctx , r .ProjectID , r .Region , r .ID ).Execute (); err != nil {
63+ return fmt .Errorf ("delete instance: %w" , err )
64+ }
65+ if err := r .waitUntilDeleted (ctx ); err != nil {
66+ return err
67+ }
68+ }
69+ if err := r .api .ForceDeleteInstance (ctx , r .ProjectID , r .Region , r .ID ).Execute (); err != nil {
70+ return fmt .Errorf ("force delete instance: %w" , err )
71+ }
72+ return nil
73+ }
74+
75+ // waitUntilDeleted polls the instance until the soft delete has landed
76+ // (status "Deleted"); force delete is rejected before that.
77+ func (r * PostgresFlexInstance ) waitUntilDeleted (ctx context.Context ) error {
78+ ctx , cancel := context .WithTimeout (ctx , 10 * time .Minute )
79+ defer cancel ()
80+ ticker := time .NewTicker (5 * time .Second )
81+ defer ticker .Stop ()
82+
83+ for {
84+ resp , err := r .api .GetInstance (ctx , r .ProjectID , r .Region , r .ID ).Execute ()
85+ if err != nil {
86+ return fmt .Errorf ("get instance while waiting for soft delete: %w" , err )
87+ }
88+ if item , ok := resp .GetItemOk (); ok && item .GetStatus () == postgresFlexStatusDeleted {
89+ return nil
90+ }
91+ select {
92+ case <- ctx .Done ():
93+ return fmt .Errorf ("timed out waiting for instance %s to reach %s state: %w" , r .ID , postgresFlexStatusDeleted , ctx .Err ())
94+ case <- ticker .C :
95+ }
5896 }
59- return r .api .DeleteInstance (ctx , r .ProjectID , r .Region , r .ID ).Execute ()
6097}
6198
6299func (r * PostgresFlexInstance ) Properties () types.Properties { return PropsFromStruct (r ) }
0 commit comments