diff --git a/tpgtools/override.go b/tpgtools/override.go index 5ce55962733c..53bd465818be 100644 --- a/tpgtools/override.go +++ b/tpgtools/override.go @@ -55,6 +55,7 @@ const ( TerraformProductName = "CUSTOM_TERRAFORM_PRODUCT_NAME" CustomTimeout = "CUSTOM_TIMEOUT" StateUpgrade = "STATE_UPGRADE" + GenerateLongFormTests = "GENERATE_LONG_FORM_TESTS" ) // Field-level Overrides diff --git a/tpgtools/property.go b/tpgtools/property.go index 62e5aa4fa51c..813b89e719ae 100644 --- a/tpgtools/property.go +++ b/tpgtools/property.go @@ -95,6 +95,11 @@ type Property struct { // the field being unset and being set to false. EnumBool bool + // Whether this field is only used as a url parameter + Parameter bool + // Whether this field has long form behavior in the DCL + HasLongForm bool + // An IdentityGetter is a function to retrieve the value of an "identity" field // from state. Identity fields will sometimes allow retrieval from multiple // fields or from the user's environment variables. @@ -210,7 +215,7 @@ func (t Type) IsSet() bool { } // Complex map is for maps of string --> object that are supported in DCL but -// not in Terraform. We handle this by adding a field in the Terraform schema +// not in Terraform. We handle this by adding a field in the Terraform schema // for the key in the map. This must be added via a COMPLEX_MAP_KEY_NAME // override func (t Type) IsComplexMap() bool { @@ -618,6 +623,9 @@ func createPropertiesFromSchema(schema *openapi.Schema, typeFetcher *TypeFetcher } } + p.Parameter, _ = v.Extension["x-dcl-parameter"].(bool) + p.HasLongForm, _ = v.Extension["x-dcl-has-long-form"].(bool) + // Handle object properties if len(v.Properties) > 0 { props, err := createPropertiesFromSchema(v, typeFetcher, overrides, resource, &p, location) @@ -691,11 +699,11 @@ func createPropertiesFromSchema(schema *openapi.Schema, typeFetcher *TypeFetcher return nil, fmt.Errorf("failed to find complex map key name for map named: %s", p.Name()) } keyProp := Property{ - title: cm.KeyName, - Type: Type{&openapi.Schema{Type: "string"}}, - resource: resource, - parent: &p, - Required: true, + title: cm.KeyName, + Type: Type{&openapi.Schema{Type: "string"}}, + resource: resource, + parent: &p, + Required: true, Description: "The name for the key in the map for which this object is mapped to in the API", } props = append([]Property{keyProp}, props...) diff --git a/tpgtools/resource.go b/tpgtools/resource.go index 5ceda2b47bb5..c7916dd303a1 100644 --- a/tpgtools/resource.go +++ b/tpgtools/resource.go @@ -178,6 +178,9 @@ type Resource struct { SchemaVersion int // The schema versions from 0 to the current schema version SchemaVersions []int + + // Whether to generate long form versions of resource sample tests + GenerateLongFormTests bool } type Link struct { @@ -711,6 +714,10 @@ func createResource(schema *openapi.Schema, info *openapi.Info, typeFetcher *Typ } } + if overrides.ResourceOverride(GenerateLongFormTests, location) { + res.GenerateLongFormTests = true + } + res.Samples = res.loadSamples() return &res, nil @@ -977,7 +984,23 @@ func (r *Resource) loadDCLSamples() []Sample { sample.IgnoreRead = append(sample.IgnoreRead, "annotations") } + if r.GenerateLongFormTests { + longFormSample := sample + longFormSample.LongForm = true + var longFormDependencies []Dependency + mainResourceLongForm := longFormSample.generateSampleDependencyWithName(primaryResource, "primary") + longFormDependencies = append(longFormDependencies, mainResourceLongForm) + for _, dFileName := range longFormSample.DependencyFileNames { + longFormDependency := sample.generateSampleDependency(dFileName) + longFormDependencies = append(longFormDependencies, longFormDependency) + } + longFormSample.DependencyList = longFormDependencies + longFormSample.TestSlug += "LongForm" + samples = append(samples, longFormSample) + } + samples = append(samples, sample) + } return samples diff --git a/tpgtools/sample.go b/tpgtools/sample.go index 057b74dfa518..c934575ea8bd 100644 --- a/tpgtools/sample.go +++ b/tpgtools/sample.go @@ -46,6 +46,9 @@ type Sample struct { // in the testcase. (if the test doesn't have a ga version of the test) HasGAEquivalent bool + // LongForm is whether this sample is a copy with long form fields expanded to include `/` + LongForm bool + // SamplesPath is the path to the directory where the original sample data is stored SamplesPath Filepath @@ -165,7 +168,7 @@ func findDCLReferencePackage(product SnakeCaseProductName) (DCLPackageName, erro } // BuildDependency produces a Dependency using a file and filename -func BuildDependency(fileName string, product SnakeCaseProductName, localname, version string, hasGAEquivalent bool, b []byte) (*Dependency, error) { +func BuildDependency(fileName string, product SnakeCaseProductName, localname, version string, hasGAEquivalent, makeLongForm bool, b []byte) (*Dependency, error) { // Miscellaneous name rather than "resource name" because this is the name in the sample json file - which might not match the TF name! // we have to account for that. var resourceName miscellaneousNameSnakeCase @@ -194,7 +197,7 @@ func BuildDependency(fileName string, product SnakeCaseProductName, localname, v return nil, fmt.Errorf("Error generating sample dependency reference %s: %s", fileName, err) } - block, err := ConvertSampleJSONToHCL(packageName, resourceName, version, hasGAEquivalent, b) + block, err := ConvertSampleJSONToHCL(packageName, resourceName, version, hasGAEquivalent, makeLongForm, b) if err != nil { glog.Errorf("failed to convert %q", fileName) return nil, fmt.Errorf("Error generating sample dependency %s: %s", fileName, err) @@ -223,7 +226,7 @@ func (s *Sample) generateSampleDependencyWithName(fileName, localname string) De dependencyBytes, err := ioutil.ReadFile(path.Join(string(s.SamplesPath), fileName)) version := s.resourceReference.versionMetadata.V product := s.resourceReference.productMetadata.ProductName - d, err := BuildDependency(fileName, product, localname, version, s.HasGAEquivalent, dependencyBytes) + d, err := BuildDependency(fileName, product, localname, version, s.HasGAEquivalent, s.LongForm, dependencyBytes) if err != nil { glog.Exit(err) } diff --git a/tpgtools/serialization.go.base b/tpgtools/serialization.go.base index 58f01e79d9ba..d29fea9ab9fd 100644 --- a/tpgtools/serialization.go.base +++ b/tpgtools/serialization.go.base @@ -8,7 +8,7 @@ func DCLToTerraformReference(product DCLPackageName, resource miscellaneousNameS return "", fmt.Errorf("unimplemented - did you run `make serialize`?") } -func ConvertSampleJSONToHCL(product DCLPackageName, resource miscellaneousNameSnakeCase, version string, hasGAEquivalent bool, b []byte) (string, error) { +func ConvertSampleJSONToHCL(product DCLPackageName, resource miscellaneousNameSnakeCase, version string, hasGAEquivalent, makeLongForm bool, b []byte) (string, error) { return "", fmt.Errorf("unimplemented - did you run `make serialize`?") } diff --git a/tpgtools/templates/serialization.go.tmpl b/tpgtools/templates/serialization.go.tmpl index faba0a38cdfc..c1c46b76f030 100644 --- a/tpgtools/templates/serialization.go.tmpl +++ b/tpgtools/templates/serialization.go.tmpl @@ -72,7 +72,7 @@ func DCLToTerraformReference(product DCLPackageName, resource miscellaneousNameS } // ConvertSampleJSONToHCL unmarshals json to an HCL string. -func ConvertSampleJSONToHCL(product DCLPackageName, resource miscellaneousNameSnakeCase, version string, hasGAEquivalent bool, b []byte) (string, error) { +func ConvertSampleJSONToHCL(product DCLPackageName, resource miscellaneousNameSnakeCase, version string, hasGAEquivalent, makeLongForm bool, b []byte) (string, error) { {{- range $version, $resList := $.Resources }} {{- if not (eq $version.V "ga") }} if version == "{{$version.V}}" { @@ -86,7 +86,7 @@ func ConvertSampleJSONToHCL(product DCLPackageName, resource miscellaneousNameSn {{- if $res.CustomSerializer }} return {{$res.CustomSerializer}}(*r, hasGAEquivalent) {{- else }} - return {{$res.TitleCaseFullName}}{{$version.SerializationSuffix}}AsHCL(*r, hasGAEquivalent) + return {{$res.TitleCaseFullName}}{{$version.SerializationSuffix}}AsHCL(*r, hasGAEquivalent, makeLongForm) {{- end }} {{- end }} } @@ -103,7 +103,7 @@ func ConvertSampleJSONToHCL(product DCLPackageName, resource miscellaneousNameSn {{- if $res.CustomSerializer }} return {{$res.CustomSerializer}}(*r, hasGAEquivalent) {{- else }} - return {{$res.TitleCaseFullName}}{{$version.SerializationSuffix}}AsHCL(*r, hasGAEquivalent) + return {{$res.TitleCaseFullName}}{{$version.SerializationSuffix}}AsHCL(*r, hasGAEquivalent, makeLongForm) {{- end }} {{- end }} default: @@ -123,7 +123,7 @@ func ConvertSampleJSONToHCL(product DCLPackageName, resource miscellaneousNameSn // the crucial point is that `terraform import; terraform apply` will not produce // any changes. We do not validate that the resource specified will pass terraform // validation unless is an object returned from the API after an Apply. -func {{ $res.TitleCaseFullName }}{{$version.SerializationSuffix}}AsHCL(r {{$res.Package}}{{$version.SerializationSuffix}}.{{$res.DCLStructName}}, hasGAEquivalent bool) (string, error) { +func {{ $res.TitleCaseFullName }}{{$version.SerializationSuffix}}AsHCL(r {{$res.Package}}{{$version.SerializationSuffix}}.{{$res.DCLStructName}}, hasGAEquivalent, makeLongForm bool) (string, error) { outputConfig := "resource \"{{$res.TerraformName}}\" \"output\" {\n" {{- range $field := $res.Properties}} {{- if $field.ShouldShowUpInSamples }} @@ -138,7 +138,15 @@ func {{ $res.TitleCaseFullName }}{{$version.SerializationSuffix}}AsHCL(r {{$res. } {{- else }} if r.{{$field.PackageName}} != nil { + {{- if or $field.Parameter $field.HasLongForm }} + if makeLongForm { + outputConfig += fmt.Sprintf("\t{{$field.Name}} = %#v\n", "long/form/" + *r.{{$field.PackageName}}) + } else { + outputConfig += fmt.Sprintf("\t{{$field.Name}} = %#v\n", *r.{{$field.PackageName}}) + } + {{- else }} outputConfig += fmt.Sprintf("\t{{$field.Name}} = %#v\n", *r.{{$field.PackageName}}) + {{- end }} } {{- end}} {{- else if $field.Type.IsObject }}