Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
bf9fee5
initial flattenerGeneration test with secretmanager.Secret
BBBmau Feb 6, 2026
25a5c91
Decoder generation in flattenerResource test with kms.cryptoKeys
BBBmau Feb 6, 2026
db20c27
PostRead + NestedQuery in flattenerResource - accesscontextmanager.Se…
BBBmau Feb 6, 2026
50f8fbc
bigquery: flattenResource in dataset
BBBmau Feb 9, 2026
8afddca
add flatten_all_in_method on all resources that have a custom_code.de…
BBBmau Feb 19, 2026
c5b32ff
remove spanner.Instance and spanner.InstanceConfig - flatteners clash
BBBmau Feb 19, 2026
121973d
conflict fixe
BBBmau Feb 21, 2026
8644ace
revert yamls that got extra updates
BBBmau Feb 21, 2026
23187d8
DomainMapping revert
BBBmau Feb 21, 2026
b76f65d
remove tgc_include_handwritten_tests
BBBmau Feb 21, 2026
35d1925
reduce the flatten_in_all_method in decoders to half
BBBmau Feb 23, 2026
8a9d071
remove flatten_all on resources producing a interaction not found error
BBBmau Feb 23, 2026
59fb2f2
remove flatten_all_method from cryptoKey
BBBmau Feb 24, 2026
d44da66
remove flatten_all_method from keyRing
BBBmau Feb 24, 2026
0159918
Revert "remove flatten_all on resources producing a interaction not f…
BBBmau Feb 25, 2026
5431d6d
resolve post merge conflicts
BBBmau Apr 7, 2026
9814732
enable flatten_all_in_method on Decoder+HasSelfLink+FlattenObject res…
BBBmau Apr 7, 2026
ae21a3f
enable flatten_all_in_method on Decoder+HasSelfLink resources
BBBmau Apr 7, 2026
c32cd06
enable flatten_all_in_method on Decoder+NestedQuery resources
BBBmau Apr 7, 2026
6e25bda
enable flatten_all_in_method on PostRead+NestedQuery resources
BBBmau Apr 7, 2026
a7b5a9c
enable flatten_all_in_method on Decoder+PostRead+HasSelfLink resources
BBBmau Apr 7, 2026
0ca57b4
enable flatten_all_in_method on Decoder+PostRead resources
BBBmau Apr 7, 2026
7fb16c6
enable flatten_all_in_method on Decoder+FlattenObject resources
BBBmau Apr 7, 2026
8baa227
enable flatten_all_in_method on remaining decoder-only resources
BBBmau Apr 7, 2026
8a29fc5
flatten{{resourceName}}Resource signature in resource.go.tmpl
BBBmau Apr 8, 2026
68b2279
default run of flattenAllInResource
BBBmau Apr 8, 2026
b6a9125
cleanup after merge-conflict resolution
BBBmau Apr 8, 2026
f08d18d
resolve diffs from rebase to magic-modules/main
BBBmau Apr 21, 2026
18860eb
chore: add list utility code for list support in google provider
BBBmau Apr 6, 2026
b7c50c1
use 1.14.1 for plugin-testing and not 1.13.3
BBBmau Apr 7, 2026
8ae8707
simpler initilization of list resources in FrameworkProvider
BBBmau Apr 8, 2026
ae0a277
add Get default provider values in list_metadata.go
BBBmau Apr 8, 2026
8a74861
move identity set logic to its own SetIdentityFields method
BBBmau Apr 9, 2026
aec56e3
use correct resourcemanager package in framework_provider_mmv1_resour…
BBBmau Apr 9, 2026
79e99c6
initial run generation WIP
BBBmau Dec 5, 2025
145e0b2
move generation of list resource to be its own file instead of within…
BBBmau Dec 9, 2025
5db7635
pseudocode list_resource
BBBmau Dec 13, 2025
4db229e
WIP
BBBmau Dec 16, 2025
0ecdc20
initial list_resource generation support - no standarized iterator (N…
BBBmau Dec 4, 2025
9f88374
resolve build errors
BBBmau Apr 9, 2026
2fbc012
refactor to use flattenResource method generated from mau/flattenReso…
BBBmau Apr 9, 2026
3583581
major refactoring
BBBmau Apr 10, 2026
fc082d3
remove cloud_clients.go.tmpl
BBBmau Apr 10, 2026
33bb947
include gneerated list resources in framework_mmv1
BBBmau Apr 10, 2026
40f59e2
first successful run of tf query on generated list resource (cloud_ru…
BBBmau Apr 10, 2026
8e91a52
fix name being empty in list query results
BBBmau Apr 10, 2026
61c0cd5
refactor to handle setResult + setIdentity in list_resource metadata …
BBBmau Apr 10, 2026
3e95750
refactor to handle Configure / Metadata / RawV5Schemas in in list_res…
BBBmau Apr 10, 2026
37ff7d7
add ListScopeProperties for values from base_url string
BBBmau Apr 10, 2026
3c2d39a
remove filter field from list resource generation
BBBmau Apr 10, 2026
21c978a
split list_resource.go.tmpl into multiple templates
BBBmau Apr 10, 2026
d43d776
migrate to framework_mmv1_resources template
BBBmau Apr 21, 2026
2887d3d
remove duplicate definitions after rebase
BBBmau Apr 21, 2026
c94410f
update to use ListPagesOptions struct
BBBmau Apr 21, 2026
b293ab3
match list_utils changes introduced in upstream magic-modules
BBBmau Apr 21, 2026
7ddcb97
refactors to allow for reuse of resourceFlattener in list resources +…
BBBmau Apr 21, 2026
dfb519b
remove common~copy changes
BBBmau Apr 21, 2026
5ecd92c
more common~copy removals
BBBmau Apr 21, 2026
c5afba9
go sum match
BBBmau Apr 21, 2026
ed642e3
remove what is unnecessary
BBBmau Apr 21, 2026
6107bfb
ListResultDisplayName for setting DisplayName of resource in query re…
BBBmau Apr 21, 2026
9dcd1d6
initial query test generation implementation
BBBmau Apr 21, 2026
fe3a6cf
simpler flattener gen
BBBmau Apr 21, 2026
1419be5
add settable_propeorties that are only set on create
BBBmau Apr 22, 2026
d96cb9b
remove unused existsInBaseUrl
BBBmau Apr 22, 2026
ceca58f
simplify ResourceFlattener call in list_resource_method.go.tmpl
BBBmau Apr 23, 2026
dc1a78a
remove comments
BBBmau Apr 23, 2026
a7f6f1d
simplify case where the res can be a TypeInt but res returns string
BBBmau Apr 23, 2026
0d75839
simplify getGeneratedListResoucesInVersion
BBBmau Apr 23, 2026
f9b0abb
more robust ListResultDsiplayNameKeyStrings method
BBBmau Apr 23, 2026
0f665a6
simplify ListScopeProperties
BBBmau Apr 23, 2026
52d70ce
remove HasLocation method
BBBmau Apr 23, 2026
a3014e4
cleanup service.yaml
BBBmau Apr 23, 2026
578ff32
more
BBBmau Apr 23, 2026
bb986b4
refactors in templates
BBBmau Apr 23, 2026
6175e49
simplify query_test_gen
BBBmau Apr 23, 2026
966f987
fixes on query test gen + passing run for cloudrun service query
BBBmau Apr 23, 2026
fd4c317
revert refactor
BBBmau Apr 23, 2026
2b5f9d1
remove more
BBBmau Apr 23, 2026
a6f61fe
improved testing on ResourceDisplayName
BBBmau Apr 23, 2026
af08744
rename of generated files for list
BBBmau Apr 23, 2026
0379abe
move Decoder, NestedQuery, and PostRead in list_resource__method.go.tmpl
BBBmau Apr 27, 2026
11304ce
use bootstrapProjects for query_test generation
BBBmau Apr 27, 2026
24ac9fe
add list_query_test_utils for explicit checking of DisplayName for pr…
BBBmau Apr 28, 2026
5b1370a
move list test utils to test_utils.go.tmpl
BBBmau Apr 29, 2026
3bcce7d
storage bucket testing + refining testing for non variable update
BBBmau Apr 30, 2026
cb989c2
remove project_service
BBBmau Apr 30, 2026
6ab46ae
add conditional for setting optional true or false for list schemas
BBBmau Apr 30, 2026
43b34f7
add itemName fallback for default collection_key_url useage
BBBmau Apr 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions mmv1/api/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ type Resource struct {
// EXPERIMENTAL: If true, resource should be autogenerated as a data source
Datasource *resource.Datasource `yaml:"datasource_experimental,omitempty"`

GenerateListResource bool `yaml:"generate_list_resource,omitempty"`

// If true, skip sweeper generation for this resource
ExcludeSweeper bool `yaml:"exclude_sweeper,omitempty"`

Expand Down Expand Up @@ -697,6 +699,28 @@ func (r Resource) IdentityProperties() []*Type {
return props
}

func (r Resource) ListScopeProperties() []*Type {
scope := r.ExtractIdentifiers(r.CollectionUrl())
return google.Select(r.IdentityProperties(), func(p *Type) bool {
return slices.Contains(scope, google.Underscore(p.Name))
})
}

func (r Resource) ListResultDisplayNameKeyStrings() []string {
var keys []string
for _, p := range r.RootProperties() {
if p.Name == "display_name" {
keys = append(keys, "display_name")
break
}
}
markers := regexp.MustCompile(`\{\{(\w+)\}\}`).FindAllStringSubmatch(r.IdFormat, -1)
if len(markers) > 0 {
keys = append(keys, markers[len(markers)-1][1])
}
return keys
}

func (r Resource) SensitiveProps() []*Type {
props := r.AllNestedProperties(r.RootProperties())
return google.Select(props, func(p *Type) bool {
Expand Down
2 changes: 2 additions & 0 deletions mmv1/products/cloudbuild/Trigger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
---
name: 'Trigger'
api_resource_type_kind: BuildTrigger
collection_url_key: 'triggers'
api_variant_patterns:
- 'projects/{project}/locations/{location}/triggers/{trigger}'
description: |
Expand All @@ -38,6 +39,7 @@ timeouts:
insert_minutes: 20
update_minutes: 20
delete_minutes: 20
generate_list_resource: true
custom_code:
constants: 'templates/terraform/constants/cloudbuild_trigger.tmpl'
post_create: 'templates/terraform/post_create/cloudbuild_trigger_id.go.tmpl'
Expand Down
1 change: 1 addition & 0 deletions mmv1/products/cloudrun/Service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import_format:
datasource_experimental:
generate: true
exclude_test: true
generate_list_resource: true
timeouts:
insert_minutes: 20
update_minutes: 20
Expand Down
1 change: 1 addition & 0 deletions mmv1/products/storage/Folder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ timeouts:
update_minutes: 20
delete_minutes: 20
exclude_sweeper: true
generate_list_resource: true
import_format:
- '{{bucket}}/folders/{{%name}}'
- '{{bucket}}/{{%name}}'
Expand Down
10 changes: 10 additions & 0 deletions mmv1/provider/template_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,16 @@ func (td *TemplateData) GenerateIamPolicyTestFile(filePath string, resource api.
td.GenerateFile(filePath, templatePath, resource, true, templates...)
}

// GenerateQueryTestFile emits a Terraform query-mode acceptance test for list resources (generate_list_resource).
func (td *TemplateData) GenerateQueryTestFile(filePath string, resource api.Resource) {
templatePath := "templates/terraform/samples/base_configs/query_test_file.go.tmpl"
templates := []string{
templatePath,
"templates/terraform/env_var_context.go.tmpl",
}
td.GenerateFile(filePath, templatePath, resource, true, templates...)
}

func (td *TemplateData) GenerateSweeperFile(filePath string, resource api.Resource) {
templatePath := "templates/terraform/sweeper_file.go.tmpl"
templates := []string{
Expand Down
90 changes: 90 additions & 0 deletions mmv1/provider/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ func (t *Terraform) GenerateObject(object api.Resource, outputFolder, productPat
t.GenerateSingularDataSourceTests(object, *templateData, outputFolder)
// log.Printf("Generating %s metadata", object.Name)
t.GenerateResourceMetadata(object, *templateData, outputFolder)
if object.GenerateListResource {
t.GenerateListResourceQueryTest(object, *templateData, outputFolder)
}
}
}

Expand Down Expand Up @@ -151,6 +154,19 @@ func (t *Terraform) GenerateResource(object api.Resource, templateData TemplateD
targetFilePath := path.Join(targetFolder, fmt.Sprintf("resource_%s.go", t.ResourceGoFilename(object)))
templateData.GenerateResourceFile(targetFilePath, object)
}
if object.GenerateListResource {
if object.ExcludeIdentityGeneration {
log.Fatalf("generate_list_resource requires identity support; remove exclude_identity_generation from resource %q or disable generate_list_resource", object.Name)
}
if object.ExcludeRead {
log.Fatalf("generate_list_resource requires read support; remove exclude_read from resource %q or disable generate_list_resource", object.Name)
}
targetFilePath := path.Join(targetFolder, fmt.Sprintf("list_%s.go", t.ResourceGoFilename(object)))
templateData.GenerateFile(targetFilePath, "templates/terraform/list_resource.go.tmpl", object, true,
"templates/terraform/list_resource.go.tmpl",
"templates/terraform/list_resource_method.go.tmpl",
)
}
}

if generateDocs {
Expand Down Expand Up @@ -254,6 +270,36 @@ func (t *Terraform) GenerateResourceTests(object api.Resource, templateData Temp
templateData.GenerateTestFile(targetFilePath, object)
}

func (t *Terraform) GenerateListResourceQueryTest(object api.Resource, templateData TemplateData, outputFolder string) {
if object.Samples != nil && object.Examples != nil {
log.Fatalf("Both Samples and Examples block exist in %v", object.Name)
}
if object.Examples == nil {
return
}

eligibleExample := false
for _, example := range object.Examples {
if !example.ExcludeTest {
if object.ProductMetadata.VersionObjOrClosest(t.Product.Version.Name).CompareTo(object.ProductMetadata.VersionObjOrClosest(example.MinVersion)) >= 0 {
eligibleExample = true
break
}
}
}
if !eligibleExample {
return
}

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

func (t *Terraform) GenerateResourceSweeper(object api.Resource, templateData TemplateData, outputFolder string) {
if !object.ShouldGenerateSweepers() {
return
Expand Down Expand Up @@ -1092,3 +1138,47 @@ type ProviderWithProducts struct {
Compiler string
Products []*api.Product
}

// GeneratedListResourceRegistration describes one MMv1-generated list resource constructor
// for framework_provider_mmv1_resources.go.tmpl.
type GeneratedListResourceRegistration struct {
Package string // Go service package name (product ApiName), e.g. "cloudrun"
NewFunc string // e.g. "NewCloudRunServiceListResource"
}

func (t Terraform) GetGeneratedListResourcesInVersion(products []*api.Product) []GeneratedListResourceRegistration {
var out []GeneratedListResourceRegistration
for _, productDefinition := range products {
for _, object := range productDefinition.Objects {
if object.NotInVersion(productDefinition.VersionObjOrClosest(t.TargetVersionName)) || object.IsExcluded() || !object.GenerateListResource {
continue
}
out = append(out, GeneratedListResourceRegistration{
Package: productDefinition.ApiName,
NewFunc: fmt.Sprintf("New%sListResource", object.ResourceName()),
})
}
}
slices.SortFunc(out, func(a, b GeneratedListResourceRegistration) int {
if c := strings.Compare(a.Package, b.Package); c != 0 {
return c
}
return strings.Compare(a.NewFunc, b.NewFunc)
})
return out
}

func (t Terraform) GetListResourceImportPackages(products []*api.Product) []string {
reg := t.GetGeneratedListResourcesInVersion(products)
seen := make(map[string]struct{}, len(reg))
var pkgs []string
for _, r := range reg {
if _, ok := seen[r.Package]; ok {
continue
}
seen[r.Package] = struct{}{}
pkgs = append(pkgs, r.Package)
}
slices.Sort(pkgs)
return pkgs
}
127 changes: 127 additions & 0 deletions mmv1/templates/terraform/list_resource.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{{/* The license inside this block applies to this file
Copyright 2024 Google LLC. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */ -}}
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: MPL-2.0

{{$.CodeHeader TemplatePath}}

package {{ lower $.ProductMetadata.Name }}

import (
"context"
"errors"
"fmt"
"net/http"
{{- if and $.HasProject $.LegacyLongFormProject }}
"strings"
{{- end }}

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/list"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

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

var _ list.ListResource = &{{ $.ResourceName -}}ListResource{}

type {{ $.ResourceName -}}ListResource struct {
tpgresource.ListResourceMetadata
}

func New{{ $.ResourceName -}}ListResource() list.ListResource {
listR := &{{ $.ResourceName -}}ListResource{}
listR.TypeName = "{{ $.TerraformName }}"
listR.SDKv2Resource = Resource{{ $.ResourceName -}}()
listR.ListConfigFields = []tpgresource.ListConfigField{
{{- range $scope := $.ListScopeProperties }}
{Name: "{{ underscore $scope.Name }}", Kind: tpgresource.ListConfigKindString, Optional: {{ if $scope.Required }}false{{ else }}true{{ end }}},
{{- end }}
}
return listR
}

// {{ $.ResourceName }}ListModel matches ListResourceMetadata.ListConfigFields (tfsdk names and types).
type {{ $.ResourceName -}}ListModel struct {
{{- range $scope := $.ListScopeProperties }}
{{ $scope.TitlelizeProperty }} types.String `tfsdk:"{{ underscore $scope.Name }}"`
{{- end }}
}

func (listR *{{ $.ResourceName -}}ListResource) List(ctx context.Context, listReq list.ListRequest, stream *list.ListResultsStream) {
var data {{ $.ResourceName -}}ListModel
diags := listReq.Config.Get(ctx, &data)
if diags.HasError() {
stream.Results = list.ListResultsStreamDiagnostics(diags)
return
}
if listR.Client == nil {
diags = append(diags, diag.NewErrorDiagnostic(
"Provider not configured",
"The Google provider client is not available; ensure the provider is configured (e.g. credentials and default project).",
))
stream.Results = list.ListResultsStreamDiagnostics(diags)
return
}

{{- range $scope := $.ListScopeProperties }}
{{- if eq $scope.Name "project" }}
{{ $scope.CamelizeProperty }} := listR.GetProject(data.{{ $scope.TitlelizeProperty }})
{{- else if eq $scope.Name "region" }}
{{ $scope.CamelizeProperty }} := listR.GetRegion(data.{{ $scope.TitlelizeProperty }})
{{- else if eq $scope.Name "zone" }}
{{ $scope.CamelizeProperty }} := listR.GetZone(data.{{ $scope.TitlelizeProperty }})
{{- else if eq $scope.Name "location" }}
{{ $scope.CamelizeProperty }} := listR.GetLocation(data.{{ $scope.TitlelizeProperty }})
{{- else }}
var {{ $scope.CamelizeProperty }} string
if !data.{{ $scope.TitlelizeProperty }}.IsNull() && !data.{{ $scope.TitlelizeProperty }}.IsUnknown() {
{{ $scope.CamelizeProperty }} = data.{{ $scope.TitlelizeProperty }}.ValueString()
}
{{- end }}
{{- end }}

stream.Results = func(push func(list.ListResult) bool) {
err := List{{ $.ResourceName }}s(
listR.Client,
{{- range $scope := $.ListScopeProperties }}
{{ $scope.CamelizeProperty }},
{{- end }}
func(rd *schema.ResourceData) error {
result := listReq.NewListResult(ctx)

if err := listR.SetResult(ctx, listReq.IncludeResource, &result, rd{{- range $k := $.ListResultDisplayNameKeyStrings }}, "{{ $k }}"{{- end }}); err != nil {
return err
}

if !push(result) {
return errors.New("stream closed")
}
return nil
},
)
if err != nil {
diags.AddError("API Error", err.Error())
result := listReq.NewListResult(ctx)
result.Diagnostics = diags
push(result)
}
}
}

{{ template "listResourceMethod" $ }}
Loading
Loading