diff --git a/mmv1/api/resource.go b/mmv1/api/resource.go index b7a19a4df776..caa727f8b570 100644 --- a/mmv1/api/resource.go +++ b/mmv1/api/resource.go @@ -456,123 +456,145 @@ func (r Resource) GetIdentity() []*Type { } -// TODO Q1 // def add_labels_related_fields(props, parent) -// props.each do |p| -// if p.is_a? Api::Type::KeyValueLabels -// add_labels_fields(props, parent, p) -// elsif p.is_a? Api::Type::KeyValueAnnotations -// add_annotations_fields(props, parent, p) -// elsif (p.is_a? Api::Type::NestedObject) && !p.all_properties.nil? -// p.properties = add_labels_related_fields(p.all_properties, p) -// end -// end -// props -// end +func (r *Resource) AddLabelsRelatedFields(props []*Type, parent *Type) []*Type { + for _, p := range props { + if p.IsA("KeyValueLabels") { + props = r.addLabelsFields(props, parent, p) + } else if p.IsA("KeyValueAnnotations") { + props = r.addAnnotationsFields(props, parent, p) + } else if p.IsA("NestedObject") && len(p.AllProperties()) > 0 { + p.Properties = r.AddLabelsRelatedFields(p.AllProperties(), p) + } + } + return props +} // def add_labels_fields(props, parent, labels) -// @custom_diff ||= [] -// if parent.nil? || parent.flatten_object -// @custom_diff.append('tpgresource.SetLabelsDiff') -// elsif parent.name == 'metadata' -// @custom_diff.append('tpgresource.SetMetadataLabelsDiff') -// end +func (r *Resource) addLabelsFields(props []*Type, parent *Type, labels *Type) []*Type { + if parent == nil || parent.FlattenObject { + r.CustomDiff = append(r.CustomDiff, "tpgresource.SetLabelsDiff") + } else if parent.Name == "metadata" { + r.CustomDiff = append(r.CustomDiff, "tpgresource.SetMetadataLabelsDiff") + } -// props << build_terraform_labels_field('labels', parent, labels) -// props << build_effective_labels_field('labels', labels) + terraformLabelsField := buildTerraformLabelsField("labels", parent, labels) + effectiveLabelsField := buildEffectiveLabelsField("labels", labels) + props = append(props, terraformLabelsField, effectiveLabelsField) -// // The effective_labels field is used to write to API, instead of the labels field. -// labels.ignore_write = true -// labels.description = "//{labels.description}\n\n//{get_labels_field_note(labels.name)}" -// return unless parent.nil? + // The effective_labels field is used to write to API, instead of the labels field. + labels.IgnoreWrite = true + labels.Description = fmt.Sprintf("%s\n\n%s", labels.Description, getLabelsFieldNote(labels.Name)) -// labels.immutable = false -// end + if parent == nil { + labels.Immutable = false + } + + return props +} // def add_annotations_fields(props, parent, annotations) -// // The effective_annotations field is used to write to API, -// // instead of the annotations field. -// annotations.ignore_write = true -// note = get_labels_field_note(annotations.name) -// annotations.description = "//{annotations.description}\n\n//{note}" - -// @custom_diff ||= [] -// if parent.nil? -// @custom_diff.append('tpgresource.SetAnnotationsDiff') -// elsif parent.name == 'metadata' -// @custom_diff.append('tpgresource.SetMetadataAnnotationsDiff') -// end - -// props << build_effective_labels_field('annotations', annotations) -// end +func (r *Resource) addAnnotationsFields(props []*Type, parent *Type, annotations *Type) []*Type { + + // The effective_annotations field is used to write to API, + // instead of the annotations field. + annotations.IgnoreWrite = true + annotations.Description = fmt.Sprintf("%s\n\n%s", annotations.Description, getLabelsFieldNote(annotations.Name)) + + if parent == nil { + r.CustomDiff = append(r.CustomDiff, "tpgresource.SetAnnotationsDiff") + } else if parent.Name == "metadata" { + r.CustomDiff = append(r.CustomDiff, "tpgresource.SetMetadataAnnotationsDiff") + } + + effectiveAnnotationsField := buildEffectiveLabelsField("annotations", annotations) + props = append(props, effectiveAnnotationsField) + return props +} // def build_effective_labels_field(name, labels) -// description = "All of //{name} (key/value pairs)\ -// present on the resource in GCP, including the //{name} configured through Terraform,\ -// other clients and services." - -// Api::Type::KeyValueEffectiveLabels.new( -// name: "effective//{name.capitalize}", -// output: true, -// api_name: name, -// description:, -// min_version: labels.field_min_version, -// update_verb: labels.update_verb, -// update_url: labels.update_url, -// immutable: labels.immutable -// ) -// end +func buildEffectiveLabelsField(name string, labels *Type) *Type { + description := fmt.Sprintf("All of %s (key/value pairs) present on the resource in GCP, "+ + "including the %s configured through Terraform, other clients and services.", name, name) + + t := "KeyValueEffectiveLabels" + if name == "annotations" { + t = "KeyValueEffectiveAnnotations" + } + + n := fmt.Sprintf("effective%s", strings.Title(name)) + + options := []func(*Type){ + propertyWithType(t), + propertyWithOutput(true), + propertyWithDescription(description), + propertyWithMinVersion(labels.fieldMinVersion()), + propertyWithUpdateVerb(labels.UpdateVerb), + propertyWithUpdateUrl(labels.UpdateUrl), + propertyWithImmutable(labels.Immutable), + } + return NewProperty(n, name, options) +} // def build_terraform_labels_field(name, parent, labels) -// description = "The combination of //{name} configured directly on the resource -// and default //{name} configured on the provider." - -// immutable = if parent.nil? -// false -// else -// labels.immutable -// end - -// Api::Type::KeyValueTerraformLabels.new( -// name: "terraform//{name.capitalize}", -// output: true, -// api_name: name, -// description:, -// min_version: labels.field_min_version, -// ignore_write: true, -// update_url: labels.update_url, -// immutable: -// ) -// end +func buildTerraformLabelsField(name string, parent *Type, labels *Type) *Type { + description := fmt.Sprintf("The combination of %s configured directly on the resource "+ + "and default %s configured on the provider.", name, name) + + immutable := false + if parent != nil { + immutable = labels.Immutable + } + + n := fmt.Sprintf("terraform%s", strings.Title(name)) + + options := []func(*Type){ + propertyWithType("KeyValueTerraformLabels"), + propertyWithOutput(true), + propertyWithDescription(description), + propertyWithMinVersion(labels.fieldMinVersion()), + propertyWithIgnoreWrite(true), + propertyWithUpdateUrl(labels.UpdateUrl), + propertyWithImmutable(immutable), + } + return NewProperty(n, name, options) +} // // Check if the resource has root "labels" field // def root_labels? -// root_properties.each do |p| -// return true if p.is_a? Api::Type::KeyValueLabels -// end -// false -// end +func (r Resource) RootLabels() bool { + for _, p := range r.RootProperties() { + if p.IsA("KeyValueLabels") { + return true + } + } + return false +} // // Return labels fields that should be added to ImportStateVerifyIgnore // def ignore_read_labels_fields(props) -// fields = [] -// props.each do |p| -// if (p.is_a? Api::Type::KeyValueLabels) || -// (p.is_a? Api::Type::KeyValueTerraformLabels) || -// (p.is_a? Api::Type::KeyValueAnnotations) -// fields << p.terraform_lineage -// elsif (p.is_a? Api::Type::NestedObject) && !p.all_properties.nil? -// fields.concat(ignore_read_labels_fields(p.all_properties)) -// end -// end -// fields -// end +func (r Resource) IgnoreReadLabelsFields(props []*Type) []string { + fields := make([]string, 0) + for _, p := range props { + if p.IsA("KeyValueLabels") || + p.IsA("KeyValueTerraformLabels") || + p.IsA("KeyValueAnnotations") { + fields = append(fields, p.TerraformLineage()) + } else if p.IsA("NestedObject") && len(p.AllProperties()) > 0 { + fields = google.Concat(fields, r.IgnoreReadLabelsFields(p.AllProperties())) + } + } + return fields +} // def get_labels_field_note(title) -// "**Note**: This field is non-authoritative, and will only manage the //{title} present " \ -// "in your configuration. -// Please refer to the field `effective_//{title}` for all of the //{title} present on the resource." -// end +func getLabelsFieldNote(title string) string { + return fmt.Sprintf( + "**Note**: This field is non-authoritative, and will only manage the %s present "+ + "in your configuration.\n"+ + "Please refer to the field `effective_%s` for all of the %s present on the resource.", + title, title, title) +} // ==================== // Version-related methods diff --git a/mmv1/api/type.go b/mmv1/api/type.go index b3923f1181fc..5c87e68a1e9f 100644 --- a/mmv1/api/type.go +++ b/mmv1/api/type.go @@ -21,10 +21,6 @@ import ( "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" ) -// require 'api/object' -// require 'google/string_utils' -// require 'provider/terraform/validation' - // Represents a property type type Type struct { NamedObject `yaml:",inline"` @@ -211,6 +207,11 @@ type Type struct { // the field in documentation. KeyDescription string `yaml:"key_description` + // ==================== + // KeyValuePairs Fields + // ==================== + IgnoreWrite bool `yaml:"ignore_write` + // ==================== // Schema Modifications // ==================== @@ -878,10 +879,73 @@ func (t *Type) RootProperties() []*Type { // end // end -// // An array of string -> string key -> value pairs, such as labels. -// // While this is technically a map, it's split out because it's a much -// // simpler property to generate and means we can avoid conditional logic -// // in Map. +// An array of string -> string key -> value pairs, such as labels. +// While this is technically a map, it's split out because it's a much +// simpler property to generate and means we can avoid conditional logic +// in Map. + +func NewProperty(name, apiName string, options []func(*Type)) *Type { + p := &Type{ + NamedObject: NamedObject{ + Name: name, + ApiName: apiName, + }, + } + + for _, option := range options { + option(p) + } + return p +} + +func propertyWithType(t string) func(*Type) { + return func(p *Type) { + p.Type = t + } +} + +func propertyWithOutput(output bool) func(*Type) { + return func(p *Type) { + p.Output = output + } +} + +func propertyWithDescription(description string) func(*Type) { + return func(p *Type) { + p.Description = description + } +} + +func propertyWithMinVersion(minVersion string) func(*Type) { + return func(p *Type) { + p.MinVersion = minVersion + } +} + +func propertyWithUpdateVerb(updateVerb string) func(*Type) { + return func(p *Type) { + p.UpdateVerb = updateVerb + } +} + +func propertyWithUpdateUrl(updateUrl string) func(*Type) { + return func(p *Type) { + p.UpdateUrl = updateUrl + } +} + +func propertyWithImmutable(immutable bool) func(*Type) { + return func(p *Type) { + p.Immutable = immutable + } +} + +func propertyWithIgnoreWrite(ignoreWrite bool) func(*Type) { + return func(p *Type) { + p.IgnoreWrite = ignoreWrite + } +} + // class KeyValuePairs < Composite // // Ignore writing the "effective_labels" and "effective_annotations" fields to API. // ignore_write @@ -951,10 +1015,10 @@ func (t *Type) RootProperties() []*Type { // end // end -// func (t *Type) field_min_version -// @min_version -// end -// end +// def field_min_version +func (t Type) fieldMinVersion() string { + return t.MinVersion +} // // An array of string -> string key -> value pairs used specifically for the "labels" field. // // The field name with this type should be "labels" literally. diff --git a/mmv1/main.go b/mmv1/main.go index 11f04126cc44..1a2f570ba6a9 100644 --- a/mmv1/main.go +++ b/mmv1/main.go @@ -25,6 +25,8 @@ var outputPath = flag.String("output", "", "path to output generated files to") // Example usage: --version beta var version = flag.String("version", "", "optional version name. If specified, this version is preferred for resource generation when applicable") +var product = flag.String("product", "", "optional product name. If specified, the resources under the specific product will be generated. Otherwise, resources under all products will be generated.") + func main() { flag.Parse() var generateCode = true @@ -38,11 +40,14 @@ func main() { log.Fatalf("No version specified") } - // TODO Q1: allow specifying one product (flag or hardcoded) - // var productsToGenerate []string - // var allProducts = true - var productsToGenerate = []string{"products/datafusion"} + var productsToGenerate []string var allProducts = false + if product == nil || *product == "" { + allProducts = true + } else { + var productToGenerate = fmt.Sprintf("products/%s", *product) + productsToGenerate = []string{productToGenerate} + } var allProductFiles []string = make([]string, 0) @@ -90,9 +95,6 @@ func main() { // TODO Q2: product overrides if _, err := os.Stat(productYamlPath); err == nil { - // TODO Q1: remove these lines, which are for debugging - // log.Printf("productYamlPath %#v", productYamlPath) - var resources []*api.Resource = make([]*api.Resource, 0) productYaml, err := os.ReadFile(productYamlPath) @@ -102,10 +104,6 @@ func main() { productApi := &api.Product{} yamlValidator.Parse(productYaml, productApi) - // TODO Q1: remove these lines, which are for debugging - // prod, _ := json.Marshal(productApi) - // log.Printf("prod %s", string(prod)) - if !productApi.ExistsAtVersionOrLower(*version) { log.Printf("%s does not have a '%s' version, skipping", productName, *version) continue @@ -125,8 +123,6 @@ func main() { continue } - // TODO Q1: remove these lines, which are for debugging - // log.Printf(" resourceYamlPath %s", resourceYamlPath) resourceYaml, err := os.ReadFile(resourceYamlPath) if err != nil { log.Fatalf("Cannot open the file: %v", resourceYamlPath) @@ -134,11 +130,7 @@ func main() { resource := &api.Resource{} yamlValidator.Parse(resourceYaml, resource) - // TODO Q1: remove these lines, which are for debugging - // res, _ := json.Marshal(resource) - // log.Printf("resource %s", string(res)) - - // TODO Q1: add labels related fields + resource.Properties = resource.AddLabelsRelatedFields(resource.PropertiesWithExcluded(), nil) resource.Validate() resources = append(resources, resource) diff --git a/mmv1/products/datafusion/go_instance.yaml b/mmv1/products/datafusion/go_instance.yaml index 838891f4643b..25594fab1649 100644 --- a/mmv1/products/datafusion/go_instance.yaml +++ b/mmv1/products/datafusion/go_instance.yaml @@ -142,11 +142,7 @@ pipelines at low cost." - name: 'labels' type: KeyValueLabels description: "The resource labels for instance to use to annotate any related underlying resources, -such as Compute Engine VMs. - - -**Note**: This field is non-authoritative, and will only manage the labels present in your configuration. -Please refer to the field `effective_labels` for all of the labels present on the resource." +such as Compute Engine VMs." immutable: false - name: 'options' type: KeyValuePairs