diff --git a/mmv1/api/compiler.go b/mmv1/api/compiler.go index 00a2bd136d14..b62aab20f7e3 100644 --- a/mmv1/api/compiler.go +++ b/mmv1/api/compiler.go @@ -27,5 +27,5 @@ func Compile(yamlPath string, obj interface{}) { } yamlValidator := google.YamlValidator{} - yamlValidator.Parse(objYaml, obj) + yamlValidator.Parse(objYaml, obj, yamlPath) } diff --git a/mmv1/api/resource.rb b/mmv1/api/resource.rb index b4c6eea2515d..de04aafe0e83 100644 --- a/mmv1/api/resource.rb +++ b/mmv1/api/resource.rb @@ -393,6 +393,12 @@ def all_nested_properties(props) nested end + def convert_go_file(file) + dir, base = File.split(file) + base.slice! '.erb' + "#{dir}/go/#{base}.tmpl" + end + # All settable properties in the resource. # Fingerprints aren't *really" settable properties, but they behave like one. # At Create, they have no value but they can just be read in anyways, and after a Read diff --git a/mmv1/api/type.rb b/mmv1/api/type.rb index dd66b3d8a800..9e78817fcabc 100644 --- a/mmv1/api/type.rb +++ b/mmv1/api/type.rb @@ -583,7 +583,9 @@ def nested_properties def item_type_class return @item_type \ - if @item_type.instance_of?(Class) + if @item_type.instance_of?(Class) \ + || @item_type.is_a?(Api::Type::ResourceRef) \ + || @item_type.is_a?(Api::Type::Enum) Object.const_get(@item_type) end diff --git a/mmv1/compiler.rb b/mmv1/compiler.rb index 0f61a8eb4ccc..43211deb1a8e 100755 --- a/mmv1/compiler.rb +++ b/mmv1/compiler.rb @@ -36,6 +36,7 @@ products_to_generate = nil all_products = false yaml_dump = false +go_yaml = false generate_code = true generate_docs = true output_path = nil @@ -94,6 +95,9 @@ opt.on('--openapi-generate', 'Generate MMv1 YAML from openapi directory (Experimental)') do openapi_generate = true end + opt.on('--go-yaml', 'Generate MMv1 Go YAML from Ruby YAML') do + go_yaml = true + end end.parse! # rubocop:enable Metrics/BlockLength @@ -270,7 +274,8 @@ product_name, yaml_dump, generate_code, - generate_docs + generate_docs, + go_yaml ) # we need to preserve a single provider instance to use outside of this loop. diff --git a/mmv1/description-copy.go b/mmv1/description-copy.go new file mode 100644 index 000000000000..5e16c0758e4d --- /dev/null +++ b/mmv1/description-copy.go @@ -0,0 +1,147 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "path/filepath" + "regexp" + "strings" +) + +// Used to copy/paste text from Ruby -> Go YAML files +func CopyText(identifier string) { + var allProductFiles []string = make([]string, 0) + files, err := filepath.Glob("products/**/go_product.yaml") + if err != nil { + return + } + for _, filePath := range files { + dir := filepath.Dir(filePath) + allProductFiles = append(allProductFiles, fmt.Sprintf("products/%s", filepath.Base(dir))) + } + + for _, productPath := range allProductFiles { + // Gather go and ruby file pairs + yamlMap := make(map[string][]string) + yamlPaths, err := filepath.Glob(fmt.Sprintf("%s/*", productPath)) + if err != nil { + log.Fatalf("Cannot get yaml files: %v", err) + } + for _, yamlPath := range yamlPaths { + if strings.HasSuffix(yamlPath, "_new") { + continue + } + fileName := filepath.Base(yamlPath) + baseName, found := strings.CutPrefix(fileName, "go_") + if yamlMap[baseName] == nil { + yamlMap[baseName] = make([]string, 2) + } + if found { + yamlMap[baseName][1] = yamlPath + } else { + yamlMap[baseName][0] = yamlPath + } + } + + for _, files := range yamlMap { + rubyPath := files[0] + goPath := files[1] + var text []string + currText := "" + recording := false + + if strings.Contains(rubyPath, "product.yaml") { + // log.Printf("skipping %s", rubyPath) + continue + } + + // Ready Ruby yaml + file, _ := os.Open(rubyPath) + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, identifier) && !strings.HasPrefix(strings.TrimSpace(line), "#") { + currText = strings.SplitAfter(line, identifier)[1] + recording = true + } else if recording { + if terminateText(line) { + text = append(text, currText) + currText = "" + recording = false + } else { + currText = fmt.Sprintf("%s\n%s", currText, line) + } + } + } + if recording { + text = append(text, currText) + } + + // Read Go yaml while writing to a temp file + index := 0 + firstLine := true + newFilePath := fmt.Sprintf("%s_new", goPath) + fo, _ := os.Create(newFilePath) + w := bufio.NewWriter(fo) + file, _ = os.Open(goPath) + defer file.Close() + scanner = bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if firstLine { + if line != "NOT CONVERTED - RUN YAML MODE" { + // log.Printf("skipping %s", goPath) + break + } else { + firstLine = false + continue + } + } + if strings.Contains(line, identifier) { + if index >= len(text) { + log.Printf("did not replace %s correctly! Is the file named correctly?", goPath) + w.Flush() + break + } + line = fmt.Sprintf("%s%s", line, text[index]) + index += 1 + } + w.WriteString(fmt.Sprintf("%s\n", line)) + } + + if !firstLine { + if index != len(text) { + log.Printf("potential issue with %s, only completed %d index out of %d replacements", goPath, index, len(text)) + } + if err = w.Flush(); err != nil { + panic(err) + } + + // Overwrite original file with temp + os.Rename(newFilePath, goPath) + } else { + os.Remove(newFilePath) + } + } + + } + +} + +// quick and dirty logic to determine if a description/note is terminated +func terminateText(line string) bool { + terminalStrings := []string{ + "!ruby/", + } + + for _, t := range terminalStrings { + if strings.Contains(line, t) { + return true + } + } + + return regexp.MustCompile(`^\s*[a-z_]+:[\s$]*`).MatchString(line) +} diff --git a/mmv1/google/yaml_validator.go b/mmv1/google/yaml_validator.go index 4986e21e8e94..485db94d1dd0 100644 --- a/mmv1/google/yaml_validator.go +++ b/mmv1/google/yaml_validator.go @@ -22,12 +22,12 @@ import ( // A helper class to validate contents coming from YAML files. type YamlValidator struct{} -func (v *YamlValidator) Parse(content []byte, obj interface{}) { +func (v *YamlValidator) Parse(content []byte, obj interface{}, yamlPath string) { // TODO(nelsonjr): Allow specifying which symbols to restrict it further. // But it requires inspecting all configuration files for symbol sources, // such as Enum values. Leaving it as a nice-to-have for the future. if err := yaml.Unmarshal(content, obj); err != nil { - log.Fatalf("Cannot unmarshal data: %v", err) + log.Fatalf("Cannot unmarshal data from file %s: %v", yamlPath, err) } } diff --git a/mmv1/main.go b/mmv1/main.go index 59088cf3bb2d..24795b8ddaf3 100644 --- a/mmv1/main.go +++ b/mmv1/main.go @@ -27,8 +27,18 @@ var version = flag.String("version", "", "optional version name. If specified, t 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.") +// Example usage: --yaml +var yamlMode = flag.Bool("yaml", false, "strictly copy text over from ruby yaml to go yaml") + func main() { flag.Parse() + + if *yamlMode { + CopyText("description:") + CopyText("note:") + return + } + var generateCode = true var generateDocs = true diff --git a/mmv1/products/compute/ForwardingRule.yaml b/mmv1/products/compute/ForwardingRule.yaml index d0258cfdf70a..e2cfde7e7d5d 100644 --- a/mmv1/products/compute/ForwardingRule.yaml +++ b/mmv1/products/compute/ForwardingRule.yaml @@ -220,6 +220,12 @@ custom_code: !ruby/object:Provider::Terraform::CustomCode custom_diff: [ 'forwardingRuleCustomizeDiff', ] +virtual_fields: + - !ruby/object:Api::Type::Boolean + name: recreate_closed_psc + description: + This is used in PSC consumer ForwardingRule to make terraform recreate the ForwardingRule when the status is closed + default_value: false parameters: - !ruby/object:Api::Type::ResourceRef name: 'region' @@ -656,9 +662,3 @@ properties: - :IPV6 immutable: true default_from_api: true -virtual_fields: - - !ruby/object:Api::Type::Boolean - name: recreate_closed_psc - description: - This is used in PSC consumer ForwardingRule to make terraform recreate the ForwardingRule when the status is closed - default_value: false diff --git a/mmv1/products/compute/HaVpnGateway.yaml b/mmv1/products/compute/HaVpnGateway.yaml new file mode 100644 index 000000000000..a0435e33e39f --- /dev/null +++ b/mmv1/products/compute/HaVpnGateway.yaml @@ -0,0 +1,167 @@ +# Copyright 2023 Google Inc. +# 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. + +--- !ruby/object:Api::Resource +name: 'HaVpnGateway' +kind: 'compute#vpnGateway' +base_url: projects/{{project}}/regions/{{region}}/vpnGateways +collection_url_key: 'items' +immutable: true +has_self_link: true +description: | + Represents a VPN gateway running in GCP. This virtual device is managed + by Google, but used only by you. This type of VPN Gateway allows for the creation + of VPN solutions with higher availability than classic Target VPN Gateways. +references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Choosing a VPN': https://cloud.google.com/vpn/docs/how-to/choosing-a-vpn + 'Cloud VPN Overview': 'https://cloud.google.com/vpn/docs/concepts/overview' + api: https://cloud.google.com/compute/docs/reference/rest/v1/vpnGateways +async: !ruby/object:Api::OpAsync + operation: !ruby/object:Api::OpAsync::Operation + kind: 'compute#operation' + path: 'name' + base_url: 'projects/{{project}}/regions/{{region}}/operations/{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::OpAsync::Result + path: 'targetLink' + status: !ruby/object:Api::OpAsync::Status + path: 'status' + complete: 'DONE' + allowed: + - 'PENDING' + - 'RUNNING' + - 'DONE' + error: !ruby/object:Api::OpAsync::Error + path: 'error/errors' + message: 'message' +examples: + - !ruby/object:Provider::Terraform::Examples + name: 'ha_vpn_gateway_basic' + primary_resource_id: 'ha_gateway1' + vars: + ha_vpn_gateway1_name: 'ha-vpn-1' + network1_name: 'network1' + - !ruby/object:Provider::Terraform::Examples + name: 'ha_vpn_gateway_ipv6' + primary_resource_id: 'ha_gateway1' + vars: + ha_vpn_gateway1_name: 'ha-vpn-1' + network1_name: 'network1' + - !ruby/object:Provider::Terraform::Examples + name: 'ha_vpn_gateway_gcp_to_gcp' + primary_resource_id: + 'ha_gateway1' + # Multiple fine-grained resources + skip_vcr: true + skip_test: true + skip_docs: true + vars: + ha_vpn_gateway1_name: 'ha-vpn-1' + network1_name: 'network1' + router1_name: 'ha-vpn-router1' + ha_vpn_gateway2_name: 'ha-vpn-2' + network2_name: 'network2' + router2_name: 'ha-vpn-router2' + - !ruby/object:Provider::Terraform::Examples + name: 'compute_ha_vpn_gateway_encrypted_interconnect' + primary_resource_id: + 'vpn-gateway' + # TODO: https://github.com/hashicorp/terraform-provider-google/issues/11504 + skip_test: true + vars: + ha_vpn_gateway_name: 'test-ha-vpngw' + interconnect_attachment1_name: 'test-interconnect-attachment1' + interconnect_attachment2_name: 'test-interconnect-attachment2' + address1_name: 'test-address1' + address2_name: 'test-address2' + router_name: 'test-router' + network_name: 'test-network' +parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'name' + description: | + The region this gateway should sit in. + required: false + default_from_api: true + custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.erb' + custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb' +properties: + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + immutable: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and + match the regular expression `[a-z]([-a-z0-9]*[a-z0-9])?` which means + the first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + required: true + immutable: true + validation: !ruby/object:Provider::Terraform::Validation + function: 'verify.ValidateGCEName' + - !ruby/object:Api::Type::ResourceRef + name: 'network' + resource: 'Network' + imports: 'selfLink' + description: | + The network this VPN gateway is accepting traffic for. + required: true + immutable: true + custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb' + - !ruby/object:Api::Type::Enum + name: 'stackType' + description: | + The stack type for this VPN gateway to identify the IP protocols that are enabled. + If not specified, IPV4_ONLY will be used. + default_value: :IPV4_ONLY + values: + - :IPV4_ONLY + - :IPV4_IPV6 + immutable: true + custom_flatten: 'templates/terraform/custom_flatten/default_if_empty.erb' + - !ruby/object:Api::Type::Array + name: 'vpnInterfaces' + description: | + A list of interfaces on this VPN gateway. + default_from_api: true + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The numeric ID of this VPN gateway interface.' + - !ruby/object:Api::Type::String + name: 'ipAddress' + description: 'The external IP address for this VPN gateway interface.' + output: true + - !ruby/object:Api::Type::ResourceRef + name: 'interconnectAttachment' + resource: 'InterconnectAttachment' + imports: 'selfLink' + description: | + URL of the interconnect attachment resource. When the value + of this field is present, the VPN Gateway will be used for + IPsec-encrypted Cloud Interconnect; all Egress or Ingress + traffic for this VPN Gateway interface will go through the + specified interconnect attachment resource. + + Not currently available publicly. + custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb' + immutable: true diff --git a/mmv1/products/compute/InstanceGroup.yaml b/mmv1/products/compute/InstanceGroup.yaml index 933fc5894f6d..e0f5cd7f1126 100644 --- a/mmv1/products/compute/InstanceGroup.yaml +++ b/mmv1/products/compute/InstanceGroup.yaml @@ -50,26 +50,6 @@ parameters: description: 'A reference to the zone where the instance group resides.' required: true custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb' - - !ruby/object:Api::Type::Array - name: 'instances' - description: | - The list of instances associated with this InstanceGroup. - All instances must be created before being added to an InstanceGroup. - All instances not in this list will be removed from the InstanceGroup - and will not be deleted. - Only the full identifier of the instance will be returned. - - !> **WARNING** If a user will be recreating instances under the same name - (eg. via `terraform taint`), please consider adding instances to an instance - group via the `instance_group_membership` resource, along side the - `replace_triggered_by` lifecycle method with an instance's ID. - exclude: true - item_type: !ruby/object:Api::Type::ResourceRef - name: 'instance' - description: 'An instance being added to the InstanceGroup' - resource: 'Instance' - imports: 'selfLink' - custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb' properties: - !ruby/object:Api::Type::Time name: 'creationTimestamp' diff --git a/mmv1/products/compute/Interconnect.yaml b/mmv1/products/compute/Interconnect.yaml index 4ccbbf8062d3..1a242cbe5e4b 100644 --- a/mmv1/products/compute/Interconnect.yaml +++ b/mmv1/products/compute/Interconnect.yaml @@ -146,7 +146,7 @@ properties: - !ruby/object:Api::Type::Enum name: 'operationalStatus' description: | - The current status of this Interconnect's functionality, which can take one of the following values: + The current status of this Interconnect's functionality, which can take one of the following: - OS_ACTIVE: A valid Interconnect, which is turned up and is ready to use. Attachments may be provisioned on this Interconnect. - OS_UNPROVISIONED: An Interconnect that has not completed turnup. No attachments may be diff --git a/mmv1/products/compute/RegionGroupInstanceManager.yaml b/mmv1/products/compute/RegionInstanceGroupManager.yaml similarity index 100% rename from mmv1/products/compute/RegionGroupInstanceManager.yaml rename to mmv1/products/compute/RegionInstanceGroupManager.yaml diff --git a/mmv1/products/compute/TargetVpnGateway.yaml b/mmv1/products/compute/TargetVpnGateway.yaml deleted file mode 100644 index 7087bb6be486..000000000000 --- a/mmv1/products/compute/TargetVpnGateway.yaml +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2023 Google Inc. -# 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. - ---- !ruby/object:Api::Resource -name: 'VpnGateway' -kind: 'compute#targetVpnGateway' -base_url: projects/{{project}}/regions/{{region}}/targetVpnGateways -collection_url_key: 'items' -immutable: true -has_self_link: true -description: | - Represents a VPN gateway running in GCP. This virtual device is managed - by Google, but used only by you. -references: !ruby/object:Api::Resource::ReferenceLinks - api: https://cloud.google.com/compute/docs/reference/rest/v1/targetVpnGateways -async: !ruby/object:Api::OpAsync - operation: !ruby/object:Api::OpAsync::Operation - kind: 'compute#operation' - path: 'name' - base_url: 'projects/{{project}}/regions/{{region}}/operations/{{op_id}}' - wait_ms: 1000 - result: !ruby/object:Api::OpAsync::Result - path: 'targetLink' - status: !ruby/object:Api::OpAsync::Status - path: 'status' - complete: 'DONE' - allowed: - - 'PENDING' - - 'RUNNING' - - 'DONE' - error: !ruby/object:Api::OpAsync::Error - path: 'error/errors' - message: 'message' -docs: !ruby/object:Provider::Terraform::Docs - warning: | - Classic VPN is deprecating certain functionality on October 31, 2021. For more information, - see the [Classic VPN partial deprecation page](https://cloud.google.com/network-connectivity/docs/vpn/deprecations/classic-vpn-deprecation). -examples: - - !ruby/object:Provider::Terraform::Examples - name: 'target_vpn_gateway_basic' - primary_resource_id: 'target_gateway' - vars: - target_vpn_gateway_name: 'vpn-1' - network_name: 'network-1' - address_name: 'vpn-static-ip' - esp_forwarding_rule_name: 'fr-esp' - udp500_forwarding_rule_name: 'fr-udp500' - udp4500_forwarding_rule_name: 'fr-udp4500' - vpn_tunnel_name: 'tunnel1' - route_name: 'route1' -parameters: - - !ruby/object:Api::Type::ResourceRef - name: 'region' - resource: 'Region' - imports: 'name' - description: | - The region this gateway should sit in. - required: false - default_from_api: true - custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.erb' - custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb' -properties: - - !ruby/object:Api::Type::Time - name: 'creationTimestamp' - description: 'Creation timestamp in RFC3339 text format.' - output: true - - !ruby/object:Api::Type::String - name: 'description' - description: 'An optional description of this resource.' - immutable: true - - !ruby/object:Api::Type::String - name: 'name' - description: | - Name of the resource. Provided by the client when the resource is - created. The name must be 1-63 characters long, and comply with - RFC1035. Specifically, the name must be 1-63 characters long and - match the regular expression `[a-z]([-a-z0-9]*[a-z0-9])?` which means - the first character must be a lowercase letter, and all following - characters must be a dash, lowercase letter, or digit, except the last - character, which cannot be a dash. - required: true - immutable: true - - !ruby/object:Api::Type::Integer - name: 'gateway_id' - api_name: 'id' - description: 'The unique identifier for the resource.' - output: true - - !ruby/object:Api::Type::ResourceRef - name: 'network' - resource: 'Network' - imports: 'selfLink' - description: | - The network this VPN gateway is accepting traffic for. - required: true - custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb' - immutable: true diff --git a/mmv1/products/compute/VpnGateway.yaml b/mmv1/products/compute/VpnGateway.yaml index a0435e33e39f..7087bb6be486 100644 --- a/mmv1/products/compute/VpnGateway.yaml +++ b/mmv1/products/compute/VpnGateway.yaml @@ -12,21 +12,17 @@ # limitations under the License. --- !ruby/object:Api::Resource -name: 'HaVpnGateway' -kind: 'compute#vpnGateway' -base_url: projects/{{project}}/regions/{{region}}/vpnGateways +name: 'VpnGateway' +kind: 'compute#targetVpnGateway' +base_url: projects/{{project}}/regions/{{region}}/targetVpnGateways collection_url_key: 'items' immutable: true has_self_link: true description: | Represents a VPN gateway running in GCP. This virtual device is managed - by Google, but used only by you. This type of VPN Gateway allows for the creation - of VPN solutions with higher availability than classic Target VPN Gateways. + by Google, but used only by you. references: !ruby/object:Api::Resource::ReferenceLinks - guides: - 'Choosing a VPN': https://cloud.google.com/vpn/docs/how-to/choosing-a-vpn - 'Cloud VPN Overview': 'https://cloud.google.com/vpn/docs/concepts/overview' - api: https://cloud.google.com/compute/docs/reference/rest/v1/vpnGateways + api: https://cloud.google.com/compute/docs/reference/rest/v1/targetVpnGateways async: !ruby/object:Api::OpAsync operation: !ruby/object:Api::OpAsync::Operation kind: 'compute#operation' @@ -45,48 +41,23 @@ async: !ruby/object:Api::OpAsync error: !ruby/object:Api::OpAsync::Error path: 'error/errors' message: 'message' +docs: !ruby/object:Provider::Terraform::Docs + warning: | + Classic VPN is deprecating certain functionality on October 31, 2021. For more information, + see the [Classic VPN partial deprecation page](https://cloud.google.com/network-connectivity/docs/vpn/deprecations/classic-vpn-deprecation). examples: - !ruby/object:Provider::Terraform::Examples - name: 'ha_vpn_gateway_basic' - primary_resource_id: 'ha_gateway1' + name: 'target_vpn_gateway_basic' + primary_resource_id: 'target_gateway' vars: - ha_vpn_gateway1_name: 'ha-vpn-1' - network1_name: 'network1' - - !ruby/object:Provider::Terraform::Examples - name: 'ha_vpn_gateway_ipv6' - primary_resource_id: 'ha_gateway1' - vars: - ha_vpn_gateway1_name: 'ha-vpn-1' - network1_name: 'network1' - - !ruby/object:Provider::Terraform::Examples - name: 'ha_vpn_gateway_gcp_to_gcp' - primary_resource_id: - 'ha_gateway1' - # Multiple fine-grained resources - skip_vcr: true - skip_test: true - skip_docs: true - vars: - ha_vpn_gateway1_name: 'ha-vpn-1' - network1_name: 'network1' - router1_name: 'ha-vpn-router1' - ha_vpn_gateway2_name: 'ha-vpn-2' - network2_name: 'network2' - router2_name: 'ha-vpn-router2' - - !ruby/object:Provider::Terraform::Examples - name: 'compute_ha_vpn_gateway_encrypted_interconnect' - primary_resource_id: - 'vpn-gateway' - # TODO: https://github.com/hashicorp/terraform-provider-google/issues/11504 - skip_test: true - vars: - ha_vpn_gateway_name: 'test-ha-vpngw' - interconnect_attachment1_name: 'test-interconnect-attachment1' - interconnect_attachment2_name: 'test-interconnect-attachment2' - address1_name: 'test-address1' - address2_name: 'test-address2' - router_name: 'test-router' - network_name: 'test-network' + target_vpn_gateway_name: 'vpn-1' + network_name: 'network-1' + address_name: 'vpn-static-ip' + esp_forwarding_rule_name: 'fr-esp' + udp500_forwarding_rule_name: 'fr-udp500' + udp4500_forwarding_rule_name: 'fr-udp4500' + vpn_tunnel_name: 'tunnel1' + route_name: 'route1' parameters: - !ruby/object:Api::Type::ResourceRef name: 'region' @@ -99,6 +70,10 @@ parameters: custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.erb' custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb' properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true - !ruby/object:Api::Type::String name: 'description' description: 'An optional description of this resource.' @@ -115,8 +90,11 @@ properties: character, which cannot be a dash. required: true immutable: true - validation: !ruby/object:Provider::Terraform::Validation - function: 'verify.ValidateGCEName' + - !ruby/object:Api::Type::Integer + name: 'gateway_id' + api_name: 'id' + description: 'The unique identifier for the resource.' + output: true - !ruby/object:Api::Type::ResourceRef name: 'network' resource: 'Network' @@ -124,44 +102,5 @@ properties: description: | The network this VPN gateway is accepting traffic for. required: true - immutable: true custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb' - - !ruby/object:Api::Type::Enum - name: 'stackType' - description: | - The stack type for this VPN gateway to identify the IP protocols that are enabled. - If not specified, IPV4_ONLY will be used. - default_value: :IPV4_ONLY - values: - - :IPV4_ONLY - - :IPV4_IPV6 immutable: true - custom_flatten: 'templates/terraform/custom_flatten/default_if_empty.erb' - - !ruby/object:Api::Type::Array - name: 'vpnInterfaces' - description: | - A list of interfaces on this VPN gateway. - default_from_api: true - item_type: !ruby/object:Api::Type::NestedObject - properties: - - !ruby/object:Api::Type::Integer - name: 'id' - description: 'The numeric ID of this VPN gateway interface.' - - !ruby/object:Api::Type::String - name: 'ipAddress' - description: 'The external IP address for this VPN gateway interface.' - output: true - - !ruby/object:Api::Type::ResourceRef - name: 'interconnectAttachment' - resource: 'InterconnectAttachment' - imports: 'selfLink' - description: | - URL of the interconnect attachment resource. When the value - of this field is present, the VPN Gateway will be used for - IPsec-encrypted Cloud Interconnect; all Egress or Ingress - traffic for this VPN Gateway interface will go through the - specified interconnect attachment resource. - - Not currently available publicly. - custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.erb' - immutable: true diff --git a/mmv1/provider/terraform.rb b/mmv1/provider/terraform.rb index a7bc76a516af..89e7f21b099e 100644 --- a/mmv1/provider/terraform.rb +++ b/mmv1/provider/terraform.rb @@ -89,8 +89,9 @@ def check_goformat end # Main entry point for generation. - def generate(output_folder, types, product_path, dump_yaml, generate_code, generate_docs) - generate_objects(output_folder, types, generate_code, generate_docs) + def generate(output_folder, types, product_path, dump_yaml, generate_code, generate_docs, \ + go_yaml) + generate_objects(output_folder, types, generate_code, generate_docs, product_path, go_yaml) FileUtils.mkpath output_folder pwd = Dir.pwd @@ -358,7 +359,8 @@ def get_mmv1_services_in_version(products, version) services end - def generate_objects(output_folder, types, generate_code, generate_docs) + def generate_objects(output_folder, types, generate_code, generate_docs, product_path, \ + go_yaml) (@api.objects || []).each do |object| if !types.empty? && !types.include?(object.name) Google::LOGGER.info "Excluding #{object.name} per user request" @@ -378,8 +380,7 @@ def generate_objects(output_folder, types, generate_code, generate_docs) generate_object object, output_folder, @target_version_name, generate_code, generate_docs end - # Uncomment for go YAML - # generate_object_modified object, output_folder, @target_version_name + generate_object_modified object, product_path, @target_version_name if go_yaml end end @@ -411,28 +412,23 @@ def generate_object(object, output_folder, version_name, generate_code, generate def generate_object_modified(object, output_folder, version_name) pwd = Dir.pwd data = build_object_data(pwd, object, output_folder, version_name) - FileUtils.mkpath output_folder Dir.chdir output_folder - Google::LOGGER.debug "Generating #{object.name} rewrite yaml" + Google::LOGGER.info "Generating #{object.name} rewrite yaml" generate_newyaml(pwd, data.clone) Dir.chdir pwd end def generate_newyaml(pwd, data) - # @api.api_name is the service folder name - product_name = @api.api_name - target_folder = File.join(folder_name(data.version), 'services', product_name) - FileUtils.mkpath target_folder data.generate(pwd, '/templates/terraform/yaml_conversion.erb', - "#{target_folder}/go_#{data.object.name}.yaml", - self) - return if File.exist?("#{target_folder}/go_product.yaml") - - data.generate(pwd, - '/templates/terraform/product_yaml_conversion.erb', - "#{target_folder}/go_product.yaml", + "go_#{data.object.name}.yaml", self) + unless File.exist?('go_product.yaml') && File.mtime('go_product.yaml') > data.env[:start_time] + data.generate(pwd, + '/templates/terraform/product_yaml_conversion.erb', + 'go_product.yaml', + self) + end end def build_env diff --git a/mmv1/provider/terraform_kcc.rb b/mmv1/provider/terraform_kcc.rb index 682f9c5bc640..54a7c2552231 100644 --- a/mmv1/provider/terraform_kcc.rb +++ b/mmv1/provider/terraform_kcc.rb @@ -48,9 +48,11 @@ def generating_hashicorp_repo? false end - def generate(output_folder, types, _product_path, _dump_yaml, generate_code, generate_docs) + def generate(output_folder, types, product_path, _dump_yaml, generate_code, generate_docs, \ + go_yaml) @base_url = @version.base_url - generate_objects(output_folder, types, generate_code, generate_docs) + generate_objects(output_folder, types, generate_code, generate_docs, product_path, \ + go_yaml) compile_product_files(output_folder) end diff --git a/mmv1/provider/terraform_oics.rb b/mmv1/provider/terraform_oics.rb index a4e1a6c16d7d..d5d44a134176 100644 --- a/mmv1/provider/terraform_oics.rb +++ b/mmv1/provider/terraform_oics.rb @@ -24,12 +24,15 @@ def generating_hashicorp_repo? # We don't want *any* static generation, so we override generate to only # generate objects. - def generate(output_folder, types, _product_path, _dump_yaml, generate_code, generate_docs) + def generate(output_folder, types, product_path, _dump_yaml, generate_code, generate_docs, \ + go_yaml) generate_objects( output_folder, types, generate_code, - generate_docs + generate_docs, + product_path, + go_yaml ) end diff --git a/mmv1/provider/terraform_tgc.rb b/mmv1/provider/terraform_tgc.rb index 3674fe99cd77..2ea63de82a4a 100644 --- a/mmv1/provider/terraform_tgc.rb +++ b/mmv1/provider/terraform_tgc.rb @@ -23,7 +23,8 @@ def generating_hashicorp_repo? false end - def generate(output_folder, types, _product_path, _dump_yaml, generate_code, generate_docs) + def generate(output_folder, types, product_path, _dump_yaml, generate_code, generate_docs, \ + go_yaml) # Temporary shim to generate the missing resources directory. Can be removed # once the folder exists downstream. resources_folder = File.join(output_folder, 'converters/google/resources') @@ -34,7 +35,9 @@ def generate(output_folder, types, _product_path, _dump_yaml, generate_code, gen output_folder, types, generate_code, - generate_docs + generate_docs, + product_path, + go_yaml ) end diff --git a/mmv1/provider/terraform_tgc_cai2hcl.rb b/mmv1/provider/terraform_tgc_cai2hcl.rb index fb5a0c8d96e0..4daed0648ec6 100644 --- a/mmv1/provider/terraform_tgc_cai2hcl.rb +++ b/mmv1/provider/terraform_tgc_cai2hcl.rb @@ -22,7 +22,9 @@ def generating_hashicorp_repo? false end - def generate(output_folder, types, _product_path, _dump_yaml, generate_code, generate_docs) end + # rubocop:disable Layout/LineLength + def generate(output_folder, types, _product_path, _dump_yaml, generate_code, generate_docs, _go_yaml) end + # rubocop:enable Layout/LineLength def generate_resource(pwd, data, _generate_code, _generate_docs) end diff --git a/mmv1/templates/terraform/product_yaml_conversion.erb b/mmv1/templates/terraform/product_yaml_conversion.erb index cade809c4334..9888415c054f 100644 --- a/mmv1/templates/terraform/product_yaml_conversion.erb +++ b/mmv1/templates/terraform/product_yaml_conversion.erb @@ -55,6 +55,7 @@ scopes: -%> <% unless object.__product.async.nil? -%> async: + type: "OpAsync" <% if object.__product.async.is_a? Provider::Terraform::PollAsync -%> <% unless object.__product.async.check_response_func_existence.nil? -%> check_response_func_existence: '<%= object.__product.async.check_response_func_existence %>' diff --git a/mmv1/templates/terraform/yaml_conversion.erb b/mmv1/templates/terraform/yaml_conversion.erb index 63497552b8b7..2dcd831c565f 100644 --- a/mmv1/templates/terraform/yaml_conversion.erb +++ b/mmv1/templates/terraform/yaml_conversion.erb @@ -1,3 +1,4 @@ +NOT CONVERTED - RUN YAML MODE # Copyright 2024 Google Inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,8 +24,7 @@ kind: '<%= object.kind %>' <% unless object.legacy_name.nil? -%> legacy_name: '<%= object.legacy_name %>' <% end -%> -description: | - <%= object.description.gsub(/\n/, "\n ") %> +description: <% unless object.min_version.nil? -%> <% unless object.min_version.name == 'ga' -%> min_version: '<%= object.min_version.name %>' @@ -63,7 +63,7 @@ docs: warning: '<%= object.docs.warning %>' <% end -%> <% unless object.docs.note.nil? -%> - note: '<%= object.docs.note %>' + note: <% end -%> <% unless object.docs.required_properties.nil? -%> required_properties: '<%= object.docs.required_properties %>' @@ -184,6 +184,7 @@ async: <% unless object.async.target_occurrences.nil? -%> target_occurrences: <%= object.async.target_occurrences %> <% end -%> + actions: ['<%= object.async.actions.join('\',\'') %>'] <% end -%> <% if object.async.is_a? Api::OpAsync -%> type: 'OpAsync' @@ -251,8 +252,8 @@ collection_url_key: '<%= object.collection_url_key %>' nested_query: <% unless object.nested_query.keys.nil? -%> keys: -<% object.nested_query.keys.each do |key| %> - - <%= key -%> +<% object.nested_query.keys.each do |key| -%> + - '<%= key -%>' <% end -%> <% end -%> <% unless object.nested_query.is_list_of_ids.nil? -%> @@ -340,52 +341,52 @@ iam_policy: <% unless object.custom_code.nil? -%> custom_code: <% unless object.custom_code.extra_schema_entry.nil? -%> - extra_schema_entry: '<%= object.custom_code.extra_schema_entry %>' + extra_schema_entry: '<%= object.convert_go_file( object.custom_code.extra_schema_entry) %>' <% end -%> <% unless object.custom_code.constants.nil? -%> - constants: '<%= object.custom_code.constants %>' + constants: '<%= object.convert_go_file( object.custom_code.constants) %>' <% end -%> <% unless object.custom_code.encoder.nil? -%> - encoder: '<%= object.custom_code.encoder %>' + encoder: '<%= object.convert_go_file( object.custom_code.encoder )%>' <% end -%> <% unless object.custom_code.update_encoder.nil? -%> - update_encoder: '<%= object.custom_code.update_encoder %>' + update_encoder: '<%= object.convert_go_file( object.custom_code.update_encoder )%>' <% end -%> <% unless object.custom_code.decoder.nil? -%> - decoder: '<%= object.custom_code.decoder %>' + decoder: '<%= object.convert_go_file( object.custom_code.decoder )%>' <% end -%> <% unless object.custom_code.pre_create.nil? -%> - pre_create: '<%= object.custom_code.pre_create %>' + pre_create: '<%= object.convert_go_file( object.custom_code.pre_create )%>' <% end -%> <% unless object.custom_code.post_create.nil? -%> - post_create: '<%= object.custom_code.post_create %>' + post_create: '<%= object.convert_go_file( object.custom_code.post_create )%>' <% end -%> <% unless object.custom_code.custom_create.nil? -%> - custom_create: '<%= object.custom_code.custom_create %>' + custom_create: '<%= object.convert_go_file( object.custom_code.custom_create )%>' <% end -%> <% unless object.custom_code.pre_read.nil? -%> - pre_read: '<%= object.custom_code.pre_read %>' + pre_read: '<%= object.convert_go_file( object.custom_code.pre_read )%>' <% end -%> <% unless object.custom_code.pre_update.nil? -%> - pre_update: '<%= object.custom_code.pre_update %>' + pre_update: '<%= object.convert_go_file( object.custom_code.pre_update )%>' <% end -%> <% unless object.custom_code.post_update.nil? -%> - post_update: '<%= object.custom_code.post_update %>' + post_update: '<%= object.convert_go_file( object.custom_code.post_update )%>' <% end -%> <% unless object.custom_code.custom_update.nil? -%> - custom_update: '<%= object.custom_code.custom_update %>' + custom_update: '<%= object.convert_go_file( object.custom_code.custom_update )%>' <% end -%> <% unless object.custom_code.pre_delete.nil? -%> - pre_delete: '<%= object.custom_code.pre_delete %>' + pre_delete: '<%= object.convert_go_file( object.custom_code.pre_delete )%>' <% end -%> <% unless object.custom_code.custom_import.nil? -%> - custom_import: '<%= object.custom_code.custom_import %>' + custom_import: '<%= object.convert_go_file( object.custom_code.custom_import )%>' <% end -%> <% unless object.custom_code.post_import.nil? -%> - post_import: '<%= object.custom_code.post_import %>' + post_import: '<%= object.convert_go_file( object.custom_code.post_import )%>' <% end -%> <% unless object.custom_code.test_check_destroy.nil? -%> - test_check_destroy: '<%= object.custom_code.test_check_destroy %>' + test_check_destroy: '<%= object.convert_go_file( object.custom_code.test_check_destroy )%>' <% end -%> <% end -%> <% unless object.custom_diff.empty? || (object.custom_diff.size == 1 && object.custom_diff.include?("tpgresource.SetLabelsDiff")) -%> @@ -494,7 +495,7 @@ examples: <% unless example.ignore_read_extra.empty? -%> ignore_read_extra: <% example.ignore_read_extra.each do |irextra| -%> - '<%= irextra %>' + - '<%= irextra %>' <% end -%> <% end -%> <% unless example.external_providers.nil? -%> @@ -521,7 +522,7 @@ examples: virtual_fields: <% object.virtual_fields.each do |vfield| -%> - name: '<%= vfield.name %>' - description: '<%= vfield.description %>' + description: <% unless vfield.type.nil? -%> type: <%= tf_type(vfield.type) %> <% end -%> diff --git a/mmv1/templates/terraform/yaml_conversion_field.erb b/mmv1/templates/terraform/yaml_conversion_field.erb index 4432130ab1a7..3a56f8e8f0aa 100644 --- a/mmv1/templates/terraform/yaml_conversion_field.erb +++ b/mmv1/templates/terraform/yaml_conversion_field.erb @@ -2,15 +2,18 @@ <% unless property.class.to_s == 'Api::Type::KeyValueTerraformLabels' || property.class.to_s == 'Api::Type::KeyValueEffectiveLabels' -%> - name: '<%= property.name -%>' type: <%= property.class.to_s.gsub("Api::Type::", "") %> -<% unless property.description.nil? -%> +<% unless property.description.nil? || property.description == "A nested object resource" -%> <% des = property.description.strip.gsub('"', '\'') -%> <% if property.is_a?(Api::Type::KeyValueLabels) || property.is_a?(Api::Type::KeyValueAnnotations) -%> <% index = des.index("\n\n**Note**: This field is non-authoritative") -%> - description: "<%= des[0, index] -%>" + description: <% else -%> - description: "<%= des -%>" + description: <% end -%> <% end -%> +<% unless property.api_name == property.name -%> + api_name: <%= property.api_name %> +<% end -%> <% unless !property.unordered_list -%> unordered_list: <%= property.unordered_list %> <% end -%> @@ -67,7 +70,7 @@ <% unless property.update_url.nil? -%> update_url: '<%= property.update_url %>' <% end -%> -<% unless property.update_verb == property.__resource&.update_verb -%> +<% unless property.update_verb == property.__resource&.update_verb || property.update_verb.to_s.strip.empty? -%> update_verb: '<%= property.update_verb.to_s %>' <% end -%> <% unless property.update_id.nil? -%> @@ -114,7 +117,7 @@ <% end -%> <% end -%> <% end -%> -<% unless property.key_expander == 'tpgresource.ExpandString' -%> +<% unless property.key_expander == 'tpgresource.ExpandString' || property.update_verb.to_s.strip.empty? -%> key_expander: '<%= property.key_expander %>' <% end -%> <% unless property.key_diff_suppress_func.nil? -%> @@ -130,13 +133,13 @@ set_hash_func: '<%= property.set_hash_func %>' <% end -%> <% unless property.custom_flatten.nil? -%> - custom_flatten: '<%= property.custom_flatten %>' + custom_flatten: '<%= object.convert_go_file( property.custom_flatten )%>' <% end -%> <% unless property.custom_expand.nil? -%> - custom_expand: '<%= property.custom_expand %>' + custom_expand: '<%= object.convert_go_file(property.custom_expand )%>' <% end -%> <% unless property.flatten_object.nil? -%> - flatten_object: '<%= property.flatten_object %>' + flatten_object: <%= property.flatten_object %> <% end -%> <% unless property.validation.nil? -%> validation: @@ -158,15 +161,46 @@ <% end -%> <% if property.is_a?(Api::Type::Array) -%> <% if property.item_type.is_a?(Api::Type::NestedObject) -%> - item_type: <%= property.item_type.type.to_s %> + item_type: +<% unless property.item_type.description.nil? || property.item_type.description == "A nested object resource" -%> + description: +<% end -%> + type: <%= property.item_type.type.to_s.gsub("Api::Type::", "") %> <% unless property.item_type.properties.nil? -%> - properties: + properties: <% property.item_type.properties.each do |prop| -%> -<%= lines(indent(build_newyaml_field(prop, object, pwd), 4)) -%> +<%= lines(indent(build_newyaml_field(prop, object, pwd), 6)) -%> <% end -%> <% end -%> +<% elsif property.item_type.is_a?(Api::Type::ResourceRef) -%> + item_type: + name: '<%= property.item_type.name -%>' + type: ResourceRef +<% unless property.item_type.description.nil? || property.item_type.description == "A nested object resource" -%> + description: +<% end -%> +<% unless property.item_type.resource.nil? -%> + resource: '<%= property.item_type.resource -%>' +<% end -%> +<% unless property.item_type.imports.nil? -%> + imports: '<%= property.item_type.imports.to_s -%>' +<% end -%> <% else -%> - item_type: <%= property.item_type.to_s %> + item_type: +<% if property.item_type.is_a?(Api::Type::Enum) -%> + type: Enum +<% unless property.item_type.description.nil? || property.item_type.description == "A nested object resource" -%> + description: +<% end -%> +<% unless property.item_type.values.nil? -%> + enum_values: +<% property.item_type.values.reject{|v| v == '' }.each do |enumval| -%> + - '<%= enumval %>' +<% end -%> +<% end -%> +<% else -%> + type: <%= property.item_type_class.to_s.gsub("Api::Type::", "") %> +<% end -%> <% end -%> <% unless property.min_size.nil? -%> min_size: <%= property.min_size %> @@ -186,7 +220,7 @@ <% if property.is_a?(Api::Type::Enum) -%> <% unless property.values.nil? -%> enum_values: -<% property.values.each do |enumval| -%> +<% property.values.reject{|v| v == '' }.each do |enumval| -%> - '<%= enumval %>' <% end -%> <% end -%> @@ -195,14 +229,28 @@ <% end -%> <% end -%> <% if property.is_a?(Api::Type::Map) -%> -<% unless property.value_type.nil? -%> - value_type: '<%= property.value_type.to_s %>' -<% end -%> <% unless property.key_name.nil? -%> key_name: '<%= property.key_name %>' <% end -%> <% unless property.key_description.nil? -%> - key_description: '<%= property.key_description %>' + key_description: +<% end -%> +<% unless property.value_type.nil? -%> +<% if property.value_type.is_a?(Api::Type::NestedObject) -%> + value_type: +<% unless property.value_type.description.nil? || property.value_type.description == "A nested object resource" -%> + description: +<% end -%> + type: <%= property.value_type.type.to_s.gsub("Api::Type::", "") %> +<% unless property.value_type.properties.nil? -%> + properties: +<% property.value_type.properties.each do |prop| -%> +<%= lines(indent(build_newyaml_field(prop, object, pwd), 6)) -%> +<% end -%> +<% end -%> +<% else -%> + value_type: '<%= property.value_type.to_s %>' +<% end -%> <% end -%> <% end -%> <% if property.is_a?(Api::Type::NestedObject) -%>