Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -225,27 +225,33 @@ fun ParametrizedWithType.sweeperParameters(sweeperRegions: String, sweepRun: Str
text("SWEEP_RUN", sweepRun)
}

// ParametrizedWithType.terraformSkipProjectSweeper sets an environment variable to skip the sweeper for project resources
fun ParametrizedWithType.terraformSkipProjectSweeper() {
text("env.SKIP_PROJECT_SWEEPER", "1")
// ParametrizedWithType.terraformSkipSweeper sets an environment variable used to skip the sweeper for resources
fun ParametrizedWithType.terraformSkipSweeper(resourceType: String) {
// Converts "PROJECT" into "env.SKIP_PROJECT_SWEEPER"
// Converts "FOLDER" into "env.SKIP_FOLDER_SWEEPER"
text("env.SKIP_${resourceType.uppercase()}_SWEEPER", "1")
}

// BuildType.disableProjectSweep disabled sweeping project resources after a build configuration has been initialised
fun BuildType.disableProjectSweep(){
params {
terraformSkipProjectSweeper()
terraformSkipSweeper("PROJECT")
terraformSkipSweeper("FOLDER")
}
}

// ParametrizedWithType.terraformEnableProjectSweeper unsets an environment variable used to skip the sweeper for project resources
fun ParametrizedWithType.terraformEnableProjectSweeper() {
text("env.SKIP_PROJECT_SWEEPER", "")
// ParametrizedWithType.terraformEnableSweeper unsets an environment variable used to skip the sweeper for resources
fun ParametrizedWithType.terraformEnableSweeper(resourceType: String) {
// Converts "PROJECT" into "env.SKIP_PROJECT_SWEEPER"
// Converts "FOLDER" into "env.SKIP_FOLDER_SWEEPER"
text("env.SKIP_${resourceType.uppercase()}_SWEEPER", "")
}

// BuildType.enableProjectSweep enables sweeping project resources after a build configuration has been initialised
fun BuildType.enableProjectSweep(){
params {
terraformEnableProjectSweeper()
terraformEnableSweeper("PROJECT")
terraformEnableSweeper("FOLDER")
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package resourcemanager

import (
"context"
"fmt"
"log"
"os"
"strings"
"time"

"github.com/hashicorp/terraform-provider-google/google/envvar"
"github.com/hashicorp/terraform-provider-google/google/services/resourcemanager3"
"github.com/hashicorp/terraform-provider-google/google/sweeper"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
resourceManagerV3 "google.golang.org/api/cloudresourcemanager/v3"
"google.golang.org/api/googleapi"
)

func init() {
// SKIP_FOLDER_SWEEPER can be set for a sweeper run to prevent it from
// sweeping folders. This can be useful when running sweepers in
// organizations where acceptance tests intiated by another folder may
// already be in-progress.
// Example: SKIP_FOLDER_SWEEPER=1 go test ./google -v -sweep=us-central1 -sweep-run=
if os.Getenv("SKIP_FOLDER_SWEEPER") != "" {
return
}

sweeper.AddTestSweepersLegacy("GoogleFolder", testSweepFolder)
}

func testSweepFolder(region string) error {
config, err := sweeper.SharedConfigForRegion(region)
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err)
return err
}

err = config.LoadAndValidate(context.Background())
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err)
return err
}

org := envvar.UnsafeGetTestOrgFromEnv()

if org == "" {
log.Printf("[INFO][SWEEPER_LOG] no organization set, failing folder sweeper")
return fmt.Errorf("no organization set")
}

parent := "organizations/" + org

token := ""
svc := config.NewResourceManagerV3Client(config.UserAgent)
for paginate := true; paginate; {
found, err := config.NewResourceManagerV3Client(config.UserAgent).Folders.List().Parent(parent).PageToken(token).Do()
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] error listing folders: %s", err)
return nil
}

for _, folder := range found.Folders {
if !strings.HasPrefix(folder.DisplayName, TestPrefix) {
continue
}
log.Printf("[INFO][SWEEPER_LOG] Sweeping Folder id: %s, name: %s", folder.Name, folder.DisplayName)
_, err := config.NewResourceManagerV3Client(config.UserAgent).Folders.Delete(folder.Name).Do()
if err != nil {
if isCapabilityError(err) {
log.Println("[INFO][SWEEPER_LOG]Detected 'configured capability' violation. Starting cleanup...")

// 2. Get Folder to find ManagementProject
folder, err := config.NewResourceManagerV3Client(config.UserAgent).Folders.Get(folder.Name).Do()
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] Error, failed to delete folder %s: %s", folder.Name, err)
continue
}

// 3. Handle Liens on Management Project
if folder.ManagementProject != "" {
cleanupLiens(svc, folder.ManagementProject)
}

// 4. Disable configured capability
disableCapability(folder.Name)

// 5. Retry Delete
log.Println("[INFO][SWEEPER_LOG]Retrying folder deletion...")
_, err = config.NewResourceManagerV3Client(config.UserAgent).Folders.Delete(folder.Name).Do()
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] Error, failed to delete folder %s: %s", folder.Name, err)
continue
}
} else {
log.Printf("[INFO][SWEEPER_LOG] Error, failed to delete folder %s: %s", folder.Name, err)
continue
}
}
}
token = found.NextPageToken
paginate = token != ""
}

return nil
}

// isCapabilityError parses the GCP error for the specific PreconditionFailure
func isCapabilityError(err error) bool {
if gErr, ok := err.(*googleapi.Error); ok {
if gErr.Code == 400 && strings.Contains(gErr.Message, "Precondition check failed") {
// Deep check for the description in the error details
for _, detail := range gErr.Details {
if dMap, ok := detail.(map[string]interface{}); ok {
if violations, ok := dMap["violations"].([]interface{}); ok {
for _, v := range violations {
if vMap, ok := v.(map[string]interface{}); ok {
if strings.Contains(fmt.Sprint(vMap["description"]), "configured capability") {
return true
}
}
}
}
}
}
}
}
return false
}

func cleanupLiens(svc *resourceManagerV3.Service, project string) {
log.Printf("[INFO][SWEEPER_LOG]Checking liens on %s...\n", project)
resp, err := svc.Liens.List().Parent(project).Do()
if err != nil {
log.Printf("[INFO][SWEEPER_LOG]Failed to list liens: %v", err)
return
}
for _, l := range resp.Liens {
log.Printf("[INFO][SWEEPER_LOG]Deleting lien: %s\n", l.Name)
_, err = svc.Liens.Delete(l.Name).Do()

if err != nil {
log.Printf("[INFO][SWEEPER_LOG] Error deleting lien: %s, err: %s", l.Name, err)
}
}
}

func disableCapability(folderName string) {
// Format is folders/{id}/capabilities/app-management
capName := fmt.Sprintf("%s/capabilities/app-management", folderName)
log.Printf("[INFO][SWEEPER_LOG]Disabling capability: %s\n", capName)

config, err := sweeper.SharedConfigForRegion("global")
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err)
return
}

err = config.LoadAndValidate(context.Background())
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err)
return
}

obj := map[string]interface{}{
"value": false,
}

url := "https://cloudresourcemanager.googleapis.com/v3/" + capName

res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "PATCH",
Project: config.Project,
RawURL: url,
UserAgent: config.UserAgent,
Body: obj,
})

if err != nil {
log.Printf("[INFO][SWEEPER_LOG] Error disabling Capability: %s, err: %s", capName, err)
} else {
log.Printf("[INFO][SWEEPER_LOG] Finished disabled Capability: %s", capName)
}

err = resourcemanager3.ResourceManager3OperationWaitTime(
config, res, "Updating Capability", config.UserAgent,
20*time.Minute)

if err != nil {
log.Printf("[INFO][SWEEPER_LOG] Error for disabling Capability operation: %s, err: %s", capName, err)
}
}
Loading