Skip to content

Commit 5f1a13f

Browse files
authored
Switched metadata.yaml generation to marshal-based implementation (#15948)
1 parent 09127da commit 5f1a13f

File tree

8 files changed

+314
-49
lines changed

8 files changed

+314
-49
lines changed

docs/content/reference/metadata.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,39 @@ This page documents all properties for metadata. Metadata does not impact the pr
1111

1212
### `resource`
1313

14-
The name of the Terraform resource e.g., "google_cloudfunctions2_function".
14+
The name of the Terraform resource. For example, "google_cloudfunctions2_function".
1515

1616
### `generation_type`
1717

18-
The generation method used to create the Terraform resource e.g., "mmv1", "dcl", "handwritten".
18+
The generation method used to create the Terraform resource. For example, "mmv1", "dcl", "handwritten".
1919

2020
## Optional
2121

2222
### `api_service_name`
2323

24-
The base name of the API used for this resource e.g., "cloudfunctions.googleapis.com".
24+
The base name of the API used for this resource. For example, "cloudfunctions.googleapis.com".
2525

2626
### `api_version`
2727

28-
The version of the API used for this resource e.g., "v2".
28+
The version of the API used for this resource. For example, "v2".
2929

3030
### `api_resource_type_kind`
3131

32-
The API "resource type kind" used for this resource e.g., "Function".
32+
The API "resource type kind" used for this resource. For example, "Function".
3333

3434
### `cai_asset_name_format`
3535

36-
The custom CAI asset name format for this resource is typically specified (e.g., //cloudsql.googleapis.com/projects/{{project}}/instances/{{name}}). If this format is not provided, the Terraform resource ID format is used instead.
36+
The custom CAI asset name format for this resource is typically specified (for example, //cloudsql.googleapis.com/projects/{{project}}/instances/{{name}}). This should only have a value if it's different than the Terraform resource ID format.
3737

3838
### `api_variant_patterns`
3939

40-
The API URL patterns used by this resource that represent variants e.g., "folders/{folder}/feeds/{feed}". Each pattern must match the value defined in the API exactly. The use of `api_variant_patterns` is only meaningful when the resource type has multiple parent types available.
40+
The API URL patterns used by this resource that represent variants. For example, "folders/{folder}/feeds/{feed}". Each pattern must match the value defined in the API exactly. The use of `api_variant_patterns` is only meaningful when the resource type has multiple parent types available.
4141

4242
### `fields`
4343

4444
The list of fields used by this resource. Each field can contain the following attributes:
4545

46-
- `api_field`: The name of the field in the REST API, including the path e.g., "buildConfig.source.storageSource.bucket".
47-
- `field`: The name of the field in Terraform, including the path e.g., "build_config.source.storage_source.bucket". Defaults to the value of `api_field` converted to snake_case.
46+
- `api_field`: The name of the field in the REST API, including the path. For example, "buildConfig.source.storageSource.bucket".
47+
- `field`: The name of the field in Terraform, including the path. For example, "build_config.source.storage_source.bucket". Defaults to the value of `api_field` converted to snake_case.
4848
- `provider_only`: If true, the field is only present in the provider. This primarily applies for virtual fields and url-only parameters. When set to true, `field` should be set and `api_field` should be left empty. Default: `false`.
4949
- `json`: If true, this is a JSON field which "covers" all child API fields. As a special case, JSON fields which cover an entire resource can have `api_field` set to `*`.

mmv1/api/metadata/field.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package metadata
2+
3+
import (
4+
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api"
5+
)
6+
7+
func FromProperties(props []*api.Type) []Field {
8+
var fields []Field
9+
for _, p := range props {
10+
f := Field{
11+
Json: p.IsJsonField(),
12+
ProviderOnly: p.ProviderOnly(),
13+
}
14+
if !p.ProviderOnly() {
15+
f.ApiField = p.MetadataApiLineage()
16+
}
17+
if p.ProviderOnly() || p.MetadataLineage() != p.MetadataDefaultLineage() {
18+
f.Field = p.MetadataLineage()
19+
}
20+
fields = append(fields, f)
21+
}
22+
return fields
23+
}
24+
25+
// Field is a field in a metadata.yaml file.
26+
type Field struct {
27+
// The name of the field in the REST API, including the path. For example, "buildConfig.source.storageSource.bucket".
28+
ApiField string `yaml:"api_field,omitempty"`
29+
// The name of the field in Terraform, including the path. For example, "build_config.source.storage_source.bucket". Defaults to the value
30+
// of `api_field` converted to snake_case.
31+
Field string `yaml:"field,omitempty"`
32+
// If true, the field is only present in the provider. This primarily applies for virtual fields and url-only parameters. When set to true,
33+
// `field` should be set and `api_field` should be left empty. Default: `false`.
34+
ProviderOnly bool `yaml:"provider_only,omitempty"`
35+
// If true, this is a JSON field which "covers" all child API fields. As a special case, JSON fields which cover an entire resource can
36+
// have `api_field` set to `*`.
37+
Json bool `yaml:"json,omitempty"`
38+
}

mmv1/api/metadata/field_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package metadata
2+
3+
import (
4+
"testing"
5+
6+
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api"
7+
"github.com/google/go-cmp/cmp"
8+
)
9+
10+
func TestFromProperties(t *testing.T) {
11+
cases := []struct {
12+
name string
13+
resourceMetadata *api.Resource
14+
properties []*api.Type
15+
wantFields []Field
16+
}{
17+
{
18+
name: "json field",
19+
properties: []*api.Type{
20+
{
21+
Name: "fieldName",
22+
CustomFlatten: "templates/terraform/custom_flatten/json_schema.tmpl",
23+
},
24+
},
25+
wantFields: []Field{
26+
{
27+
Json: true,
28+
ApiField: "fieldName",
29+
},
30+
},
31+
},
32+
{
33+
name: "fine-grained resource field",
34+
resourceMetadata: &api.Resource{ApiResourceField: "parentField"},
35+
properties: []*api.Type{
36+
{
37+
Name: "fieldName",
38+
},
39+
},
40+
wantFields: []Field{
41+
{
42+
ApiField: "parentField.fieldName",
43+
Field: "field_name",
44+
},
45+
},
46+
},
47+
{
48+
name: "provider-only",
49+
properties: []*api.Type{
50+
{
51+
Name: "fieldName",
52+
UrlParamOnly: true,
53+
},
54+
},
55+
wantFields: []Field{
56+
{
57+
Field: "field_name",
58+
ProviderOnly: true,
59+
},
60+
},
61+
},
62+
}
63+
64+
for _, tc := range cases {
65+
t.Run(tc.name, func(t *testing.T) {
66+
t.Parallel()
67+
68+
r := tc.resourceMetadata
69+
if r == nil {
70+
r = &api.Resource{}
71+
}
72+
73+
for _, p := range tc.properties {
74+
p.SetDefault(r)
75+
}
76+
77+
got := FromProperties(tc.properties)
78+
if diff := cmp.Diff(tc.wantFields, got); diff != "" {
79+
t.Errorf("FromProperties() mismatch (-want +got):\n%s", diff)
80+
}
81+
})
82+
}
83+
}

mmv1/api/metadata/metadata.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package metadata
2+
3+
import (
4+
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api"
5+
)
6+
7+
// FromResource returns a Metadata object based on a Resource.
8+
func FromResource(r api.Resource) Metadata {
9+
m := Metadata{
10+
Resource: r.TerraformName(),
11+
GenerationType: "mmv1",
12+
SourceFile: r.SourceYamlFile,
13+
ApiServiceName: r.ProductMetadata.ServiceName(),
14+
ApiVersion: r.ProductMetadata.ServiceVersion(),
15+
ApiResourceTypeKind: r.ApiResourceTypeKind,
16+
CaiAssetNameFormat: r.CAIFormatOverride(),
17+
ApiVariantPatterns: r.ApiVariantPatterns,
18+
AutogenStatus: r.AutogenStatus != "",
19+
Fields: FromProperties(r.LeafProperties()),
20+
}
21+
22+
if m.ApiVersion == "" {
23+
m.ApiVersion = r.ServiceVersion()
24+
}
25+
if m.ApiResourceTypeKind == "" {
26+
m.ApiResourceTypeKind = r.Name
27+
}
28+
29+
if r.HasSelfLink {
30+
m.Fields = append(m.Fields, Field{
31+
ApiField: "selfLink",
32+
})
33+
}
34+
return m
35+
}
36+
37+
// Metadata represents a metadata.yaml file for a single Terraform resource.
38+
type Metadata struct {
39+
// The name of the Terraform resource. For example, "google_cloudfunctions2_function".
40+
Resource string `yaml:"resource"`
41+
42+
// The generation method used to create the Terraform resource. For example, "mmv1", "dcl", "handwritten".
43+
GenerationType string `yaml:"generation_type"`
44+
45+
// The source file of this metadata. This will only be set for generated resources, and will be the yaml file that contains the resource definition.
46+
SourceFile string `yaml:"source_file"`
47+
48+
// The base name of the API used for this resource. For example, "cloudfunctions.googleapis.com".
49+
ApiServiceName string `yaml:"api_service_name"`
50+
51+
// The version of the API used for this resource. For example, "v2".
52+
ApiVersion string `yaml:"api_version"`
53+
54+
// The API "resource type kind" used for this resource. For example, "Function".
55+
ApiResourceTypeKind string `yaml:"api_resource_type_kind"`
56+
57+
// The custom CAI asset name format for this resource is typically specified (for example, //cloudsql.googleapis.com/projects/{{project}}/instances/{{name}}).
58+
// This will only have a value if it's different than the Terraform resource ID format.
59+
CaiAssetNameFormat string `yaml:"cai_asset_name_format,omitempty"`
60+
61+
// The API URL patterns used by this resource that represent variants. For example, "folders/{folder}/feeds/{feed}". Each pattern must match the value defined
62+
// in the API exactly. The use of `api_variant_patterns` is only meaningful when the resource type has multiple parent types available.
63+
ApiVariantPatterns []string `yaml:"api_variant_patterns,omitempty"`
64+
65+
// Whether the resource was autogenerated from OpenAPI specs.
66+
AutogenStatus bool `yaml:"autogen_status,omitempty"`
67+
68+
// List of fields on the resource.
69+
Fields []Field `yaml:"fields"`
70+
}

mmv1/api/metadata/metadata_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package metadata
2+
3+
import (
4+
"testing"
5+
6+
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api"
7+
"github.com/google/go-cmp/cmp"
8+
)
9+
10+
func TestFromResource(t *testing.T) {
11+
product := &api.Product{
12+
Name: "Product",
13+
BaseUrl: "https://compute.googleapis.com/beta",
14+
}
15+
cases := []struct {
16+
name string
17+
resource api.Resource
18+
wantMetadata Metadata
19+
}{
20+
{
21+
name: "empty resource",
22+
resource: api.Resource{},
23+
wantMetadata: Metadata{
24+
Resource: "google_product_",
25+
GenerationType: "mmv1",
26+
ApiServiceName: "compute.googleapis.com",
27+
ApiVersion: "beta",
28+
},
29+
},
30+
{
31+
name: "standard",
32+
resource: api.Resource{
33+
Name: "Test",
34+
AutogenStatus: "base64",
35+
SourceYamlFile: "Test.yaml",
36+
Properties: []*api.Type{
37+
{
38+
Name: "field",
39+
ApiName: "field",
40+
},
41+
},
42+
},
43+
wantMetadata: Metadata{
44+
Resource: "google_product_test",
45+
GenerationType: "mmv1",
46+
SourceFile: "Test.yaml",
47+
ApiServiceName: "compute.googleapis.com",
48+
ApiVersion: "beta",
49+
ApiResourceTypeKind: "Test",
50+
AutogenStatus: true,
51+
Fields: []Field{
52+
{
53+
ApiField: "field",
54+
},
55+
},
56+
},
57+
},
58+
{
59+
name: "selfLink",
60+
resource: api.Resource{
61+
Name: "Test",
62+
AutogenStatus: "base64",
63+
SourceYamlFile: "Test.yaml",
64+
Properties: []*api.Type{
65+
{
66+
Name: "field",
67+
ApiName: "field",
68+
},
69+
},
70+
HasSelfLink: true,
71+
},
72+
wantMetadata: Metadata{
73+
Resource: "google_product_test",
74+
GenerationType: "mmv1",
75+
SourceFile: "Test.yaml",
76+
ApiServiceName: "compute.googleapis.com",
77+
ApiVersion: "beta",
78+
ApiResourceTypeKind: "Test",
79+
AutogenStatus: true,
80+
Fields: []Field{
81+
{
82+
ApiField: "field",
83+
},
84+
{
85+
ApiField: "selfLink",
86+
},
87+
},
88+
},
89+
},
90+
}
91+
92+
for _, tc := range cases {
93+
t.Run(tc.name, func(t *testing.T) {
94+
t.Parallel()
95+
tc.resource.SetDefault(product)
96+
97+
got := FromResource(tc.resource)
98+
if diff := cmp.Diff(tc.wantMetadata, got); diff != "" {
99+
t.Errorf("FromResource() mismatch (-want +got):\n%s", diff)
100+
}
101+
})
102+
}
103+
}

mmv1/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require github.com/golang/glog v1.2.0
1111

1212
require (
1313
github.com/getkin/kin-openapi v0.127.0
14+
github.com/google/go-cmp v0.6.0
1415
github.com/otiai10/copy v1.9.0
1516
)
1617

mmv1/provider/template_data.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import (
2222
"text/template"
2323

2424
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api"
25+
"github.com/GoogleCloudPlatform/magic-modules/mmv1/api/metadata"
2526
"github.com/GoogleCloudPlatform/magic-modules/mmv1/google"
2627
"github.com/golang/glog"
28+
"gopkg.in/yaml.v3"
2729
)
2830

2931
type TemplateData struct {
@@ -73,11 +75,15 @@ func (td *TemplateData) GenerateFWResourceFile(filePath string, resource api.Res
7375
}
7476

7577
func (td *TemplateData) GenerateMetadataFile(filePath string, resource api.Resource) {
76-
templatePath := "templates/terraform/metadata.yaml.tmpl"
77-
templates := []string{
78-
templatePath,
78+
metadata := metadata.FromResource(resource)
79+
bytes, err := yaml.Marshal(metadata)
80+
if err != nil {
81+
glog.Exit("error marshalling yaml %v: %v", filePath)
82+
}
83+
err = os.WriteFile(filePath, bytes, 0644)
84+
if err != nil {
85+
glog.Exit(err)
7986
}
80-
td.GenerateFile(filePath, templatePath, resource, false, templates...)
8187
}
8288

8389
func (td *TemplateData) GenerateDataSourceFile(filePath string, resource api.Resource) {

0 commit comments

Comments
 (0)