@@ -5,14 +5,20 @@ import (
55 "fmt"
66 "log"
77 "strings"
8+ "time"
89
910 "github.com/hashicorp/terraform-provider-google/google/envvar"
11+ "github.com/hashicorp/terraform-provider-google/google/services/resourcemanager3"
1012 "github.com/hashicorp/terraform-provider-google/google/sweeper"
13+ transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
14+ "google.golang.org/api/cloudresourcemanager/v1"
15+ "google.golang.org/api/googleapi"
1116)
1217
1318func init () {
1419 sweeper .AddTestSweepersLegacy ("GoogleFolder" , testSweepFolder )
1520}
21+
1622func testSweepFolder (region string ) error {
1723 config , err := sweeper .SharedConfigForRegion (region )
1824 if err != nil {
@@ -27,7 +33,6 @@ func testSweepFolder(region string) error {
2733 }
2834
2935 org := envvar .UnsafeGetTestOrgFromEnv ()
30- log .Printf ("[DEBUG] org %s" , org )
3136
3237 if org == "" {
3338 log .Printf ("[INFO][SWEEPER_LOG] no organization set, failing folder sweeper" )
@@ -37,9 +42,8 @@ func testSweepFolder(region string) error {
3742 parent := "organizations/" + org
3843
3944 token := ""
45+ svc := config .NewResourceManagerV3Client (config .UserAgent )
4046 for paginate := true ; paginate ; {
41- // Filter for folders with test prefix
42- // filter := fmt.Sprintf("id:\"%s*\" -lifecycleState:DELETE_REQUESTED parent.id:%v", TestPrefix, org)
4347 found , err := config .NewResourceManagerV3Client (config .UserAgent ).Folders .List ().Parent (parent ).PageToken (token ).Do ()
4448 if err != nil {
4549 log .Printf ("[INFO][SWEEPER_LOG] error listing folders: %s" , err )
@@ -53,8 +57,35 @@ func testSweepFolder(region string) error {
5357 log .Printf ("[INFO][SWEEPER_LOG] Sweeping Folder id: %s, name: %s" , folder .Name , folder .DisplayName )
5458 _ , err := config .NewResourceManagerV3Client (config .UserAgent ).Folders .Delete (folder .Name ).Do ()
5559 if err != nil {
56- log .Printf ("[INFO][SWEEPER_LOG] Error, failed to delete folder %s: %s" , folder .Name , err )
57- continue
60+ if isCapabilityError (err ) {
61+ log .Println ("[INFO][SWEEPER_LOG]Detected 'configured capability' violation. Starting cleanup..." )
62+
63+ // 2. Get Folder to find ManagementProject
64+ folder , err := config .NewResourceManagerV3Client (config .UserAgent ).Folders .Get (folder .Name ).Do ()
65+ if err != nil {
66+ log .Printf ("[INFO][SWEEPER_LOG] Error, failed to delete folder %s: %s" , folder .Name , err )
67+ continue
68+ }
69+
70+ // 3. Handle Liens on Management Project
71+ if folder .ManagementProject != "" {
72+ cleanupLiens (svc , folder .ManagementProject )
73+ }
74+
75+ // 4. Disable configured capability
76+ disableCapability (folder .Name )
77+
78+ // 5. Retry Delete
79+ log .Println ("[INFO][SWEEPER_LOG]Retrying folder deletion..." )
80+ _ , err = config .NewResourceManagerV3Client (config .UserAgent ).Folders .Delete (folder .Name ).Do ()
81+ if err != nil {
82+ log .Printf ("[INFO][SWEEPER_LOG] Error, failed to delete folder %s: %s" , folder .Name , err )
83+ continue
84+ }
85+ } else {
86+ log .Printf ("[INFO][SWEEPER_LOG] Error, failed to delete folder %s: %s" , folder .Name , err )
87+ continue
88+ }
5889 }
5990 }
6091 token = found .NextPageToken
@@ -63,3 +94,86 @@ func testSweepFolder(region string) error {
6394
6495 return nil
6596}
97+
98+ // isCapabilityError parses the GCP error for the specific PreconditionFailure
99+ func isCapabilityError (err error ) bool {
100+ if gErr , ok := err .(* googleapi.Error ); ok {
101+ if gErr .Code == 400 && strings .Contains (gErr .Message , "Precondition check failed" ) {
102+ // Deep check for the description in the error details
103+ for _ , detail := range gErr .Details {
104+ if dMap , ok := detail .(map [string ]interface {}); ok {
105+ if violations , ok := dMap ["violations" ].([]interface {}); ok {
106+ for _ , v := range violations {
107+ if vMap , ok := v .(map [string ]interface {}); ok {
108+ if strings .Contains (fmt .Sprint (vMap ["description" ]), "configured capability" ) {
109+ return true
110+ }
111+ }
112+ }
113+ }
114+ }
115+ }
116+ }
117+ }
118+ return false
119+ }
120+
121+ func cleanupLiens (svc * cloudresourcemanager.Service , project string ) {
122+ log .Printf ("[INFO][SWEEPER_LOG]Checking liens on %s...\n " , project )
123+ resp , err := svc .Liens .List ().Parent (project ).Do ()
124+ if err != nil {
125+ log .Printf ("[INFO][SWEEPER_LOG]Failed to list liens: %v" , err )
126+ return
127+ }
128+ for _ , l := range resp .Liens {
129+ log .Printf ("[INFO][SWEEPER_LOG]Deleting lien: %s\n " , l .Name )
130+ _ , _ = svc .Liens .Delete (l .Name ).Do ()
131+ }
132+ }
133+
134+ func disableCapability (folderName string ) {
135+ // Format is folders/{id}/capabilities/app-management
136+ capName := fmt .Sprintf ("%s/capabilities/app-management" , folderName )
137+ log .Printf ("[INFO][SWEEPER_LOG]Disabling capability: %s\n " , capName )
138+
139+ config , err := sweeper .SharedConfigForRegion ("global" )
140+ if err != nil {
141+ log .Printf ("[INFO][SWEEPER_LOG] error getting shared config for region: %s" , err )
142+ return
143+ }
144+
145+ err = config .LoadAndValidate (context .Background ())
146+ if err != nil {
147+ log .Printf ("[INFO][SWEEPER_LOG] error loading: %s" , err )
148+ return
149+ }
150+
151+ obj := map [string ]interface {}{
152+ "value" : false ,
153+ }
154+
155+ url := "https://cloudresourcemanager.googleapis.com/v3/" + capName
156+
157+ res , err := transport_tpg .SendRequest (transport_tpg.SendRequestOptions {
158+ Config : config ,
159+ Method : "PATCH" ,
160+ Project : config .Project ,
161+ RawURL : url ,
162+ UserAgent : config .UserAgent ,
163+ Body : obj ,
164+ })
165+
166+ if err != nil {
167+ log .Printf ("[INFO][SWEEPER_LOG] Error disabling Capability: %s, err: %s" , capName , err )
168+ } else {
169+ log .Printf ("[INFO][SWEEPER_LOG] Finished disabled Capability: %s" , capName )
170+ }
171+
172+ err = resourcemanager3 .ResourceManager3OperationWaitTime (
173+ config , res , "Updating Capability" , config .UserAgent ,
174+ 20 * time .Minute )
175+
176+ if err != nil {
177+ log .Printf ("[INFO][SWEEPER_LOG] Error for disabling Capability operation: %s, err: %s" , capName , err )
178+ }
179+ }
0 commit comments