Skip to content

Commit

Permalink
Go rewrite - Add resource delete and import functions (GoogleCloudPla…
Browse files Browse the repository at this point in the history
  • Loading branch information
c2thorn authored and Cheriit committed Jun 4, 2024
1 parent 920dcc5 commit 67c3545
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 31 deletions.
31 changes: 31 additions & 0 deletions mmv1/google/string_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,34 @@ func Camelize(term string, firstLetter string) string {
})
return res
}

/*
Transforms a format string with field markers to a regex string with capture groups.
For instance,
projects/{{project}}/global/networks/{{name}}
is transformed to
projects/(?P<project>[^/]+)/global/networks/(?P<name>[^/]+)
Values marked with % are URL-encoded, and will match any number of /'s.
Note: ?P indicates a Python-compatible named capture group. Named groups
aren't common in JS-based regex flavours, but are in Perl-based ones
*/
func Format2Regex(format string) string {
re := regexp.MustCompile(`\{\{%([[:word:]]+)\}\}`)
result := re.ReplaceAllStringFunc(format, func(match string) string {
// TODO: the trims may not be needed with more effecient regex
word := strings.TrimPrefix(match, "{{")
word = strings.TrimSuffix(word, "}}")
return fmt.Sprintf("(?P<%s>.+)", word)
})
re = regexp.MustCompile(`\{\{([[:word:]]+)\}\}`)
result = re.ReplaceAllStringFunc(result, func(match string) string {
word := strings.TrimPrefix(match, "{{")
word = strings.TrimSuffix(word, "}}")
return fmt.Sprintf("(?P<%s>[^/]+)", word)
})
return result
}
21 changes: 11 additions & 10 deletions mmv1/provider/template_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,17 @@ func wrapMultipleParams(params ...interface{}) (map[string]interface{}, error) {
}

var TemplateFunctions = template.FuncMap{
"title": google.SpaceSeparatedTitle,
"replace": strings.Replace,
"camelize": google.Camelize,
"underscore": google.Underscore,
"plural": google.Plural,
"contains": strings.Contains,
"join": strings.Join,
"lower": strings.ToLower,
"upper": strings.ToUpper,
"dict": wrapMultipleParams,
"title": google.SpaceSeparatedTitle,
"replace": strings.Replace,
"camelize": google.Camelize,
"underscore": google.Underscore,
"plural": google.Plural,
"contains": strings.Contains,
"join": strings.Join,
"lower": strings.ToLower,
"upper": strings.ToUpper,
"dict": wrapMultipleParams,
"format2regex": google.Format2Regex,
}

var GA_VERSION = "ga"
Expand Down
20 changes: 0 additions & 20 deletions mmv1/provider/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -864,26 +864,6 @@ func (t Terraform) replaceImportPath(outputFolder, target string) {
//
// end
//
// # Transforms a format string with field markers to a regex string with
// # capture groups.
// #
// # For instance,
// # projects/{{project}}/global/networks/{{name}}
// # is transformed to
// # projects/(?P<project>[^/]+)/global/networks/(?P<name>[^/]+)
// #
// # Values marked with % are URL-encoded, and will match any number of /'s.
// #
// # Note: ?P indicates a Python-compatible named capture group. Named groups
// # aren't common in JS-based regex flavours, but are in Perl-based ones
// def format2regex(format)
//
// format
// .gsub(/\{\{%([[:word:]]+)\}\}/, '(?P<\1>.+)')
// .gsub(/\{\{([[:word:]]+)\}\}/, '(?P<\1>[^/]+)')
//
// end
//
// # Capitalize the first letter of a property name.
// # E.g. "creationTimestamp" becomes "CreationTimestamp".
// def titlelize_property(property)
Expand Down
170 changes: 169 additions & 1 deletion mmv1/templates/terraform/resource.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
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. */}}
limitations under the License. */ -}}
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

Expand Down Expand Up @@ -1004,3 +1004,171 @@ func resource{{ $.ResourceName -}}Update(d *schema.ResourceData, meta interface{
return resource{{ $.ResourceName -}}Read(d, meta)
}
{{- end}}
func resource{{ $.ResourceName }}Delete(d *schema.ResourceData, meta interface{}) error {
{{- if and (and ($.GetAsync.IsA "OpAsync") $.GetAsync.IncludeProject) ($.GetAsync.Allow "delete")}}
var project string
{{- end }}
{{- if $.SkipDelete }}
log.Printf("[WARNING] {{ $.ProductMetadata.Name }}{{" "}}{{ $.Name }} resources" +
" cannot be deleted from Google Cloud. The resource %s will be removed from Terraform" +
" state, but will still be present on Google Cloud.", d.Id())
d.SetId("")

return nil
{{- else }}
config := meta.(*transport_tpg.Config)
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
if err != nil {
return err
}

{{- if $.CustomCode.CustomDelete }}
{{/* TODO CustomDelete */}}
{{- else }}

billingProject := ""
{{ if $.HasProject }}
project, err := tpgresource.GetProject(d, config)
if err != nil {
return fmt.Errorf("Error fetching project for {{ $.Name }}: %s", err)
}
{{- if $.LegacyLongFormProject }}
billingProject = strings.TrimPrefix(project, "projects/")
{{- else }}
billingProject = project
{{- end }}
{{- end }}
{{- if $.Mutex }}
lockName, err := tpgresource.ReplaceVars(d, config, "{{ $.Mutex }}")
if err != nil {
return err
}
transport_tpg.MutexStore.Lock(lockName)
defer transport_tpg.MutexStore.Unlock(lockName)
{{- end }}

url, err := tpgresource.ReplaceVars{{if $.LegacyLongFormProject -}}ForId{{ end -}}(d, config, "{{"{{"}}{{$.ProductMetadata.Name}}BasePath{{"}}"}}{{$.DeleteUri}}")
if err != nil {
return err
}
{{/*If the deletion of the object requires sending a request body, the custom code will set 'obj' */}}
var obj map[string]interface{}
{{- if and $.NestedQuery $.NestedQuery.ModifyByPatch }}
{{/*Keep this after mutex - patch request data relies on current resource state*/}}
obj, err = resource{{ $.ResourceName }}PatchDeleteEncoder(d, meta, obj)
if err != nil {
return transport_tpg.HandleNotFoundError(err, d, "{{ $.ResourceName }}")
}
{{- if $.UpdateMask }}
url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": "{{- join $.NestedQuery.Keys "," -}}"})
if err != nil {
return err
}
{{- end }}
{{- end }}
{{- if $.SupportsIndirectUserProjectOverride }}
if parts := regexp.MustCompile(`projects\/([^\/]+)\/`).FindStringSubmatch(url); parts != nil {
billingProject = parts[1]
}
{{- end }}

// err == nil indicates that the billing_project value was found
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
billingProject = bp
}

headers := make(http.Header)
{{- if $.CustomCode.PreDelete }}
{{/* TODO PreDelete */}}
{{- end }}

log.Printf("[DEBUG] Deleting {{ $.Name }} %q", d.Id())
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "{{ camelize $.DeleteVerb "upper" -}}",
Project: billingProject,
RawURL: url,
UserAgent: userAgent,
Body: obj,
Timeout: d.Timeout(schema.TimeoutDelete),
Headers: headers,
{{- if $.ErrorRetryPredicates }}
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{{"{"}}{{- join $.ErrorRetryPredicates "," -}}{{"}"}},
{{- end }}
{{- if $.ErrorAbortPredicates }}
ErrorAbortPredicates: []transport_tpg.RetryErrorPredicateFunc{{"{"}}{{- join $.ErrorAbortPredicates "," -}}{{"}"}},
{{- end }}
})
if err != nil {
return transport_tpg.HandleNotFoundError(err, d, "{{ $.Name }}")
}
{{ if $.GetAsync.Allow "Delete" -}}
{{ if $.GetAsync.IsA "PollAsync" -}}
err = transport_tpg.PollingWaitTime(resource{{ $.ResourceName }}PollRead(d, meta), {{ $.GetAsync.CheckResponseFuncExistence }}, "Deleting {{ $.Name }}}", d.Timeout(schema.TimeoutCreate), {{ $.Async.TargetOccurrences }})
if err != nil {
{{- if $.Async.SuppressError }}
log.Printf("[ERROR] Unable to confirm eventually consistent {{ $.Name }} %q finished updating: %q", d.Id(), err)
{{- else }}
return fmt.Errorf("Error waiting to delete {{ $.Name }}: %s", err)
{{- end }}
}
{{- else }}
err = {{ $.ClientNamePascal }}OperationWaitTime(
config, res, {{if or $.HasProject $.GetAsync.IncludeProject -}} {{if $.LegacyLongFormProject -}}tpgresource.GetResourceNameFromSelfLink(project){{ else }}project{{ end }}, {{ end -}} "Deleting {{ $.Name -}}", userAgent,
d.Timeout(schema.TimeoutDelete))

if err != nil {
return err
}
{{- end }}
{{- end }}
{{- if $.CustomCode.PostDelete }}
{{/* TODO PostDelete */}}
{{- end }}

log.Printf("[DEBUG] Finished deleting {{ $.Name }} %q: %#v", d.Id(), res)
return nil
{{- end }}{{/* custom code */}}
{{- end }}{{/* pre delete */}}
}

{{ if not $.ExcludeImport -}}
func resource{{ $.ResourceName }}Import(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
{{- if $.CustomCode.CustomImport }}
{{/* TODO CustomImport */}}
{{- else }}
config := meta.(*transport_tpg.Config)
if err := tpgresource.ParseImportId([]string{
{{- range $id := $.ImportIdFormatsFromResource }}
"^{{ format2regex $id }}$",
{{- end }}
}, d, config); err != nil {
return nil, err
}

// Replace import id for the resource id
id, err := tpgresource.ReplaceVars{{ if $.LegacyLongFormProject -}}ForId{{ end -}}(d, config, "{{ $.IdFormat }}")
if err != nil {
return nil, fmt.Errorf("Error constructing id: %s", err)
}
d.SetId(id)
{{- if $.VirtualFields -}}
// Explicitly set virtual fields to default values on import
{{- range $vf := $.VirtualFields }}
{{- if $vf.DefaultValue }}
if err := d.Set("{{ $.vf.Name }}", {{ $.vf.DefaultValue }}); err != nil {
return nil, fmt.Errorf("Error setting {{ $.vf.Name }}: %s", err)
}
{{- end }}
{{- end }}
{{- end }}
{{- if $.CustomCode.PostImport }}
{{/* TODO PostImport */}}
{{- end }}

return []*schema.ResourceData{d}, nil
{{- end }}
}
{{- end }}
{{/* TODO Flatteners */}}
{{/* TODO Expanders */}}

0 comments on commit 67c3545

Please sign in to comment.