From 4d2e8e590ec95ebc8c74bd1fe9332693bd768605 Mon Sep 17 00:00:00 2001 From: Mauricio Alvarez Leon <65101411+BBBmau@users.noreply.github.com> Date: Wed, 1 May 2024 09:49:17 -0700 Subject: [PATCH 1/6] chore(ci): Update .goreleaser.yml to include LICENSE.txt file (#10575) --- mmv1/third_party/terraform/.goreleaser.yml.erb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mmv1/third_party/terraform/.goreleaser.yml.erb b/mmv1/third_party/terraform/.goreleaser.yml.erb index f7a1328d9f5f..36562813e805 100644 --- a/mmv1/third_party/terraform/.goreleaser.yml.erb +++ b/mmv1/third_party/terraform/.goreleaser.yml.erb @@ -2,7 +2,8 @@ archives: - files: # Include built binary and license files in archive - - 'LICENSE' + - src: 'LICENSE' + dst: 'LICENSE.txt' format: zip name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' builds: From bfe4bec52d2ca92cf42459a51185220a70a00a65 Mon Sep 17 00:00:00 2001 From: "Stephen Lewis (Burrows)" Date: Wed, 1 May 2024 09:54:18 -0700 Subject: [PATCH 2/6] Update enrolled_teams.yml (#10574) --- tools/issue-labeler/labeler/enrolled_teams.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/issue-labeler/labeler/enrolled_teams.yml b/tools/issue-labeler/labeler/enrolled_teams.yml index 6aa16a51a36d..cb7568cd7828 100755 --- a/tools/issue-labeler/labeler/enrolled_teams.yml +++ b/tools/issue-labeler/labeler/enrolled_teams.yml @@ -572,11 +572,6 @@ service/sourcerepo: service/spanner: resources: - google_spanner_.* -service/sqladmin: - resources: - - google_sql_database - - google_sql_databases - - google_sql_user service/sqladmin-cp: resources: - google_sql_database_instance @@ -587,6 +582,11 @@ service/sqladmin-dp: resources: - google_sql_backup_run - google_sql_database_instance_latest_recovery_time +service/sqladmin-infra: + resources: + - google_sql_user + - google_sql_database + - google_sql_databases service/sqladmin-security: resources: - google_sql_ca_certs From 4bd60ea47904fa3deb33fe4fa1297ed9227d7423 Mon Sep 17 00:00:00 2001 From: Nick Elliot Date: Wed, 1 May 2024 14:17:17 -0700 Subject: [PATCH 3/6] add Update command to go resource template (#10551) --- mmv1/templates/terraform/resource.go.tmpl | 337 ++++++++++++++++++++++ 1 file changed, 337 insertions(+) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index c02558a7367f..c745b83f9622 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -667,3 +667,340 @@ func resource{{ $.ResourceName -}}Read(d *schema.ResourceData, meta interface{}) return nil {{ end -}} } + +{{if $.Updatable -}} +func resource{{ $.ResourceName -}}Update(d *schema.ResourceData, meta interface{}) error { +{{if and ($.GetAsync.IsA "OpAsync") ($.GetAsync.IncludeProject) ($.GetAsync.Allow "update") -}} + var project string +{{- end}} + config := meta.(*transport_tpg.Config) +{{if $.CustomCode.CustomUpdate -}} +//TODO custom update +{{ else -}} + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + 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 not $.Immutable -}} + obj := make(map[string]interface{}) +{{- range $prop := $.UpdateBodyProperties }} + {{/* flattened $s won't have something stored in state so instead nil is passed to the next expander. */}} + {{ $prop.ApiName -}}Prop, err := expand{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}({{ if $prop.FlattenObject }}nil{{else}}d.Get("{{underscore $prop.Name}}"){{ end }}, d, config) + if err != nil { + return err +{{if $prop.SendEmptyValue -}} + } else if v, ok := d.GetOkExists("{{ underscore $prop.Name -}}"); ok || !reflect.DeepEqual(v, {{ $prop.ApiName -}}Prop) { +{{ else if $prop.FlattenObject -}} + } else if !tpgresource.IsEmptyValue(reflect.ValueOf({{ $prop.ApiName -}}Prop)) { +{{ else -}} + } else if v, ok := d.GetOkExists("{{ underscore $prop.Name -}}"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, {{ $prop.ApiName -}}Prop)) { +{{- end}} + obj["{{ $prop.ApiName -}}"] = {{ $prop.ApiName -}}Prop + } +{{- end}} + +{{/* We need to decide what encoder to use here - if there's an update encoder, use that! -*/}} +{{if $.CustomCode.UpdateEncoder -}} + obj, err = resource{{ $.ResourceName -}}UpdateEncoder(d, meta, obj) + if err != nil { + return err + } +{{ else if $.CustomCode.Encoder -}} + obj, err = resource{{ $.ResourceName -}}Encoder(d, meta, obj) + if err != nil { + return err + } +{{- 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{{"}}"}}{{ $.UpdateUrl }}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating {{ $.Name -}} %q: %#v", d.Id(), obj) + headers := make(http.Header) +{{ if $.UpdateMask -}} +//TODO updatemask +{{end}} +{{ if $.CustomCode.PreUpdate -}} +//TODO Preupdate +{{end}} +{{if $.NestedQuery -}} +{{if $.NestedQuery.ModifyByPatch -}} +{{/*# Keep this after mutex - patch request data relies on current resource state */}} + obj, err = resource{{ $.ResourceName -}}PatchUpdateEncoder(d, meta, obj) + 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 + } + +{{if $.UpdateMask -}} +// if updateMask is empty we are not updating anything so skip the post +if len(updateMask) > 0 { +{{- end}} + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "{{ $.UpdateVerb -}}", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), +{{if $.ErrorRetryPredicates -}} + ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{{"{"}}{{ join $.ErrorRetryPredicates "," -}}{{"}"}}, +{{- end}} +{{if $.ErrorAbortPredicates -}} + ErrorAbortPredicates: []transport_tpg.RetryErrorPredicateFunc{{"{"}}{{ join $.ErrorRetryPredicates "," -}}{{"}"}}, +{{- end}} + Headers: headers, + }) + + if err != nil { + return fmt.Errorf("Error updating {{ $.Name -}} %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating {{ $.Name -}} %q: %#v", d.Id(), res) + } + +{{if $.GetAsync.Allow "update" -}} +{{if $.GetAsync.IsA "OpAsync" -}} + err = {{ $.ClientNamePascal -}}OperationWaitTime( + config, res, {{if or $.HasProject $.GetAsync.IncludeProject -}} {{if $.LegacyLongFormProject -}}tpgresource.GetResourceNameFromSelfLink(project){{ else }}project{{ end }}, {{ end -}} "Updating {{ $.Name -}}", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } +{{ else if $.GetAsync.IsA "PollAsync" -}} + err = transport_tpg.PollingWaitTime(resource{{ $.ResourceName -}}PollRead(d, meta), {{ $.GetAsync.CheckResponseFuncExistence -}}, "Updating {{ $.Name -}}", d.Timeout(schema.TimeoutUpdate), {{ $.GetAsync.TargetOccurrences -}}) + if err != nil { +{{if $.GetAsync.SuppressError -}} + log.Printf("[ERROR] Unable to confirm eventually consistent {{ $.Name -}} %q finished updating: %q", d.Id(), err) +{{ else -}} + return err +{{- end}} + } +{{- end}} +{{- end}} +{{if $.UpdateMask -}} + } +{{- end}} +{{ end -}} +{{if eq 0 1 -}} +{{/* TODO THIS BLOCK NEEDS FUNCTIONS TO WORK -- LINE 982 + +* field_specific_update_methods +* properties_by_custom_update +* group_by / key[:] + +*/}} +//TODO field_specific_update_methods($.root_properties) + d.Partial(true) + +{{/* properties_by_custom_update($.root_properties) + .sort_by {|k, _| k.nil? ? "" : k[:update_id].to_s} + .each do |key, props| +-*/}} + if {{/* props.map { |prop| "d.HasChange(\"#{underscore $prop.Name }\")" }.join ' || ' -*/}} { + obj := make(map[string]interface{}) + +{{/*- TODO 878 if key[:fingerprint_name] -*/}} + getUrl, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}{{$.ProductMetadata.Name}}BasePath{{"}}"}}{{$.SelfLinkUri}}" -}}") + if err != nil { + return err + } +{{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 + } + + getRes, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "{{ upper $.ReadVerb -}}", + Project: billingProject, + RawURL: getUrl, + UserAgent: userAgent, +{{if $.ErrorRetryPredicates -}} + ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{{"{"}}{{ join $.ErrorRetryPredicates "," -}}{{"}"}}, +{{- end}} +{{if $.ErrorAbortPredicates -}} + ErrorAbortPredicates: []transport_tpg.RetryErrorPredicateFunc{{"{"}}{{ join $.ErrorRetryPredicates "," -}}{{"}"}}, +{{- end}} + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("{{ $.ResourceName -}} %q", d.Id())) + } + + obj["{{/* key[:fingerprint_name] */}}"] = getRes["{{/* key[:fingerprint_name] */}}"] + +{{/* end -*/}} +{{/*- TODO range $prop := $.CustomUpdatePropertiesByKey $.AllUserProperties key */}} +{{ range $prop := $.AllProperties }} + {{ $prop.ApiName -}}Prop, err := expand{{ if $.NestedQuery -}}Nested{{ end }}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}({{ if $prop.FlattenObject }}nil{{else}}d.Get("{{underscore $prop.Name}}"){{ end }}, d, config) + if err != nil { + return err +{{/* There is some nuance in when we choose to send a value to an update function. + This is unfortunate, but it's because of the way that GCP works, so there's + no easy way out. Some APIs require you to send `enable_foo: false`, while + others will crash if you send `attribute: ''`. We require this nuance to + be annotated in ResourceName.yaml, since it is not discoverable automatically. + + The behavior here, which we believe to be correct, is to send a value if + * It is non-empty OR + * It is marked send_empty_value in ResourceName.yaml. + AND + * It has been set by the user OR + * It has been modified by the expander in any way + + This subsumes both `ForceSendFields` and `NullFields` in the go API client - + `NullFields` is a special case of `send_empty_value` where the empty value + in question is go's literal nil. +-*/}} +{{if $prop.SendEmptyValue -}} + } else if v, ok := d.GetOkExists("{{ underscore $prop.Name -}}"); ok || !reflect.DeepEqual(v, {{ $prop.ApiName -}}Prop) { +{{ else if $prop.FlattenObject -}} + } else if !tpgresource.IsEmptyValue(reflect.ValueOf({{ $prop.ApiName -}}Prop)) { +{{ else -}} + } else if v, ok := d.GetOkExists("{{ underscore $prop.Name -}}"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, {{ $prop.ApiName -}}Prop)) { +{{- end}} + obj["{{ $prop.ApiName -}}"] = {{ $prop.ApiName -}}Prop + } +{{/* end # props.each -*/}} + +{{/* We need to decide what encoder to use here - if there's an update encoder, use that! -*/}} +{{if $.CustomCode.update_encoder -}} + obj, err = resource{{ $.ResourceName -}}UpdateEncoder(d, meta, obj) + if err != nil { + return err + } +{{- 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{{"}}"}}{{ $.UpdateUrl }}")-}}") + if err != nil { + return err + } + + +{{ if $.CustomCode.PreUpdate -}} +//TODO Preupdate +{{ 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 + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "{{/* key[:update_verb] -*/}}", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), +{{if $.ErrorRetryPredicates -}} + ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{{"{"}}{{ join $.ErrorRetryPredicates "," -}}{{"}"}}, +{{- end}} +{{if $.ErrorAbortPredicates -}} + ErrorAbortPredicates: []transport_tpg.RetryErrorPredicateFunc{{"{"}}{{ join $.ErrorRetryPredicates "," -}}{{"}"}}, +{{- end}} + Headers: headers, + }) + if err != nil { + return fmt.Errorf("Error updating {{ $.Name -}} %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating {{ $.Name -}} %q: %#v", d.Id(), res) + } + +{{if $.GetAsync.Allow "update" -}} +{{if $.GetAsync.IsA "OpAsync" -}} + err = {{ $.ClientNamePascal -}}OperationWaitTime( + config, res, {{if or $.HasProject $.GetAsync.IncludeProject -}} {{if $.LegacyLongFormProject -}}tpgresource.GetResourceNameFromSelfLink(project){{ else }}project{{ end }}, {{ end -}} "Updating {{ $.Name -}}", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } +{{ else if $.GetAsync.IsA "PollAsync" -}} + err = transport_tpg.PollingWaitTime(resource{{ $.ResourceName -}}PollRead(d, meta), {{ $.GetAsync.CheckResponseFuncExistence -}}, "Updating {{ $.Name -}}", d.Timeout(schema.TimeoutUpdate), {{ $.GetAsync.TargetOccurrences -}}) + if err != nil { +{{if $.GetAsync.SuppressError -}} + log.Printf("[ERROR] Unable to confirm eventually consistent {{ $.Name -}} %q finished updating: %q", d.Id(), err) +{{ else -}} + return err +{{- end}} + } +{{- end}} +{{- end}} + } +{{/* TODO THIS BLOCK NEEDS FUNCTIONS TO WORK -- LINE 824 */}} +{{- end}} + + d.Partial(false) +{{- end}} + +{{ if $.CustomCode.PostUpdate -}} //TODO POST UPDATE {{end}} + return resource{{ $.ResourceName -}}Read(d, meta) +{{ end -}} +} +{{ else if $.RootLabels -}} +func resource{{ $.ResourceName -}}Update(d *schema.ResourceData, meta interface{}) error { + // Only the root field "labels" and "terraform_labels" are mutable + return resource{{ $.ResourceName -}}Read(d, meta) +} +{{- end}} From 0b5518b2d4fb333ea8f09c88cad09f1be8022273 Mon Sep 17 00:00:00 2001 From: Luca Prete Date: Wed, 1 May 2024 23:57:46 +0200 Subject: [PATCH 4/6] Upgrade DCL to 1.66.0 (#10564) Co-authored-by: Luca Prete --- mmv1/third_party/terraform/go.mod.erb | 2 +- mmv1/third_party/terraform/go.sum | 4 ++-- .../api/privateca/samples/basic.certificate_template.json | 1 + .../api/privateca/samples/update.certificate_template.json | 1 + tpgtools/go.mod | 2 +- tpgtools/go.sum | 4 ++-- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mmv1/third_party/terraform/go.mod.erb b/mmv1/third_party/terraform/go.mod.erb index fbcb0911e924..a2cab776d78e 100644 --- a/mmv1/third_party/terraform/go.mod.erb +++ b/mmv1/third_party/terraform/go.mod.erb @@ -5,7 +5,7 @@ go 1.21 require ( cloud.google.com/go/bigtable v1.19.0 - github.com/GoogleCloudPlatform/declarative-resource-client-library v1.64.0 + github.com/GoogleCloudPlatform/declarative-resource-client-library v1.66.0 github.com/apparentlymart/go-cidr v1.1.0 github.com/davecgh/go-spew v1.1.1 github.com/dnaeon/go-vcr v1.0.1 diff --git a/mmv1/third_party/terraform/go.sum b/mmv1/third_party/terraform/go.sum index 4a68953c65c1..b936e1ca8590 100644 --- a/mmv1/third_party/terraform/go.sum +++ b/mmv1/third_party/terraform/go.sum @@ -16,8 +16,8 @@ cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDn dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.64.0 h1:QA90iKudX8ijAW795f/jVbo0oEo7VoevwxLCNyi2qRc= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.64.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.66.0 h1:9C++tMcDcwgT8QTaq2bRtuAB5Tg4o4I4CkDQD57i914= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.66.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.0-alpha.0 h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE9N5vPhgY2I+j0= diff --git a/tpgtools/api/privateca/samples/basic.certificate_template.json b/tpgtools/api/privateca/samples/basic.certificate_template.json index 58503fef9d28..1e06a1f5af75 100755 --- a/tpgtools/api/privateca/samples/basic.certificate_template.json +++ b/tpgtools/api/privateca/samples/basic.certificate_template.json @@ -2,6 +2,7 @@ "name": "{{template}}", "project": "{{project}}", "location": "{{region}}", + "maximumLifetime": "86400s", "predefinedValues": { "keyUsage": { "baseKeyUsage": { diff --git a/tpgtools/api/privateca/samples/update.certificate_template.json b/tpgtools/api/privateca/samples/update.certificate_template.json index 809a143d4c98..90b423f6d11a 100755 --- a/tpgtools/api/privateca/samples/update.certificate_template.json +++ b/tpgtools/api/privateca/samples/update.certificate_template.json @@ -2,6 +2,7 @@ "name": "{{template}}", "project": "{{project}}", "location": "{{region}}", + "maximumLifetime": "172800s", "predefinedValues": { "keyUsage": { "baseKeyUsage": { diff --git a/tpgtools/go.mod b/tpgtools/go.mod index 62811783798b..f2c9231bd6eb 100644 --- a/tpgtools/go.mod +++ b/tpgtools/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( bitbucket.org/creachadair/stringset v0.0.11 - github.com/GoogleCloudPlatform/declarative-resource-client-library v1.64.0 + github.com/GoogleCloudPlatform/declarative-resource-client-library v1.66.0 github.com/golang/glog v1.1.2 github.com/hashicorp/hcl v1.0.0 github.com/kylelemons/godebug v1.1.0 diff --git a/tpgtools/go.sum b/tpgtools/go.sum index 1f2bf8524869..b03ba910fa6e 100644 --- a/tpgtools/go.sum +++ b/tpgtools/go.sum @@ -6,8 +6,8 @@ cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdi cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.64.0 h1:QA90iKudX8ijAW795f/jVbo0oEo7VoevwxLCNyi2qRc= -github.com/GoogleCloudPlatform/declarative-resource-client-library v1.64.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.66.0 h1:9C++tMcDcwgT8QTaq2bRtuAB5Tg4o4I4CkDQD57i914= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.66.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= From 8c35b73a3e68e13c872f5f16db8015026c90d8ca Mon Sep 17 00:00:00 2001 From: Lixia Chen Date: Thu, 2 May 2024 12:13:50 -0400 Subject: [PATCH 5/6] Add support for AuthorizedView resource in Bigtable (#10203) Co-authored-by: trollyxia --- mmv1/third_party/terraform/go.mod.erb | 34 +- mmv1/third_party/terraform/go.sum | 39 ++ .../provider/provider_mmv1_resources.go.erb | 1 + .../resource_bigtable_authorized_view.go | 463 ++++++++++++++++++ ..._bigtable_authorized_view_internal_test.go | 346 +++++++++++++ .../resource_bigtable_authorized_view_test.go | 449 +++++++++++++++++ .../r/bigtable_authorized_view.html.markdown | 161 ++++++ 7 files changed, 1477 insertions(+), 16 deletions(-) create mode 100644 mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view.go create mode 100644 mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view_internal_test.go create mode 100644 mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view_test.go create mode 100644 mmv1/third_party/terraform/website/docs/r/bigtable_authorized_view.html.markdown diff --git a/mmv1/third_party/terraform/go.mod.erb b/mmv1/third_party/terraform/go.mod.erb index a2cab776d78e..33575c5a327f 100644 --- a/mmv1/third_party/terraform/go.mod.erb +++ b/mmv1/third_party/terraform/go.mod.erb @@ -4,7 +4,7 @@ module github.com/hashicorp/terraform-provider-google go 1.21 require ( - cloud.google.com/go/bigtable v1.19.0 + cloud.google.com/go/bigtable v1.23.0 github.com/GoogleCloudPlatform/declarative-resource-client-library v1.66.0 github.com/apparentlymart/go-cidr v1.1.0 github.com/davecgh/go-spew v1.1.1 @@ -27,21 +27,23 @@ require ( github.com/mitchellh/hashstructure v1.1.0 github.com/sirupsen/logrus v1.8.1 golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 - golang.org/x/net v0.22.0 - golang.org/x/oauth2 v0.18.0 - google.golang.org/api v0.171.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c - google.golang.org/grpc v1.62.1 + golang.org/x/net v0.24.0 + golang.org/x/oauth2 v0.19.0 + google.golang.org/api v0.176.1 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be + google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 ) require ( bitbucket.org/creachadair/stringset v0.0.8 // indirect - cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.23.4 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.6 // indirect - cloud.google.com/go/longrunning v0.5.5 // indirect + cloud.google.com/go v0.112.2 // indirect + cloud.google.com/go/auth v0.3.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect + cloud.google.com/go/compute v1.25.1 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/iam v1.1.7 // indirect + cloud.google.com/go/longrunning v0.5.6 // indirect github.com/ProtonMail/go-crypto v1.1.0-alpha.0 // indirect github.com/agext/levenshtein v1.2.2 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect @@ -60,7 +62,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/glog v1.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect @@ -98,14 +100,14 @@ require ( go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.22.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/mmv1/third_party/terraform/go.sum b/mmv1/third_party/terraform/go.sum index b936e1ca8590..35128dd71308 100644 --- a/mmv1/third_party/terraform/go.sum +++ b/mmv1/third_party/terraform/go.sum @@ -3,16 +3,32 @@ bitbucket.org/creachadair/stringset v0.0.8/go.mod h1:AgthVMyMxC/6FK1KBJ2ALdqkZOb cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw= +cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= +cloud.google.com/go/auth v0.3.0 h1:PRyzEpGfx/Z9e8+lHsbkoUVXD0gnu4MNmm7Gp8TQNIs= +cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/bigtable v1.19.0 h1:wiq9LT0kukfInzvy1joMDijCw/OD1UChpSbORXYn0LI= cloud.google.com/go/bigtable v1.19.0/go.mod h1:xl5kPa8PTkJjdBxg6qdGH88464nNqmbISHSRU+D2yFE= +cloud.google.com/go/bigtable v1.23.0 h1:ufk3XFeq5ZmFmkTZrWiCEMjn9kefKbHT8LVsrX+iqqc= +cloud.google.com/go/bigtable v1.23.0/go.mod h1:WWRrYMBZpmHUO76ccwN7lx681FdyUWbIK2B4DDly0P4= cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw= cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= +cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM= +cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA= cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= +cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXmjuEaE= +cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -114,6 +130,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -257,6 +275,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -283,6 +302,7 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -294,6 +314,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc= golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= @@ -318,9 +340,13 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -348,10 +374,13 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -378,6 +407,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= +google.golang.org/api v0.176.1 h1:DJSXnV6An+NhJ1J+GWtoF2nHEuqB1VNoTfnIbjNvwD4= +google.golang.org/api v0.176.1/go.mod h1:j2MaSDYcvYV1lkZ1+SMW4IeF90SrEyFA+tluDYWRrFg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -388,10 +419,16 @@ google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 h1:g/4bk7P6TPMkAUbUhquq98xey1slwvuVJPosdBqYJlU= google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= +google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU= +google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -400,6 +437,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb index fae027d235ad..237477004ae5 100644 --- a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb +++ b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.erb @@ -289,6 +289,7 @@ var handwrittenResources = map[string]*schema.Resource{ "google_bigtable_gc_policy": bigtable.ResourceBigtableGCPolicy(), "google_bigtable_instance": bigtable.ResourceBigtableInstance(), "google_bigtable_table": bigtable.ResourceBigtableTable(), + "google_bigtable_authorized_view": bigtable.ResourceBigtableAuthorizedView(), "google_billing_subaccount": resourcemanager.ResourceBillingSubaccount(), "google_cloudfunctions_function": cloudfunctions.ResourceCloudFunctionsFunction(), "google_composer_environment": composer.ResourceComposerEnvironment(), diff --git a/mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view.go b/mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view.go new file mode 100644 index 000000000000..4a26b6e82a8b --- /dev/null +++ b/mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view.go @@ -0,0 +1,463 @@ +package bigtable + +import ( + "context" + "encoding/base64" + "fmt" + "log" + "reflect" + "time" + + "cloud.google.com/go/bigtable" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +var familySubsetSchemaElem *schema.Resource = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "family_name": { + Type: schema.TypeString, + Required: true, + Description: `Name of the column family to be included in the authorized view.`, + }, + "qualifiers": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: `Base64-encoded individual exact column qualifiers of the column family to be included in the authorized view.`, + }, + "qualifier_prefixes": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: `Base64-encoded prefixes for qualifiers of the column family to be included in the authorized view. Every qualifier starting with one of these prefixes is included in the authorized view. To provide access to all qualifiers, include the empty string as a prefix ("").`, + }, + }, +} + +func ResourceBigtableAuthorizedView() *schema.Resource { + return &schema.Resource{ + Create: resourceBigtableAuthorizedViewCreate, + Read: resourceBigtableAuthorizedViewRead, + Update: resourceBigtableAuthorizedViewUpdate, + Delete: resourceBigtableAuthorizedViewDestroy, + + Importer: &schema.ResourceImporter{ + State: resourceBigtableAuthorizedViewImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + }, + + CustomizeDiff: customdiff.All( + tpgresource.DefaultProviderProject, + ), + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The name of the authorized view. Must be 1-50 characters and must only contain hyphens, underscores, periods, letters and numbers.`, + }, + + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Description: `The ID of the project in which the resource belongs. If it is not provided, the provider project is used.`, + }, + + "instance_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareResourceNames, + Description: `The name of the Bigtable instance in which the authorized view belongs.`, + }, + + "table_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareResourceNames, + Description: `The name of the Bigtable table in which the authorized view belongs.`, + }, + + "deletion_protection": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{"PROTECTED", "UNPROTECTED"}, false), + Description: `A field to make the authorized view protected against data loss i.e. when set to PROTECTED, deleting the authorized view, the table containing the authorized view, and the instance containing the authorized view would be prohibited. +If not provided, currently deletion protection will be set to UNPROTECTED as it is the API default value. Note this field configs the deletion protection provided by the API in the backend, and should not be confused with Terraform-side deletion protection.`, + }, + + "subset_view": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: `An AuthorizedView permitting access to an explicit subset of a Table.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "row_prefixes": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: `Base64-encoded row prefixes to be included in the authorized view. To provide access to all rows, include the empty string as a prefix ("").`, + }, + "family_subsets": { + Type: schema.TypeSet, + Optional: true, + Description: `Subsets of column families to be included in the authorized view.`, + Elem: familySubsetSchemaElem, + }, + }, + }, + }, + }, + UseJSONNumber: true, + } +} + +func resourceBigtableAuthorizedViewCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + ctx := context.Background() + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return err + } + + instanceName := tpgresource.GetResourceNameFromSelfLink(d.Get("instance_name").(string)) + c, err := config.BigTableClientFactory(userAgent).NewAdminClient(project, instanceName) + if err != nil { + return fmt.Errorf("Error starting admin client. %s", err) + } + if err := d.Set("instance_name", instanceName); err != nil { + return fmt.Errorf("Error setting instance_name: %s", err) + } + + defer c.Close() + + authorizedViewId := d.Get("name").(string) + tableId := d.Get("table_name").(string) + authorizedViewConf := bigtable.AuthorizedViewConf{ + AuthorizedViewID: authorizedViewId, + TableID: tableId, + } + + // Check if deletion protection is given + // If not given, currently tblConf.DeletionProtection will be set to false in the API + deletionProtection := d.Get("deletion_protection") + if deletionProtection == "PROTECTED" { + authorizedViewConf.DeletionProtection = bigtable.Protected + } else if deletionProtection == "UNPROTECTED" { + authorizedViewConf.DeletionProtection = bigtable.Unprotected + } + + subsetView, ok := d.GetOk("subset_view") + if !ok || len(subsetView.([]interface{})) != 1 { + return fmt.Errorf("subset_view must be specified for authorized view %s", authorizedViewId) + } + subsetViewConf, err := generateSubsetViewConfig(subsetView.([]interface{})) + if err != nil { + return err + } + authorizedViewConf.AuthorizedView = subsetViewConf + + ctxWithTimeout, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate)) + defer cancel() // Always call cancel. + err = c.CreateAuthorizedView(ctxWithTimeout, &authorizedViewConf) + if err != nil { + return fmt.Errorf("Error creating authorized view. %s", err) + } + + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/instances/{{instance_name}}/tables/{{table_name}}/authorizedViews/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return resourceBigtableAuthorizedViewRead(d, meta) +} + +func resourceBigtableAuthorizedViewRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + ctx := context.Background() + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return err + } + + instanceName := tpgresource.GetResourceNameFromSelfLink(d.Get("instance_name").(string)) + c, err := config.BigTableClientFactory(userAgent).NewAdminClient(project, instanceName) + if err != nil { + return fmt.Errorf("Error starting admin client. %s", err) + } + + defer c.Close() + + authorizedViewId := d.Get("name").(string) + tableId := d.Get("table_name").(string) + authorizedViewInfo, err := c.AuthorizedViewInfo(ctx, tableId, authorizedViewId) + if err != nil { + if tpgresource.IsNotFoundGrpcError(err) { + log.Printf("[WARN] Removing %s because it's gone", authorizedViewId) + d.SetId("") + return nil + } + return err + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error setting project: %s", err) + } + + deletionProtection := authorizedViewInfo.DeletionProtection + if deletionProtection == bigtable.Protected { + if err := d.Set("deletion_protection", "PROTECTED"); err != nil { + return fmt.Errorf("Error setting deletion_protection: %s", err) + } + } else if deletionProtection == bigtable.Unprotected { + if err := d.Set("deletion_protection", "UNPROTECTED"); err != nil { + return fmt.Errorf("Error setting deletion_protection: %s", err) + } + } else { + return fmt.Errorf("Error setting deletion_protection, it should be either PROTECTED or UNPROTECTED") + } + + if sv, ok := authorizedViewInfo.AuthorizedView.(*bigtable.SubsetViewInfo); ok { + subsetView := flattenSubsetViewInfo(sv) + if err := d.Set("subset_view", subsetView); err != nil { + return fmt.Errorf("Error setting subset_view: %s", err) + } + } else { + return fmt.Errorf("Error parsing server returned subset_view since it's empty") + } + + return nil +} + +func resourceBigtableAuthorizedViewUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + ctx := context.Background() + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return err + } + + instanceName := tpgresource.GetResourceNameFromSelfLink(d.Get("instance_name").(string)) + c, err := config.BigTableClientFactory(userAgent).NewAdminClient(project, instanceName) + if err != nil { + return fmt.Errorf("Error starting admin client. %s", err) + } + defer c.Close() + + authorizedViewId := d.Get("name").(string) + tableId := d.Get("table_name").(string) + authorizedViewConf := bigtable.AuthorizedViewConf{ + AuthorizedViewID: authorizedViewId, + TableID: tableId, + } + + if d.HasChange("subset_view") { + subsetView := d.Get("subset_view") + if len(subsetView.([]interface{})) != 1 { + return fmt.Errorf("subset_view must be specified for authorized view %s", authorizedViewId) + } + subsetViewConf, err := generateSubsetViewConfig(subsetView.([]interface{})) + if err != nil { + return err + } + authorizedViewConf.AuthorizedView = subsetViewConf + } + + if d.HasChange("deletion_protection") { + deletionProtection := d.Get("deletion_protection") + if deletionProtection == "PROTECTED" { + authorizedViewConf.DeletionProtection = bigtable.Protected + } else if deletionProtection == "UNPROTECTED" { + authorizedViewConf.DeletionProtection = bigtable.Unprotected + } + } + + updateAuthorizedViewConf := bigtable.UpdateAuthorizedViewConf{ + AuthorizedViewConf: authorizedViewConf, + IgnoreWarnings: true, + } + + ctxWithTimeout, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutUpdate)) + defer cancel() // Always call cancel. + err = c.UpdateAuthorizedView(ctxWithTimeout, updateAuthorizedViewConf) + if err != nil { + return fmt.Errorf("Error updating authorized view. %s", err) + } + + return resourceBigtableAuthorizedViewRead(d, meta) +} + +func resourceBigtableAuthorizedViewDestroy(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + ctx := context.Background() + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return err + } + + instanceName := tpgresource.GetResourceNameFromSelfLink(d.Get("instance_name").(string)) + c, err := config.BigTableClientFactory(userAgent).NewAdminClient(project, instanceName) + if err != nil { + return fmt.Errorf("Error starting admin client. %s", err) + } + + defer c.Close() + + authorizedViewId := d.Get("name").(string) + tableId := d.Get("table_name").(string) + err = c.DeleteAuthorizedView(ctx, tableId, authorizedViewId) + if err != nil { + return fmt.Errorf("Error deleting authorized view. %s", err) + } + + d.SetId("") + + return nil +} + +func resourceBigtableAuthorizedViewImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "projects/(?P[^/]+)/instances/(?P[^/]+)/tables/(?P[^/]+)/authorizedViews/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/instances/{{instance_name}}/tables/{{table_name}}/authorizedViews/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func generateSubsetViewConfig(subsetView []interface{}) (*bigtable.SubsetViewConf, error) { + subsetViewConf := bigtable.SubsetViewConf{} + + if len(subsetView) == 0 { + return nil, fmt.Errorf("Error constructing SubsetViewConfig; empty subset_view list") + } + if subsetView[0] == nil { + return &subsetViewConf, nil + } + sv, ok := subsetView[0].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("Error constructing SubsetViewConfig; element in subset_view list has wrong type: %s", reflect.TypeOf(subsetView[0])) + } + if rowPrefixes, ok := sv["row_prefixes"]; ok { + for _, rowPrefix := range rowPrefixes.(*schema.Set).List() { + decodedRowPrefix, err := base64.StdEncoding.DecodeString(rowPrefix.(string)) + if err != nil { + return nil, err + } + subsetViewConf.AddRowPrefix(decodedRowPrefix) + } + } + if familySubsets, ok := sv["family_subsets"]; ok { + for _, familySubset := range familySubsets.(*schema.Set).List() { + familySubsetElement := familySubset.(map[string]interface{}) + familyName := familySubsetElement["family_name"].(string) + if qualifiers, ok := familySubsetElement["qualifiers"]; ok { + for _, qualifier := range qualifiers.(*schema.Set).List() { + decodedQualifier, err := base64.StdEncoding.DecodeString(qualifier.(string)) + if err != nil { + return nil, err + } + subsetViewConf.AddFamilySubsetQualifier(familyName, decodedQualifier) + } + } + if qualifierPrefixes, ok := familySubsetElement["qualifier_prefixes"]; ok { + for _, qualifierPrefix := range qualifierPrefixes.(*schema.Set).List() { + decodedQualifierPrefix, err := base64.StdEncoding.DecodeString(qualifierPrefix.(string)) + if err != nil { + return nil, err + } + subsetViewConf.AddFamilySubsetQualifierPrefix(familyName, decodedQualifierPrefix) + } + } + } + } + return &subsetViewConf, nil +} + +func flattenSubsetViewInfo(subsetViewInfo *bigtable.SubsetViewInfo) []map[string]interface{} { + subsetView := make([]map[string]interface{}, 1) + + rowPrefixes := []string{} + for _, prefix := range subsetViewInfo.RowPrefixes { + encodedRowPrefix := base64.StdEncoding.EncodeToString(prefix) + rowPrefixes = append(rowPrefixes, encodedRowPrefix) + } + familySubsets := []map[string]interface{}{} + for k, v := range subsetViewInfo.FamilySubsets { + familySubsetElement := make(map[string]interface{}) + familySubsetElement["family_name"] = k + qualifiers := []string{} + for _, qualifier := range v.Qualifiers { + encodedQualifier := base64.StdEncoding.EncodeToString(qualifier) + qualifiers = append(qualifiers, encodedQualifier) + } + if len(qualifiers) > 0 { + familySubsetElement["qualifiers"] = qualifiers + } + qualifierPrefixes := []string{} + for _, qualifierPrefix := range v.QualifierPrefixes { + encodedQualifierPrefix := base64.StdEncoding.EncodeToString(qualifierPrefix) + qualifierPrefixes = append(qualifierPrefixes, encodedQualifierPrefix) + } + if len(qualifierPrefixes) > 0 { + familySubsetElement["qualifier_prefixes"] = qualifierPrefixes + } + familySubsets = append(familySubsets, familySubsetElement) + } + subsetView[0] = make(map[string]interface{}) + if len(rowPrefixes) > 0 { + subsetView[0]["row_prefixes"] = rowPrefixes + } + if len(familySubsets) > 0 { + subsetView[0]["family_subsets"] = familySubsets + } + + return subsetView +} diff --git a/mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view_internal_test.go b/mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view_internal_test.go new file mode 100644 index 000000000000..7b431838426b --- /dev/null +++ b/mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view_internal_test.go @@ -0,0 +1,346 @@ +package bigtable + +import ( + "reflect" + "strings" + "testing" + + "cloud.google.com/go/bigtable" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestUnitBigtable_flattenSubsetViewInfo(t *testing.T) { + cases := map[string]struct { + sv bigtable.SubsetViewInfo + want []map[string]interface{} + orWant []map[string]interface{} + }{ + "empty subset view": { + sv: bigtable.SubsetViewInfo{}, + want: []map[string]interface{}{ + map[string]interface{}{}, + }, + orWant: nil, + }, + "subset view with row prefixes only": { + sv: bigtable.SubsetViewInfo{ + RowPrefixes: [][]byte{[]byte("row1"), []byte("row2")}, + }, + want: []map[string]interface{}{ + map[string]interface{}{ + "row_prefixes": []string{"cm93MQ==", "cm93Mg=="}, + }, + }, + orWant: nil, + }, + "subset view with family subsets only": { + sv: bigtable.SubsetViewInfo{ + FamilySubsets: map[string]bigtable.FamilySubset{ + "fam1": { + QualifierPrefixes: [][]byte{[]byte("col")}, + }, + "fam2": { + Qualifiers: [][]byte{[]byte("col1"), []byte("col2")}, + }, + }, + }, + want: []map[string]interface{}{ + map[string]interface{}{ + "family_subsets": []map[string]interface{}{ + map[string]interface{}{ + "family_name": "fam1", + "qualifier_prefixes": []string{"Y29s"}, + }, map[string]interface{}{ + "family_name": "fam2", + "qualifiers": []string{"Y29sMQ==", "Y29sMg=="}, + }, + }, + }, + }, + orWant: []map[string]interface{}{ + map[string]interface{}{ + "family_subsets": []map[string]interface{}{ + map[string]interface{}{ + "family_name": "fam2", + "qualifiers": []string{"Y29sMQ==", "Y29sMg=="}, + }, + map[string]interface{}{ + "family_name": "fam1", + "qualifier_prefixes": []string{"Y29s"}, + }, + }, + }, + }, + }, + "subset view with qualifiers only": { + sv: bigtable.SubsetViewInfo{ + FamilySubsets: map[string]bigtable.FamilySubset{ + "fam": { + Qualifiers: [][]byte{[]byte("col")}, + }, + }, + }, + want: []map[string]interface{}{ + map[string]interface{}{ + "family_subsets": []map[string]interface{}{ + map[string]interface{}{ + "family_name": "fam", + "qualifiers": []string{"Y29s"}, + }, + }, + }, + }, + orWant: nil, + }, + "subset view with qualifier prefixes only": { + sv: bigtable.SubsetViewInfo{ + FamilySubsets: map[string]bigtable.FamilySubset{ + "fam": { + QualifierPrefixes: [][]byte{[]byte("col")}, + }, + }, + }, + want: []map[string]interface{}{ + map[string]interface{}{ + "family_subsets": []map[string]interface{}{ + map[string]interface{}{ + "family_name": "fam", + "qualifier_prefixes": []string{"Y29s"}, + }, + }, + }, + }, + orWant: nil, + }, + "subset view with empty arrays": { + sv: bigtable.SubsetViewInfo{ + RowPrefixes: [][]byte{}, + FamilySubsets: map[string]bigtable.FamilySubset{}, + }, + want: []map[string]interface{}{ + map[string]interface{}{}, + }, + orWant: nil, + }, + } + + for tn, tc := range cases { + got := flattenSubsetViewInfo(&tc.sv) + if tc.want != nil && !(reflect.DeepEqual(got, tc.want) || reflect.DeepEqual(got, tc.orWant)) { + t.Errorf("bad: %s, got %q, want %q", tn, got, tc.want) + } + } +} + +func TestUnitBigtable_generateSubsetViewConfig(t *testing.T) { + cases := map[string]struct { + sv []interface{} + want *bigtable.SubsetViewConf + orWant *bigtable.SubsetViewConf + wantError string + }{ + "empty subset view list": { + sv: []interface{}{}, + want: nil, + orWant: nil, + wantError: "empty subset_view list", + }, + "subset view list with wrong type element": { + sv: []interface{}{ + "random-string", + }, + want: nil, + orWant: nil, + wantError: "element in subset_view list has wrong type", + }, + "subset view list with nil element": { + sv: []interface{}{ + nil, + }, + want: &bigtable.SubsetViewConf{}, + orWant: nil, + wantError: "", + }, + "subset view list with empty element": { + sv: []interface{}{ + map[string]interface{}{}, + }, + want: &bigtable.SubsetViewConf{}, + orWant: nil, + wantError: "", + }, + "subset view list with empty lists": { + sv: []interface{}{ + map[string]interface{}{ + "row_prefixes": schema.NewSet(schema.HashString, []interface{}{}), + "family_subsets": schema.NewSet(schema.HashResource(familySubsetSchemaElem), []interface{}{}), + }, + }, + want: &bigtable.SubsetViewConf{}, + orWant: nil, + wantError: "", + }, + "subset view list with row prefixes only": { + sv: []interface{}{ + map[string]interface{}{ + "row_prefixes": schema.NewSet(schema.HashString, []interface{}{"cm93MQ==", "cm93Mg=="}), + }, + }, + want: &bigtable.SubsetViewConf{ + RowPrefixes: [][]byte{[]byte("row1"), []byte("row2")}, + }, + orWant: &bigtable.SubsetViewConf{ + RowPrefixes: [][]byte{[]byte("row2"), []byte("row1")}, + }, + wantError: "", + }, + "subset view list with invalid row prefixes encoding": { + sv: []interface{}{ + map[string]interface{}{ + "row_prefixes": schema.NewSet(schema.HashString, []interface{}{"#"}), + }, + }, + want: nil, + orWant: nil, + wantError: "illegal base64 data", + }, + "subset view list with empty row prefixes element": { + sv: []interface{}{ + map[string]interface{}{ + "row_prefixes": schema.NewSet(schema.HashString, []interface{}{""}), + }, + }, + want: &bigtable.SubsetViewConf{ + RowPrefixes: [][]byte{[]byte("")}, + }, + orWant: nil, + wantError: "", + }, + "subset view list with family subsets only": { + sv: []interface{}{ + map[string]interface{}{ + "family_subsets": schema.NewSet(schema.HashResource(familySubsetSchemaElem), []interface{}{ + map[string]interface{}{ + "family_name": "fam1", + "qualifier_prefixes": schema.NewSet(schema.HashString, []interface{}{"Y29s"}), + }, map[string]interface{}{ + "family_name": "fam2", + "qualifiers": schema.NewSet(schema.HashString, []interface{}{"Y29sMQ==", "Y29sMg=="}), + }, + }), + }, + }, + want: &bigtable.SubsetViewConf{ + FamilySubsets: map[string]bigtable.FamilySubset{ + "fam1": { + QualifierPrefixes: [][]byte{[]byte("col")}, + }, + "fam2": { + Qualifiers: [][]byte{[]byte("col1"), []byte("col2")}, + }, + }, + }, + orWant: &bigtable.SubsetViewConf{ + FamilySubsets: map[string]bigtable.FamilySubset{ + "fam1": { + QualifierPrefixes: [][]byte{[]byte("col")}, + }, + "fam2": { + Qualifiers: [][]byte{[]byte("col2"), []byte("col1")}, + }, + }, + }, + wantError: "", + }, + "subset view list with qualifiers only": { + sv: []interface{}{ + map[string]interface{}{ + "family_subsets": schema.NewSet(schema.HashResource(familySubsetSchemaElem), []interface{}{ + map[string]interface{}{ + "family_name": "fam", + "qualifiers": schema.NewSet(schema.HashString, []interface{}{"Y29sMQ==", "Y29sMg=="}), + }, + }), + }, + }, + want: &bigtable.SubsetViewConf{ + FamilySubsets: map[string]bigtable.FamilySubset{ + "fam": { + Qualifiers: [][]byte{[]byte("col1"), []byte("col2")}, + }, + }, + }, + orWant: &bigtable.SubsetViewConf{ + FamilySubsets: map[string]bigtable.FamilySubset{ + "fam": { + Qualifiers: [][]byte{[]byte("col2"), []byte("col1")}, + }, + }, + }, + wantError: "", + }, + "subset view list with qualifier prefixes only": { + sv: []interface{}{ + map[string]interface{}{ + "family_subsets": schema.NewSet(schema.HashResource(familySubsetSchemaElem), []interface{}{ + map[string]interface{}{ + "family_name": "fam", + "qualifier_prefixes": schema.NewSet(schema.HashString, []interface{}{"Y29s"}), + }, + }), + }, + }, + want: &bigtable.SubsetViewConf{ + FamilySubsets: map[string]bigtable.FamilySubset{ + "fam": { + QualifierPrefixes: [][]byte{[]byte("col")}, + }, + }, + }, + orWant: nil, + wantError: "", + }, + "subset view list with invalid qualifiers encoding": { + sv: []interface{}{ + map[string]interface{}{ + "family_subsets": schema.NewSet(schema.HashResource(familySubsetSchemaElem), []interface{}{ + map[string]interface{}{ + "family_name": "fam", + "qualifiers": schema.NewSet(schema.HashString, []interface{}{"#"}), + }, + }), + }, + }, + want: nil, + orWant: nil, + wantError: "illegal base64 data", + }, + "subset view list with invalid qualifier prefixes encoding": { + sv: []interface{}{ + map[string]interface{}{ + "family_subsets": schema.NewSet(schema.HashResource(familySubsetSchemaElem), []interface{}{ + map[string]interface{}{ + "family_name": "fam", + "qualifier_prefixes": schema.NewSet(schema.HashString, []interface{}{"#"}), + }, + }), + }, + }, + want: nil, + orWant: nil, + wantError: "illegal base64 data", + }, + } + + for tn, tc := range cases { + got, gotErr := generateSubsetViewConfig(tc.sv) + if (gotErr != nil && tc.wantError == "") || + (gotErr == nil && tc.wantError != "") || + (gotErr != nil && !strings.Contains(gotErr.Error(), tc.wantError)) { + t.Errorf("bad error: %s, got %q, want %q", tn, gotErr, tc.wantError) + } + if tc.want != nil && !(reflect.DeepEqual(got, tc.want) || reflect.DeepEqual(got, tc.orWant)) { + t.Errorf("bad: %s, got %q, want %q", tn, got, tc.want) + } + } +} diff --git a/mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view_test.go b/mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view_test.go new file mode 100644 index 000000000000..15e87531b051 --- /dev/null +++ b/mmv1/third_party/terraform/services/bigtable/resource_bigtable_authorized_view_test.go @@ -0,0 +1,449 @@ +package bigtable_test + +import ( + "context" + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccBigtableAuthorizedView_basic(t *testing.T) { + // bigtable instance does not use the shared HTTP client, this test creates an instance + acctest.SkipIfVcr(t) + t.Parallel() + + instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + tableName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + authorizedViewName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + familyName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckBigtableTableDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBigtableAuthorizedViewInvalidDeletionProtection(instanceName, tableName, authorizedViewName, familyName), + ExpectError: regexp.MustCompile(".*expected deletion_protection to be one of.*"), + }, + { + Config: testAccBigtableAuthorizedViewInvalidSubsetView(instanceName, tableName, authorizedViewName, familyName), + ExpectError: regexp.MustCompile(".*subset_view must be specified for authorized view.*"), + }, + { + Config: testAccBigtableAuthorizedViewInvalidEncoding(instanceName, tableName, authorizedViewName, familyName), + ExpectError: regexp.MustCompile(".*illegal base64 data.*"), + }, + { + Config: testAccBigtableAuthorizedViewBasic(instanceName, tableName, authorizedViewName, familyName), + }, + { + ResourceName: "google_bigtable_authorized_view.authorized_view", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccBigtableAuthorizedViewWithRowPrefixesOnly(instanceName, tableName, authorizedViewName, familyName), + }, + { + ResourceName: "google_bigtable_authorized_view.authorized_view", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccBigtableAuthorizedView_update(t *testing.T) { + // bigtable instance does not use the shared HTTP client, this test creates an instance + acctest.SkipIfVcr(t) + t.Parallel() + + instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + tableName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + authorizedViewName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + familyName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckBigtableTableDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBigtableAuthorizedViewWithQualifiersOnly(instanceName, tableName, authorizedViewName, familyName), + }, + { + ResourceName: "google_bigtable_authorized_view.authorized_view", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccBigtableAuthorizedViewWithFamilySubsetsOnly(instanceName, tableName, authorizedViewName, familyName), + }, + { + ResourceName: "google_bigtable_authorized_view.authorized_view", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"subset_view.0.family_subsets"}, // The order of the two family subsets is indeterministic. + }, + }, + }) +} + +func TestAccBigtableAuthorizedView_destroy(t *testing.T) { + // bigtable instance does not use the shared HTTP client, this test creates an instance + acctest.SkipIfVcr(t) + t.Parallel() + + instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + tableName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + authorizedViewName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + familyName := fmt.Sprintf("tf-test-%s", acctest.RandString(t, 10)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckBigtableTableDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccBigtableAuthorizedViewWithQualifierPrefixesOnly(instanceName, tableName, authorizedViewName, familyName), + }, + { + ResourceName: "google_bigtable_authorized_view.authorized_view", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccBigtableAuthorizedViewDestroy(instanceName, tableName, familyName), + }, + { + ResourceName: "google_bigtable_table.table", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckBigtableAuthorizedViewDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + var ctx = context.Background() + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_bigtable_authorized_view" { + continue + } + + config := acctest.GoogleProviderConfig(t) + c, err := config.BigTableClientFactory(config.UserAgent).NewAdminClient(config.Project, rs.Primary.Attributes["instance_name"]) + if err != nil { + // The instance is already gone + return nil + } + + _, err = c.AuthorizedViewInfo(ctx, rs.Primary.Attributes["table_name"], rs.Primary.Attributes["name"]) + if err == nil { + return fmt.Errorf("AuthorizedView still present. Found %s in %s.", rs.Primary.Attributes["name"], rs.Primary.Attributes["table_name"]) + } + + c.Close() + } + + return nil + } +} + +func testAccBigtableAuthorizedViewInvalidDeletionProtection(instanceName, tableName, authorizedViewName, familyName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + } + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + column_family { + family = "%s" + } +} +resource "google_bigtable_authorized_view" "authorized_view" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + table_name = google_bigtable_table.table.name + deletion_protection = "random" + subset_view {} +} +`, instanceName, instanceName, tableName, familyName, authorizedViewName) +} + +func testAccBigtableAuthorizedViewInvalidSubsetView(instanceName, tableName, authorizedViewName, familyName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + } + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + column_family { + family = "%s" + } +} +resource "google_bigtable_authorized_view" "authorized_view" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + table_name = google_bigtable_table.table.name + deletion_protection = "UNPROTECTED" +} +`, instanceName, instanceName, tableName, familyName, authorizedViewName) +} + +func testAccBigtableAuthorizedViewInvalidEncoding(instanceName, tableName, authorizedViewName, familyName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + } + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + column_family { + family = "%s" + } +} +resource "google_bigtable_authorized_view" "authorized_view" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + table_name = google_bigtable_table.table.name + deletion_protection = "UNPROTECTED" + subset_view { + row_prefixes = ["#"] + } +} +`, instanceName, instanceName, tableName, familyName, authorizedViewName) +} + +func testAccBigtableAuthorizedViewBasic(instanceName, tableName, authorizedViewName, familyName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + } + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + column_family { + family = "%s" + } +} + +resource "google_bigtable_authorized_view" "authorized_view" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + table_name = google_bigtable_table.table.name + deletion_protection = "UNPROTECTED" + subset_view {} +} +`, instanceName, instanceName, tableName, familyName, authorizedViewName) +} + +func testAccBigtableAuthorizedViewWithRowPrefixesOnly(instanceName, tableName, authorizedViewName, familyName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + } + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + column_family { + family = "%s" + } +} + +resource "google_bigtable_authorized_view" "authorized_view" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + table_name = google_bigtable_table.table.name + deletion_protection = "UNPROTECTED" + + subset_view { + row_prefixes = [base64encode("row1#"), base64encode("row2#")] + } +} +`, instanceName, instanceName, tableName, familyName, authorizedViewName) +} + +func testAccBigtableAuthorizedViewWithFamilySubsetsOnly(instanceName, tableName, authorizedViewName, familyName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + } + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + column_family { + family = "%s" + } + column_family { + family = "%s-second" + } +} + +resource "google_bigtable_authorized_view" "authorized_view" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + table_name = google_bigtable_table.table.name + deletion_protection = "UNPROTECTED" + + subset_view { + family_subsets { + family_name = "%s" + qualifiers = [base64encode("qualifier"), base64encode("qualifier-second")] + } + family_subsets { + family_name = "%s-second" + qualifier_prefixes = [""] + } + } +} +`, instanceName, instanceName, tableName, familyName, familyName, authorizedViewName, familyName, familyName) +} + +func testAccBigtableAuthorizedViewWithQualifiersOnly(instanceName, tableName, authorizedViewName, familyName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + } + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + column_family { + family = "%s" + } +} + +resource "google_bigtable_authorized_view" "authorized_view" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + table_name = google_bigtable_table.table.name + deletion_protection = "UNPROTECTED" + + subset_view { + family_subsets { + family_name = "%s" + qualifiers = [base64encode("qualifier")] + } + } +} +`, instanceName, instanceName, tableName, familyName, authorizedViewName, familyName) +} + +func testAccBigtableAuthorizedViewWithQualifierPrefixesOnly(instanceName, tableName, authorizedViewName, familyName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + } + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + column_family { + family = "%s" + } +} + +resource "google_bigtable_authorized_view" "authorized_view" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + table_name = google_bigtable_table.table.name + deletion_protection = "UNPROTECTED" + + subset_view { + family_subsets { + family_name = "%s" + qualifier_prefixes = [""] + } + } +} +`, instanceName, instanceName, tableName, familyName, authorizedViewName, familyName) +} + +func testAccBigtableAuthorizedViewDestroy(instanceName, tableName, familyName string) string { + return fmt.Sprintf(` +resource "google_bigtable_instance" "instance" { + name = "%s" + cluster { + cluster_id = "%s" + zone = "us-central1-b" + num_nodes = 1 + } + deletion_protection = false +} + +resource "google_bigtable_table" "table" { + name = "%s" + instance_name = google_bigtable_instance.instance.id + column_family { + family = "%s" + } +} +`, instanceName, instanceName, tableName, familyName) +} diff --git a/mmv1/third_party/terraform/website/docs/r/bigtable_authorized_view.html.markdown b/mmv1/third_party/terraform/website/docs/r/bigtable_authorized_view.html.markdown new file mode 100644 index 000000000000..b69ad6f4b349 --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/r/bigtable_authorized_view.html.markdown @@ -0,0 +1,161 @@ +--- +subcategory: "Cloud Bigtable" +description: |- + Creates a Google Cloud Bigtable authorized view inside a table. +--- + +# google_bigtable_authorized_view + +Creates a Google Cloud Bigtable authorized view inside a table. For more information see +[the official documentation](https://cloud.google.com/bigtable/) and +[API](https://cloud.google.com/bigtable/docs/go/reference). + +-> **Note:** It is strongly recommended to set `lifecycle { prevent_destroy = true }` +on authorized views in order to prevent accidental data loss. See +[Terraform docs](https://www.terraform.io/docs/configuration/resources.html#prevent_destroy) +for more information on lifecycle parameters. + + +## Example Usage + +```hcl +resource "google_bigtable_instance" "instance" { + name = "tf-instance" + + cluster { + cluster_id = "tf-instance-cluster" + zone = "us-central1-b" + num_nodes = 3 + storage_type = "HDD" + } + + lifecycle { + prevent_destroy = true + } +} + +resource "google_bigtable_table" "table" { + name = "tf-table" + instance_name = google_bigtable_instance.instance.name + split_keys = ["a", "b", "c"] + + lifecycle { + prevent_destroy = true + } + + column_family { + family = "family-first" + } + + column_family { + family = "family-second" + } + + change_stream_retention = "24h0m0s" +} + +resource "google_bigtable_authorized_view" "authorized_view" { + name = "tf-authorized-view" + instance_name = google_bigtable_instance.instance.name + table_name = google_bigtable_table.table.name + + lifecycle { + prevent_destroy = true + } + + subset_view { + row_prefixes = [base64encode("prefix#)] + + family_subsets { + family_name = "family-first" + qualifiers = [base64encode("qualifier"), base64encode("qualifier-second")] + } + + family_subsets { + family_name = "family-second" + qualifier_prefixes = [""] + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the authorized view. Must be 1-50 characters and must only contain hyphens, underscores, periods, letters and numbers. + +* `project` - (Optional) The ID of the project in which the resource belongs. If it + is not provided, the provider project is used. + +* `instance_name` - (Required) The name of the Bigtable instance in which the authorized view belongs. + +* `table_name` - (Required) The name of the Bigtable table in which the authorized view belongs. + +* `column_family` - (Optional) A group of columns within a table which share a common configuration. This can be specified multiple times. Structure is documented below. + +* `deletion_protection` - (Optional) A field to make the table protected against data loss i.e. when set to PROTECTED, deleting the table, the column families in the table, and the instance containing the table would be prohibited. +If not provided, currently deletion protection will be set to UNPROTECTED as it is the API default value. Note this field configs the deletion protection provided by the API in the backend, and should not be confused with Terraform-side deletion protection. + +* `subset_view` - (Optional) An AuthorizedView permitting access to an explicit subset of a Table. Structure is documented below. + +----- + +`subset_view` supports the following arguments: + +* `row_prefixes` - (Optional) A list of Base64-encoded row prefixes to be included in the authorized view. To provide access to all rows, include the empty string as a prefix (""). + +* `family_subsets` - (Optional) A group of column family subsets to be included in the authorized view. This can be specified multiple times. Structure is documented below. + +----- + +`family_subsets` supports the following arguments: + +* `family_name` - (Required) Name of the column family to be included in the authorized view. The specified column family must exist in the parent table of this authorized view. + +* `qualifiers` - (Optional) A list of Base64-encoded individual exact column qualifiers of the column family to be included in the authorized view. + +* `qualifier_prefixes` - (Optional) A list of Base64-encoded prefixes for qualifiers of the column family to be included in the authorized view. +Every qualifier starting with one of these prefixes is included in the authorized view. To provide access to all qualifiers, include the empty string as a prefix (""). + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/instances/{{instance_name}}/tables/{{table_name}}/authorizedViews/{{name}}` + +## Timeouts + +This resource provides the following +[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. + +## Import + +Bigtable Authorized Views can be imported using any of these accepted formats: + +* `projects/{{project}}/instances/{{instance_name}}/tables/{{table_name}}/authorizedViews/{{name}}` +* `{{project}}/{{instance_name}}/{{table_name}}/{{name}}` +* `{{instance_name}}/{{table_name}}/{{name}}` + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Bigtable Authorized Views using one of the formats above. For example: + +```tf +import { + id = "projects/{{project}}/instances/{{instance_name}}/tables/{{table_name}}/authorizedViews/{{name}}" + to = google_bigtable_authorized_view.default +} +``` + +When using the [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import), Bigtable Authorized Views can be imported using one of the formats above. For example: + +``` +$ terraform import google_bigtable_authorized_view.default projects/{{project}}/instances/{{instance_name}}/tables/{{table_name}}/authorizedViews/{{name}} +$ terraform import google_bigtable_authorized_view.default {{project}}/{{instance_name}}/{{table_name}}/{{name}} +$ terraform import google_bigtable_authorized_view.default {{instance_name}}/{{table_name}}/{{name}} +``` + + From 20568a0cd4ba9f1e76723da7e3dd6cbf5f0affcb Mon Sep 17 00:00:00 2001 From: Cameron Thornton Date: Thu, 2 May 2024 12:30:16 -0500 Subject: [PATCH 6/6] Go rewrite - Add resource delete and import functions (#10581) --- mmv1/google/string_utils.go | 31 ++++ mmv1/provider/template_data.go | 21 +-- mmv1/provider/terraform.go | 20 --- mmv1/templates/terraform/resource.go.tmpl | 170 +++++++++++++++++++++- 4 files changed, 211 insertions(+), 31 deletions(-) diff --git a/mmv1/google/string_utils.go b/mmv1/google/string_utils.go index 57786ba96952..daeaf56baaf6 100644 --- a/mmv1/google/string_utils.go +++ b/mmv1/google/string_utils.go @@ -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[^/]+)/global/networks/(?P[^/]+) + +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 +} diff --git a/mmv1/provider/template_data.go b/mmv1/provider/template_data.go index 4dd8a2126c4d..4d038f93b978 100644 --- a/mmv1/provider/template_data.go +++ b/mmv1/provider/template_data.go @@ -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" diff --git a/mmv1/provider/terraform.go b/mmv1/provider/terraform.go index 7a08685141c7..ac36423cd9f2 100644 --- a/mmv1/provider/terraform.go +++ b/mmv1/provider/terraform.go @@ -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[^/]+)/global/networks/(?P[^/]+) -// # -// # 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) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index c745b83f9622..7389197edec3 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -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 @@ -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 */}} \ No newline at end of file