diff --git a/tools/reaper/gcp.go b/tools/reaper/gcp.go index 0166916..563c0f4 100644 --- a/tools/reaper/gcp.go +++ b/tools/reaper/gcp.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "strings" + "time" "github.com/fluxcd/test-infra/tftestenv" ) @@ -35,6 +36,22 @@ func queryGCP(binPath, jqPath, project, labelKey, labelVal string) string { binPath, jqPath, project, labelKey, labelVal) } +// queryGCPSourceRepos returns a GCP command for querying all the source +// repositories in a specific format compatible with the resource type. +func queryGCPSourceRepos(binPath, jqPath, project string) string { + // TODO: Figure out a better way to detect the age of the repository. + // Currently, all the repositories are deleted with fixed now-1hr createdat + // time. + now := time.Now().UTC() + createdat := now.Add(-time.Hour) + tagVal := createdat.Format(tftestenv.CreatedAtTimeLayout) + return fmt.Sprintf(`%[1]s source repos list --project %[3]s --format=json | + %[2]s '.[] | + {name, "type": "cloud-source-repository", "tags": { "createdat": "%[4]s" }}' | + %[2]s -s '.'`, + binPath, jqPath, project, tagVal) +} + // deleteGCPArtifactRepositoryCmd returns a gcloud command for deleting a Google // Artifact Repository instance. func deleteGCPArtifactRepositoryCmd(binPath, project, name, location string) string { @@ -48,8 +65,27 @@ func deleteGCPClusterCmd(binPath, project, name, location string) string { binPath, project, name, location) } +// deleteGCPSourceRepoCmd returns a gcloud command for deleting cloud source +// repository. +func deleteGCPSourceRepoCmd(binPath, project, name string) string { + return fmt.Sprintf(`%[1]s source repos delete %[3]s --project %[2]s --quiet`, + binPath, project, name) +} + +func getGCPSourceRepos(ctx context.Context, cliPath, jqPath string) ([]resource, error) { + output, err := tftestenv.RunCommandWithOutput(ctx, "./", + queryGCPSourceRepos(cliPath, jqPath, *gcpProject), + tftestenv.RunCommandOptions{}, + ) + if err != nil { + return nil, err + } + return parseJSONResources(output) +} + // getGCPResources queries GCP for resources. func getGCPResources(ctx context.Context, cliPath, jqPath string) ([]resource, error) { + result := []resource{} output, err := tftestenv.RunCommandWithOutput(ctx, "./", queryGCP(cliPath, jqPath, *gcpProject, tagKey, tagVal), tftestenv.RunCommandOptions{}, @@ -57,7 +93,19 @@ func getGCPResources(ctx context.Context, cliPath, jqPath string) ([]resource, e if err != nil { return nil, err } - return parseJSONResources(output) + r, err := parseJSONResources(output) + if err != nil { + return nil, err + } + result = append(result, r...) + + sr, err := getGCPSourceRepos(ctx, cliPath, jqPath) + if err != nil { + return nil, err + } + result = append(result, sr...) + + return result, nil } // getGCPDefaultProject queries for the gcloud default/current project. @@ -94,3 +142,12 @@ func deleteGCPArtifactRepository(ctx context.Context, cliPath string, res resour ) return err } + +// deleteGCPSourceRepo deletes a Google cloud source repository. +func deleteGCPSourceRepo(ctx context.Context, cliPath string, res resource) error { + _, err := tftestenv.RunCommandWithOutput(ctx, "./", + deleteGCPSourceRepoCmd(cliPath, *gcpProject, res.Name), + tftestenv.RunCommandOptions{AttachConsole: true}, + ) + return err +} diff --git a/tools/reaper/main.go b/tools/reaper/main.go index 113369f..8fe4e2d 100644 --- a/tools/reaper/main.go +++ b/tools/reaper/main.go @@ -57,6 +57,15 @@ var clusterTypes map[string]string = map[string]string{ gcp: "container.googleapis.com/Cluster", } +// sourceRepoTypes maps the source repository type resource with their +// resource.Type value in different providers. This is used to identify that a +// given resource is a source repository in a particular provider. +var sourceRepoTypes map[string]string = map[string]string{ + aws: "", + azure: "", + gcp: "cloud-source-repository", +} + // resource is a common representation of a cloud resource with the minimal // attributes needed to uniquely identify them. type resource struct { @@ -246,6 +255,13 @@ func main() { log.Fatalf("Failed to delete cluster: %v", err) } } + + srcRepos := getSourceRepos(*targetProvider, resources) + for _, repo := range srcRepos { + if err := deleteGCPSourceRepo(ctx, gcloudPath, repo); err != nil { + log.Fatalf("Failed to delete source repository: %v", err) + } + } case awsnuke: if err := awsNuker.Delete(ctx); err != nil { log.Fatalf("Failed to delete resources: %v", err) @@ -279,6 +295,16 @@ func getClusters(provider string, resources []resource) []resource { return result } +func getSourceRepos(provider string, resources []resource) []resource { + result := []resource{} + for _, r := range resources { + if r.Type == sourceRepoTypes[provider] { + result = append(result, r) + } + } + return result +} + func getAzureResourceGroups(resources []resource) []resource { result := []resource{} for _, r := range resources { diff --git a/tools/reaper/main_test.go b/tools/reaper/main_test.go index 320b49a..d13df29 100644 --- a/tools/reaper/main_test.go +++ b/tools/reaper/main_test.go @@ -148,6 +148,21 @@ func TestParseJSONResources(t *testing.T) { "type": "Microsoft.Resources/resourceGroups" } ] +`), + wantItems: 1, + }, + { + name: "GCP source repository", + data: []byte(` +[ + { + "name": "projects/cncf-flux/repos/fleet-infra-sure-marmot", + "type": "cloud-source-repository", + "tags": { + "createdat": "aaaa" + } + } +] `), wantItems: 1, },