Skip to content

Commit

Permalink
Convert sweeeper template with Go (GoogleCloudPlatform#10862)
Browse files Browse the repository at this point in the history
  • Loading branch information
zli82016 authored Jun 3, 2024
1 parent 3fc405d commit a1e28b5
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 40 deletions.
26 changes: 26 additions & 0 deletions mmv1/api/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ type Resource struct {
// If true, skip sweeper generation for this resource
SkipSweeper bool `yaml:"skip_sweeper"`

// Override sweeper settings
Sweeper resource.Sweeper

Timeouts *Timeouts

// An array of function names that determine whether an error is retryable.
Expand Down Expand Up @@ -1308,3 +1311,26 @@ func FormatDocDescription(desc string) string {
func (r Resource) CustomTemplate(templatePath string, appendNewline bool) string {
return resource.ExecuteTemplate(&r, templatePath, appendNewline)
}

// Returns the key of the list of resources in the List API response
// Used to get the list of resources to sweep
func (r Resource) ResourceListKey() string {
var k string
if r.NestedQuery != nil && len(r.NestedQuery.Keys) > 0 {
k = r.NestedQuery.Keys[0]
}

if k == "" {
k = r.CollectionUrlKey
}

return k
}

func (r Resource) ListUrlTemplate() string {
return strings.Replace(r.CollectionUrl(), "zones/{{zone}}", "aggregated", 1)
}

func (r Resource) DeleteUrlTemplate() string {
return fmt.Sprintf("%s%s", r.ProductMetadata.BaseUrl, r.DeleteUri())
}
27 changes: 27 additions & 0 deletions mmv1/api/resource/sweeper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2024 Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package resource

type Sweeper struct {
//Google::YamlValidator
// The field checked by sweeper to determine
// eligibility for deletion for generated resources
SweepableIdentifierField string `yaml:"sweepable_identifier_field"`
}

// def validate
// super

// check :sweepable_identifier_field, type: String
// end
8 changes: 8 additions & 0 deletions mmv1/provider/template_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,14 @@ func (td *TemplateData) GenerateIamPolicyTestFile(filePath string, resource api.
td.GenerateFile(filePath, templatePath, resource, true, templates...)
}

func (td *TemplateData) GenerateSweeperFile(filePath string, resource api.Resource) {
templatePath := "templates/terraform/sweeper_file.go.tmpl"
templates := []string{
templatePath,
}
td.GenerateFile(filePath, templatePath, resource, false, templates...)
}

func (td *TemplateData) GenerateFile(filePath, templatePath string, input any, goFormat bool, templates ...string) {
log.Printf("Generating %s", filePath)

Expand Down
58 changes: 18 additions & 40 deletions mmv1/provider/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type Terraform struct {

Version product.Version

Product api.Product
Product *api.Product

StartTime time.Time
}
Expand All @@ -61,7 +61,7 @@ func NewTerraform(product *api.Product, versionName string, startTime time.Time)
t := Terraform{
ResourceCount: 0,
IAMResourceCount: 0,
Product: *product,
Product: product,
TargetVersionName: versionName,
Version: *product.VersionObjOrClosest(versionName),
StartTime: startTime,
Expand Down Expand Up @@ -122,8 +122,7 @@ func (t *Terraform) GenerateObject(object api.Resource, outputFolder, productPat
if generateCode {
log.Printf("Generating %s tests", object.Name)
t.GenerateResourceTests(object, *templateData, outputFolder)
// TODO Q2
// generate_resource_sweepers(pwd, data.clone)
t.GenerateResourceSweeper(object, *templateData, outputFolder)
}
}

Expand Down Expand Up @@ -179,6 +178,20 @@ func (t *Terraform) GenerateResourceTests(object api.Resource, templateData Temp
templateData.GenerateTestFile(targetFilePath, object)
}

func (t *Terraform) GenerateResourceSweeper(object api.Resource, templateData TemplateData, outputFolder string) {
if object.SkipSweeper || object.CustomCode.CustomDelete != "" || object.CustomCode.PreDelete != "" || object.CustomCode.PostDelete != "" || object.SkipDelete {
return
}

productName := t.Product.ApiName
targetFolder := path.Join(outputFolder, t.FolderName(), "services", productName)
if err := os.MkdirAll(targetFolder, os.ModePerm); err != nil {
log.Println(fmt.Errorf("error creating parent directory %v: %v", targetFolder, err))
}
targetFilePath := path.Join(targetFolder, fmt.Sprintf("resource_%s_sweeper.go", t.FullResourceName(object)))
templateData.GenerateSweeperFile(targetFilePath, object)
}

func (t *Terraform) GenerateOperation(outputFolder string) {

// TODO Q2
Expand Down Expand Up @@ -245,6 +258,7 @@ func (t *Terraform) GenerateIamDocumentation(object api.Resource, templateData T
templateData.GenerateIamDatasourceDocumentationFile(targetFilePath, object)
}

// Finds the folder name for a given version of the terraform provider
func (t *Terraform) FolderName() string {
if t.TargetVersionName == "ga" {
return "google"
Expand Down Expand Up @@ -1016,42 +1030,6 @@ func languageFromFilename(filename string) string {
}
}

// # Finds the folder name for a given version of the terraform provider
// def folder_name(version)
//
// version == 'ga' ? 'google' : "google-#{version}"
//
// end
//
// def generate_documentation(pwd, data)
//
// target_folder = data.output_folder
// target_folder = File.join(target_folder, 'website', 'docs', 'r')
// FileUtils.mkpath target_folder
// filepath = File.join(target_folder, "#{full_resource_name(data)}.html.markdown")
// data.generate(pwd, 'templates/terraform/resource.html.markdown.erb', filepath, self)
//
// end
//
// def generate_resource_sweepers(pwd, data)
//
// return if data.object.skip_sweeper ||
// data.object.custom_code.custom_delete ||
// data.object.custom_code.pre_delete ||
// data.object.custom_code.post_delete ||
// data.object.skip_delete
//
// product_name = @api.api_name
// target_folder = File.join(folder_name(data.version), 'services', product_name)
// file_name =
// "#{target_folder}/resource_#{full_resource_name(data)}_sweeper.go"
// FileUtils.mkpath folder_name(data.version)
// data.generate(pwd,
// 'templates/terraform/sweeper_file.go.erb',
// file_name,
// self)
//
// end
//
// # Returns the id format of an object, or self_link_uri if none is explicitly defined
// # We prefer the long name of a resource as the id so that users can reference
Expand Down
182 changes: 182 additions & 0 deletions mmv1/templates/terraform/sweeper_file.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
{{/* <% if hc_downstream */ -}}
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

// ----------------------------------------------------------------------------
//
// *** AUTO GENERATED CODE *** Type: MMv1 ***
//
// ----------------------------------------------------------------------------
//
// This file is automatically generated by Magic Modules and manual
// changes will be clobbered when the file is regenerated.
//
// Please read more about how to change this file in
// .github/CONTRIBUTING.md.
//
// ----------------------------------------------------------------------------

package {{ lower $.ProductMetadata.Name }}

import (
"context"
"log"
"strings"
"testing"

"{{ $.ImportPath }}/envvar"
"{{ $.ImportPath }}/sweeper"
"{{ $.ImportPath }}/tpgresource"
transport_tpg "{{ $.ImportPath }}/transport"
)

func init() {
sweeper.AddTestSweepers("{{ $.ResourceName }}", testSweep{{ $.ResourceName }})
}

// At the time of writing, the CI only passes us-central1 as the region
func testSweep{{ $.ResourceName }}(region string) error {
resourceName := "{{ $.ResourceName }}"
log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName)

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
}

t := &testing.T{}
billingId := envvar.GetTestBillingAccountFromEnv(t)

// Setup variables to replace in list template
d := &tpgresource.ResourceDataMock{
FieldsInSchema: map[string]interface{}{
"project": config.Project,
"region": region,
"location": region,
"zone": "-",
"billing_account": billingId,
},
}

listTemplate := strings.Split("{{ $.ListUrlTemplate }}", "?")[0]
listUrl, err := tpgresource.ReplaceVars(d, config, listTemplate)
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err)
return nil
}

res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: config.Project,
RawURL: listUrl,
UserAgent: config.UserAgent,
})
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err)
return nil
}

resourceList, ok := res["{{ $.ResourceListKey }}"]
if !ok {
log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.")
return nil
}
{{- if contains $.ListUrlTemplate "/aggregated/" }}
var rl []interface{}
zones := resourceList.(map[string]interface{})
// Loop through every zone in the list response
for _, zonesValue := range zones {
zone := zonesValue.(map[string]interface{})
for k, v := range zone {
// Zone map either has resources or a warning stating there were no resources found in the zone
if k != "warning" {
resourcesInZone := v.([]interface{})
rl = append(rl, resourcesInZone...)
}
}
}
{{- else }}

rl := resourceList.([]interface{})
{{- end }}

log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName)
// Keep count of items that aren't sweepable for logging.
nonPrefixCount := 0
for _, ri := range rl {
obj := ri.(map[string]interface{})
{{- if contains $.DeleteUrlTemplate "_id" }}
var name string
// Id detected in the delete URL, attempt to use id.
if obj["id"] != nil {
name = tpgresource.GetResourceNameFromSelfLink(obj["id"].(string))
} else if obj["name"] != nil {
name = tpgresource.GetResourceNameFromSelfLink(obj["name"].(string))
} else {
log.Printf("[INFO][SWEEPER_LOG] %s resource name and id were nil", resourceName)
return nil
}
{{- else }}
if obj["name"] == nil {
log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName)
return nil
}

name := tpgresource.GetResourceNameFromSelfLink(obj["name"].(string))
{{- end }}
// Skip resources that shouldn't be sweeped
{{- if $.Sweeper.SweepableIdentifierField }}
if !sweeper.IsSweepableTestResource(obj["{{ $.Sweeper.SweepableIdentifierField }}"].(string)) {
{{- else }}
if !sweeper.IsSweepableTestResource(name) {
{{- end }}
nonPrefixCount++
continue
}

deleteTemplate := "{{ $.DeleteUrlTemplate }}"
{{- if contains $.ListUrlTemplate "/aggregated/" }}
if obj["zone"] == nil {
log.Printf("[INFO][SWEEPER_LOG] %s resource zone was nil", resourceName)
return nil
}
zone := tpgresource.GetResourceNameFromSelfLink(obj["zone"].(string))
deleteTemplate = strings.Replace(deleteTemplate, "{{"{{zone}}"}}", zone, -1)

{{- end }}
deleteUrl, err := tpgresource.ReplaceVars(d, config, deleteTemplate)
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err)
return nil
}
deleteUrl = deleteUrl + name

// Don't wait on operations as we may have a lot to delete
_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "DELETE",
Project: config.Project,
RawURL: deleteUrl,
UserAgent: config.UserAgent,
})
if err != nil {
log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err)
} else {
log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name)
}
}

if nonPrefixCount > 0 {
log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount)
}

return nil
}

0 comments on commit a1e28b5

Please sign in to comment.