From 6499220d18969da1a1626fd6bbaa47ef62016e5f Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Wed, 2 Apr 2025 14:38:19 -0700 Subject: [PATCH 01/18] Switched subscription_id to use a standard custom_flatten Following https://github.com/GoogleCloudPlatform/magic-modules/pull/12939 this will trigger proper generation of post-create code --- .../bigqueryanalyticshub/ListingSubscription.yaml | 3 +-- .../post_create/analytics_hub_subscription.go.tmpl | 10 ---------- 2 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 mmv1/templates/terraform/post_create/analytics_hub_subscription.go.tmpl diff --git a/mmv1/products/bigqueryanalyticshub/ListingSubscription.yaml b/mmv1/products/bigqueryanalyticshub/ListingSubscription.yaml index cdf599e98cc0..cfa16b34209c 100644 --- a/mmv1/products/bigqueryanalyticshub/ListingSubscription.yaml +++ b/mmv1/products/bigqueryanalyticshub/ListingSubscription.yaml @@ -30,7 +30,6 @@ immutable: true import_format: - 'projects/{{project}}/locations/{{location}}/subscriptions/{{subscription_id}}' custom_code: - post_create: templates/terraform/post_create/analytics_hub_subscription.go.tmpl pre_read: 'templates/terraform/pre_read/bigqueryanalyticshub_listing_subscription.tmpl' pre_delete: 'templates/terraform/pre_read/bigqueryanalyticshub_listing_subscription.tmpl' post_import: templates/terraform/post_import/analytics_hub_subscription.go.tmpl @@ -122,7 +121,7 @@ properties: description: |- The subscription id used to reference the subscription. output: true - ignore_read: true + custom_flatten: 'templates/terraform/custom_flatten/id_from_name.tmpl' - name: 'creationTime' type: Time description: |- diff --git a/mmv1/templates/terraform/post_create/analytics_hub_subscription.go.tmpl b/mmv1/templates/terraform/post_create/analytics_hub_subscription.go.tmpl deleted file mode 100644 index 808fd5657fa2..000000000000 --- a/mmv1/templates/terraform/post_create/analytics_hub_subscription.go.tmpl +++ /dev/null @@ -1,10 +0,0 @@ -subscription, ok := res["subscription"] -if ok { - name, nok := subscription.(map[string]interface{})["name"] - if nok { - parts := strings.Split(name.(string), "/") - d.SetId(name.(string)) - d.Set("name", name.(string)) - d.Set("subscription_id", parts[5]) - } -} \ No newline at end of file From 75c78bb03b46c2755dd7f70013dc567c923d592d Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Thu, 3 Apr 2025 12:02:35 -0700 Subject: [PATCH 02/18] Handle decoders when setting computed fields in post-create --- .../bigqueryanalyticshub/ListingSubscription.yaml | 1 + ...gqueryanalyticshub_listing_subscription.go.tmpl | 5 +++++ mmv1/templates/terraform/resource.go.tmpl | 14 +++++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 mmv1/templates/terraform/decoders/bigqueryanalyticshub_listing_subscription.go.tmpl diff --git a/mmv1/products/bigqueryanalyticshub/ListingSubscription.yaml b/mmv1/products/bigqueryanalyticshub/ListingSubscription.yaml index cfa16b34209c..ccbdc6bc6e07 100644 --- a/mmv1/products/bigqueryanalyticshub/ListingSubscription.yaml +++ b/mmv1/products/bigqueryanalyticshub/ListingSubscription.yaml @@ -30,6 +30,7 @@ immutable: true import_format: - 'projects/{{project}}/locations/{{location}}/subscriptions/{{subscription_id}}' custom_code: + decoder: 'templates/terraform/decoders/bigqueryanalyticshub_listing_subscription.go.tmpl' pre_read: 'templates/terraform/pre_read/bigqueryanalyticshub_listing_subscription.tmpl' pre_delete: 'templates/terraform/pre_read/bigqueryanalyticshub_listing_subscription.tmpl' post_import: templates/terraform/post_import/analytics_hub_subscription.go.tmpl diff --git a/mmv1/templates/terraform/decoders/bigqueryanalyticshub_listing_subscription.go.tmpl b/mmv1/templates/terraform/decoders/bigqueryanalyticshub_listing_subscription.go.tmpl new file mode 100644 index 000000000000..f3ccec912f77 --- /dev/null +++ b/mmv1/templates/terraform/decoders/bigqueryanalyticshub_listing_subscription.go.tmpl @@ -0,0 +1,5 @@ +// The response from Create nests the resource inside a "subscription" key. +if s, ok := res["subscription"]; ok { + return s.(map[string]interface{}), nil +} +return res, nil \ No newline at end of file diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index 6ed343f0ccf4..fe873fed9a6f 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -291,13 +291,25 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- /* Set output-only resource properties from create API response (as long as Create doesn't use an async operation) */}} {{- /* This is necessary so that the ID is built correctly. */}} {{- if or (not $.GetAsync) (not (and ($.GetAsync.IsA "OpAsync") ($.GetAsync.Allow "Create"))) }} +{{- $renderedIdFromName := "false" }} +{{- $renderedDecoder := "false" }} {{- range $prop := $.GettableProperties }} {{- if $.InIdFormat $prop }} -{{- if eq $prop.CustomFlatten "templates/terraform/custom_flatten/id_from_name.tmpl" }} +{{- if and ($.CustomCode.Decoder) (eq $renderedDecoder "false") }} + res, err = resource{{ $.ResourceName -}}Decoder(d, meta, res) + if err != nil { + return nil, err + } + if res == nil { + return nil, tpgresource.Fake404("decoded", "{{ $.ResourceName }}") + } +{{- end }} +{{- if and (eq $prop.CustomFlatten "templates/terraform/custom_flatten/id_from_name.tmpl") (eq $renderedIdFromName "false") }} // Setting `name` field so that `id_from_name` flattener will work properly. if err := d.Set("name", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}Name(res["name"], d, config)); err != nil { return fmt.Errorf(`Error setting computed identity field "name": %s`, err) } + {{- $renderedIdFromName = "true" }} {{- end }} {{- if and $prop.Output (not $prop.IgnoreRead) }} if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { From 79dce723a59419b71953db8280bcca1af8f65019 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Thu, 3 Apr 2025 12:12:42 -0700 Subject: [PATCH 03/18] Fixed return values --- mmv1/templates/terraform/resource.go.tmpl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index fe873fed9a6f..38db8693ee9f 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -292,17 +292,19 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- /* This is necessary so that the ID is built correctly. */}} {{- if or (not $.GetAsync) (not (and ($.GetAsync.IsA "OpAsync") ($.GetAsync.Allow "Create"))) }} {{- $renderedIdFromName := "false" }} -{{- $renderedDecoder := "false" }} +{{- $renderedInitial := "false" }} {{- range $prop := $.GettableProperties }} {{- if $.InIdFormat $prop }} -{{- if and ($.CustomCode.Decoder) (eq $renderedDecoder "false") }} +{{- if and ($.CustomCode.Decoder) (eq $renderedInitial "false") }} + // Set output-only resource properties from create API response res, err = resource{{ $.ResourceName -}}Decoder(d, meta, res) if err != nil { - return nil, err + return fmt.Errorf("Error decoding response: %s", err) } if res == nil { - return nil, tpgresource.Fake404("decoded", "{{ $.ResourceName }}") + return fmt.Errorf("Error decoding response, could not find object") } + {{- $renderedInitial = "true" }} {{- end }} {{- if and (eq $prop.CustomFlatten "templates/terraform/custom_flatten/id_from_name.tmpl") (eq $renderedIdFromName "false") }} // Setting `name` field so that `id_from_name` flattener will work properly. From 8258d4494099dcab6ffc3cee4260e35eb01334e0 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Apr 2025 09:37:45 -0700 Subject: [PATCH 04/18] Don't set post-create fields for polling async resources --- mmv1/templates/terraform/resource.go.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index 38db8693ee9f..82ed8fab1dd4 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -288,9 +288,9 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- end}} return fmt.Errorf("Error creating {{ $.Name -}}: %s", err) } -{{- /* Set output-only resource properties from create API response (as long as Create doesn't use an async operation) */}} +{{- /* Set output-only resource properties from create API response (as long as Create isn't async) */}} {{- /* This is necessary so that the ID is built correctly. */}} -{{- if or (not $.GetAsync) (not (and ($.GetAsync.IsA "OpAsync") ($.GetAsync.Allow "Create"))) }} +{{- if or (not $.GetAsync) (not ($.GetAsync.Allow "Create")) }} {{- $renderedIdFromName := "false" }} {{- $renderedInitial := "false" }} {{- range $prop := $.GettableProperties }} From 54ee3ae4f4d72269bf3226f4289280e707a02540 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Apr 2025 09:56:21 -0700 Subject: [PATCH 05/18] Marked MonitoredProject as using async for create and delete Previously this didn't matter because it returns immediately, but we need to mark it as async to prevent trying to decode --- mmv1/products/monitoring/MonitoredProject.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mmv1/products/monitoring/MonitoredProject.yaml b/mmv1/products/monitoring/MonitoredProject.yaml index 1132bcae27f3..1dd96336ab04 100644 --- a/mmv1/products/monitoring/MonitoredProject.yaml +++ b/mmv1/products/monitoring/MonitoredProject.yaml @@ -29,6 +29,10 @@ timeouts: insert_minutes: 20 update_minutes: 20 delete_minutes: 20 +async: + actions: ['create', 'delete'] + operation: + base_url: '{{op_id}}' custom_code: constants: 'templates/terraform/constants/monitoring_monitored_project.go.tmpl' encoder: 'templates/terraform/encoders/monitoring_monitored_project.go.tmpl' From 3d29765bac7857055c09975ba89d39a16dfb06cc Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Apr 2025 09:57:00 -0700 Subject: [PATCH 06/18] Only render decoder / id_from_name handling if the prop actually needs to be set --- mmv1/templates/terraform/resource.go.tmpl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index 82ed8fab1dd4..3b4427a76984 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -294,7 +294,8 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- $renderedIdFromName := "false" }} {{- $renderedInitial := "false" }} {{- range $prop := $.GettableProperties }} -{{- if $.InIdFormat $prop }} +{{- /* Check if prop is potentially computed */}} +{{- if and ($.InIdFormat $prop) (and (or $prop.Output $prop.DefaultFromApi) (not $prop.IgnoreRead)) }} {{- if and ($.CustomCode.Decoder) (eq $renderedInitial "false") }} // Set output-only resource properties from create API response res, err = resource{{ $.ResourceName -}}Decoder(d, meta, res) @@ -325,9 +326,9 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ } } {{- end }} -{{- end}} -{{- end}} -{{- end}} +{{- end }}{{/* prop is potentially computed */}} +{{- end }}{{/* range */}} +{{- end }}{{/* not async create */}} // Store the ID now id, err := tpgresource.ReplaceVars{{if $.LegacyLongFormProject -}}ForId{{ end -}}(d, config, "{{ $.IdFormat -}}") From 8e4c1ed375eb0d32a41e160945e0a8c609688f4e Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Apr 2025 10:34:21 -0700 Subject: [PATCH 07/18] Autogen async for monitored project --- mmv1/products/monitoring/MonitoredProject.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/mmv1/products/monitoring/MonitoredProject.yaml b/mmv1/products/monitoring/MonitoredProject.yaml index 1dd96336ab04..1cb351d2d1a6 100644 --- a/mmv1/products/monitoring/MonitoredProject.yaml +++ b/mmv1/products/monitoring/MonitoredProject.yaml @@ -29,6 +29,7 @@ timeouts: insert_minutes: 20 update_minutes: 20 delete_minutes: 20 +autogen_async: true async: actions: ['create', 'delete'] operation: From 56625628746145f34cc184d93daede7be53ceae9 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Apr 2025 14:06:11 -0700 Subject: [PATCH 08/18] Factored post-create setting of id format fields into a separate function that is shared with polling async post-create --- mmv1/templates/terraform/resource.go.tmpl | 94 ++++++++++++++--------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index 3b4427a76984..afcf0d150c1e 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -291,43 +291,10 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- /* Set output-only resource properties from create API response (as long as Create isn't async) */}} {{- /* This is necessary so that the ID is built correctly. */}} {{- if or (not $.GetAsync) (not ($.GetAsync.Allow "Create")) }} -{{- $renderedIdFromName := "false" }} -{{- $renderedInitial := "false" }} -{{- range $prop := $.GettableProperties }} -{{- /* Check if prop is potentially computed */}} -{{- if and ($.InIdFormat $prop) (and (or $prop.Output $prop.DefaultFromApi) (not $prop.IgnoreRead)) }} -{{- if and ($.CustomCode.Decoder) (eq $renderedInitial "false") }} - // Set output-only resource properties from create API response - res, err = resource{{ $.ResourceName -}}Decoder(d, meta, res) + err = resource{{ $.ResourceName }}SetComputedIdFormatFields(d, meta, res) if err != nil { - return fmt.Errorf("Error decoding response: %s", err) - } - if res == nil { - return fmt.Errorf("Error decoding response, could not find object") - } - {{- $renderedInitial = "true" }} -{{- end }} -{{- if and (eq $prop.CustomFlatten "templates/terraform/custom_flatten/id_from_name.tmpl") (eq $renderedIdFromName "false") }} - // Setting `name` field so that `id_from_name` flattener will work properly. - if err := d.Set("name", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}Name(res["name"], d, config)); err != nil { - return fmt.Errorf(`Error setting computed identity field "name": %s`, err) - } - {{- $renderedIdFromName = "true" }} -{{- end }} -{{- if and $prop.Output (not $prop.IgnoreRead) }} - if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { - return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %s`, err) + return fmt.Errorf("Error setting computed ID format fields: %w") } -{{- else if and $prop.DefaultFromApi (not $prop.IgnoreRead) }} - // {{ underscore $prop.Name }} is set by API when unset - if tpgresource.IsEmptyValue(reflect.ValueOf(d.Get("{{ underscore $prop.Name }}"))) { - if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { - return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %s`, err) - } - } -{{- end }} -{{- end }}{{/* prop is potentially computed */}} -{{- end }}{{/* range */}} {{- end }}{{/* not async create */}} // Store the ID now @@ -435,7 +402,17 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error waiting to create {{ $.Name -}}: %s", err) {{- end}} } + err = resource{{ $.ResourceName }}SetComputedIdFormatFields(d, meta, res) + if err != nil { + return fmt.Errorf("Error setting computed ID format fields: %w") + } + // This may have caused the ID to update - update it if so. + id, err = tpgresource.ReplaceVars{{if $.LegacyLongFormProject -}}ForId{{ end -}}(d, config, "{{ $.IdFormat -}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) {{- end}} {{- end}} @@ -1233,3 +1210,50 @@ func resource{{ $.ResourceName -}}PostCreateFailure(d *schema.ResourceData, meta {{ $.CustomTemplate $.StateMigrationFile false -}} {{- end }} +{{- /* Only render SetComputedIdFormatFields for resources with non-async create or polling async */}} +{{- if or (or (not $.GetAsync) (not ($.GetAsync.Allow "Create"))) (and $.GetAsync (and ($.GetAsync.IsA "PollAsync") ($.GetAsync.Allow "Create")))}} + +func resource{{ $.ResourceName }}SetComputedIdFormatFields(d *schema.ResourceData, meta interface{}, res map[string]interface{}) error { +{{- $renderedIdFromName := "false" }} +{{- $renderedInitial := "false" }} +{{- range $prop := $.GettableProperties }} +{{- /* Check if prop is potentially computed */}} +{{- if and ($.InIdFormat $prop) (and (or $prop.Output $prop.DefaultFromApi) (not $prop.IgnoreRead)) }} +{{- if eq $renderedInitial "false" }} + config := meta.(*transport_tpg.Config) +{{- if $.CustomCode.Decoder }} + // Set output-only resource properties from create API response + res, err := resource{{ $.ResourceName -}}Decoder(d, meta, res) + if err != nil { + return fmt.Errorf("decoding response: %w", err) + } + if res == nil { + return fmt.Errorf("decoding response, could not find object") + } +{{- end }} +{{- $renderedInitial = "true" }} +{{- end }} +{{- if and (eq $prop.CustomFlatten "templates/terraform/custom_flatten/id_from_name.tmpl") (eq $renderedIdFromName "false") }} + // Setting `name` field so that `id_from_name` flattener will work properly. + if err := d.Set("name", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}Name(res["name"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "name": %s`, err) + } + {{- $renderedIdFromName = "true" }} +{{- end }} +{{- if and $prop.Output (not $prop.IgnoreRead) }} + if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %s`, err) + } +{{- else if and $prop.DefaultFromApi (not $prop.IgnoreRead) }} + // {{ underscore $prop.Name }} is set by API when unset + if tpgresource.IsEmptyValue(reflect.ValueOf(d.Get("{{ underscore $prop.Name }}"))) { + if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %s`, err) + } + } +{{- end }} +{{- end }}{{/* prop is potentially computed */}} +{{- end }}{{/* range */}} + return nil +} +{{- end}} From f68972f49c32b74ed8e68e05ccb1f3be6da726fc Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Apr 2025 14:45:15 -0700 Subject: [PATCH 09/18] Only render new code if there are computed id format fields --- mmv1/api/resource.go | 18 ++++++++++++++++++ mmv1/templates/terraform/resource.go.tmpl | 22 ++++++++++------------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/mmv1/api/resource.go b/mmv1/api/resource.go index 8fd13845051b..e74ec84356c6 100644 --- a/mmv1/api/resource.go +++ b/mmv1/api/resource.go @@ -1217,6 +1217,24 @@ func (r Resource) InIdFormat(prop Type) bool { return slices.Contains(fields, google.Underscore(prop.Name)) } +// Returns true if at least one of the fields in the ID format is computed +func (r Resource) HasComputedIdFormatFields() bool { + idFormatFields := map[string]struct{}{} + for _, f := range r.ExtractIdentifiers(r.GetIdFormat()) { + idFormatFields[f] = struct{}{} + } + for _, p := range r.GettableProperties() { + // Skip fields not in the id format + if _, ok := idFormatFields[p.Name]; !ok { + continue + } + if (p.Output || p.DefaultFromApi) && !p.IgnoreRead { + return true + } + } + return false +} + // ==================== // Template Methods // ==================== diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index afcf0d150c1e..68a6d35a26f1 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -290,7 +290,7 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ } {{- /* Set output-only resource properties from create API response (as long as Create isn't async) */}} {{- /* This is necessary so that the ID is built correctly. */}} -{{- if or (not $.GetAsync) (not ($.GetAsync.Allow "Create")) }} +{{- if and $.HasComputedIdFormatFields (or (not $.GetAsync) (not ($.GetAsync.Allow "Create"))) }} err = resource{{ $.ResourceName }}SetComputedIdFormatFields(d, meta, res) if err != nil { return fmt.Errorf("Error setting computed ID format fields: %w") @@ -402,10 +402,12 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error waiting to create {{ $.Name -}}: %s", err) {{- end}} } +{{- if $.HasComputedIdFormatFields }} err = resource{{ $.ResourceName }}SetComputedIdFormatFields(d, meta, res) if err != nil { return fmt.Errorf("Error setting computed ID format fields: %w") } +{{- end}} // This may have caused the ID to update - update it if so. id, err = tpgresource.ReplaceVars{{if $.LegacyLongFormProject -}}ForId{{ end -}}(d, config, "{{ $.IdFormat -}}") @@ -1211,17 +1213,11 @@ func resource{{ $.ResourceName -}}PostCreateFailure(d *schema.ResourceData, meta {{ $.CustomTemplate $.StateMigrationFile false -}} {{- end }} {{- /* Only render SetComputedIdFormatFields for resources with non-async create or polling async */}} -{{- if or (or (not $.GetAsync) (not ($.GetAsync.Allow "Create"))) (and $.GetAsync (and ($.GetAsync.IsA "PollAsync") ($.GetAsync.Allow "Create")))}} +{{- if and $.HasComputedIdFormatFields (or (or (not $.GetAsync) (not ($.GetAsync.Allow "Create"))) (and $.GetAsync (and ($.GetAsync.IsA "PollAsync") ($.GetAsync.Allow "Create"))))}} func resource{{ $.ResourceName }}SetComputedIdFormatFields(d *schema.ResourceData, meta interface{}, res map[string]interface{}) error { -{{- $renderedIdFromName := "false" }} -{{- $renderedInitial := "false" }} -{{- range $prop := $.GettableProperties }} -{{- /* Check if prop is potentially computed */}} -{{- if and ($.InIdFormat $prop) (and (or $prop.Output $prop.DefaultFromApi) (not $prop.IgnoreRead)) }} -{{- if eq $renderedInitial "false" }} config := meta.(*transport_tpg.Config) -{{- if $.CustomCode.Decoder }} +{{- if $.CustomCode.Decoder }} // Set output-only resource properties from create API response res, err := resource{{ $.ResourceName -}}Decoder(d, meta, res) if err != nil { @@ -1230,9 +1226,11 @@ func resource{{ $.ResourceName }}SetComputedIdFormatFields(d *schema.ResourceDat if res == nil { return fmt.Errorf("decoding response, could not find object") } -{{- end }} -{{- $renderedInitial = "true" }} -{{- end }} +{{- end }} +{{- $renderedIdFromName := "false" }} +{{- range $prop := $.GettableProperties }} +{{- /* Check if prop is potentially computed */}} +{{- if and ($.InIdFormat $prop) (and (or $prop.Output $prop.DefaultFromApi) (not $prop.IgnoreRead)) }} {{- if and (eq $prop.CustomFlatten "templates/terraform/custom_flatten/id_from_name.tmpl") (eq $renderedIdFromName "false") }} // Setting `name` field so that `id_from_name` flattener will work properly. if err := d.Set("name", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}Name(res["name"], d, config)); err != nil { From 5cff5a800a18ad1214c92bb213e93b6a1a6fa2cb Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Apr 2025 14:52:15 -0700 Subject: [PATCH 10/18] Fixed unit test errors --- mmv1/templates/terraform/resource.go.tmpl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index 68a6d35a26f1..87d840a675f7 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -293,7 +293,7 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- if and $.HasComputedIdFormatFields (or (not $.GetAsync) (not ($.GetAsync.Allow "Create"))) }} err = resource{{ $.ResourceName }}SetComputedIdFormatFields(d, meta, res) if err != nil { - return fmt.Errorf("Error setting computed ID format fields: %w") + return fmt.Errorf("Error setting computed ID format fields: %w", err) } {{- end }}{{/* not async create */}} @@ -405,7 +405,7 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- if $.HasComputedIdFormatFields }} err = resource{{ $.ResourceName }}SetComputedIdFormatFields(d, meta, res) if err != nil { - return fmt.Errorf("Error setting computed ID format fields: %w") + return fmt.Errorf("Error setting computed ID format fields: %w", err) } {{- end}} @@ -1234,19 +1234,19 @@ func resource{{ $.ResourceName }}SetComputedIdFormatFields(d *schema.ResourceDat {{- if and (eq $prop.CustomFlatten "templates/terraform/custom_flatten/id_from_name.tmpl") (eq $renderedIdFromName "false") }} // Setting `name` field so that `id_from_name` flattener will work properly. if err := d.Set("name", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}Name(res["name"], d, config)); err != nil { - return fmt.Errorf(`Error setting computed identity field "name": %s`, err) + return fmt.Errorf(`Error setting computed identity field "name": %w`, err) } {{- $renderedIdFromName = "true" }} {{- end }} {{- if and $prop.Output (not $prop.IgnoreRead) }} if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { - return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %s`, err) + return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %w`, err) } {{- else if and $prop.DefaultFromApi (not $prop.IgnoreRead) }} // {{ underscore $prop.Name }} is set by API when unset if tpgresource.IsEmptyValue(reflect.ValueOf(d.Get("{{ underscore $prop.Name }}"))) { if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { - return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %s`, err) + return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %w`, err) } } {{- end }} From 60545a32a3bfdc73baf4036c76b5c3c0edda039a Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Apr 2025 15:19:27 -0700 Subject: [PATCH 11/18] Only SetId after polling if there are computed id format fields --- mmv1/templates/terraform/resource.go.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index 87d840a675f7..08aa4c5bcccc 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -407,7 +407,6 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ if err != nil { return fmt.Errorf("Error setting computed ID format fields: %w", err) } -{{- end}} // This may have caused the ID to update - update it if so. id, err = tpgresource.ReplaceVars{{if $.LegacyLongFormProject -}}ForId{{ end -}}(d, config, "{{ $.IdFormat -}}") @@ -416,6 +415,7 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ } d.SetId(id) {{- end}} +{{- end}} {{- end}} log.Printf("[DEBUG] Finished creating {{ $.Name }} %q: %#v", d.Id(), res) From 4891e935408c20046bfc6bae22d5b77608888ed1 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Mon, 7 Apr 2025 16:07:21 -0700 Subject: [PATCH 12/18] Added TestHasComputedIdFormatFields --- mmv1/api/resource_test.go | 120 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/mmv1/api/resource_test.go b/mmv1/api/resource_test.go index 7d78a4b89871..38fa49bbf444 100644 --- a/mmv1/api/resource_test.go +++ b/mmv1/api/resource_test.go @@ -357,3 +357,123 @@ func TestMagicianLocation(t *testing.T) { t.Errorf("Current package is not under %s. Path from magician dir to current dir: %s", RELATIVE_MAGICIAN_LOCATION, relPath) } } + + +func TestHasComputedIdFormatFields(t *testing.T) { + cases := []struct{ + name, description string + resource Resource + want bool + }{ + { + name: "no properties", + resource: Resource{ + IdFormat: "projects/{{project}}/resource/{{resource}}", + }, + want: false, + }, + { + name: "no computed properties", + resource: Resource{ + IdFormat: "projects/{{project}}/resource/{{resource}}", + Properties: []*Type{ + { + Name: "resource", + }, + }, + }, + want: false, + }, + { + name: "output-only property", + resource: Resource{ + IdFormat: "projects/{{project}}/resource/{{resource}}", + Properties: []*Type{ + { + Name: "field", + Output: true, + }, + }, + }, + want: false, + }, + { + name: "output-only property in id_format", + resource: Resource{ + IdFormat: "projects/{{project}}/resource/{{resource}}", + Properties: []*Type{ + { + Name: "resource", + Output: true, + }, + }, + }, + want: true, + }, + { + name: "output-only property in id_format with ignore_read", + resource: Resource{ + IdFormat: "projects/{{project}}/resource/{{resource}}", + Properties: []*Type{ + { + Name: "resource", + Output: true, + IgnoreRead: true, + }, + }, + }, + want: false, + }, + { + name: "default_from_api property", + resource: Resource{ + IdFormat: "projects/{{project}}/resource/{{resource}}", + Properties: []*Type{ + { + Name: "field", + DefaultFromApi: true, + }, + }, + }, + want: false, + }, + { + name: "default_from_api property in id_format", + resource: Resource{ + IdFormat: "projects/{{project}}/resource/{{resource}}", + Properties: []*Type{ + { + Name: "resource", + DefaultFromApi: true, + }, + }, + }, + want: true, + }, + { + name: "default_from_api property in id_format with ignore_read", + resource: Resource{ + IdFormat: "projects/{{project}}/resource/{{resource}}", + Properties: []*Type{ + { + Name: "resource", + DefaultFromApi: true, + IgnoreRead: true, + }, + }, + }, + want: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + got := tc.resource.HasComputedIdFormatFields() + if got != tc.want { + t.Errorf("HasComputedIdFormatFields(%q) returned unexpected value. got %t; want %t.", tc.name, got, tc.want) + } + }) + } +} From 281186379e00c69989a1e1eb2213f42242d82b73 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Tue, 8 Apr 2025 09:50:40 -0700 Subject: [PATCH 13/18] Made HasComputedIdFormatFields convert prop name to snake case --- mmv1/api/resource.go | 2 +- mmv1/api/resource_test.go | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/mmv1/api/resource.go b/mmv1/api/resource.go index e74ec84356c6..42b2315c2d14 100644 --- a/mmv1/api/resource.go +++ b/mmv1/api/resource.go @@ -1225,7 +1225,7 @@ func (r Resource) HasComputedIdFormatFields() bool { } for _, p := range r.GettableProperties() { // Skip fields not in the id format - if _, ok := idFormatFields[p.Name]; !ok { + if _, ok := idFormatFields[google.Underscore(p.Name)]; !ok { continue } if (p.Output || p.DefaultFromApi) && !p.IgnoreRead { diff --git a/mmv1/api/resource_test.go b/mmv1/api/resource_test.go index 38fa49bbf444..65e736f6b27d 100644 --- a/mmv1/api/resource_test.go +++ b/mmv1/api/resource_test.go @@ -464,6 +464,19 @@ func TestHasComputedIdFormatFields(t *testing.T) { }, want: false, }, + { + name: "converts prop.name to snake case", + resource: Resource{ + IdFormat: "projects/{{project}}/resource/{{resource_id}}", + Properties: []*Type{ + { + Name: "resourceId", + Output: true, + }, + }, + }, + want: true, + }, } for _, tc := range cases { From e8485b9511c4bbf25af71153b0b2d3641e7f7c59 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Tue, 8 Apr 2025 10:11:10 -0700 Subject: [PATCH 14/18] gofmt --- mmv1/api/resource_test.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/mmv1/api/resource_test.go b/mmv1/api/resource_test.go index 65e736f6b27d..6f88fc0ac35b 100644 --- a/mmv1/api/resource_test.go +++ b/mmv1/api/resource_test.go @@ -358,12 +358,11 @@ func TestMagicianLocation(t *testing.T) { } } - func TestHasComputedIdFormatFields(t *testing.T) { - cases := []struct{ + cases := []struct { name, description string - resource Resource - want bool + resource Resource + want bool }{ { name: "no properties", @@ -390,7 +389,7 @@ func TestHasComputedIdFormatFields(t *testing.T) { IdFormat: "projects/{{project}}/resource/{{resource}}", Properties: []*Type{ { - Name: "field", + Name: "field", Output: true, }, }, @@ -403,7 +402,7 @@ func TestHasComputedIdFormatFields(t *testing.T) { IdFormat: "projects/{{project}}/resource/{{resource}}", Properties: []*Type{ { - Name: "resource", + Name: "resource", Output: true, }, }, @@ -416,8 +415,8 @@ func TestHasComputedIdFormatFields(t *testing.T) { IdFormat: "projects/{{project}}/resource/{{resource}}", Properties: []*Type{ { - Name: "resource", - Output: true, + Name: "resource", + Output: true, IgnoreRead: true, }, }, @@ -430,7 +429,7 @@ func TestHasComputedIdFormatFields(t *testing.T) { IdFormat: "projects/{{project}}/resource/{{resource}}", Properties: []*Type{ { - Name: "field", + Name: "field", DefaultFromApi: true, }, }, @@ -443,7 +442,7 @@ func TestHasComputedIdFormatFields(t *testing.T) { IdFormat: "projects/{{project}}/resource/{{resource}}", Properties: []*Type{ { - Name: "resource", + Name: "resource", DefaultFromApi: true, }, }, @@ -456,9 +455,9 @@ func TestHasComputedIdFormatFields(t *testing.T) { IdFormat: "projects/{{project}}/resource/{{resource}}", Properties: []*Type{ { - Name: "resource", + Name: "resource", DefaultFromApi: true, - IgnoreRead: true, + IgnoreRead: true, }, }, }, @@ -470,7 +469,7 @@ func TestHasComputedIdFormatFields(t *testing.T) { IdFormat: "projects/{{project}}/resource/{{resource_id}}", Properties: []*Type{ { - Name: "resourceId", + Name: "resourceId", Output: true, }, }, From da3e9e1d62f231496d1a9b485712be5be6e42ab1 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Tue, 8 Apr 2025 10:21:17 -0700 Subject: [PATCH 15/18] Moved comment --- mmv1/templates/terraform/resource.go.tmpl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index 08aa4c5bcccc..b0e5133ad1ef 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -1215,10 +1215,11 @@ func resource{{ $.ResourceName -}}PostCreateFailure(d *schema.ResourceData, meta {{- /* Only render SetComputedIdFormatFields for resources with non-async create or polling async */}} {{- if and $.HasComputedIdFormatFields (or (or (not $.GetAsync) (not ($.GetAsync.Allow "Create"))) (and $.GetAsync (and ($.GetAsync.IsA "PollAsync") ($.GetAsync.Allow "Create"))))}} +// resource{{$.ResourceName}}SetComputedIdFormatFields sets computed resource properties from create API response +// so that they're available on the subsequent Read call. func resource{{ $.ResourceName }}SetComputedIdFormatFields(d *schema.ResourceData, meta interface{}, res map[string]interface{}) error { config := meta.(*transport_tpg.Config) {{- if $.CustomCode.Decoder }} - // Set output-only resource properties from create API response res, err := resource{{ $.ResourceName -}}Decoder(d, meta, res) if err != nil { return fmt.Errorf("decoding response: %w", err) From 40a713b278990a66519f8dbb8c3f34c4007485d1 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Tue, 8 Apr 2025 14:41:21 -0700 Subject: [PATCH 16/18] Handle post_create before pollasync reads and don't render decoders in that case pollasync resources use decoders to determine whether the resource exists at all, but they need to set fields for the id before that, so there can't be a dependency between those fields and the decoder --- mmv1/templates/terraform/resource.go.tmpl | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index b0e5133ad1ef..c101e714526a 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -288,9 +288,10 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- end}} return fmt.Errorf("Error creating {{ $.Name -}}: %s", err) } -{{- /* Set output-only resource properties from create API response (as long as Create isn't async) */}} -{{- /* This is necessary so that the ID is built correctly. */}} -{{- if and $.HasComputedIdFormatFields (or (not $.GetAsync) (not ($.GetAsync.Allow "Create"))) }} +{{- /* Set computed resource properties required for building the ID from create API response (as long as Create doesn't use an async operation) */}} +{{- /* This is necessary so that the ID is set correctly (and so that the following Read can succeed.) */}} +{{- /* Technically this should possibly use the read URL explicitly, since id_format could differ - but that might need to be in addition to id_format anyway. */}} +{{- if and $.HasComputedIdFormatFields (or (not $.GetAsync) (not (and ($.GetAsync.IsA "OpAsync") ($.GetAsync.Allow "Create")))) }} err = resource{{ $.ResourceName }}SetComputedIdFormatFields(d, meta, res) if err != nil { return fmt.Errorf("Error setting computed ID format fields: %w", err) @@ -402,19 +403,6 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ return fmt.Errorf("Error waiting to create {{ $.Name -}}: %s", err) {{- end}} } -{{- if $.HasComputedIdFormatFields }} - err = resource{{ $.ResourceName }}SetComputedIdFormatFields(d, meta, res) - if err != nil { - return fmt.Errorf("Error setting computed ID format fields: %w", err) - } - - // This may have caused the ID to update - update it if so. - id, err = tpgresource.ReplaceVars{{if $.LegacyLongFormProject -}}ForId{{ end -}}(d, config, "{{ $.IdFormat -}}") - if err != nil { - return fmt.Errorf("Error constructing id: %s", err) - } - d.SetId(id) -{{- end}} {{- end}} {{- end}} @@ -1219,7 +1207,8 @@ func resource{{ $.ResourceName -}}PostCreateFailure(d *schema.ResourceData, meta // so that they're available on the subsequent Read call. func resource{{ $.ResourceName }}SetComputedIdFormatFields(d *schema.ResourceData, meta interface{}, res map[string]interface{}) error { config := meta.(*transport_tpg.Config) -{{- if $.CustomCode.Decoder }} +{{- /* Don't render decoder for PollAsync resources - their decoders are expected to return `nil` until the resource completion completes, but we need to set their computed fields in order to call PollRead - so can never be a dependency on the decoder. */}} +{{- if and $.CustomCode.Decoder (or (not $.GetAsync) (not ($.GetAsync.IsA "PollAsync"))) }} res, err := resource{{ $.ResourceName -}}Decoder(d, meta, res) if err != nil { return fmt.Errorf("decoding response: %w", err) From 34d7da16f74d6abe51371246105c5131bb348c21 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Tue, 8 Apr 2025 14:58:46 -0700 Subject: [PATCH 17/18] Backed out the change to split setting of computed fields into a separate function Since PollAsync and standard resources actually need to handle these in the same spot, there's no benefit to factoring out a function at this time; reverting that makes the resulting diff easier to understand. --- mmv1/templates/terraform/resource.go.tmpl | 86 ++++++++++------------- 1 file changed, 37 insertions(+), 49 deletions(-) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index c101e714526a..30118aa1e779 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -291,12 +291,45 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- /* Set computed resource properties required for building the ID from create API response (as long as Create doesn't use an async operation) */}} {{- /* This is necessary so that the ID is set correctly (and so that the following Read can succeed.) */}} {{- /* Technically this should possibly use the read URL explicitly, since id_format could differ - but that might need to be in addition to id_format anyway. */}} -{{- if and $.HasComputedIdFormatFields (or (not $.GetAsync) (not (and ($.GetAsync.IsA "OpAsync") ($.GetAsync.Allow "Create")))) }} - err = resource{{ $.ResourceName }}SetComputedIdFormatFields(d, meta, res) +{{- if and $.HasComputedIdFormatFields (or (or (not $.GetAsync) (not ($.GetAsync.Allow "Create"))) (and $.GetAsync (and ($.GetAsync.IsA "PollAsync") ($.GetAsync.Allow "Create"))))}} + // Set computed resource properties from create API response so that they're available on the subsequent Read + // call. +{{- /* Don't render decoder for PollAsync resources - their decoders are expected to return `nil` until the resource completion completes, but we need to set their computed fields in order to call PollRead - so can never be a dependency on the decoder. */}} +{{- if and $.CustomCode.Decoder (or (not $.GetAsync) (not ($.GetAsync.IsA "PollAsync"))) }} + res, err = resource{{ $.ResourceName -}}Decoder(d, meta, res) if err != nil { - return fmt.Errorf("Error setting computed ID format fields: %w", err) + return fmt.Errorf("decoding response: %w", err) + } + if res == nil { + return fmt.Errorf("decoding response, could not find object") + } +{{- end }} +{{- $renderedIdFromName := "false" }} +{{- range $prop := $.GettableProperties }} +{{- /* Check if prop is potentially computed */}} +{{- if and ($.InIdFormat $prop) (and (or $prop.Output $prop.DefaultFromApi) (not $prop.IgnoreRead)) }} +{{- if and (eq $prop.CustomFlatten "templates/terraform/custom_flatten/id_from_name.tmpl") (eq $renderedIdFromName "false") }} + // Setting `name` field so that `id_from_name` flattener will work properly. + if err := d.Set("name", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}Name(res["name"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "name": %s`, err) } -{{- end }}{{/* not async create */}} + {{- $renderedIdFromName = "true" }} +{{- end }} +{{- if and $prop.Output (not $prop.IgnoreRead) }} + if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %s`, err) + } +{{- else if and $prop.DefaultFromApi (not $prop.IgnoreRead) }} + // {{ underscore $prop.Name }} is set by API when unset + if tpgresource.IsEmptyValue(reflect.ValueOf(d.Get("{{ underscore $prop.Name }}"))) { + if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %s`, err) + } + } +{{- end }} +{{- end }}{{/* prop is potentially computed */}} +{{- end }}{{/* range */}} +{{- end}}{{/* Set computed resource properties... */}} // Store the ID now id, err := tpgresource.ReplaceVars{{if $.LegacyLongFormProject -}}ForId{{ end -}}(d, config, "{{ $.IdFormat -}}") @@ -1200,48 +1233,3 @@ func resource{{ $.ResourceName -}}PostCreateFailure(d *schema.ResourceData, meta {{ $.CustomTemplate $.StateMigrationFile false -}} {{- end }} -{{- /* Only render SetComputedIdFormatFields for resources with non-async create or polling async */}} -{{- if and $.HasComputedIdFormatFields (or (or (not $.GetAsync) (not ($.GetAsync.Allow "Create"))) (and $.GetAsync (and ($.GetAsync.IsA "PollAsync") ($.GetAsync.Allow "Create"))))}} - -// resource{{$.ResourceName}}SetComputedIdFormatFields sets computed resource properties from create API response -// so that they're available on the subsequent Read call. -func resource{{ $.ResourceName }}SetComputedIdFormatFields(d *schema.ResourceData, meta interface{}, res map[string]interface{}) error { - config := meta.(*transport_tpg.Config) -{{- /* Don't render decoder for PollAsync resources - their decoders are expected to return `nil` until the resource completion completes, but we need to set their computed fields in order to call PollRead - so can never be a dependency on the decoder. */}} -{{- if and $.CustomCode.Decoder (or (not $.GetAsync) (not ($.GetAsync.IsA "PollAsync"))) }} - res, err := resource{{ $.ResourceName -}}Decoder(d, meta, res) - if err != nil { - return fmt.Errorf("decoding response: %w", err) - } - if res == nil { - return fmt.Errorf("decoding response, could not find object") - } -{{- end }} -{{- $renderedIdFromName := "false" }} -{{- range $prop := $.GettableProperties }} -{{- /* Check if prop is potentially computed */}} -{{- if and ($.InIdFormat $prop) (and (or $prop.Output $prop.DefaultFromApi) (not $prop.IgnoreRead)) }} -{{- if and (eq $prop.CustomFlatten "templates/terraform/custom_flatten/id_from_name.tmpl") (eq $renderedIdFromName "false") }} - // Setting `name` field so that `id_from_name` flattener will work properly. - if err := d.Set("name", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}Name(res["name"], d, config)); err != nil { - return fmt.Errorf(`Error setting computed identity field "name": %w`, err) - } - {{- $renderedIdFromName = "true" }} -{{- end }} -{{- if and $prop.Output (not $prop.IgnoreRead) }} - if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { - return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %w`, err) - } -{{- else if and $prop.DefaultFromApi (not $prop.IgnoreRead) }} - // {{ underscore $prop.Name }} is set by API when unset - if tpgresource.IsEmptyValue(reflect.ValueOf(d.Get("{{ underscore $prop.Name }}"))) { - if err := d.Set("{{ underscore $prop.Name -}}", flatten{{ if $.NestedQuery -}}Nested{{end}}{{ $.ResourceName -}}{{ camelize $prop.Name "upper" -}}(res["{{ $prop.ApiName -}}"], d, config)); err != nil { - return fmt.Errorf(`Error setting computed identity field "{{ underscore $prop.Name }}": %w`, err) - } - } -{{- end }} -{{- end }}{{/* prop is potentially computed */}} -{{- end }}{{/* range */}} - return nil -} -{{- end}} From 2eb034cabf0345bddef30a9d5e09f70a9331311c Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Tue, 8 Apr 2025 15:00:43 -0700 Subject: [PATCH 18/18] Fixed typo in comment --- mmv1/templates/terraform/resource.go.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index 30118aa1e779..62b568e4a248 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -294,7 +294,7 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ {{- if and $.HasComputedIdFormatFields (or (or (not $.GetAsync) (not ($.GetAsync.Allow "Create"))) (and $.GetAsync (and ($.GetAsync.IsA "PollAsync") ($.GetAsync.Allow "Create"))))}} // Set computed resource properties from create API response so that they're available on the subsequent Read // call. -{{- /* Don't render decoder for PollAsync resources - their decoders are expected to return `nil` until the resource completion completes, but we need to set their computed fields in order to call PollRead - so can never be a dependency on the decoder. */}} +{{- /* Don't render decoder for PollAsync resources - their decoders are expected to return `nil` until the resource completion completes, but we need to set their computed fields in order to call PollRead - so there can never be a dependency on the decoder. */}} {{- if and $.CustomCode.Decoder (or (not $.GetAsync) (not ($.GetAsync.IsA "PollAsync"))) }} res, err = resource{{ $.ResourceName -}}Decoder(d, meta, res) if err != nil {