From 03df3b2c751ce0e8d10998b903fa0ba51c2cbb8b Mon Sep 17 00:00:00 2001 From: Cameron Thornton Date: Thu, 29 Feb 2024 16:19:31 -0600 Subject: [PATCH] make go-converted YAML compatible with go compiler (#10033) Co-authored-by: Zhenhua Li Co-authored-by: Nick Elliot --- mmv1/api/async.go | 220 ++++ mmv1/api/object.go | 82 ++ mmv1/api/product.go | 207 ++++ mmv1/api/product.rb | 4 - mmv1/api/product/version.go | 52 + mmv1/api/resource.go | 275 +++++ mmv1/api/resource/iam_policy.go | 168 +++ mmv1/api/resource/nested_query.go | 65 ++ mmv1/api/resource/reference_links.go | 39 + mmv1/api/timeouts.go | 51 + mmv1/api/type.go | 977 ++++++++++++++++++ mmv1/compiler.rb | 6 +- mmv1/go.mod | 5 + mmv1/go.sum | 3 + mmv1/google/yaml_validator.go | 155 +++ mmv1/main.go | 108 ++ mmv1/products/datafusion/go_instance.yaml | 298 ++++++ mmv1/products/datafusion/go_product.yaml | 35 + mmv1/products/pubsub/go_Schema.yaml | 84 ++ mmv1/products/pubsub/go_Subscription.yaml | 420 ++++++++ mmv1/products/pubsub/go_Topic.yaml | 150 +++ mmv1/products/pubsub/go_product.yaml | 22 + mmv1/provider/terraform.rb | 30 +- mmv1/provider/terraform/custom_code.go | 206 ++++ mmv1/provider/terraform/docs.go | 61 ++ mmv1/provider/terraform/examples.go | 339 ++++++ mmv1/provider/terraform/sub_template.rb | 7 + .../terraform/product_yaml_conversion.erb | 135 +++ mmv1/templates/terraform/yaml_conversion.erb | 550 ++++++++++ .../terraform/yaml_conversion_field.erb | 210 ++++ mmv1/third_party/go.mod | 2 + 31 files changed, 4959 insertions(+), 7 deletions(-) create mode 100644 mmv1/api/async.go create mode 100644 mmv1/api/object.go create mode 100644 mmv1/api/product.go create mode 100644 mmv1/api/product/version.go create mode 100644 mmv1/api/resource.go create mode 100644 mmv1/api/resource/iam_policy.go create mode 100644 mmv1/api/resource/nested_query.go create mode 100644 mmv1/api/resource/reference_links.go create mode 100644 mmv1/api/timeouts.go create mode 100644 mmv1/api/type.go create mode 100644 mmv1/go.mod create mode 100644 mmv1/go.sum create mode 100644 mmv1/google/yaml_validator.go create mode 100644 mmv1/main.go create mode 100644 mmv1/products/datafusion/go_instance.yaml create mode 100644 mmv1/products/datafusion/go_product.yaml create mode 100644 mmv1/products/pubsub/go_Schema.yaml create mode 100644 mmv1/products/pubsub/go_Subscription.yaml create mode 100644 mmv1/products/pubsub/go_Topic.yaml create mode 100644 mmv1/products/pubsub/go_product.yaml create mode 100644 mmv1/provider/terraform/custom_code.go create mode 100644 mmv1/provider/terraform/docs.go create mode 100644 mmv1/provider/terraform/examples.go create mode 100644 mmv1/templates/terraform/product_yaml_conversion.erb create mode 100644 mmv1/templates/terraform/yaml_conversion.erb create mode 100644 mmv1/templates/terraform/yaml_conversion_field.erb create mode 100644 mmv1/third_party/go.mod diff --git a/mmv1/api/async.go b/mmv1/api/async.go new file mode 100644 index 000000000000..55d756fa8312 --- /dev/null +++ b/mmv1/api/async.go @@ -0,0 +1,220 @@ +// 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. +// 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. + +package api + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" +) + +// require 'api/object' +// require 'api/timeout' + +// Base class from which other Async classes can inherit. +type Async struct { + // Embed YamlValidator object + google.YamlValidator + + // Describes an operation + Operation Operation + + // The list of methods where operations are used. + Actions []string +} + +// def validate +// super + +// check :operation, type: Operation +// check :actions, default: %w[create delete update], type: ::Array, item_type: ::String +// end + +// def allow?(method) +// @actions.include?(method.downcase) +// end + +// Base async operation type +type Operation struct { + google.YamlValidator + + // Contains information about an long-running operation, to make + // requests for the state of an operation. + + Timeouts Timeouts + + Result Result +} + +// def validate +// check :result, type: Result +// check :timeouts, type: Api::Timeouts +// end + +// Base result class +type Result struct { + google.YamlValidator + + // Contains information about the result of an Operation + + ResourceInsideResponse bool +} + +// def validate +// super +// check :resource_inside_response, type: :boolean, default: false +// end + +// Represents an asynchronous operation definition +type OpAsync struct { + // TODO: Should embed Async or not? + // < Async + + Operation OpAsyncOperation + + Result OpAsyncResult + + Status OpAsyncStatus + + Error OpAsyncError + + // If true, include project as an argument to OperationWaitTime. + // It is intended for resources that calculate project/region from a selflink field + IncludeProject bool `yaml:"include_project"` + + // The list of methods where operations are used. + Actions []string +} + +// def initialize(operation, result, status, error) +// super() +// @operation = operation +// @result = result +// @status = status +// @error = error +// end + +// def validate +// super + +// check :operation, type: Operation, required: true +// check :result, type: Result, default: Result.new +// check :status, type: Status +// check :error, type: Error +// check :actions, default: %w[create delete update], type: ::Array, item_type: ::String +// check :include_project, type: :boolean, default: false +// end + +// The main implementation of Operation, +// corresponding to common GCP Operation resources. +type OpAsyncOperation struct { + // TODO: Should embed Operation or not? + // < Async::Operation + Kind string + + Path string + + BaseUrl string `yaml:"base_url"` + + WaitMs int `yaml:"wait_ms"` + + Timeouts Timeouts + + // Use this if the resource includes the full operation url. + FullUrl string `yaml:"full_url"` +} + +// def initialize(path, base_url, wait_ms, timeouts) +// super() +// @path = path +// @base_url = base_url +// @wait_ms = wait_ms +// @timeouts = timeouts +// end + +// def validate +// super + +// check :kind, type: String +// check :path, type: String +// check :base_url, type: String +// check :wait_ms, type: Integer + +// check :full_url, type: String + +// conflicts %i[base_url full_url] +// end + +// Represents the results of an Operation request +type OpAsyncResult struct { + Result Result `yaml:",inline"` + + Path string +} + +// def initialize(path = nil, resource_inside_response = nil) +// super() +// @path = path +// @resource_inside_response = resource_inside_response +// end + +// def validate +// super + +// check :path, type: String +// end + +// Provides information to parse the result response to check operation +// status +type OpAsyncStatus struct { + google.YamlValidator + + Path string + + Complete bool + + Allowed []bool +} + +// def initialize(path, complete, allowed) +// super() +// @path = path +// @complete = complete +// @allowed = allowed +// end + +// def validate +// super +// check :path, type: String +// check :allowed, type: Array, item_type: [::String, :boolean] +// end + +// Provides information on how to retrieve errors of the executed operations +type OpAsyncError struct { + google.YamlValidator + + Path string + + Message string +} + +// def initialize(path, message) +// super() +// @path = path +// @message = message +// end + +// def validate +// super +// check :path, type: String +// check :message, type: String +// end diff --git a/mmv1/api/object.go b/mmv1/api/object.go new file mode 100644 index 000000000000..5b2edb1f67ca --- /dev/null +++ b/mmv1/api/object.go @@ -0,0 +1,82 @@ +// 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. +// 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. + +package api + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" +) + +// require 'google/extensions' +// require 'google/logger' +// require 'google/yaml_validator' + +// Represents an object that has a (mandatory) name +type NamedObject struct { + google.YamlValidator + + Name string + + // original value of :name before the provider override happens + // same as :name if not overridden in provider + ApiName string `yaml:"api_name"` +} + +// func (n *Named) string_array(arr) { +// types = arr.map(&:class).uniq +// types.size == 1 && types[0] == String +// } + +// func (n *Named) deep_merge(arr1, arr2) { +// // Scopes is an array of standard strings. In which case return the +// // version in the overrides. This allows scopes to be removed rather +// // than allowing for a merge of the two arrays +// if string_array?(arr1) +// return arr2.nil? ? arr1 : arr2 +// end + +// // Merge any elements that exist in both +// result = arr1.map do |el1| +// other = arr2.select { |el2| el1.name == el2.name }.first +// other.nil? ? el1 : el1.merge(other) +// end + +// // Add any elements of arr2 that don't exist in arr1 +// result + arr2.reject do |el2| +// arr1.any? { |el1| el2.name == el1.name } +// end +// } + +// func (n *Named) merge(other) { +// result = self.class.new +// instance_variables.each do |v| +// result.instance_variable_set(v, instance_variable_get(v)) +// end + +// other.instance_variables.each do |v| +// if other.instance_variable_get(v).instance_of?(Array) +// result.instance_variable_set(v, deep_merge(result.instance_variable_get(v), +// other.instance_variable_get(v))) +// else +// result.instance_variable_set(v, other.instance_variable_get(v)) +// end +// end + +// result +// } + +// func (n *Named) validate() { +// super +// check :name, type: String, required: true +// check :api_name, type: String, default: @name +// } diff --git a/mmv1/api/product.go b/mmv1/api/product.go new file mode 100644 index 000000000000..f7b7b4235804 --- /dev/null +++ b/mmv1/api/product.go @@ -0,0 +1,207 @@ +// 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. +// 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. + +package api + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/api/product" +) + +// require 'api/object' +// require 'api/product/version' +// require 'google/logger' +// require 'compile/core' +// require 'json' + +// Represents a product to be managed +type Product struct { + NamedObject `yaml:",inline"` + + // include Compile::Core + + // Inherited: + // The name of the product's API capitalised in the appropriate places. + // This isn't just the API name because it doesn't meaningfully separate + // words in the api name - "accesscontextmanager" vs "AccessContextManager" + // Example inputs: "Compute", "AccessContextManager" + // Name string + + // Display Name: The full name of the GCP product; eg "Cloud Bigtable" + + Objects []interface{} + + // The list of permission scopes available for the service + // For example: `https://www.googleapis.com/auth/compute` + + Scopes []string + + // The API versions of this product + + Versions []product.Version + + // The base URL for the service API endpoint + // For example: `https://www.googleapis.com/compute/v1/` + + BaseUrl string `yaml:"base_url"` + + // A function reference designed for the rare case where you + // need to use retries in operation calls. Used for the service api + // as it enables itself (self referential) and can result in occasional + // failures on operation_get. see github.com/hashicorp/terraform-provider-google/issues/9489 + + OperationRetry string `yaml:"operation_retry"` + + Async OpAsync + + LegacyName string `yaml:"legacy_name"` + + ClientName string `yaml:"client_name"` +} + +// def validate +// super +// set_variables @objects, :__product + +// // name comes from Named, and product names must start with a capital +// caps = ('A'..'Z').to_a +// unless caps.include? @name[0] +// raise "product name `//{@name}` must start with a capital letter." +// end + +// check :display_name, type: String +// check :objects, type: Array, item_type: Api::Resource +// check :scopes, type: Array, item_type: String, required: true +// check :operation_retry, type: String + +// check :async, type: Api::Async +// check :legacy_name, type: String +// check :client_name, type: String + +// check :versions, type: Array, item_type: Api::Product::Version, required: true +// end + +// // ==================== +// // Custom Getters +// // ==================== + +// // The name of the product's API; "compute", "accesscontextmanager" +// def api_name +// name.downcase +// end + +// // The product full name is the "display name" in string form intended for +// // users to read in documentation; "Google Compute Engine", "Cloud Bigtable" +// def display_name +// if @display_name.nil? +// name.space_separated +// else +// @display_name +// end +// end + +// // Most general version that exists for the product +// // If GA is present, use that, else beta, else alpha +// def lowest_version +// Version::ORDER.each do |ordered_version_name| +// @versions.each do |product_version| +// return product_version if ordered_version_name == product_version.name +// end +// end +// raise "Unable to find lowest version for product //{display_name}" +// end + +// def version_obj(name) +// @versions.each do |v| +// return v if v.name == name +// end + +// raise "API version '//{name}' does not exist for product '//{@name}'" +// end + +// // Get the version of the object specified by the version given if present +// // Or else fall back to the closest version in the chain defined by Version::ORDER +// def version_obj_or_closest(name) +// return version_obj(name) if exists_at_version(name) + +// // versions should fall back to the closest version to them that exists +// name ||= Version::ORDER[0] +// lower_versions = Version::ORDER[0..Version::ORDER.index(name)] + +// lower_versions.reverse_each do |version| +// return version_obj(version) if exists_at_version(version) +// end + +// raise "Could not find object for version //{name} and product //{display_name}" +// end + +// def exists_at_version_or_lower(name) +// // Versions aren't normally going to be empty since products need a +// // base_url. This nil check exists for atypical products, like _bundle. +// return true if @versions.nil? + +// name ||= Version::ORDER[0] +// return false unless Version::ORDER.include?(name) + +// (0..Version::ORDER.index(name)).each do |i| +// return true if exists_at_version(Version::ORDER[i]) +// end +// false +// end + +// def exists_at_version(name) +// // Versions aren't normally going to be empty since products need a +// // base_url. This nil check exists for atypical products, like _bundle. +// return true if @versions.nil? + +// @versions.any? { |v| v.name == name } +// end + +// // Not a conventional setter, so ignore rubocop's warning +// // rubocop:disable Naming/AccessorMethodName +// def set_properties_based_on_version(version) +// @base_url = version.base_url +// end +// // rubocop:enable Naming/AccessorMethodName + +// // ==================== +// // Debugging Methods +// // ==================== + +// def to_s +// // relies on the custom to_json definitions +// JSON.pretty_generate(self) +// end + +// // Prints a dot notation path to where the field is nested within the parent +// // object when called on a property. eg: parent.meta.label.foo +// // Redefined on Product to terminate the calls up the parent chain. +// def lineage +// name +// end + +// def to_json(opts = nil) +// json_out = {} + +// instance_variables.each do |v| +// if v == :@objects +// json_out['@resources'] = objects.to_h { |o| [o.name, o] } +// elsif instance_variable_get(v) == false || instance_variable_get(v).nil? +// // ignore false or missing because omitting them cleans up result +// // and both are the effective defaults of their types +// else +// json_out[v] = instance_variable_get(v) +// end +// end + +// JSON.generate(json_out, opts) +// end diff --git a/mmv1/api/product.rb b/mmv1/api/product.rb index cd5f11b560c5..66e56c87d6ab 100644 --- a/mmv1/api/product.rb +++ b/mmv1/api/product.rb @@ -51,10 +51,6 @@ class Product < Api::NamedObject # failures on operation_get. see github.com/hashicorp/terraform-provider-google/issues/9489 attr_reader :operation_retry - # The APIs required to be enabled for this product. - # Usually just the product's API - attr_reader :apis_required - attr_reader :async attr_reader :legacy_name diff --git a/mmv1/api/product/version.go b/mmv1/api/product/version.go new file mode 100644 index 000000000000..16027fa61035 --- /dev/null +++ b/mmv1/api/product/version.go @@ -0,0 +1,52 @@ +// 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. +// 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. + +package product + +// require 'api/object' + +var ORDER = [...]string{"ga", "beta", "alpha", "private"} + +// A version of the API for a given product / API group +// In GCP, different product versions are generally ordered where alpha is +// a superset of beta, and beta a superset of GA. Each version will have a +// different version url. +type Version struct { + // TODO: Should embed NamedObject or not? + // < Api::NamedObject + // include Comparable + + // attr_reader + CaiBaseUrl string `yaml:"cai_base_url"` + + // attr_accessor + BaseUrl string `yaml:"base_url"` + + // attr_accessor + Name string +} + +// def validate +// super +// check :cai_base_url, type: String, required: false +// check :base_url, type: String, required: true +// check :name, type: String, allowed: ORDER, required: true +// end + +// def to_s +// "//{name}: //{base_url}" +// end + +// def <=>(other) +// ORDER.index(name) <=> ORDER.index(other.name) if other.is_a?(Version) +// end diff --git a/mmv1/api/resource.go b/mmv1/api/resource.go new file mode 100644 index 000000000000..75557aaee73b --- /dev/null +++ b/mmv1/api/resource.go @@ -0,0 +1,275 @@ +// 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. +// 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. +package api + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/api/resource" + "github.com/GoogleCloudPlatform/magic-modules/mmv1/provider/terraform" +) + +type Resource struct { + // Embed NamedObject + NamedObject `yaml:",inline"` + + // [Required] A description of the resource that's surfaced in provider + // documentation. + Description string + + // [Required] (Api::Resource::ReferenceLinks) Reference links provided in + // downstream documentation. + References resource.ReferenceLinks + + // [Required] The GCP "relative URI" of a resource, relative to the product + // base URL. It can often be inferred from the `create` path. + BaseUrl string `yaml:"base_url"` + + // ==================== + // Common Configuration + // ==================== + // + // [Optional] The minimum API version this resource is in. Defaults to ga. + MinVersion string `yaml:"min_version"` + + // [Optional] If set to true, don't generate the resource. + Exclude bool + + // [Optional] If set to true, the resource is not able to be updated. + Immutable bool + + // [Optional] If set to true, this resource uses an update mask to perform + // updates. This is typical of newer GCP APIs. + UpdateMask bool `yaml:"update_mask"` + + // [Optional] If set to true, the object has a `self_link` field. This is + // typical of older GCP APIs. + HasSelfLink bool `yaml:"has_self_link"` + + // [Optional] The validator "relative URI" of a resource, relative to the product + // base URL. Specific to defining the resource as a CAI asset. + CaiBaseUrl string `yaml:"cai_base_url"` + + // ==================== + // URL / HTTP Configuration + // ==================== + // + // [Optional] The "identity" URL of the resource. Defaults to: + // * base_url when the create_verb is :POST + // * self_link when the create_verb is :PUT or :PATCH + SelfLink string `yaml:"self_link"` + + // [Optional] The URL used to creating the resource. Defaults to: + // * collection url when the create_verb is :POST + // * self_link when the create_verb is :PUT or :PATCH + CreateUrl string `yaml:"create_url"` + + // [Optional] The URL used to delete the resource. Defaults to the self + // link. + DeleteUrl string `yaml:"delete_url"` + + // [Optional] The URL used to update the resource. Defaults to the self + // link. + UpdateUrl string `yaml:"update_url"` + // [Optional] The HTTP verb used during create. Defaults to :POST. + CreateVerb string `yaml:"create_verb"` + + // [Optional] The HTTP verb used during read. Defaults to :GET. + ReadVerb string `yaml:"read_verb"` + + // [Optional] The HTTP verb used during update. Defaults to :PUT. + UpdateVerb string `yaml:"update_verb"` + + // [Optional] The HTTP verb used during delete. Defaults to :DELETE. + DeleteVerb string `yaml:"delete_verb"` + + // [Optional] Additional Query Parameters to append to GET. Defaults to "" + ReadQueryParams string `yaml:"read_query_params"` + + // ==================== + // Collection / Identity URL Configuration + // ==================== + // + // [Optional] This is the name of the list of items + // within the collection (list) json. Will default to the + // camelcase plural name of the resource. + CollectionUrlKey string `yaml:"collection_url_key"` + + // [Optional] An ordered list of names of parameters that uniquely identify + // the resource. + // Generally, it's safe to leave empty, in which case it defaults to `name`. + // Other values are normally useful in cases where an object has a parent + // and is identified by some non-name value, such as an ip+port pair. + // If you're writing a fine-grained resource (eg with nested_query) a value + // must be set. + Identity []string + + // [Optional] (Api::Resource::NestedQuery) This is useful in case you need + // to change the query made for GET requests only. In particular, this is + // often used to extract an object from a parent object or a collection. + // Note that if both nested_query and custom_code.decoder are provided, + // the decoder will be included within the code handling the nested query. + NestedQuery resource.NestedQuery `yaml:"nested_query"` + + // ==================== + // IAM Configuration + // ==================== + // + // [Optional] (Api::Resource::IamPolicy) Configuration of a resource's + // resource-specific IAM Policy. + IamPolicy resource.IamPolicy `yaml:"iam_policy"` + + // [Optional] If set to true, don't generate the resource itself; only + // generate the IAM policy. + // TODO rewrite: rename? + ExcludeResource bool `yaml:"exclude_resource"` + + // [Optional] GCP kind, e.g. `compute//disk` + Kind string + + // [Optional] If set to true, indicates that a resource is not configurable + // such as GCP regions. + Readonly bool + + // ==================== + // Terraform Overrides + // ==================== + // [Optional] If non-empty, overrides the full filename prefix + // i.e. google/resource_product_{{resource_filename_override}}.go + // i.e. google/resource_product_{{resource_filename_override}}_test.go + FilenameOverride string `yaml:"filename_override"` + + // If non-empty, overrides the full given resource name. + // i.e. 'google_project' for resourcemanager.Project + // Use Provider::Terraform::Config.legacy_name to override just + // product name. + // Note: This should not be used for vanity names for new products. + // This was added to handle preexisting handwritten resources that + // don't match the natural generated name exactly, and to support + // services with a mix of handwritten and generated resources. + LegacyName string `yaml:"legacy_name"` + + // The Terraform resource id format used when calling //setId(...). + // For instance, `{{name}}` means the id will be the resource name. + IdFormat string `yaml:"id_format"` + + // Override attribute used to handwrite the formats for generating regex strings + // that match templated values to a self_link when importing, only necessary when + // a resource is not adequately covered by the standard provider generated options. + // Leading a token with `%` + // i.e. {{%parent}}/resource/{{resource}} + // will allow that token to hold multiple /'s. + ImportFormat []string `yaml:"import_format"` + + CustomCode terraform.CustomCode `yaml:"custom_code"` + + Docs terraform.Docs + + // This block inserts entries into the customdiff.All() block in the + // resource schema -- the code for these custom diff functions must + // be included in the resource constants or come from tpgresource + CustomDiff []string `yaml:"custom_diff"` + + // Lock name for a mutex to prevent concurrent API calls for a given + // resource. + Mutex string + + // Examples in documentation. Backed by generated tests, and have + // corresponding OiCS walkthroughs. + Examples []terraform.Examples + + // Virtual fields on the Terraform resource. Usage and differences from url_param_only + // are documented in provider/terraform/virtual_fields.rb + VirtualFields interface{} `yaml:"virtual_fields"` + + // If true, generates product operation handling logic. + AutogenAsync bool `yaml:"autogen_async"` + + // If true, resource is not importable + ExcludeImport bool `yaml:"exclude_import"` + + // If true, exclude resource from Terraform Validator + // (i.e. terraform-provider-conversion) + ExcludeTgc bool `yaml:"exclude_tgc"` + + // If true, skip sweeper generation for this resource + SkipSweeper bool `yaml:"skip_sweeper"` + + Timeouts Timeouts + + // An array of function names that determine whether an error is retryable. + ErrorRetryPredicates []string `yaml:"error_retry_predicates"` + + // An array of function names that determine whether an error is not retryable. + ErrorAbortPredicates []string `yaml:"error_abort_predicates"` + + // Optional attributes for declaring a resource's current version and generating + // state_upgrader code to the output .go file from files stored at + // mmv1/templates/terraform/state_migrations/ + // used for maintaining state stability with resources first provisioned on older api versions. + SchemaVersion int `yaml:"schema_version"` + + // From this schema version on, state_upgrader code is generated for the resource. + // When unset, state_upgrade_base_schema_version defauts to 0. + // Normally, it is not needed to be set. + StateUpgradeBaseSchemaVersion int `yaml:"state_upgrade_base_schema_version"` + + StateUpgraders bool `yaml:"state_upgraders"` + + // This block inserts the named function and its attribute into the + // resource schema -- the code for the migrate_state function must + // be included in the resource constants or come from tpgresource + // included for backwards compatibility as an older state migration method + // and should not be used for new resources. + MigrateState string `yaml:"migrate_state"` + + // Set to true for resources that are unable to be deleted, such as KMS keyrings or project + // level resources such as firebase project + SkipDelete bool `yaml:"skip_delete"` + + // Set to true for resources that are unable to be read from the API, such as + // public ca external account keys + SkipRead bool `yaml:"skip_read"` + + // Set to true for resources that wish to disable automatic generation of default provider + // value customdiff functions + // TODO rewrite: 1 instance used + SkipDefaultCdiff bool `yaml:"skip_default_cdiff"` + + // This enables resources that get their project via a reference to a different resource + // instead of a project field to use User Project Overrides + SupportsIndirectUserProjectOverride bool `yaml:"supports_indirect_user_project_override"` + + // If true, the resource's project field can be specified as either the short form project + // id or the long form projects/project-id. The extra projects/ string will be removed from + // urls and ids. This should only be used for resources that previously supported long form + // project ids for backwards compatibility. + LegacyLongFormProject bool `yaml:"legacy_long_form_project"` + + // Function to transform a read error so that handleNotFound recognises + // it as a 404. This should be added as a handwritten fn that takes in + // an error and returns one. + ReadErrorTransform string `yaml:"read_error_transform"` + + // If true, resources that failed creation will be marked as tainted. As a consequence + // these resources will be deleted and recreated on the next apply call. This pattern + // is preferred over deleting the resource directly in post_create_failure hooks. + TaintResourceOnFailedCreate bool `yaml:"taint_resource_on_failed_create"` + + // Add a deprecation message for a resource that's been deprecated in the API. + DeprecationMessage string `yaml:"deprecation_message"` + + Properties []Type + + Parameters []Type +} + +// TODO: rewrite functions diff --git a/mmv1/api/resource/iam_policy.go b/mmv1/api/resource/iam_policy.go new file mode 100644 index 000000000000..1c47394ad0fc --- /dev/null +++ b/mmv1/api/resource/iam_policy.go @@ -0,0 +1,168 @@ +// 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. +// 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. + +package resource + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" +) + +// Information about the IAM policy for this resource +// Several GCP resources have IAM policies that are scoped to +// and accessed via their parent resource +// See: https://cloud.google.com/iam/docs/overview +type IamPolicy struct { + google.YamlValidator + + // boolean of if this binding should be generated + + // attr_reader + Exclude bool + + // boolean of if this binding should be generated + + // attr_reader + ExcludeTgc bool + + // Boolean of if tests for IAM resources should exclude import test steps + // Used to handle situations where typical generated IAM tests cannot import + // due to the parent resource having an API-generated id + + // attr_reader + SkipImportTest bool + + // Character that separates resource identifier from method call in URL + // For example, PubSub subscription uses {resource}:getIamPolicy + // While Compute subnetwork uses {resource}/getIamPolicy + + // attr_reader + MethodNameSeparator string + + // The terraform type of the parent resource if it is not the same as the + // IAM resource. The IAP product needs these as its IAM policies refer + // to compute resources + + // attr_reader + ParentResourceType string + + // Some resources allow retrieving the IAM policy with GET requests, + // others expect POST requests + + // attr_reader + FetchIamPolicyVerb string + + // Last part of URL for fetching IAM policy. + + // attr_reader + FetchIamPolicyMethod string + + // Some resources allow setting the IAM policy with POST requests, + // others expect PUT requests + + // attr_reader + SetIamPolicyVerb string + + // Last part of URL for setting IAM policy. + + // attr_reader + SetIamPolicyMethod string + + // Whether the policy JSON is contained inside of a 'policy' object. + + // attr_reader + WrappedPolicyObj bool + + // Certain resources allow different sets of roles to be set with IAM policies + // This is a role that is acceptable for the given IAM policy resource for use in tests + + // attr_reader + AllowedIamRole string + + // This is a role that grants create/read/delete for the parent resource for use in tests. + // If set, the test runner will receive a binding to this role in _policy tests in order to + // avoid getting locked out of the resource. + + // attr_reader + AdminIamRole string + + // Certain resources need an attribute other than "id" from their parent resource + // Especially when a parent is not the same type as the IAM resource + + // attr_reader + ParentResourceAttribute string + + // If the IAM resource test needs a new project to be created, this is the name of the project + + // attr_reader + TestProjectName string + + // Resource name may need a custom diff suppress function. Default is to use + // CompareSelfLinkOrResourceName + + // attr_reader + CustomDiffSuppress *string + + // Some resources (IAP) use fields named differently from the parent resource. + // We need to use the parent's attributes to create an IAM policy, but they may not be + // named as the IAM IAM resource expects. + // This allows us to specify a file (relative to MM root) containing a partial terraform + // config with the test/example attributes of the IAM resource. + + // attr_reader + ExampleConfigBody string + + // How the API supports IAM conditions + + // attr_reader + IamConditionsRequestType string + + // Allows us to override the base_url of the resource. This is required for Cloud Run as the + // IAM resources use an entirely different base URL from the actual resource + + // attr_reader + BaseUrl string + + // Allows us to override the import format of the resource. Useful for Cloud Run where we need + // variables that are outside of the base_url qualifiers. + + // attr_reader + ImportFormat []string + + // Allows us to override the self_link of the resource. This is required for Artifact Registry + // to prevent breaking changes + + // attr_reader + SelfLink string + + // [Optional] Version number in the request payload. + // if set, it overrides the default IamPolicyVersion + + // attr_reader + IamPolicyVersion string + + // [Optional] Min version to make IAM resources available at + // If unset, defaults to 'ga' + + // attr_reader + MinVersion string + + // [Optional] Check to see if zone value should be replaced with GOOGLE_ZONE in iam tests + // Defaults to true + + // attr_reader + SubstituteZoneValue bool +} + +// func (p *IamPolicy) validate() { + +// } diff --git a/mmv1/api/resource/nested_query.go b/mmv1/api/resource/nested_query.go new file mode 100644 index 000000000000..9cc55e737037 --- /dev/null +++ b/mmv1/api/resource/nested_query.go @@ -0,0 +1,65 @@ +// 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. +// 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. + +package resource + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" +) + +// require 'api/object' +// require 'google/string_utils' + +// Metadata for resources that are nested within a parent resource, as +// a list of resources or single object within the parent. +// e.g. Fine-grained resources +type NestedQuery struct { + google.YamlValidator + + // A list of keys to traverse in order. + // i.e. backendBucket --> cdnPolicy.signedUrlKeyNames + // should be ["cdnPolicy", "signedUrlKeyNames"] + + // attr_reader : + Keys []string + + // If true, we expect the the nested list to be + // a list of IDs for the nested resource, rather + // than a list of nested resource objects + // i.e. backendBucket.cdnPolicy.signedUrlKeyNames is a list of key names + // rather than a list of the actual key objects + + // attr_reader : + IsListOfIds bool + + // If true, the resource is created/updated/deleted by patching + // the parent resource and appropriate encoders/update_encoders/pre_delete + // custom code will be included automatically. Only use if parent resource + // does not have a separate endpoint (set as create/delete/update_urls) + // for updating this resource. + // The resulting encoded data will be mapped as + // { + // keys[-1] : list_of_objects + // } + + // attr_reader : + ModifyByPatch bool +} + +// def validate +// super + +// check :keys, type: Array, item_type: String, required: true +// check :is_list_of_ids, type: :boolean, default: false +// check :modify_by_patch, type: :boolean, default: false +// end diff --git a/mmv1/api/resource/reference_links.go b/mmv1/api/resource/reference_links.go new file mode 100644 index 000000000000..6237308ffb4d --- /dev/null +++ b/mmv1/api/resource/reference_links.go @@ -0,0 +1,39 @@ +// 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. +// 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. + +package resource + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" +) + +// Represents a list of documentation links. +type ReferenceLinks struct { + google.YamlValidator + + // guides containing + // name: The title of the link + // value: The URL to navigate on click + + //attr_reader + Guides map[string]string + + // the url of the API guide + + //attr_reader + Api string +} + +// func (l *ReferenceLinks) validate() { + +// } diff --git a/mmv1/api/timeouts.go b/mmv1/api/timeouts.go new file mode 100644 index 000000000000..41134697a969 --- /dev/null +++ b/mmv1/api/timeouts.go @@ -0,0 +1,51 @@ +// 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. +// 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. + +package api + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" +) + +// require 'api/object' + +// Default timeout for all operation types is 20, the Terraform default (https://www.terraform.io/plugin/sdkv2/resources/retries-and-customizable-timeouts) +// minutes. This can be overridden for each resource. +const DEFAULT_INSERT_TIMEOUT_MINUTES = 20 +const DEFAULT_UPDATE_TIMEOUT_MINUTES = 20 +const DEFAULT_DELETE_TIMEOUT_MINUTES = 20 + +// Provides timeout information for the different operation types +type Timeouts struct { + google.YamlValidator + + InsertMinutes int `yaml:"insert_minutes"` + + UpdateMinutes int `yaml:"update_minutes"` + + DeleteMinutes int `yaml:"delete_minutes"` +} + +// def initialize +// super + +// validate +// end + +// def validate +// super + +// check :insert_minutes, type: Integer, default: DEFAULT_INSERT_TIMEOUT_MINUTES +// check :update_minutes, type: Integer, default: DEFAULT_UPDATE_TIMEOUT_MINUTES +// check :delete_minutes, type: Integer, default: DEFAULT_DELETE_TIMEOUT_MINUTES +// end diff --git a/mmv1/api/type.go b/mmv1/api/type.go new file mode 100644 index 000000000000..b65547f1c9f3 --- /dev/null +++ b/mmv1/api/type.go @@ -0,0 +1,977 @@ +// 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. +// 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. + +package api + +// require 'api/object' +// require 'google/string_utils' +// require 'provider/terraform/validation' + +// Represents a property type +type Type struct { + NamedObject `yaml:",inline"` + + // TODO: improve the parsing of properties based on type in resource yaml files. + Type string + + // TODO: set a specific type intead of interface{} + DefaultValue interface{} `yaml:"default_value"` + + Description string + + Exclude bool + + // Add a deprecation message for a field that's been deprecated in the API + // use the YAML chomping folding indicator (>-) if this is a multiline + // string, as providers expect a single-line one w/o a newline. + DeprecationMessage string `yaml:"deprecation_message"` + + // Add a removed message for fields no longer supported in the API. This should + // be used for fields supported in one version but have been removed from + // a different version. + RemovedMessage string `yaml:"removed_message"` + + // If set value will not be sent to server on sync. + // For nested fields, this also needs to be set on each descendant (ie. self, + // child, etc.). + Output bool + + // If set to true, changes in the field's value require recreating the + // resource. + // For nested fields, this only applies at the current level. This means + // it should be explicitly added to each field that needs the ForceNew + // behavior. + Immutable bool + + // url_param_only will not send the field in the resource body and will + // not attempt to read the field from the API response. + // NOTE - this doesn't work for nested fields + UrlParamOnly bool `yaml:"url_param_only"` + + // For nested fields, this only applies within the parent. + // For example, an optional parent can contain a required child. + Required bool + + // Additional query Parameters to append to GET calls. + ReadQueryParams string `yaml:"read_query_params"` + + UpdateVerb string `yaml:"update_verb"` + + UpdateUrl string `yaml:"update_url"` + + // Some updates only allow updating certain fields at once (generally each + // top-level field can be updated one-at-a-time). If this is set, we group + // fields to update by (verb, url, fingerprint, id) instead of just + // (verb, url, fingerprint), to allow multiple fields to reuse the same + // endpoints. + UpdateId string `yaml:"update_id"` + + // The fingerprint value required to update this field. Downstreams should + // GET the resource and parse the fingerprint value while doing each update + // call. This ensures we can supply the fingerprint to each distinct + // request. + FingerprintName string `yaml:"fingerprint_name"` + + // If true, we will include the empty value in requests made including + // this attribute (both creates and updates). This rarely needs to be + // set to true, and corresponds to both the "NullFields" and + // "ForceSendFields" concepts in the autogenerated API clients. + SendEmptyValue bool `yaml:"send_empty_value"` + + // [Optional] If true, empty nested objects are sent to / read from the + // API instead of flattened to null. + // The difference between this and send_empty_value is that send_empty_value + // applies when the key of an object is empty; this applies when the values + // are all nil / default. eg: "expiration: null" vs "expiration: {}" + // In the case of Terraform, this occurs when a block in config has optional + // values, and none of them are used. Terraform returns a nil instead of an + // empty map[string]interface{} like we'd expect. + AllowEmptyObject bool `yaml:"allow_empty_object"` + + MinVersion string `yaml:"min_version"` + + ExactVersion string `yaml:"exact_version"` + + // A list of properties that conflict with this property. Uses the "lineage" + // field to identify the property eg: parent.meta.label.foo + Conflicts []string + + // A list of properties that at least one of must be set. + AtLeastOneOf []string `yaml:"at_least_one_of"` + + // A list of properties that exactly one of must be set. + ExactlyOneOf []string `yaml:"exactly_one_of"` + + // A list of properties that are required to be set together. + RequiredWith []string `yaml:"required_with"` + + // Can only be overridden - we should never set this ourselves. + // TODO: set a specific type intead of interface{} + NewType interface{} + + // A pattern that maps expected user input to expected API input. + // TODO: remove? + Pattern string + + Properties []Type + + EnumValues []string `yaml:"enum_values"` + + ItemType string `yaml:"item_type"` + + // ==================== + // Terraform Overrides + // ==================== + + // Adds a DiffSuppressFunc to the schema + DiffSuppressFunc string `yaml:"diff_suppress_func"` + + StateFunc string `yaml:"state_func"` // Adds a StateFunc to the schema + + Sensitive bool // Adds `Sensitive: true` to the schema + + // Does not set this value to the returned API value. Useful for fields + // like secrets where the returned API value is not helpful. + IgnoreRead bool `yaml:"ignore_read"` + + // Adds a ValidateFunc to the schema + Validation bool + + // Indicates that this is an Array that should have Set diff semantics. + UnorderedList bool `yaml:"unordered_list"` + + IsSet bool `yaml:"is_set"` // Uses a Set instead of an Array + + // Optional function to determine the unique ID of an item in the set + // If not specified, schema.HashString (when elements are string) or + // schema.HashSchema are used. + SetHashFunc string `yaml:"set_hash_func"` + + // if true, then we get the default value from the Google API if no value + // is set in the terraform configuration for this field. + // It translates to setting the field to Computed & Optional in the schema. + // For nested fields, this only applies at the current level. This means + // it should be explicitly added to each field that needs the defaulting + // behavior. + DefaultFromApi bool `yaml:"default_from_api"` + + // https://github.com/hashicorp/terraform/pull/20837 + // Apply a ConfigMode of SchemaConfigModeAttr to the field. + // This should be avoided for new fields, and only used with old ones. + SchemaConfigModeAttr bool `yaml:"schema_config_mode_attr"` + + // Names of fields that should be included in the updateMask. + UpdateMaskFields []string `yaml:"update_mask_fields"` + + // For a TypeMap, the expander function to call on the key. + // Defaults to expandString. + KeyExpander string `yaml:"key_expander"` + + // For a TypeMap, the DSF to apply to the key. + KeyDiffSuppressFunc string `yaml:"key_diff_suppress_func"` + + // ==================== + // Schema Modifications + // ==================== + // Schema modifications change the schema of a resource in some + // fundamental way. They're not very portable, and will be hard to + // generate so we should limit their use. Generally, if you're not + // converting existing Terraform resources, these shouldn't be used. + // + // With great power comes great responsibility. + + // Flattens a NestedObject by removing that field from the Terraform + // schema but will preserve it in the JSON sent/retrieved from the API + // + // EX: a API schema where fields are nested (eg: `one.two.three`) and we + // desire the properties of the deepest nested object (eg: `three`) to + // become top level properties in the Terraform schema. By overriding + // the properties `one` and `one.two` and setting flatten_object then + // all the properties in `three` will be at the root of the TF schema. + // + // We need this for cases where a field inside a nested object has a + // default, if we can't spend a breaking change to fix a misshapen + // field, or if the UX is _much_ better otherwise. + // + // WARN: only fully flattened properties are currently supported. In the + // example above you could not flatten `one.two` without also flattening + // all of it's parents such as `one` + FlattenObject bool `yaml:"flatten_object"` + + // =========== + // Custom code + // =========== + // All custom code attributes are string-typed. The string should + // be the name of a template file which will be compiled in the + // specified / described place. + + // A custom expander replaces the default expander for an attribute. + // It is called as part of Create, and as part of Update if + // object.input is false. It can return an object of any type, + // so the function header *is* part of the custom code template. + // As with flatten, `property` and `prefix` are available. + CustomExpand string `yaml:"custom_expand"` + + // A custom flattener replaces the default flattener for an attribute. + // It is called as part of Read. It can return an object of any + // type, and may sometimes need to return an object with non-interface{} + // type so that the d.Set() call will succeed, so the function + // header *is* a part of the custom code template. To help with + // creating the function header, `property` and `prefix` are available, + // just as they are in the standard flattener template. + CustomFlatten string `yaml:"custom_flatten"` + + __resource Resource + + // TODO: set a specific type intead of interface{} + __parent interface{} // is nil for top-level properties +} + +const MAX_NAME = 20 + +// func (t *Type) validate() { +// super +// check :description, type: ::String, required: true +// check :exclude, type: :boolean, default: false, required: true +// check :deprecation_message, type: ::String +// check :removed_message, type: ::String +// check :min_version, type: ::String +// check :exact_version, type: ::String +// check :output, type: :boolean +// check :required, type: :boolean +// check :send_empty_value, type: :boolean +// check :allow_empty_object, type: :boolean +// check :url_param_only, type: :boolean +// check :read_query_params, type: ::String +// check :immutable, type: :boolean + +// raise 'Property cannot be output and required at the same time.' \ +// if @output && @required + +// check :update_verb, type: Symbol, allowed: %i[POST PUT PATCH NONE], +// default: @__resource&.update_verb + +// check :update_url, type: ::String +// check :update_id, type: ::String +// check :fingerprint_name, type: ::String +// check :pattern, type: ::String + +// check_default_value_property +// check_conflicts +// check_at_least_one_of +// check_exactly_one_of +// check_required_with + +// check :sensitive, type: :boolean, default: false +// check :is_set, type: :boolean, default: false +// check :default_from_api, type: :boolean, default: false +// check :unordered_list, type: :boolean, default: false +// check :schema_config_mode_attr, type: :boolean, default: false + +// // technically set as a default everywhere, but only maps will use this. +// check :key_expander, type: ::String, default: 'tpgresource.ExpandString' +// check :key_diff_suppress_func, type: ::String + +// check :diff_suppress_func, type: ::String +// check :state_func, type: ::String +// check :validation, type: Provider::Terraform::Validation +// check :set_hash_func, type: ::String + +// check :custom_flatten, type: ::String +// check :custom_expand, type: ::String + +// raise "'default_value' and 'default_from_api' cannot be both set" \ +// if @default_from_api && !@default_value.nil? +// } + +// func (t *Type) to_s() { +// JSON.pretty_generate(self) +// } + +// Prints a dot notation path to where the field is nested within the parent +// object. eg: parent.meta.label.foo +// The only intended purpose is to allow better error messages. Some objects +// and at some points in the build this doesn't output a valid output. +// func (t *Type) lineage() { +// return name&.underscore if __parent.nil? + +// "//{__parent.lineage}.//{name&.underscore}" +// } + +// Prints the access path of the field in the configration eg: metadata.0.labels +// The only intended purpose is to get the value of the labes field by calling d.Get(). +// func (t *Type) terraform_lineage() { +// return name&.underscore if __parent.nil? || __parent.flatten_object + +// "//{__parent.terraform_lineage}.0.//{name&.underscore}" +// } + +// func (t *Type) to_json(opts) { +// ignore fields that will contain references to parent resources and +// those which will be added later +// ignored_fields = %i[@resource @__parent @__resource @api_name @update_verb +// @__name @name @properties] +// json_out = {} + +// instance_variables.each do |v| +// if v == :@conflicts && instance_variable_get(v).empty? +// // ignore empty conflict arrays +// elsif v == :@at_least_one_of && instance_variable_get(v).empty? +// // ignore empty at_least_one_of arrays +// elsif v == :@exactly_one_of && instance_variable_get(v).empty? +// // ignore empty exactly_one_of arrays +// elsif v == :@required_with && instance_variable_get(v).empty? +// // ignore empty required_with arrays +// elsif instance_variable_get(v) == false || instance_variable_get(v).nil? +// // ignore false booleans as non-existence indicates falsey +// elsif !ignored_fields.include? v +// json_out[v] = instance_variable_get(v) +// end +// end + +// // convert properties to a hash based on name for nested readability +// json_out.merge!(properties&.map { |p| [p.name, p] }.to_h) \ +// if respond_to? 'properties' + +// JSON.generate(json_out, opts) +// } + +// func (t *Type) check_default_value_property() { +// return if @default_value.nil? + +// case self +// when Api::Type::String +// clazz = ::String +// when Api::Type::Integer +// clazz = ::Integer +// when Api::Type::Double +// clazz = ::Float +// when Api::Type::Enum +// clazz = ::Symbol +// when Api::Type::Boolean +// clazz = :boolean +// when Api::Type::ResourceRef +// clazz = [::String, ::Hash] +// else +// raise "Update 'check_default_value_property' method to support " \ +// "default value for type //{self.class}" +// end + +// check :default_value, type: clazz +// } + +// Checks that all conflicting properties actually exist. +// This currently just returns if empty, because we don't want to do the check, since +// this list will have a full path for nested attributes. +// func (t *Type) check_conflicts() { +// check :conflicts, type: ::Array, default: [], item_type: ::String + +// return if @conflicts.empty? +// } + +// Returns list of properties that are in conflict with this property. +// func (t *Type) conflicting() { +// return [] unless @__resource + +// @conflicts +// } + +// Checks that all properties that needs at least one of their fields actually exist. +// This currently just returns if empty, because we don't want to do the check, since +// this list will have a full path for nested attributes. +// func (t *Type) check_at_least_one_of() { +// check :at_least_one_of, type: ::Array, default: [], item_type: ::String + +// return if @at_least_one_of.empty? +// } + +// Returns list of properties that needs at least one of their fields set. +// func (t *Type) at_least_one_of_list() { +// return [] unless @__resource + +// @at_least_one_of +// } + +// Checks that all properties that needs exactly one of their fields actually exist. +// This currently just returns if empty, because we don't want to do the check, since +// this list will have a full path for nested attributes. +// func (t *Type) check_exactly_one_of() { +// check :exactly_one_of, type: ::Array, default: [], item_type: ::String + +// return if @exactly_one_of.empty? +// } + +// Returns list of properties that needs exactly one of their fields set. +// func (t *Type) exactly_one_of_list() { +// return [] unless @__resource + +// @exactly_one_of +// } + +// Checks that all properties that needs required with their fields actually exist. +// This currently just returns if empty, because we don't want to do the check, since +// this list will have a full path for nested attributes. +// func (t *Type) check_required_with() { +// check :required_with, type: ::Array, default: [], item_type: ::String + +// return if @required_with.empty? +// } + +// Returns list of properties that needs required with their fields set. +// func (t *Type) required_with_list() { +// // return [] unless @__resource + +// // @required_with +// } + +// func (t *Type) type() { +// // self.class.name.split('::').last +// } + +// func (t *Type) parent() { +// // @__parent +// } + +// func (t *Type) min_version() { +// // if @min_version.nil? +// // @__resource.min_version +// // else +// // @__resource.__product.version_obj(@min_version) +// // end +// } + +// func (t *Type) exact_version() { +// // return nil if @exact_version.nil? || @exact_version.empty? + +// // @__resource.__product.version_obj(@exact_version) +// } + +// func (t *Type) exclude_if_not_in_version!(version) { +// // @exclude ||= exact_version != version unless exact_version.nil? +// // @exclude ||= version < min_version +// } + +// // Overriding is_a? to enable class overrides. +// // Ruby does not let you natively change types, so this is the next best +// // thing. +// func (t *Type) is_a?(clazz) { +// // return Module.const_get(@new_type).new.is_a?(clazz) if @new_type + +// // super(clazz) +// } + +// // Overriding class to enable class overrides. +// // Ruby does not let you natively change types, so this is the next best +// // thing. +// func (t *Type) class() { +// // return Module.const_get(@new_type) if @new_type + +// // super +// } + +// // Returns nested properties for this property. +// func (t *Type) nested_properties() { +// // nil +// } + +// func (t *Type) removed() { +// // !(@removed_message.nil? || @removed_message == '') +// } + +// func (t *Type) deprecated() { +// // !(@deprecation_message.nil? || @deprecation_message == '') +// } + +// // private + +// // A constant value to be provided as field +// type Constant struct { +// // < Type +// value + +// func (t *Type) validate +// @description = "This is always //{value}." +// super +// end +// } + +// // Represents a primitive (non-composite) type. +// class Primitive < Type +// end + +// // Represents a boolean +// class Boolean < Primitive +// end + +// // Represents an integer +// class Integer < Primitive +// end + +// // Represents a double +// class Double < Primitive +// end + +// // Represents a string +// class String < Primitive +// func (t *Type) initialize(name = nil) +// super() + +// @name = name +// end + +// PROJECT = Api::Type::String.new('project') +// NAME = Api::Type::String.new('name') +// end + +// // Properties that are fetched externally +// class FetchedExternal < Type + +// func (t *Type) validate +// @conflicts ||= [] +// @at_least_one_of ||= [] +// @exactly_one_of ||= [] +// @required_with ||= [] +// end + +// func (t *Type) api_name +// name +// end +// end + +// class Path < Primitive +// end + +// // Represents a fingerprint. A fingerprint is an output-only +// // field used for optimistic locking during updates. +// // They are fetched from the GCP response. +// class Fingerprint < FetchedExternal +// func (t *Type) validate +// super +// @output = true if @output.nil? +// end +// end + +// // Represents a timestamp +// class Time < Primitive +// end + +// // A base class to tag objects that are composed by other objects (arrays, +// // nested objects, etc) +// class Composite < Type +// end + +// // Forwarding declaration to allow defining Array::NESTED_ARRAY_TYPE +// class NestedObject < Composite +// end + +// // Forwarding declaration to allow defining Array::RREF_ARRAY_TYPE +// class ResourceRef < Type +// end + +// // Represents an array, and stores its items' type +// class Array < Composite +// item_type +// min_size +// max_size + +// func (t *Type) validate +// super +// if @item_type.is_a?(NestedObject) || @item_type.is_a?(ResourceRef) +// @item_type.set_variable(@name, :__name) +// @item_type.set_variable(@__resource, :__resource) +// @item_type.set_variable(self, :__parent) +// end +// check :item_type, type: [::String, NestedObject, ResourceRef, Enum], required: true + +// unless @item_type.is_a?(NestedObject) || @item_type.is_a?(ResourceRef) \ +// || @item_type.is_a?(Enum) || type?(@item_type) +// raise "Invalid type //{@item_type}" +// end + +// check :min_size, type: ::Integer +// check :max_size, type: ::Integer +// end + +// func (t *Type) property_class +// case @item_type +// when NestedObject, ResourceRef +// type = @item_type.property_class +// when Enum +// raise 'aaaa' +// else +// type = property_ns_prefix +// type << get_type(@item_type).new(@name).type +// end +// type[-1] = "//{type[-1].camelize(:upper)}Array" +// type +// end + +// func (t *Type) exclude_if_not_in_version!(version) +// super +// @item_type.exclude_if_not_in_version!(version) \ +// if @item_type.is_a? NestedObject +// end + +// func (t *Type) nested_properties +// return @item_type.nested_properties.reject(&:exclude) \ +// if @item_type.is_a?(Api::Type::NestedObject) + +// super +// end + +// func (t *Type) item_type_class +// return @item_type \ +// if @item_type.instance_of?(Class) + +// Object.const_get(@item_type) +// end +// end + +// // Represents an enum, and store is valid values +// class Enum < Primitive +// values +// skip_docs_values + +// func (t *Type) validate +// super +// check :values, type: ::Array, item_type: [Symbol, ::String, ::Integer], required: true +// check :skip_docs_values, type: :boolean +// end + +// func (t *Type) merge(other) +// result = self.class.new +// instance_variables.each do |v| +// result.instance_variable_set(v, instance_variable_get(v)) +// end + +// other.instance_variables.each do |v| +// if other.instance_variable_get(v).instance_of?(Array) +// result.instance_variable_set(v, deep_merge(result.instance_variable_get(v), +// other.instance_variable_get(v))) +// else +// result.instance_variable_set(v, other.instance_variable_get(v)) +// end +// end + +// result +// end +// end + +// // Represents a 'selfLink' property, which returns the URI of the resource. +// class SelfLink < FetchedExternal +// EXPORT_KEY = 'selfLink'.freeze + +// resource + +// func (t *Type) name +// EXPORT_KEY +// end + +// func (t *Type) out_name +// EXPORT_KEY.underscore +// end +// end + +// // Represents a reference to another resource +// class ResourceRef < Type +// // The fields which can be overridden in provider.yaml. +// module Fields +// resource +// imports +// end +// include Fields + +// func (t *Type) validate +// super +// @name = @resource if @name.nil? +// @description = "A reference to //{@resource} resource" \ +// if @description.nil? + +// return if @__resource.nil? || @__resource.exclude || @exclude + +// check :resource, type: ::String, required: true +// check :imports, type: ::String, required: TrueClass + +// // TODO: (camthornton) product reference may not exist yet +// return if @__resource.__product.nil? + +// check_resource_ref_property_exists +// end + +// func (t *Type) property +// props = resource_ref.all_user_properties +// .select { |prop| prop.name == @imports } +// return props.first unless props.empty? +// end + +// func (t *Type) resource_ref +// product = @__resource.__product +// resources = product.objects.select { |obj| obj.name == @resource } + +// resources[0] +// end + +// func (t *Type) property_class +// type = property_ns_prefix +// type << [@resource, @imports, 'Ref'] +// type[-1] = type[-1].join('_').camelize(:upper) +// type +// end + +// private + +// func (t *Type) check_resource_ref_property_exists +// return unless defined?(resource_ref.all_user_properties) + +// exported_props = resource_ref.all_user_properties +// exported_props << Api::Type::String.new('selfLink') \ +// if resource_ref.has_self_link +// raise "'//{@imports}' does not exist on '//{@resource}'" \ +// if exported_props.none? { |p| p.name == @imports } +// end +// end + +// // An structured object composed of other objects. +// class NestedObject < Composite + +// func (t *Type) validate +// @description = 'A nested object resource' if @description.nil? +// @name = @__name if @name.nil? +// super + +// raise "Properties missing on //{name}" if @properties.nil? + +// @properties.each do |p| +// p.set_variable(@__resource, :__resource) +// p.set_variable(self, :__parent) +// end +// check :properties, type: ::Array, item_type: Api::Type, required: true +// end + +// func (t *Type) property_class +// type = property_ns_prefix +// type << [@__resource.name, @name] +// type[-1] = type[-1].join('_').camelize(:upper) +// type +// end + +// // Returns all properties including the ones that are excluded +// // This is used for PropertyOverride validation +// func (t *Type) all_properties +// @properties +// end + +// func (t *Type) properties +// raise "Field '//{lineage}' properties are nil!" if @properties.nil? + +// @properties.reject(&:exclude) +// end + +// func (t *Type) nested_properties +// properties +// end + +// // Returns the list of top-level properties once any nested objects with +// // flatten_object set to true have been collapsed +// func (t *Type) root_properties +// properties.flat_map do |p| +// if p.flatten_object +// p.root_properties +// else +// p +// end +// end +// end + +// func (t *Type) exclude_if_not_in_version!(version) +// super +// @properties.each { |p| p.exclude_if_not_in_version!(version) } +// 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. +// class KeyValuePairs < Composite +// // Ignore writing the "effective_labels" and "effective_annotations" fields to API. +// ignore_write + +// func (t *Type) initialize(name: nil, output: nil, api_name: nil, description: nil, min_version: nil, +// ignore_write: nil, update_verb: nil, update_url: nil, immutable: nil) +// super() + +// @name = name +// @output = output +// @api_name = api_name +// @description = description +// @min_version = min_version +// @ignore_write = ignore_write +// @update_verb = update_verb +// @update_url = update_url +// @immutable = immutable +// end + +// func (t *Type) validate +// super +// check :ignore_write, type: :boolean, default: false + +// return if @__resource.__product.nil? + +// product_name = @__resource.__product.name +// resource_name = @__resource.name + +// if lineage == 'labels' || lineage == 'metadata.labels' || +// lineage == 'configuration.labels' +// if !(is_a? Api::Type::KeyValueLabels) && +// // The label value must be empty string, so skip this resource +// !(product_name == 'CloudIdentity' && resource_name == 'Group') && + +// // The "labels" field has type Array, so skip this resource +// !(product_name == 'DeploymentManager' && resource_name == 'Deployment') && + +// // https://github.com/hashicorp/terraform-provider-google/issues/16219 +// !(product_name == 'Edgenetwork' && resource_name == 'Network') && + +// // https://github.com/hashicorp/terraform-provider-google/issues/16219 +// !(product_name == 'Edgenetwork' && resource_name == 'Subnet') && + +// // "userLabels" is the resource labels field +// !(product_name == 'Monitoring' && resource_name == 'NotificationChannel') && + +// // The "labels" field has type Array, so skip this resource +// !(product_name == 'Monitoring' && resource_name == 'MetricDescriptor') +// raise "Please use type KeyValueLabels for field //{lineage} " \ +// "in resource //{product_name}///{resource_name}" +// end +// elsif is_a? Api::Type::KeyValueLabels +// raise "Please don't use type KeyValueLabels for field //{lineage} " \ +// "in resource //{product_name}///{resource_name}" +// end + +// if lineage == 'annotations' || lineage == 'metadata.annotations' +// if !(is_a? Api::Type::KeyValueAnnotations) && +// // The "annotations" field has "ouput: true", so skip this eap resource +// !(product_name == 'Gkeonprem' && resource_name == 'BareMetalAdminClusterEnrollment') +// raise "Please use type KeyValueAnnotations for field //{lineage} " \ +// "in resource //{product_name}///{resource_name}" +// end +// elsif is_a? Api::Type::KeyValueAnnotations +// raise "Please don't use type KeyValueAnnotations for field //{lineage} " \ +// "in resource //{product_name}///{resource_name}" +// end +// end + +// func (t *Type) field_min_version +// @min_version +// end +// end + +// // An array of string -> string key -> value pairs used specifically for the "labels" field. +// // The field name with this type should be "labels" literally. +// class KeyValueLabels < KeyValuePairs +// func (t *Type) validate +// super +// return unless @name != 'labels' + +// raise "The field //{name} has the type KeyValueLabels, but the field name is not 'labels'!" +// end +// end + +// // An array of string -> string key -> value pairs used for the "terraform_labels" field. +// class KeyValueTerraformLabels < KeyValuePairs +// end + +// // An array of string -> string key -> value pairs used for the "effective_labels" +// // and "effective_annotations" fields. +// class KeyValueEffectiveLabels < KeyValuePairs +// end + +// // An array of string -> string key -> value pairs used specifically for the "annotations" field. +// // The field name with this type should be "annotations" literally. +// class KeyValueAnnotations < KeyValuePairs +// func (t *Type) validate +// super +// return unless @name != 'annotations' + +// raise "The field //{name} has the type KeyValueAnnotations,\ +// but the field name is not 'annotations'!" +// end +// end + +// // Map from string keys -> nested object entries +// class Map < Composite +// // .yaml. +// module Fields +// // The type definition of the contents of the map. +// value_type + +// // While the API doesn't give keys an explicit name, we specify one +// // because in Terraform the key has to be a property of the object. +// // +// // The name of the key. Used in the Terraform schema as a field name. +// key_name + +// // A description of the key's format. Used in Terraform to describe +// // the field in documentation. +// key_description +// end +// include Fields + +// func (t *Type) validate +// super +// check :key_name, type: ::String, required: true +// check :key_description, type: ::String + +// @value_type.set_variable(@name, :__name) +// @value_type.set_variable(@__resource, :__resource) +// @value_type.set_variable(self, :__parent) +// check :value_type, type: Api::Type::NestedObject, required: true +// raise "Invalid type //{@value_type}" unless type?(@value_type) +// end + +// func (t *Type) nested_properties +// @value_type.nested_properties.reject(&:exclude) +// end +// end + +// // Support for schema ValidateFunc functionality. +// class Validation < Object +// // Ensures the value matches this regex +// regex +// function + +// func (t *Type) validate +// super + +// check :regex, type: String +// check :function, type: String +// end +// end + +// func (t *Type) type?(type) +// type.is_a?(Type) || !get_type(type).nil? +// end + +// func (t *Type) get_type(type) +// Module.const_get(type) +// end + +// func (t *Type) property_ns_prefix +// [ +// 'Google', +// @__resource.__product.name.camelize(:upper), +// 'Property' +// ] +// end +// end diff --git a/mmv1/compiler.rb b/mmv1/compiler.rb index 4767f53c5f6d..9b2160026bc4 100755 --- a/mmv1/compiler.rb +++ b/mmv1/compiler.rb @@ -177,7 +177,8 @@ resources = [] Dir["#{product_name}/*"].each do |file_path| next if File.basename(file_path) == 'product.yaml' \ - || File.extname(file_path) != '.yaml' + || File.extname(file_path) != '.yaml' \ + || File.basename(file_path).include?('go_') if override_dir # Skip if resource will be merged in the override loop @@ -197,7 +198,8 @@ ovr_prod_dir = File.join(override_dir, product_name) Dir["#{ovr_prod_dir}/*"].each do |override_path| next if File.basename(override_path) == 'product.yaml' \ - || File.extname(override_path) != '.yaml' + || File.extname(override_path) != '.yaml' \ + || File.basename(file_path).include?('go_') file_path = File.join(product_name, File.basename(override_path)) res_yaml = if File.exist?(file_path) diff --git a/mmv1/go.mod b/mmv1/go.mod new file mode 100644 index 000000000000..e9a5cebeee40 --- /dev/null +++ b/mmv1/go.mod @@ -0,0 +1,5 @@ +module github.com/GoogleCloudPlatform/magic-modules/mmv1 + +go 1.20 + +require gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/mmv1/go.sum b/mmv1/go.sum new file mode 100644 index 000000000000..75346616b19b --- /dev/null +++ b/mmv1/go.sum @@ -0,0 +1,3 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/mmv1/google/yaml_validator.go b/mmv1/google/yaml_validator.go new file mode 100644 index 000000000000..282b43b5f41e --- /dev/null +++ b/mmv1/google/yaml_validator.go @@ -0,0 +1,155 @@ +// 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. +// 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. + +package google + +import ( + "log" + + "gopkg.in/yaml.v2" +) + +// A helper class to validate contents coming from YAML files. +type YamlValidator struct{} + +func (v *YamlValidator) Parse(content []byte, obj interface{}) { + // 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) + } +} + +// func (v *YamlValidator) allowed_classes() { +// ObjectSpace.each_object(Class).select do |klass| +// klass < Google::YamlValidator +// end.push(Time, Symbol) +// } + +// func (v *YamlValidator) validate() { +// Google::LOGGER.debug "Validating //{self.class} '//{@name}'" +// check_extraneous_properties +// } + +// func (v *YamlValidator) set_variable(value, property) { +// Google::LOGGER.debug "Setting variable of //{value} to //{self}" +// instance_variable_set("@//{property}", value) +// } + +// Does all validation checking for a particular variable. +// options: +// :default - the default value for this variable if its nil +// :type - the allowed types (single or array) that this value can be +// :item_type - the allowed types that all values in this array should be +// (implied that type == array) +// :allowed - the allowed values that this non-array variable should be. +// :required - is the variable required? (defaults: false) +// func (v *YamlValidator) check(variable, **opts) { +// value = instance_variable_get("@//{variable}") + +// // Set default value. +// if !opts[:default].nil? && value.nil? +// instance_variable_set("@//{variable}", opts[:default]) +// value = instance_variable_get("@//{variable}") +// end + +// // Check if value is required. Print nested path if available. +// lineage_path = respond_to?('lineage') ? lineage : '' +// raise "//{lineage_path} > Missing '//{variable}'" if value.nil? && opts[:required] +// return if value.nil? + +// // Check type +// check_property_value(variable, value, opts[:type]) if opts[:type] + +// // Check item_type +// if value.is_a?(Array) +// raise "//{lineage_path} > //{variable} must have item_type on arrays" unless opts[:item_type] + +// value.each_with_index do |o, index| +// check_property_value("//{variable}[//{index}]", o, opts[:item_type]) +// end +// end + +// // Check if value is allowed +// return unless opts[:allowed] +// raise "//{value} on //{variable} should be one of //{opts[:allowed]}" \ +// unless opts[:allowed].include?(value) +// } + +// func (v *YamlValidator) conflicts(list) { +// value_checked = false +// list.each do |item| +// next if instance_variable_get("@//{item}").nil? +// raise "//{list.join(',')} cannot be set at the same time" if value_checked + +// value_checked = true +// end +// } + +// private + +// func (v *YamlValidator) check_type(name, object, type) { +// if type == :boolean +// return unless [TrueClass, FalseClass].find_index(object.class).nil? +// elsif type.is_a? ::Array +// return if type.find_index(:boolean) && [TrueClass, FalseClass].find_index(object.class) +// return unless type.find_index(object.class).nil? +// // check if class is or inherits from type +// elsif object.class <= type +// return +// end +// raise "Property '//{name}' is '//{object.class}' instead of '//{type}'" +// } + +// func (v *YamlValidator) log_check_type(object) { +// if object.respond_to?(:name) +// Google::LOGGER.debug "Checking object //{object.name}" +// else +// Google::LOGGER.debug "Checking object //{object}" +// end +// } + +// func (v *YamlValidator) check_property_value(property, prop_value, type) { +// Google::LOGGER.debug "Checking '//{property}' on //{object_display_name}" +// check_type property, prop_value, type unless type.nil? +// prop_value.validate if prop_value.is_a?(Api::Object) +// } + +// func (v *YamlValidator) check_extraneous_properties() { +// instance_variables.each do |variable| +// var_name = variable.id2name[1..] +// next if var_name.start_with?('__') + +// Google::LOGGER.debug "Validating '//{var_name}' on //{object_display_name}" +// raise "Extraneous variable '//{var_name}' in //{object_display_name}" \ +// unless methods.include?(var_name.intern) +// end +// } + +// func (v *YamlValidator) set_variables(objects, property) { +// return if objects.nil? + +// objects.each do |object| +// object.set_variable(self, property) if object.respond_to?(:set_variable) +// end +// } + +// func (v *YamlValidator) ensure_property_does_not_exist(property) { +// raise "Conflict of property '//{property}' for object '//{self}'" \ +// unless instance_variable_get("@//{property}").nil? +// } + +// func (v *YamlValidator) object_display_name() { +// "//{@name}" +// } diff --git a/mmv1/main.go b/mmv1/main.go new file mode 100644 index 000000000000..a7726a9a22f1 --- /dev/null +++ b/mmv1/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path" + "path/filepath" + "sort" + "strings" + + "github.com/GoogleCloudPlatform/magic-modules/mmv1/api" + "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" +) + +func main() { + var products_to_generate []string + var all_products = true + + var all_product_files []string = make([]string, 0) + + files, err := filepath.Glob("products/**/product.yaml") + if err != nil { + return + } + for _, file_path := range files { + dir := filepath.Dir(file_path) + all_product_files = append(all_product_files, fmt.Sprintf("products/%s", filepath.Base(dir))) + } + + if all_products { + products_to_generate = all_product_files + } + + if products_to_generate == nil || len(products_to_generate) == 0 { + log.Fatalf("No product.yaml file found.") + } + + // Building compute takes a long time and can't be parallelized within the product + // so lets build it first + sort.Slice(all_product_files, func(i int, j int) bool { + if all_product_files[i] == "compute" { + return true + } + return false + }) + + yamlValidator := google.YamlValidator{} + + for _, product_name := range all_product_files { + product_yaml_path := path.Join(product_name, "go_product.yaml") + + // TODO: uncomment the error check that if the product.yaml exists for each product + // after Go-converted product.yaml files are complete for all products + + // if _, err := os.Stat(product_yaml_path); errors.Is(err, os.ErrNotExist) { + // log.Fatalf("%s does not contain a product.yaml file", product_name) + // } + + if _, err := os.Stat(product_yaml_path); err == nil { + log.Printf("product_yaml_path %#v", product_yaml_path) + + productYaml, err := os.ReadFile(product_yaml_path) + if err != nil { + log.Fatalf("Cannot open the file: %v", productYaml) + } + productApi := api.Product{} + yamlValidator.Parse(productYaml, &productApi) + + // TODO: remove these lines, which are for debugging + prod, _ := json.Marshal(&productApi) + log.Printf("prod %s", string(prod)) + + resourceFiles, err := filepath.Glob(fmt.Sprintf("%s/*", product_name)) + if err != nil { + log.Fatalf("Cannot get resources files: %v", err) + } + for _, resourceYamlPath := range resourceFiles { + if filepath.Base(resourceYamlPath) == "product.yaml" || filepath.Ext(resourceYamlPath) != ".yaml" { + continue + } + + // TODO REMOVE: limiting test block + // if !strings.Contains(resourceYamlPath, "datafusion") { + // continue + // } + + // Prepend "go_" to the Go yaml files' name to distinguish with the ruby yaml files + if filepath.Base(resourceYamlPath) == "go_product.yaml" || !strings.HasPrefix(filepath.Base(resourceYamlPath), "go_") { + continue + } + + log.Printf(" resourceYamlPath %s", resourceYamlPath) + resourceYaml, err := os.ReadFile(resourceYamlPath) + if err != nil { + log.Fatalf("Cannot open the file: %v", resourceYamlPath) + } + resource := api.Resource{} + yamlValidator.Parse(resourceYaml, &resource) + + // TODO: remove these lines, which are for debugging + res, _ := json.Marshal(&resource) + log.Printf("resource %s", string(res)) + } + } + } +} diff --git a/mmv1/products/datafusion/go_instance.yaml b/mmv1/products/datafusion/go_instance.yaml new file mode 100644 index 000000000000..729cf2079292 --- /dev/null +++ b/mmv1/products/datafusion/go_instance.yaml @@ -0,0 +1,298 @@ +# 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. +# 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. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'Instance' +description: | + Represents a Data Fusion instance. +references: + guides: + 'Official Documentation': 'https://cloud.google.com/data-fusion/docs/' + api: 'https://cloud.google.com/data-fusion/docs/reference/rest/v1beta1/projects.locations.instances' +docs: +base_url: 'projects/{{project}}/locations/{{region}}/instances' +create_url: 'projects/{{project}}/locations/{{region}}/instances?instanceId={{name}}' +update_verb: 'PATCH' +timeouts: + insert_minutes: 90 + update_minutes: 25 + delete_minutes: 50 +autogen_async: true +async: + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' +iam_policy: + method_name_separator: ':' + parent_resource_attribute: 'name' + import_format: + - 'projects/{{project}}/locations/{{location}}/instances/{{name}}' + - '{{name}}' +custom_code: + constants: 'templates/terraform/constants/data_fusion_instance_option.go.erb' + pre_update: 'templates/terraform/pre_update/datafusion_instance_update.go.erb' +examples: + - name: 'data_fusion_instance_basic' + primary_resource_id: 'basic_instance' + primary_resource_name: 'basic_instance' + vars: + instance_name: 'my-instance' + prober_test_run: '' + test_vars_overrides: + 'prober_test_run': '`options = { prober_test_run = "true" }`' + - name: 'data_fusion_instance_full' + primary_resource_id: 'extended_instance' + primary_resource_name: 'extended_instance' + vars: + instance_name: 'my-instance' + ip_alloc: 'datafusion-ip-alloc' + network_name: 'datafusion-full-network' + prober_test_run: '' + test_vars_overrides: + 'prober_test_run': '`options = { prober_test_run = "true" }`' + - name: 'data_fusion_instance_cmek' + primary_resource_id: 'cmek' + primary_resource_name: 'cmek' + vars: + instance_name: 'my-instance' + - name: 'data_fusion_instance_enterprise' + primary_resource_id: 'enterprise_instance' + primary_resource_name: 'enterprise_instance' + vars: + instance_name: 'my-instance' + prober_test_run: '' + test_vars_overrides: + 'prober_test_run': '`options = { prober_test_run = "true" }`' + - name: 'data_fusion_instance_event' + primary_resource_id: 'event' + primary_resource_name: 'event' + vars: + instance_name: 'my-instance' + - name: 'data_fusion_instance_zone' + primary_resource_id: 'zone' + primary_resource_name: 'zone' + vars: + instance_name: 'my-instance' +parameters: + - name: 'region' + type: String + description: "The region of the Data Fusion instance." + url_param_only: true + required: false + immutable: true + ignore_read: true + default_from_api: true +properties: + - name: 'name' + type: String + description: "The ID of the instance or a fully qualified identifier for the instance." + required: true + immutable: true + custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.erb' + custom_expand: 'templates/terraform/custom_expand/shortname_to_url.go.erb' + - name: 'description' + type: String + description: "An optional description of the instance." + immutable: true + - name: 'type' + type: Enum + description: "Represents the type of Data Fusion instance. Each type is configured with +the default settings for processing and memory. +- BASIC: Basic Data Fusion instance. In Basic type, the user will be able to create data pipelines +using point and click UI. However, there are certain limitations, such as fewer number +of concurrent pipelines, no support for streaming pipelines, etc. +- ENTERPRISE: Enterprise Data Fusion instance. In Enterprise type, the user will have more features +available, such as support for streaming pipelines, higher number of concurrent pipelines, etc. +- DEVELOPER: Developer Data Fusion instance. In Developer type, the user will have all features available but +with restrictive capabilities. This is to help enterprises design and develop their data ingestion and integration +pipelines at low cost." + required: true + immutable: true + enum_values: + - 'BASIC' + - 'ENTERPRISE' + - 'DEVELOPER' + - name: 'enableStackdriverLogging' + type: Boolean + description: "Option to enable Stackdriver Logging." + - name: 'enableStackdriverMonitoring' + type: Boolean + description: "Option to enable Stackdriver Monitoring." + - name: 'enableRbac' + type: Boolean + description: "Option to enable granular role-based access control." + - 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." + immutable: false + - name: 'options' + type: KeyValuePairs + description: "Map of additional options used to configure the behavior of Data Fusion instance." + immutable: true + default_from_api: true + diff_suppress_func: 'instanceOptionsDiffSuppress' + - name: 'createTime' + type: String + description: "The time the instance was created in RFC3339 UTC 'Zulu' format, accurate to nanoseconds." + output: true + - name: 'updateTime' + type: String + description: "The time the instance was last updated in RFC3339 UTC 'Zulu' format, accurate to nanoseconds." + output: true + - name: 'state' + type: Enum + description: "The current state of this Data Fusion instance. +- CREATING: Instance is being created +- RUNNING: Instance is running and ready for requests +- FAILED: Instance creation failed +- DELETING: Instance is being deleted +- UPGRADING: Instance is being upgraded +- RESTARTING: Instance is being restarted" + output: true + enum_values: + - 'CREATING' + - 'RUNNING' + - 'FAILED' + - 'DELETING' + - 'UPGRADING' + - 'RESTARTING' + - name: 'stateMessage' + type: String + description: "Additional information about the current state of this Data Fusion instance if available." + output: true + - name: 'serviceEndpoint' + type: String + description: "Endpoint on which the Data Fusion UI and REST APIs are accessible." + output: true + - name: 'version' + type: String + description: "Current version of the Data Fusion." + default_from_api: true + - name: 'serviceAccount' + type: String + description: "Service account which will be used to access resources in the customer project." + min_version: 'beta' + output: true + deprecation_message: '`service_account` is deprecated and will be removed in a future major release. Instead, use `tenant_project_id` to extract the tenant project ID.' + - name: 'privateInstance' + type: Boolean + description: "Specifies whether the Data Fusion instance should be private. If set to +true, all Data Fusion nodes will have private IP addresses and will not be +able to access the public internet." + immutable: true + - name: 'dataprocServiceAccount' + type: String + description: "User-managed service account to set on Dataproc when Cloud Data Fusion creates Dataproc to run data processing pipelines." + immutable: true + - name: 'tenantProjectId' + type: String + description: "The name of the tenant project." + output: true + - name: 'gcsBucket' + type: String + description: "Cloud Storage bucket generated by Data Fusion in the customer project." + output: true + - name: 'networkConfig' + type: NestedObject + description: "Network configuration options. These are required when a private Data Fusion instance is to be created." + immutable: true + properties: + - name: 'ipAllocation' + type: String + description: "The IP range in CIDR notation to use for the managed Data Fusion instance + nodes. This range must not overlap with any other ranges used in the Data Fusion instance network." + required: true + immutable: true + - name: 'network' + type: String + description: "Name of the network in the project with which the tenant project + will be peered for executing pipelines. In case of shared VPC where the network resides in another host + project the network should specified in the form of projects/{host-project-id}/global/networks/{network}" + required: true + immutable: true + - name: 'zone' + type: String + description: "Name of the zone in which the Data Fusion instance will be created. Only DEVELOPER instances use this field." + immutable: true + default_from_api: true + - name: 'displayName' + type: String + description: "Display name for an instance." + immutable: true + - name: 'apiEndpoint' + type: String + description: "Endpoint on which the REST APIs is accessible." + output: true + - name: 'p4ServiceAccount' + type: String + description: "P4 service account for the customer project." + output: true + - name: 'cryptoKeyConfig' + type: NestedObject + description: "The crypto key configuration. This field is used by the Customer-Managed Encryption Keys (CMEK) feature." + immutable: true + properties: + - name: 'keyReference' + type: String + description: "The name of the key which is used to encrypt/decrypt customer data. For key in Cloud KMS, the key should be in the format of projects/*/locations/*/keyRings/*/cryptoKeys/*." + required: true + immutable: true + - name: 'eventPublishConfig' + type: NestedObject + description: "Option to enable and pass metadata for event publishing." + properties: + - name: 'enabled' + type: Boolean + description: "Option to enable Event Publishing." + required: true + - name: 'topic' + type: String + description: "The resource name of the Pub/Sub topic. Format: projects/{projectId}/topics/{topic_id}" + required: true + immutable: true + - name: 'accelerators' + type: Array + description: "List of accelerators enabled for this CDF instance. + +If accelerators are enabled it is possible a permadiff will be created with the Options field. +Users will need to either manually update their state file to include these diffed options, or include the field in a [lifecycle ignore changes block](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#ignore_changes)." + item_type: NestedObject + properties: + - name: 'acceleratorType' + type: Enum + description: "The type of an accelator for a CDF instance." + required: true + enum_values: + - 'CDC' + - 'HEALTHCARE' + - 'CCAI_INSIGHTS' + - name: 'state' + type: Enum + description: "The type of an accelator for a CDF instance." + required: true + enum_values: + - 'ENABLED' + - 'DISABLED' diff --git a/mmv1/products/datafusion/go_product.yaml b/mmv1/products/datafusion/go_product.yaml new file mode 100644 index 000000000000..69c8c197dddc --- /dev/null +++ b/mmv1/products/datafusion/go_product.yaml @@ -0,0 +1,35 @@ +# 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. +# 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. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'DataFusion' +display_name: 'Cloud Data Fusion' +versions: + - name: 'beta' + base_url: 'https://datafusion.googleapis.com/v1beta1/' + - name: 'ga' + base_url: 'https://datafusion.googleapis.com/v1/' +scopes: + - 'https://www.googleapis.com/auth/cloud-platform' +async: + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: true + error: + path: 'error' + message: 'message' diff --git a/mmv1/products/pubsub/go_Schema.yaml b/mmv1/products/pubsub/go_Schema.yaml new file mode 100644 index 000000000000..edcdbbd7d7d5 --- /dev/null +++ b/mmv1/products/pubsub/go_Schema.yaml @@ -0,0 +1,84 @@ +# 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. +# 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. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'Schema' +description: | + A schema is a format that messages must follow, + creating a contract between publisher and subscriber that Pub/Sub will enforce. +references: + guides: + 'Creating and managing schemas': 'https://cloud.google.com/pubsub/docs/schemas' + api: 'https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.schemas' +docs: +base_url: 'projects/{{project}}/schemas' +create_url: 'projects/{{project}}/schemas?schemaId={{name}}' +update_url: 'projects/{{project}}/schemas/{{name}}:commit' +update_verb: 'POST' +update_mask: false +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +async: + check_response_func_existence: 'transport_tpg.PollCheckForExistence' + check_response_func_absence: 'transport_tpg.PollCheckForAbsence' + suppress_error: false + target_occurrences: 10 +iam_policy: + method_name_separator: ':' + parent_resource_attribute: 'schema' +custom_code: + update_encoder: 'templates/terraform/update_encoder/pubsub_schema.erb' +examples: + - name: 'pubsub_schema_basic' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + schema_name: 'example-schema' + - name: 'pubsub_schema_protobuf' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + schema_name: 'example' + test_env_vars: + project_name: 'PROJECT_NAME' +parameters: + - name: 'name' + type: String + description: "The ID to use for the schema, which will become the final component of the schema's resource name." + required: true + immutable: true + diff_suppress_func: 'tpgresource.CompareSelfLinkOrResourceName' + custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.erb' + custom_expand: 'templates/terraform/custom_expand/resource_from_self_link.go.erb' +properties: + - name: 'type' + type: Enum + description: "The type of the schema definition" + default_value: TYPE_UNSPECIFIED + enum_values: + - 'TYPE_UNSPECIFIED' + - 'PROTOCOL_BUFFER' + - 'AVRO' + - '' + - name: 'definition' + type: String + description: "The definition of the schema. +This should contain a string representing the full definition of the schema +that is a valid schema definition of the type specified in type. Changes +to the definition commit new [schema revisions](https://cloud.google.com/pubsub/docs/commit-schema-revision). +A schema can only have up to 20 revisions, so updates that fail with an +error indicating that the limit has been reached require manually +[deleting old revisions](https://cloud.google.com/pubsub/docs/delete-schema-revision)." diff --git a/mmv1/products/pubsub/go_Subscription.yaml b/mmv1/products/pubsub/go_Subscription.yaml new file mode 100644 index 000000000000..ad7bd11d8ddd --- /dev/null +++ b/mmv1/products/pubsub/go_Subscription.yaml @@ -0,0 +1,420 @@ +# 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. +# 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. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'Subscription' +description: | + A named resource representing the stream of messages from a single, + specific topic, to be delivered to the subscribing application. +references: + guides: + 'Managing Subscriptions': 'https://cloud.google.com/pubsub/docs/admin#managing_subscriptions' + api: 'https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.subscriptions' +docs: + note: 'You can retrieve the email of the Google Managed Pub/Sub Service Account used for forwarding +by using the `google_project_service_identity` resource. +' +base_url: 'projects/{{project}}/subscriptions' +create_verb: 'PUT' +update_url: 'projects/{{project}}/subscriptions/{{name}}' +update_verb: 'PATCH' +update_mask: true +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +async: + check_response_func_existence: 'transport_tpg.PollCheckForExistence' + check_response_func_absence: 'transport_tpg.PollCheckForAbsence' + suppress_error: true + target_occurrences: 1 +custom_code: + constants: 'templates/terraform/constants/subscription.go.erb' + encoder: 'templates/terraform/encoders/no_send_name.go.erb' + update_encoder: 'templates/terraform/update_encoder/pubsub_subscription.erb' +examples: + - name: 'pubsub_subscription_push' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + subscription_name: 'example-subscription' + - name: 'pubsub_subscription_pull' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + subscription_name: 'example-subscription' + - name: 'pubsub_subscription_dead_letter' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + subscription_name: 'example-subscription' + - name: 'pubsub_subscription_push_bq' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + subscription_name: 'example-subscription' + dataset_id: 'example_dataset' + table_id: 'example_table' + - name: 'pubsub_subscription_push_bq_table_schema' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + subscription_name: 'example-subscription' + dataset_id: 'example_dataset' + table_id: 'example_table' + - name: 'pubsub_subscription_push_cloudstorage' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + subscription_name: 'example-subscription' + bucket_name: 'example-bucket' + - name: 'pubsub_subscription_push_cloudstorage_avro' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + subscription_name: 'example-subscription' + bucket_name: 'example-bucket' +parameters: +properties: + - name: 'name' + type: String + description: "Name of the subscription." + pattern: 'projects/{{project}}/subscriptions/{{name}}' + required: true + immutable: true + custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.erb' + custom_expand: 'templates/terraform/custom_expand/shortname_to_url.go.erb' + - name: 'topic' + type: ResourceRef + description: "A reference to a Topic resource, of the form projects/{project}/topics/{{name}} +(as in the id property of a google_pubsub_topic), or just a topic name if +the topic is in the same project as the subscription." + pattern: 'projects/{{project}}/topics/{{topic}}' + required: true + immutable: true + diff_suppress_func: 'tpgresource.CompareSelfLinkOrResourceName' + custom_expand: 'templates/terraform/custom_expand/computed_subscription_topic.erb' + resource: 'Topic' + imports: 'name' + - name: 'labels' + type: KeyValueLabels + description: "A set of key/value label pairs to assign to this Subscription. + + +**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." + immutable: false + - name: 'bigqueryConfig' + type: NestedObject + description: "If delivery to BigQuery is used with this subscription, this field is used to configure it. +Either pushConfig, bigQueryConfig or cloudStorageConfig can be set, but not combined. +If all three are empty, then the subscriber will pull and ack messages using API methods." + conflicts: + - push_config + - cloud_storage_config + properties: + - name: 'table' + type: String + description: "The name of the table to which to write data, of the form {projectId}:{datasetId}.{tableId}" + required: true + - name: 'useTopicSchema' + type: Boolean + description: "When true, use the topic's schema as the columns to write to in BigQuery, if it exists. + Only one of use_topic_schema and use_table_schema can be set." + conflicts: + - use_table_schema + - name: 'useTableSchema' + type: Boolean + description: "When true, use the BigQuery table's schema as the columns to write to in BigQuery. Messages + must be published in JSON format. Only one of use_topic_schema and use_table_schema can be set." + conflicts: + - use_topic_schema + - name: 'writeMetadata' + type: Boolean + description: "When true, write the subscription name, messageId, publishTime, attributes, and orderingKey to additional columns in the table. + The subscription name, messageId, and publishTime fields are put in their own columns while all other message properties (other than data) are written to a JSON object in the attributes column." + - name: 'dropUnknownFields' + type: Boolean + description: "When true and use_topic_schema or use_table_schema is true, any fields that are a part of the topic schema or message schema that + are not part of the BigQuery table schema are dropped when writing to BigQuery. Otherwise, the schemas must be kept in sync + and any messages with extra fields are not written and remain in the subscription's backlog." + - name: 'cloudStorageConfig' + type: NestedObject + description: "If delivery to Cloud Storage is used with this subscription, this field is used to configure it. +Either pushConfig, bigQueryConfig or cloudStorageConfig can be set, but not combined. +If all three are empty, then the subscriber will pull and ack messages using API methods." + conflicts: + - push_config + - bigquery_config + properties: + - name: 'bucket' + type: String + description: "User-provided name for the Cloud Storage bucket. The bucket must be created by the user. The bucket name must be without any prefix like 'gs://'." + required: true + - name: 'filenamePrefix' + type: String + description: "User-provided prefix for Cloud Storage filename." + - name: 'filenameSuffix' + type: String + description: "User-provided suffix for Cloud Storage filename. Must not end in '/'." + - name: 'maxDuration' + type: String + description: "The maximum duration that can elapse before a new Cloud Storage file is created. Min 1 minute, max 10 minutes, default 5 minutes. + May not exceed the subscription's acknowledgement deadline. + A duration in seconds with up to nine fractional digits, ending with 's'. Example: '3.5s'." + default_value: 300s + - name: 'maxBytes' + type: Integer + description: "The maximum bytes that can be written to a Cloud Storage file before a new file is created. Min 1 KB, max 10 GiB. + The maxBytes limit may be exceeded in cases where messages are larger than the limit." + - name: 'state' + type: Enum + description: "An output-only field that indicates whether or not the subscription can receive messages." + output: true + enum_values: + - 'ACTIVE' + - 'PERMISSION_DENIED' + - 'NOT_FOUND' + - name: 'avroConfig' + type: NestedObject + description: "If set, message data will be written to Cloud Storage in Avro format." + properties: + - name: 'writeMetadata' + type: Boolean + description: "When true, write the subscription name, messageId, publishTime, attributes, and orderingKey as additional fields in the output." + - name: 'pushConfig' + type: NestedObject + description: "If push delivery is used with this subscription, this field is used to +configure it. An empty pushConfig signifies that the subscriber will +pull and ack messages using API methods." + conflicts: + - bigquery_config + - cloud_storage_config + properties: + - name: 'oidcToken' + type: NestedObject + description: "If specified, Pub/Sub will generate and attach an OIDC JWT token as + an Authorization header in the HTTP request for every pushed message." + properties: + - name: 'serviceAccountEmail' + type: String + description: "Service account email to be used for generating the OIDC token. + The caller (for subscriptions.create, subscriptions.patch, and + subscriptions.modifyPushConfig RPCs) must have the + iam.serviceAccounts.actAs permission for the service account." + required: true + - name: 'audience' + type: String + description: "Audience to be used when generating OIDC token. The audience claim + identifies the recipients that the JWT is intended for. The audience + value is a single case-sensitive string. Having multiple values (array) + for the audience field is not supported. More info about the OIDC JWT + token audience here: https://tools.ietf.org/html/rfc7519#section-4.1.3 + Note: if not specified, the Push endpoint URL will be used." + - name: 'pushEndpoint' + type: String + description: "A URL locating the endpoint to which messages should be pushed. + For example, a Webhook endpoint might use + 'https://example.com/push'." + required: true + - name: 'attributes' + type: KeyValuePairs + description: "Endpoint configuration attributes. + + Every endpoint has a set of API supported attributes that can + be used to control different aspects of the message delivery. + + The currently supported attribute is x-goog-version, which you + can use to change the format of the pushed message. This + attribute indicates the version of the data expected by + the endpoint. This controls the shape of the pushed message + (i.e., its fields and metadata). The endpoint version is + based on the version of the Pub/Sub API. + + If not present during the subscriptions.create call, + it will default to the version of the API used to make + such call. If not present during a subscriptions.modifyPushConfig + call, its value will not be changed. subscriptions.get + calls will always return a valid version, even if the + subscription was created without this attribute. + + The possible values for this attribute are: + + - v1beta1: uses the push format defined in the v1beta1 Pub/Sub API. + - v1 or v1beta2: uses the push format defined in the v1 Pub/Sub API." + diff_suppress_func: 'tpgresource.IgnoreMissingKeyInMap("x-goog-version")' + - name: 'noWrapper' + type: NestedObject + description: "When set, the payload to the push endpoint is not wrapped.Sets the + `data` field as the HTTP body for delivery." + custom_flatten: 'templates/terraform/custom_flatten/pubsub_no_wrapper_write_metadata_flatten.go.erb' + properties: + - name: 'writeMetadata' + type: Boolean + description: "When true, writes the Pub/Sub message metadata to + `x-goog-pubsub-:` headers of the HTTP request. Writes the + Pub/Sub message attributes to `:` headers of the HTTP request." + required: true + send_empty_value: true + - name: 'ackDeadlineSeconds' + type: Integer + description: "This value is the maximum time after a subscriber receives a message +before the subscriber should acknowledge the message. After message +delivery but before the ack deadline expires and before the message is +acknowledged, it is an outstanding message and will not be delivered +again during that time (on a best-effort basis). + +For pull subscriptions, this value is used as the initial value for +the ack deadline. To override this value for a given message, call +subscriptions.modifyAckDeadline with the corresponding ackId if using +pull. The minimum custom deadline you can specify is 10 seconds. The +maximum custom deadline you can specify is 600 seconds (10 minutes). +If this parameter is 0, a default value of 10 seconds is used. + +For push delivery, this value is also used to set the request timeout +for the call to the push endpoint. + +If the subscriber never acknowledges the message, the Pub/Sub system +will eventually redeliver the message." + default_from_api: true + - name: 'messageRetentionDuration' + type: String + description: "How long to retain unacknowledged messages in the subscription's +backlog, from the moment a message is published. If +retain_acked_messages is true, then this also configures the retention +of acknowledged messages, and thus configures how far back in time a +subscriptions.seek can be done. Defaults to 7 days. Cannot be more +than 7 days (`'604800s'`) or less than 10 minutes (`'600s'`). + +A duration in seconds with up to nine fractional digits, terminated +by 's'. Example: `'600.5s'`." + default_value: 604800s + - name: 'retainAckedMessages' + type: Boolean + description: "Indicates whether to retain acknowledged messages. If `true`, then +messages are not expunged from the subscription's backlog, even if +they are acknowledged, until they fall out of the +messageRetentionDuration window." + - name: 'expirationPolicy' + type: NestedObject + description: "A policy that specifies the conditions for this subscription's expiration. +A subscription is considered active as long as any connected subscriber +is successfully consuming messages from the subscription or is issuing +operations on the subscription. If expirationPolicy is not set, a default +policy with ttl of 31 days will be used. If it is set but ttl is '', the +resource never expires. The minimum allowed value for expirationPolicy.ttl +is 1 day." + default_from_api: true + send_empty_value: true + allow_empty_object: true + properties: + - name: 'ttl' + type: String + description: "Specifies the 'time-to-live' duration for an associated resource. The + resource expires if it is not active for a period of ttl. + If ttl is set to '', the associated resource never expires. + A duration in seconds with up to nine fractional digits, terminated by 's'. + Example - '3.5s'." + required: true + diff_suppress_func: 'comparePubsubSubscriptionExpirationPolicy' + - name: 'filter' + type: String + description: "The subscription only delivers the messages that match the filter. +Pub/Sub automatically acknowledges the messages that don't match the filter. You can filter messages +by their attributes. The maximum length of a filter is 256 bytes. After creating the subscription, +you can't modify the filter." + required: false + immutable: true + - name: 'deadLetterPolicy' + type: NestedObject + description: "A policy that specifies the conditions for dead lettering messages in +this subscription. If dead_letter_policy is not set, dead lettering +is disabled. + +The Cloud Pub/Sub service account associated with this subscription's +parent project (i.e., +service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com) must have +permission to Acknowledge() messages on this subscription." + send_empty_value: true + properties: + - name: 'deadLetterTopic' + type: String + description: "The name of the topic to which dead letter messages should be published. + Format is `projects/{project}/topics/{topic}`. + + The Cloud Pub/Sub service account associated with the enclosing subscription's + parent project (i.e., + service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com) must have + permission to Publish() to this topic. + + The operation will fail if the topic does not exist. + Users should ensure that there is a subscription attached to this topic + since messages published to a topic with no subscriptions are lost." + - name: 'maxDeliveryAttempts' + type: Integer + description: "The maximum number of delivery attempts for any message. The value must be + between 5 and 100. + + The number of delivery attempts is defined as 1 + (the sum of number of + NACKs and number of times the acknowledgement deadline has been exceeded for the message). + + A NACK is any call to ModifyAckDeadline with a 0 deadline. Note that + client libraries may automatically extend ack_deadlines. + + This field will be honored on a best effort basis. + + If this parameter is 0, a default value of 5 is used." + - name: 'retryPolicy' + type: NestedObject + description: "A policy that specifies how Pub/Sub retries message delivery for this subscription. + +If not set, the default retry policy is applied. This generally implies that messages will be retried as soon as possible for healthy subscribers. +RetryPolicy will be triggered on NACKs or acknowledgement deadline exceeded events for a given message" + properties: + - name: 'minimumBackoff' + type: String + description: "The minimum delay between consecutive deliveries of a given message. Value should be between 0 and 600 seconds. Defaults to 10 seconds. + A duration in seconds with up to nine fractional digits, terminated by 's'. Example: '3.5s'." + default_from_api: true + diff_suppress_func: 'tpgresource.DurationDiffSuppress' + - name: 'maximumBackoff' + type: String + description: "The maximum delay between consecutive deliveries of a given message. Value should be between 0 and 600 seconds. Defaults to 600 seconds. + A duration in seconds with up to nine fractional digits, terminated by 's'. Example: '3.5s'." + default_from_api: true + diff_suppress_func: 'tpgresource.DurationDiffSuppress' + - name: 'enableMessageOrdering' + type: Boolean + description: "If `true`, messages published with the same orderingKey in PubsubMessage will be delivered to +the subscribers in the order in which they are received by the Pub/Sub system. Otherwise, they +may be delivered in any order." + immutable: true + - name: 'enableExactlyOnceDelivery' + type: Boolean + description: "If `true`, Pub/Sub provides the following guarantees for the delivery +of a message with a given value of messageId on this Subscriptions': + +- The message sent to a subscriber is guaranteed not to be resent before the message's acknowledgement deadline expires. + +- An acknowledged message will not be resent to a subscriber. + +Note that subscribers may still receive multiple copies of a message when `enable_exactly_once_delivery` +is true if the message was published multiple times by a publisher client. These copies are considered distinct by Pub/Sub and have distinct messageId values" diff --git a/mmv1/products/pubsub/go_Topic.yaml b/mmv1/products/pubsub/go_Topic.yaml new file mode 100644 index 000000000000..17cddf8c9ea7 --- /dev/null +++ b/mmv1/products/pubsub/go_Topic.yaml @@ -0,0 +1,150 @@ +# 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. +# 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. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'Topic' +description: | + A named resource to which messages are sent by publishers. +references: + guides: + 'Managing Topics': 'https://cloud.google.com/pubsub/docs/admin#managing_topics' + api: 'https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics' +docs: + note: 'You can retrieve the email of the Google Managed Pub/Sub Service Account used for forwarding +by using the `google_project_service_identity` resource. +' +base_url: 'projects/{{project}}/topics' +create_verb: 'PUT' +update_url: 'projects/{{project}}/topics/{{name}}' +update_verb: 'PATCH' +update_mask: true +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +async: + check_response_func_existence: 'transport_tpg.PollCheckForExistence' + check_response_func_absence: 'transport_tpg.PollCheckForAbsence' + suppress_error: true + target_occurrences: 1 +iam_policy: + method_name_separator: ':' + parent_resource_attribute: 'topic' +custom_code: + encoder: 'templates/terraform/encoders/no_send_name.go.erb' + update_encoder: 'templates/terraform/update_encoder/pubsub_topic.erb' +error_retry_predicates: + + - 'transport_tpg.PubsubTopicProjectNotReady' +examples: + - name: 'pubsub_topic_basic' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + - name: 'pubsub_topic_cmek' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + key_name: 'example-key' + keyring_name: 'example-keyring' + skip_test: true + - name: 'pubsub_topic_geo_restricted' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + - name: 'pubsub_topic_schema_settings' + primary_resource_id: 'example' + primary_resource_name: 'example' + vars: + topic_name: 'example-topic' + schema_name: 'example' + test_env_vars: + project_name: 'PROJECT_NAME' +parameters: +properties: + - name: 'name' + type: String + description: "Name of the topic." + pattern: 'projects/{{project}}/topics/{{name}}' + required: true + immutable: true + diff_suppress_func: 'tpgresource.CompareSelfLinkOrResourceName' + custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.erb' + custom_expand: 'templates/terraform/custom_expand/resource_from_self_link.go.erb' + - name: 'kmsKeyName' + type: String + description: "The resource name of the Cloud KMS CryptoKey to be used to protect access +to messages published on this topic. Your project's PubSub service account +(`service-{{PROJECT_NUMBER}}@gcp-sa-pubsub.iam.gserviceaccount.com`) must have +`roles/cloudkms.cryptoKeyEncrypterDecrypter` to use this feature. +The expected format is `projects/*/locations/*/keyRings/*/cryptoKeys/*`" + - name: 'labels' + type: KeyValueLabels + description: "A set of key/value label pairs to assign to this Topic. + + +**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." + immutable: false + - name: 'messageStoragePolicy' + type: NestedObject + description: "Policy constraining the set of Google Cloud Platform regions where +messages published to the topic may be stored. If not present, then no +constraints are in effect." + default_from_api: true + properties: + - name: 'allowedPersistenceRegions' + type: Array + description: "A list of IDs of GCP regions where messages that are published to + the topic may be persisted in storage. Messages published by + publishers running in non-allowed GCP regions (or running outside + of GCP altogether) will be routed for storage in one of the + allowed regions. An empty list means that no regions are allowed, + and is not a valid configuration." + required: true + item_type: Api::Type::String + - name: 'schemaSettings' + type: NestedObject + description: "Settings for validating messages published against a schema." + default_from_api: true + properties: + - name: 'schema' + type: String + description: "The name of the schema that messages published should be + validated against. Format is projects/{project}/schemas/{schema}. + The value of this field will be _deleted-schema_ + if the schema has been deleted." + required: true + - name: 'encoding' + type: Enum + description: "The encoding of messages validated against schema." + default_value: ENCODING_UNSPECIFIED + enum_values: + - 'ENCODING_UNSPECIFIED' + - 'JSON' + - 'BINARY' + - '' + - name: 'messageRetentionDuration' + type: String + description: "Indicates the minimum duration to retain a message after it is published +to the topic. If this field is set, messages published to the topic in +the last messageRetentionDuration are always available to subscribers. +For instance, it allows any attached subscription to seek to a timestamp +that is up to messageRetentionDuration in the past. If this field is not +set, message retention is controlled by settings on individual subscriptions. +The rotation period has the format of a decimal number, followed by the +letter `s` (seconds). Cannot be more than 31 days or less than 10 minutes." diff --git a/mmv1/products/pubsub/go_product.yaml b/mmv1/products/pubsub/go_product.yaml new file mode 100644 index 000000000000..f3f63389edd2 --- /dev/null +++ b/mmv1/products/pubsub/go_product.yaml @@ -0,0 +1,22 @@ +# 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. +# 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. + +# Warning: This is a temporary file, and should not be edited directly +--- +name: 'Pubsub' +display_name: 'Cloud Pub/Sub' +versions: + - name: 'ga' + base_url: 'https://pubsub.googleapis.com/v1/' +scopes: + - 'https://www.googleapis.com/auth/pubsub' diff --git a/mmv1/provider/terraform.rb b/mmv1/provider/terraform.rb index 3edea972c351..37fd2de9f525 100644 --- a/mmv1/provider/terraform.rb +++ b/mmv1/provider/terraform.rb @@ -377,6 +377,8 @@ 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 end end @@ -395,7 +397,6 @@ def generate_object(object, output_folder, version_name, generate_code, generate end Dir.chdir pwd end - # if iam_policy is not defined or excluded, don't generate it return if object.iam_policy.nil? || object.iam_policy.exclude @@ -406,6 +407,33 @@ def generate_object(object, output_folder, version_name, generate_code, generate Dir.chdir pwd end + 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" + 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", + self) + end + def build_env { goformat_enabled: @go_format_enabled, diff --git a/mmv1/provider/terraform/custom_code.go b/mmv1/provider/terraform/custom_code.go new file mode 100644 index 000000000000..1213cf0b6572 --- /dev/null +++ b/mmv1/provider/terraform/custom_code.go @@ -0,0 +1,206 @@ +// 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. +// 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. + +package terraform + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" +) + +// require 'uri' +// require 'api/object' +// require 'compile/core' +// require 'google/golang_utils' + +// Inserts custom code into terraform resources. +type CustomCode struct { + google.YamlValidator + + // Collection of fields allowed in the CustomCode section for + // Terraform. + + // All custom code attributes are string-typed. The string should + // be the name of a template file which will be compiled in the + // specified / described place. + // + // ====================== + // schema.Resource stuff + // ====================== + // Extra Schema Entries go below all other schema entries in the + // resource's Resource.Schema map. They should be formatted as + // entries in the map, e.g. `"foo": &schema.Schema{ ... },`. + + // attr_reader : + ExtraSchemaEntry string + + // ==================== + // Encoders & Decoders + // ==================== + // The encoders are functions which take the `obj` map after it + // has been assembled in either "Create" or "Update" and mutate it + // before it is sent to the server. There are lots of reasons you + // might want to use these - any differences between local schema + // and remote schema will be placed here. + // Because the call signature of this function cannot be changed, + // the template will place the function header and closing } for + // you, and your custom code template should *not* include them. + + // attr_reader : + Encoder string + + // The update encoder is the encoder used in Update - if one is + // not provided, the regular encoder is used. If neither is + // provided, of course, neither is used. Similarly, the custom + // code should *not* include the function header or closing }. + // Update encoders are only used if object.input is false, + // because when object.input is true, only individual fields + // can be updated - in that case, use a custom expander. + + // attr_reader : + UpdateEncoder string + + // The decoder is the opposite of the encoder - it's called + // after the Read succeeds, rather than before Create / Update + // are called. Like with encoders, the decoder should not + // include the function header or closing }. + + // attr_reader : + Decoder string + + // ===================== + // Simple customizations + // ===================== + // Constants go above everything else in the file, and include + // things like methods that will be referred to by name elsewhere + // (e.g. "fooBarDiffSuppress") and regexes that are necessarily + // exported (e.g. "fooBarValidationRegex"). + + // attr_reader : + Constants string + + // This code is run before the Create call happens. It's placed + // in the Create function, just before the Create call is made. + + // attr_reader : + PreCreate string + + // This code is run after the Create call succeeds. It's placed + // in the Create function directly without modification. + + // attr_reader : + PostCreate string + + // This code is run after the Create call fails before the error is + // returned. It's placed in the Create function directly without + // modification. + + // attr_reader : + PostCreateFailure string + + // This code replaces the entire contents of the Create call. It + // should be used for resources that don't have normal creation + // semantics that cannot be supported well by other MM features. + + // attr_reader : + CustomCreate string + + // This code is run before the Read call happens. It's placed + // in the Read function. + + // attr_reader : + PreRead string + + // This code is run before the Update call happens. It's placed + // in the Update function, just after the encoder call, before + // the Update call. Just like the encoder, it is only used if + // object.input is false. + + // attr_reader : + PreUpdate string + + // This code is run after the Update call happens. It's placed + // in the Update function, just after the call succeeds. + // Just like the encoder, it is only used if object.input is + // false. + + // attr_reader : + PostUpdate string + + // This code replaces the entire contents of the Update call. It + // should be used for resources that don't have normal update + // semantics that cannot be supported well by other MM features. + + // attr_reader : + CustomUpdate string + + // This code is run just before the Delete call happens. It's + // useful to prepare an object for deletion, e.g. by detaching + // a disk before deleting it. + + // attr_reader : + PreDelete string + + // This code is run just after the Delete call happens. + + // attr_reader : + PostDelete string + + // This code replaces the entire delete method. Since the delete + // method's function header can't be changed, the template + // inserts that for you - do not include it in your custom code. + + // attr_reader : + CustomDelete string + + // This code replaces the entire import method. Since the import + // method's function header can't be changed, the template + // inserts that for you - do not include it in your custom code. + + // attr_reader : + CustomImport string + + // This code is run just after the import method succeeds - it + // is useful for parsing attributes that are necessary for + // the Read() method to succeed. + + // attr_reader : + PostImport string + + // This code is run in the generated test file to check that the + // resource was successfully deleted. Use this if the API responds + // with a success HTTP code for deleted resources + + // attr_reader : + TestCheckDestroy string +} + +// def validate +// super + +// check :extra_schema_entry, type: String +// check :encoder, type: String +// check :update_encoder, type: String +// check :decoder, type: String +// check :constants, type: String +// check :pre_create, type: String +// check :post_create, type: String +// check :custom_create, type: String +// check :pre_read, type: String +// check :pre_update, type: String +// check :post_update, type: String +// check :custom_update, type: String +// check :pre_delete, type: String +// check :custom_import, type: String +// check :post_import, type: String +// check :test_check_destroy, type: String +// end diff --git a/mmv1/provider/terraform/docs.go b/mmv1/provider/terraform/docs.go new file mode 100644 index 000000000000..408cb8d9fac0 --- /dev/null +++ b/mmv1/provider/terraform/docs.go @@ -0,0 +1,61 @@ +// 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. +// 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. + +package terraform + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" +) + +// require 'uri' +// require 'api/object' +// require 'compile/core' +// require 'google/golang_utils' + +// Inserts custom strings into terraform resource docs. +type Docs struct { + google.YamlValidator + + // All these values should be strings, which will be inserted + // directly into the terraform resource documentation. The + // strings should _not_ be the names of template files + // (This should be reconsidered if we find ourselves repeating + // any string more than ones), but rather the actual text + // (including markdown) which needs to be injected into the + // template. + // The text will be injected at the bottom of the specified + // section. + // attr_reader : + Warning string + + // attr_reader : + Note string + + // attr_reader : + RequiredProperties string + + // attr_reader : + OptionalProperties string + + // attr_reader : + Attributes string +} + +// def validate +// super +// check :warning, type: String +// check :note, type: String +// check :required_properties, type: String +// check :optional_properties, type: String +// check :attributes, type: String +// end diff --git a/mmv1/provider/terraform/examples.go b/mmv1/provider/terraform/examples.go new file mode 100644 index 000000000000..ff4ba716a24f --- /dev/null +++ b/mmv1/provider/terraform/examples.go @@ -0,0 +1,339 @@ +// 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. +// 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. + +package terraform + +import ( + "github.com/GoogleCloudPlatform/magic-modules/mmv1/google" +) + +// require 'uri' +// require 'api/object' +// require 'compile/core' +// require 'google/golang_utils' + +// Generates configs to be shown as examples in docs and outputted as tests +// from a shared template +type Examples struct { + google.YamlValidator + + // include Compile::Core + // include Google::GolangUtils + + // The name of the example in lower snake_case. + // Generally takes the form of the resource name followed by some detail + // about the specific test. For example, "address_with_subnetwork". + Name string + + // The id of the "primary" resource in an example. Used in import tests. + // This is the value that will appear in the Terraform config url. For + // example: + // resource "google_compute_address" {{primary_resource_id}} { + // ... + // } + PrimaryResourceId string + + // Optional resource type of the "primary" resource. Used in import tests. + // If set, this will override the default resource type implied from the + // object parent + PrimaryResourceType string + + // vars is a Hash from template variable names to output variable names. + // It will use the provided value as a prefix for generated tests, and + // insert it into the docs verbatim. + Vars map[string]string + + // Some variables need to hold special values during tests, and cannot + // be inferred by Open in Cloud Shell. For instance, org_id + // needs to be the correct value during integration tests, or else + // org tests cannot pass. Other examples include an existing project_id, + // a zone, a service account name, etc. + // + // test_env_vars is a Hash from template variable names to one of the + // following symbols: + // - :PROJECT_NAME + // - :FIRESTORE_PROJECT_NAME + // - :CREDENTIALS + // - :REGION + // - :ORG_ID + // - :ORG_TARGET + // - :BILLING_ACCT + // - :MASTER_BILLING_ACCT + // - :SERVICE_ACCT + // - :CUST_ID + // - :IDENTITY_USER + // This list corresponds to the `get*FromEnv` methods in provider_test.go. + TestEnvVars map[string]string + + // Hash to provider custom override values for generating test config + // If field my-var is set in this hash, it will replace vars[my-var] in + // tests. i.e. if vars["network"] = "my-vpc", without override: + // - doc config will have `network = "my-vpc"` + // - tests config will have `"network = my-vpc%{random_suffix}"` + // with context + // map[string]interface{}{ + // "random_suffix": acctest.RandString() + // } + // + // If test_vars_overrides["network"] = "nameOfVpc()" + // - doc config will have `network = "my-vpc"` + // - tests will replace with `"network = %{network}"` with context + // map[string]interface{}{ + // "network": nameOfVpc + // ... + // } + TestVarsOverrides map[string]string + + // Hash to provider custom override values for generating oics config + // See test_vars_overrides for more details + OicsVarsOverrides map[string]string + + // The version name of of the example's version if it's different than the + // resource version, eg. `beta` + // + // This should be the highest version of all the features used in the + // example; if there's a single beta field in an example, the example's + // min_version is beta. This is only needed if an example uses features + // with a different version than the resource; a beta resource's examples + // are all automatically versioned at beta. + // + // When an example has a version of beta, each resource must use the + // `google-beta` provider in the config. If the `google` provider is + // implicitly used, the test will fail. + // + // NOTE: Until Terraform 0.12 is released and is used in the OiCS tests, an + // explicit provider block should be defined. While the tests @ 0.12 will + // use `google-beta` automatically, past Terraform versions required an + // explicit block. + MinVersion string + + // Extra properties to ignore read on during import. + // These properties will likely be custom code. + IgnoreReadExtra []string + + // Whether to skip generating tests for this resource + SkipTest bool + + // Whether to skip generating docs for this example + SkipDocs bool + + // Whether to skip import tests for this example + SkipImportTest bool + + // The name of the primary resource for use in IAM tests. IAM tests need + // a reference to the primary resource to create IAM policies for + PrimaryResourceName string + + // The name of the location/region override for use in IAM tests. IAM + // tests may need this if the location is not inherited on the resource + // for one reason or another + RegionOverride string + + // The path to this example's Terraform config. + // Defaults to `templates/terraform/examples/{{name}}.tf.erb` + ConfigPath string + + // If the example should be skipped during VCR testing. + // This is the case when something about the resource or config causes VCR to fail for example + // a resource with a unique identifier generated within the resource via resource.UniqueId() + // Or a config with two fine grained resources that have a race condition during create + SkipVcr bool + + // Set for false by default. Set to true if you need to pull external provider for your + // testcase. Think before adding as there is latency and adds an external dependency to + // your test so avoid if you can. + PullExternal bool +} + +// func (e *Examples) config_documentation(pwd) { +// docs_defaults = { +// PROJECT_NAME: 'my-project-name', +// FIRESTORE_PROJECT_NAME: 'my-project-name', +// CREDENTIALS: 'my/credentials/filename.json', +// REGION: 'us-west1', +// ORG_ID: '123456789', +// ORG_DOMAIN: 'example.com', +// ORG_TARGET: '123456789', +// BILLING_ACCT: '000000-0000000-0000000-000000', +// MASTER_BILLING_ACCT: '000000-0000000-0000000-000000', +// SERVICE_ACCT: 'my@service-account.com', +// CUST_ID: 'A01b123xz', +// IDENTITY_USER: 'cloud_identity_user', +// PAP_DESCRIPTION: 'description' +// } +// @vars ||= {} +// @test_env_vars ||= {} +// body = lines(compile_file( +// { +// vars:, +// test_env_vars: test_env_vars.to_h { |k, v| [k, docs_defaults[v]] }, +// primary_resource_id: +// }, +// "//{pwd}///{config_path}" +// )) + +// // Remove region tags +// body = body.gsub(/// \[[a-zA-Z_ ]+\]\n/, '') +// body = body.gsub(/\n// \[[a-zA-Z_ ]+\]/, '') +// lines(compile_file( +// { content: body }, +// "//{pwd}/templates/terraform/examples/base_configs/documentation.tf.erb" +// )) +// } + +// func (e *Examples) config_test(pwd) { +// body = config_test_body(pwd) +// lines(compile_file( +// { +// content: body +// }, +// "//{pwd}/templates/terraform/examples/base_configs/test_body.go.erb" +// )) +// } + +// rubocop:disable Style/FormatStringToken +// func (e *Examples) config_test_body(pwd) { +// @vars ||= {} +// @test_env_vars ||= {} +// @test_vars_overrides ||= {} + +// // Construct map for vars to inject into config - will have +// // - "a-example-var-value%{random_suffix}"" +// // - "%{my_var}" for overrides that have custom Golang values +// rand_vars = vars.map do |k, v| +// // Some resources only allow underscores. +// testv = if v.include?('-') +// "tf-test-//{v}" +// elsif v.include?('_') +// "tf_test_//{v}" +// else +// // Some vars like descriptions shouldn't have prefix +// v +// end +// // Random suffix is 10 characters and standard name length <= 64 +// testv = "//{testv[0...54]}%{random_suffix}" +// [k, testv] +// end + +// rand_vars = rand_vars.to_h +// overrides = test_vars_overrides.to_h { |k, _| [k, "%{//{k}}"] } +// body = lines(compile_file( +// { +// vars: rand_vars.merge(overrides), +// test_env_vars: test_env_vars.to_h { |k, _| [k, "%{//{k}}"] }, +// primary_resource_id:, +// primary_resource_type: +// }, +// "//{pwd}///{config_path}" +// )) + +// // Remove region tags +// body = body.gsub(/// \[[a-zA-Z_ ]+\]\n/, '') +// body = body.gsub(/\n// \[[a-zA-Z_ ]+\]/, '') +// substitute_test_paths body +// } + +// func (e *Examples) config_oics(pwd) { +// @vars ||= [] +// @oics_vars_overrides ||= {} + +// rand_vars = vars.to_h { |k, str| [k, "//{str}-${local.name_suffix}"] } + +// // Examples with test_env_vars are skipped elsewhere +// body = lines(compile_file( +// { +// vars: rand_vars.merge(oics_vars_overrides), +// primary_resource_id: +// }, +// "//{pwd}///{config_path}" +// )) + +// // Remove region tags +// body = body.gsub(/// \[[a-zA-Z_ ]+\]\n/, '') +// body = body.gsub(/\n// \[[a-zA-Z_ ]+\]/, '') +// substitute_example_paths body +// } + +// func (e *Examples) oics_link() { +// hash = { +// cloudshell_git_repo: 'https://github.com/terraform-google-modules/docs-examples.git', +// cloudshell_working_dir: @name, +// cloudshell_image: 'gcr.io/cloudshell-images/cloudshell:latest', +// open_in_editor: 'main.tf', +// cloudshell_print: './motd', +// cloudshell_tutorial: './tutorial.md' +// } +// URI::HTTPS.build( +// host: 'console.cloud.google.com', +// path: '/cloudshell/open', +// query: URI.encode_www_form(hash) +// ) +// } + +// rubocop:disable Layout/LineLength +// func (e *Examples) substitute_test_paths(config) { +// config.gsub!('../static/img/header-logo.png', 'test-fixtures/header-logo.png') +// config.gsub!('path/to/private.key', 'test-fixtures/test.key') +// config.gsub!('path/to/certificate.crt', 'test-fixtures/test.crt') +// config.gsub!('path/to/index.zip', '%{zip_path}') +// config.gsub!('verified-domain.com', 'tf-test-domain%{random_suffix}.gcp.tfacc.hashicorptest.com') +// config.gsub!('path/to/id_rsa.pub', 'test-fixtures/ssh_rsa.pub') +// config +// } + +// func (e *Examples) substitute_example_paths(config) { +// config.gsub!('../static/img/header-logo.png', '../static/header-logo.png') +// config.gsub!('path/to/private.key', '../static/ssl_cert/test.key') +// config.gsub!('path/to/id_rsa.pub', '../static/ssh_rsa.pub') +// config.gsub!('path/to/certificate.crt', '../static/ssl_cert/test.crt') +// config +// end +// // rubocop:enable Layout/LineLength +// // rubocop:enable Style/FormatStringToken +// } + +// func (e *Examples) validate() { +// super +// check :name, type: String, required: true +// check :primary_resource_id, type: String +// check :min_version, type: String +// check :vars, type: Hash +// check :test_env_vars, type: Hash +// check :test_vars_overrides, type: Hash +// check :ignore_read_extra, type: Array, item_type: String, default: [] +// check :primary_resource_name, type: String +// check :skip_test, type: TrueClass +// check :skip_import_test, type: TrueClass +// check :skip_docs, type: TrueClass +// check :config_path, type: String, default: "templates/terraform/examples///{name}.tf.erb" +// check :skip_vcr, type: TrueClass +// check :pull_external, type: :boolean, default: false +// } + +// func (e *Examples) merge(other) { +// result = self.class.new +// instance_variables.each do |v| +// result.instance_variable_set(v, instance_variable_get(v)) +// end + +// other.instance_variables.each do |v| +// if other.instance_variable_get(v).instance_of?(Array) +// result.instance_variable_set(v, deep_merge(result.instance_variable_get(v), +// other.instance_variable_get(v))) +// else +// result.instance_variable_set(v, other.instance_variable_get(v)) +// end +// end + +// result +// } diff --git a/mmv1/provider/terraform/sub_template.rb b/mmv1/provider/terraform/sub_template.rb index e74d2ebce971..2dc16f7274f0 100644 --- a/mmv1/provider/terraform/sub_template.rb +++ b/mmv1/provider/terraform/sub_template.rb @@ -15,6 +15,13 @@ module Provider class Terraform # Functions to compile sub-templates. module SubTemplate + def build_newyaml_field(property, object, pwd) + compile_template "#{pwd}/templates/terraform/yaml_conversion_field.erb", + property:, + object:, + pwd: + end + def build_schema_property(property, object, pwd) compile_template "#{pwd}/templates/terraform/schema_property.erb", property:, diff --git a/mmv1/templates/terraform/product_yaml_conversion.erb b/mmv1/templates/terraform/product_yaml_conversion.erb new file mode 100644 index 000000000000..cade809c4334 --- /dev/null +++ b/mmv1/templates/terraform/product_yaml_conversion.erb @@ -0,0 +1,135 @@ +# 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. +# 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. + +# Warning: This is a temporary file, and should not be edited directly +--- +<% +#names +-%> +name: '<%= object.__product.name %>' +<% unless object.__product.legacy_name.nil? -%> +legacy_name: '<%= object.__product.legacy_name %>' +<% end -%> +<% unless object.__product.display_name.nil? -%> +display_name: '<%= object.__product.display_name %>' +<% end -%> +<% unless object.__product.client_name.nil? -%> +client_name: '<%= object.__product.client_name %>' +<% end -%> +<% +#versions +-%> +<% unless object.__product.versions.empty? -%> +versions: +<% object.__product.versions.each do |version| -%> + - name: '<%= version.name %>' +<% unless version.base_url.nil? -%> + base_url: '<%= version.base_url %>' +<% end -%> +<% unless version.cai_base_url.nil? -%> + cai_base_url: '<%= version.cai_base_url %>' +<% end -%> +<% end -%> +<% end -%> +<% +#scopes +-%> +<% unless object.__product.scopes.nil? -%> +scopes: +<% object.__product.scopes.each do |scope| -%> + - '<%= scope -%>' +<% end -%> +<% end -%> +<% +#async +-%> +<% unless object.__product.async.nil? -%> +async: +<% 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 %>' +<% end -%> +<% unless object.__product.async.check_response_func_absence.nil? -%> + check_response_func_absence: '<%= object.__product.async.check_response_func_absence %>' +<% end -%> +<% unless object.__product.async.custom_poll_read.nil? -%> + custom_poll_read: '<%= object.__product.async.custom_poll_read %>' +<% end -%> +<% unless object.__product.async.suppress_error.nil? -%> + suppress_error: <%= object.__product.async.suppress_error %> +<% end -%> +<% unless object.__product.async.target_occurrences.nil? -%> + target_occurrences: <%= object.__product.async.target_occurrences %> +<% end -%> +<% end -%> +<% if object.__product.async.is_a? Api::OpAsync -%> +<% #async.operation -%> +<% unless object.__product.async.operation.nil? -%> + operation: +<% unless object.__product.async.operation.base_url.nil? -%> + base_url: '<%= object.__product.async.operation.base_url %>' +<% end -%> +<% unless object.__product.async.operation.full_url.nil? -%> + full_url: '<%= object.__product.async.operation.full_url %>' +<% end -%> +<% unless object.__product.async.operation.kind.nil? -%> + kind: '<%= object.__product.async.operation.kind %>' +<% end -%> +<% unless object.__product.async.operation.path.nil? -%> + path: '<%= object.__product.async.operation.path %>' +<% end -%> +<% unless object.__product.async.operation.wait_ms.nil? -%> + wait_ms: <%= object.__product.async.operation.wait_ms %> +<% end -%> +<% #async.operation.timeouts -%> +<% unless object.__product.async.operation.timeouts.nil? -%> + timeouts: +<% unless object.__product.async.operation.timeouts.insert_minutes.nil? -%> + insert_minutes: <%= object.__product.async.operation.timeouts.insert_minutes %> +<% end -%> +<% unless object.__product.async.operation.timeouts.update_minutes.nil? -%> + update_minutes: <%= object.__product.async.operation.timeouts.update_minutes %> +<% end -%> +<% unless object.__product.async.operation.timeouts.delete_minutes.nil? -%> + delete_minutes: <%= object.__product.async.operation.timeouts.delete_minutes %> +<% end -%> +<% end -%> +<% end -%> +<% #async.result -%> +<% unless object.__product.async.result.nil? -%> + result: +<% unless object.__product.async.result.path.nil? -%> + path: '<%= object.__product.async.result.path %>' +<% end -%> +<% unless object.__product.async.result.resource_inside_response.nil? -%> + resource_inside_response: <%= object.__product.async.result.resource_inside_response %> +<% end -%> +<% end -%> +<% #async.error -%> +<% unless object.__product.async.error.nil? -%> + error: +<% unless object.__product.async.error.path.nil? -%> + path: '<%= object.__product.async.error.path %>' +<% end -%> +<% unless object.__product.async.error.message.nil? -%> + message: '<%= object.__product.async.error.message %>' +<% end -%> +<% end -%> +<% end -%> +<% end -%> +<% +#misc +-%> +<% unless object.__product.operation_retry.nil? -%> +operation_retry: '<%= object.__product.operation_retry %>' +<% end -%> diff --git a/mmv1/templates/terraform/yaml_conversion.erb b/mmv1/templates/terraform/yaml_conversion.erb new file mode 100644 index 000000000000..e89df4397fd9 --- /dev/null +++ b/mmv1/templates/terraform/yaml_conversion.erb @@ -0,0 +1,550 @@ +# 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. +# 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. + +# Warning: This is a temporary file, and should not be edited directly +--- +<% +#common attrs +-%> +name: '<%= object.name %>' +<% unless object.kind.nil? -%> +kind: '<%= object.kind %>' +<% end -%> +<% unless object.legacy_name.nil? -%> +legacy_name: '<%= object.legacy_name %>' +<% end -%> +description: | + <%= object.description.gsub(/\n/, "\n ") %> +<% unless object.min_version.nil? -%> +<% unless object.min_version.name == 'ga' -%> +min_version: '<%= object.min_version.name %>' +<% end -%> +<% end -%> +<% unless !object.exclude_resource -%> +exclude_resource: <%= object.exclude_resource %> +<% end -%> +<% unless !object.exclude -%> +exclude: <%= object.exclude %> +<% end -%> +<% unless object.readonly.nil? -%> +readonly: <%= object.readonly %> +<% end -%> +<% +#references blocks +-%> +<% unless object.references.nil? -%> +references: +<% unless object.references.guides.nil? -%> + guides: +<% object.references.guides.each do |title, link| -%> + '<%= title -%>': '<%= link %>' +<% end -%> +<% end -%> +<% unless object.references.api.nil? -%> + api: '<%= object.references.api %>' +<% end -%> +<% end -%> +<% +#docs blocks +-%> +<% unless object.docs.nil? -%> +docs: +<% unless object.docs.warning.nil? -%> + warning: '<%= object.docs.warning %>' +<% end -%> +<% unless object.docs.note.nil? -%> + note: '<%= object.docs.note %>' +<% end -%> +<% unless object.docs.required_properties.nil? -%> + required_properties: '<%= object.docs.required_properties %>' +<% end -%> +<% unless object.docs.optional_properties.nil? -%> + optional_properties: '<%= object.docs.optional_properties %>' +<% end -%> +<% unless object.docs.attributes.nil? -%> + attributes: '<%= object.docs.attributes %>' +<% end -%> +<% end -%> +<% +#url/http attrs +-%> +<% unless object.id_format.nil? -%> +id_format: '<%= object.id_format %>' +<% end -%> +<% unless object.base_url.nil? -%> +base_url: '<%= object.base_url %>' +<% end -%> +<% unless object.cai_base_url.nil? -%> +cai_base_url: '<%= object.cai_base_url %>' +<% end -%> +<% unless object.self_link.nil? -%> +self_link: '<%= object.self_link %>' +<% end -%> +<% unless !object.has_self_link -%> +has_self_link: <%= object.has_self_link %> +<% end -%> +<% unless object.create_url.nil? -%> +create_url: '<%= object.create_url %>' +<% end -%> +<% unless object.create_verb.to_s == 'POST' -%> +create_verb: '<%= object.create_verb.to_s %>' +<% end -%> +<% unless object.update_url.nil? -%> +update_url: '<%= object.update_url %>' +<% end -%> +<% unless object.update_verb.to_s == 'PUT' -%> +update_verb: '<%= object.update_verb.to_s %>' +<% end -%> +<% unless object.update_mask.nil? -%> +update_mask: <%= object.update_mask %> +<% end -%> +<% unless object.read_verb.to_s == 'GET' -%> +read_verb: '<%= object.read_verb.to_s %>' +<% end -%> +<% unless object.read_query_params.nil? %> +read_query_params: '<%= object.read_query_params %>' +<% end -%> +<% unless !object.skip_read -%> +skip_read: <%= object.skip_read %> +<% end -%> +<% unless object.delete_url.nil? -%> +delete_url: '<%= object.delete_url %>' +<% end -%> +<% unless object.delete_verb.to_s == 'DELETE' -%> +delete_verb: '<%= object.delete_verb.to_s %>' +<% end -%> +<% unless !object.skip_delete -%> +skip_delete: <%= object.skip_delete %> +<% end -%> +<% unless object.immutable.nil? -%> +immutable: <%= object.immutable %> +<% end -%> +<% unless object.mutex.nil? -%> +mutex: <%= object.mutex %> +<% end -%> +<% +#import +-%> +<% unless object.import_format.empty? -%> +import_format: +<% object.import_format.each do |iformat| -%> + - '<%= iformat %>' +<% end -%> +<% end -%> +<% unless !object.exclude_import -%> +exclude_import: <%= object.exclude_import %> +<% end -%> +<% +#timeouts +-%> +<% unless object.timeouts.nil? -%> +timeouts: +<% unless object.timeouts.insert_minutes.nil? -%> + insert_minutes: <%= object.timeouts.insert_minutes %> +<% end -%> +<% unless object.timeouts.update_minutes.nil? -%> + update_minutes: <%= object.timeouts.update_minutes %> +<% end -%> +<% unless object.timeouts.delete_minutes.nil? -%> + delete_minutes: <%= object.timeouts.delete_minutes %> +<% end -%> +<% end -%> +<% +#async +-%> +<% unless !object.autogen_async -%> +autogen_async: <%= object.autogen_async %> +<% end -%> +<% unless object.async.nil? -%> +async: +<% if object.async.is_a? Provider::Terraform::PollAsync -%> +<% unless object.async.check_response_func_existence.nil? -%> + check_response_func_existence: '<%= object.async.check_response_func_existence %>' +<% end -%> +<% unless object.async.check_response_func_absence.nil? -%> + check_response_func_absence: '<%= object.async.check_response_func_absence %>' +<% end -%> +<% unless object.async.custom_poll_read.nil? -%> + custom_poll_read: '<%= object.async.custom_poll_read %>' +<% end -%> +<% unless object.async.suppress_error.nil? -%> + suppress_error: <%= object.async.suppress_error %> +<% end -%> +<% unless object.async.target_occurrences.nil? -%> + target_occurrences: <%= object.async.target_occurrences %> +<% end -%> +<% end -%> +<% if object.async.is_a? Api::OpAsync -%> +<% #async.operation %> +<% unless object.async.operation.nil? -%> + operation: +<% unless object.async.operation.base_url.nil? -%> + base_url: '<%= object.async.operation.base_url %>' +<% end -%> +<% unless object.async.operation.full_url.nil? -%> + full_url: '<%= object.async.operation.full_url %>' +<% end -%> +<% unless object.async.operation.kind.nil? -%> + kind: '<%= object.async.operation.kind %>' +<% end -%> +<% unless object.async.operation.path.nil? -%> + path: '<%= object.async.operation.path %>' +<% end -%> +<% unless object.async.operation.wait_ms.nil? -%> + wait_ms: <%= object.async.operation.wait_ms %> +<% end -%> +<% #async.operation.timeouts %> +<% unless object.async.operation.timeouts.nil? -%> + timeouts: +<% unless object.async.operation.timeouts.insert_minutes.nil? -%> + insert_minutes: <%= object.async.operation.timeouts.insert_minutes %> +<% end -%> +<% unless object.async.operation.timeouts.update_minutes.nil? -%> + update_minutes: <%= object.async.operation.timeouts.update_minutes %> +<% end -%> +<% unless object.async.operation.timeouts.delete_minutes.nil? -%> + delete_minutes: <%= object.async.operation.timeouts.delete_minutes %> +<% end -%> +<% end -%> +<% end -%> +<% #async.result %> +<% unless object.async.result.nil? -%> + result: +<% unless object.async.result.path.nil? -%> + path: '<%= object.async.result.path %>' +<% end -%> +<% unless object.async.result.resource_inside_response.nil? -%> + resource_inside_response: <%= object.async.result.resource_inside_response %> +<% end -%> +<% end -%> +<% #async.error %> +<% unless object.async.error.nil? -%> + error: +<% unless object.async.error.path.nil? -%> + path: '<%= object.async.error.path %>' +<% end -%> +<% unless object.async.error.message.nil? -%> + message: '<%= object.async.error.message %>' +<% end -%> +<% end -%> +<% end -%> +<% end -%> +<% +#collection/identity url +-%> +<% unless object.collection_url_key == object.name.plural.camelize(:lower) -%> +collection_url_key: '<%= object.collection_url_key %>' +<% end -%> +<% unless object.nested_query.nil? -%> +nested_query: +<% unless object.nested_query.keys.nil? -%> + keys: +<% object.nested_query.keys.each do |key| %> + - <%= key -%> +<% end -%> +<% end -%> +<% unless object.nested_query.is_list_of_ids.nil? -%> + is_list_of_ids: <%= object.nested_query.is_list_of_ids %> +<% end -%> +<% unless object.nested_query.modify_by_patch.nil? -%> + modify_by_patch: <%= object.nested_query.modify_by_patch %> +<% end -%> +<% end -%> +<% +#IAM +-%> +<% unless object.iam_policy.nil? -%> +iam_policy: +<% unless !object.iam_policy.exclude -%> + exclude: <%= object.iam_policy.exclude %> +<% end -%> +<% unless !object.iam_policy.exclude_tgc -%> + exclude_tgc: <%= object.iam_policy.exclude_tgc %> +<% end -%> +<% unless !object.iam_policy.skip_import_test -%> + skip_import_test: <%= object.iam_policy.skip_import_test %> +<% end -%> +<% unless object.iam_policy.method_name_separator == '/' -%> + method_name_separator: '<%= object.iam_policy.method_name_separator %>' +<% end -%> +<% unless object.iam_policy.parent_resource_type.nil? -%> + parent_resource_type: '<%= object.iam_policy.parent_resource_type %>' +<% end -%> +<% unless object.iam_policy.fetch_iam_policy_verb.to_s == 'GET' -%> + fetch_iam_policy_verb: '<%= object.iam_policy.fetch_iam_policy_verb.to_s %>' +<% end -%> +<% unless object.iam_policy.fetch_iam_policy_method == 'getIamPolicy' -%> + fetch_iam_policy_method: '<%= object.iam_policy.fetch_iam_policy_method %>' +<% end -%> +<% unless object.iam_policy.set_iam_policy_verb.to_s == 'POST' -%> + set_iam_policy_verb: '<%= object.iam_policy.set_iam_policy_verb.to_s %>' +<% end -%> +<% unless object.iam_policy.set_iam_policy_method == 'setIamPolicy' -%> + set_iam_policy_method: '<%= object.iam_policy.set_iam_policy_method %>' +<% end -%> +<% unless object.iam_policy.wrapped_policy_obj -%> + wrapped_policy_obj: <%= object.iam_policy.wrapped_policy_obj %> +<% end -%> +<% unless object.iam_policy.allowed_iam_role == 'roles/viewer' -%> + allowed_iam_role: '<%= object.iam_policy.allowed_iam_role %>' +<% end -%> +<% unless object.iam_policy.admin_iam_role.nil? -%> + admin_iam_role: '<%= object.iam_policy.admin_iam_role %>' +<% end -%> +<% unless object.iam_policy.parent_resource_attribute == 'id' -%> + parent_resource_attribute: '<%= object.iam_policy.parent_resource_attribute %>' +<% end -%> +<% unless object.iam_policy.test_project_name.nil? -%> + test_project_name: '<%= object.iam_policy.test_project_name %>' +<% end -%> +<% unless object.iam_policy.iam_conditions_request_type.nil? -%> + iam_conditions_request_type: '<%= object.iam_policy.iam_conditions_request_type %>' +<% end -%> +<% unless object.iam_policy.base_url.nil? -%> + base_url: '<%= object.iam_policy.base_url %>' +<% end -%> +<% unless object.iam_policy.self_link.nil? -%> + self_link: '<%= object.iam_policy.self_link %>' +<% end -%> +<% unless object.iam_policy.import_format.nil? -%> + import_format: +<% object.iam_policy.import_format.each do |iformat| -%> + - '<%= iformat %>' +<% end -%> +<% end -%> +<% unless object.iam_policy.iam_policy_version.nil? -%> + iam_policy_version: '<%= object.iam_policy.iam_policy_version %>' +<% end -%> +<% unless object.iam_policy.min_version.nil? -%> + min_version: '<%= object.iam_policy.min_version %>' +<% end -%> +<% unless object.iam_policy.substitute_zone_value -%> + substitute_zone_value: <%= object.iam_policy.substitute_zone_value %> +<% end -%> +<% end -%> +<% +#custom code +-%> +<% unless object.custom_code.nil? -%> +custom_code: +<% unless object.custom_code.extra_schema_entry.nil? -%> + extra_schema_entry: '<%= object.custom_code.extra_schema_entry %>' +<% end -%> +<% unless object.custom_code.constants.nil? -%> + constants: '<%= object.custom_code.constants %>' +<% end -%> +<% unless object.custom_code.encoder.nil? -%> + encoder: '<%= object.custom_code.encoder %>' +<% end -%> +<% unless object.custom_code.update_encoder.nil? -%> + update_encoder: '<%= object.custom_code.update_encoder %>' +<% end -%> +<% unless object.custom_code.decoder.nil? -%> + decoder: '<%= object.custom_code.decoder %>' +<% end -%> +<% unless object.custom_code.pre_create.nil? -%> + pre_create: '<%= object.custom_code.pre_create %>' +<% end -%> +<% unless object.custom_code.post_create.nil? -%> + post_create: '<%= object.custom_code.post_create %>' +<% end -%> +<% unless object.custom_code.custom_create.nil? -%> + custom_create: '<%= object.custom_code.custom_create %>' +<% end -%> +<% unless object.custom_code.pre_read.nil? -%> + pre_read: '<%= object.custom_code.pre_read %>' +<% end -%> +<% unless object.custom_code.pre_update.nil? -%> + pre_update: '<%= object.custom_code.pre_update %>' +<% end -%> +<% unless object.custom_code.post_update.nil? -%> + post_update: '<%= object.custom_code.post_update %>' +<% end -%> +<% unless object.custom_code.custom_update.nil? -%> + custom_update: '<%= object.custom_code.custom_update %>' +<% end -%> +<% unless object.custom_code.pre_delete.nil? -%> + pre_delete: '<%= object.custom_code.pre_delete %>' +<% end -%> +<% unless object.custom_code.custom_import.nil? -%> + custom_import: '<%= object.custom_code.custom_import %>' +<% end -%> +<% unless object.custom_code.post_import.nil? -%> + post_import: '<%= object.custom_code.post_import %>' +<% end -%> +<% unless object.custom_code.test_check_destroy.nil? -%> + test_check_destroy: '<%= object.custom_code.test_check_destroy %>' +<% end -%> +<% end -%> +<% unless object.custom_diff.empty? || (object.custom_diff.size == 1 && object.custom_diff.include?("tpgresource.SetLabelsDiff")) -%> +custom_diff: +<% object.custom_diff.each do |cdiff| -%> + - '<%= cdiff %>' +<% end -%> +<% end -%> +<% unless !object.skip_default_cdiff -%> +skip_default_cdiff: <%= object.skip_default_cdiff %> +<% end -%> +<% +#terraform overrides +-%> +<% unless object.filename_override.nil? -%> +filename_override: '<%= object.filename_override %>' +<% end -%> +<% unless object.exclude_tgc.nil? -%> +exclude_tgc: <%= object.exclude_tgc %> +<% end -%> +<% unless !object.skip_sweeper -%> +skip_sweeper: <%= object.skip_sweeper %> +<% end -%> +<% unless object.error_retry_predicates.nil? -%> +error_retry_predicates: +<% object.error_retry_predicates.each do |erpred| %> + - '<%= erpred -%>' +<% end -%> +<% end -%> +<% unless object.error_abort_predicates.nil? -%> +error_abort_predicates: +<% object.error_abort_predicates.each do |eapred| %> + - '<%= eapred -%>' +<% end -%> +<% end -%> +<% unless object.schema_version.nil? -%> +schema_version: <%= object.schema_version %> +<% end -%> +<% unless object.state_upgrade_base_schema_version == 0 -%> +state_upgrade_base_schema_version: <%= object.state_upgrade_base_schema_version %> +<% end -%> +<% unless !object.state_upgraders -%> +state_upgraders: <%= object.state_upgraders %> +<% end -%> +<% unless object.migrate_state.nil? -%> +migrate_state: '<%= object.migrate_state %>' +<% end -%> +<% unless !object.legacy_long_form_project -%> +legacy_long_form_project: <%= object.legacy_long_form_project %> +<% end -%> +<% unless !object.supports_indirect_user_project_override -%> +supports_indirect_user_project_override: <%= object.supports_indirect_user_project_override %> +<% end -%> +<% unless object.read_error_transform.nil? -%> +read_error_transform: '<%= object.read_error_transform %>' +<% end -%> +<% unless !object.taint_resource_on_failed_create -%> +taint_resource_on_failed_create: <%= object.taint_resource_on_failed_create %> +<% end -%> +<% unless object.deprecation_message.nil? -%> +deprecation_message: '<%= object.deprecation_message %>' +<% end -%> +<% +#examples +-%> +<% unless object.examples.empty? -%> +examples: +<% object.examples.each do |example| -%> + - name: '<%= example.name %>' +<% unless example.config_path == "templates/terraform/examples/#{example.name}.tf.erb" -%> + config_path: '<%= example.config_path %>' +<% end -%> +<% unless example.primary_resource_id.nil? -%> + primary_resource_id: '<%= example.primary_resource_id %>' +<% end -%> +<% unless example.primary_resource_id.nil? -%> + primary_resource_name: '<%= example.primary_resource_id %>' +<% end -%> +<% unless example.min_version.nil? -%> + min_version: '<%= example.min_version %>' +<% end -%> +<% unless example.vars.nil? -%> +<% unless example.vars.empty? -%> + vars: +<% example.vars.each do |vname, val| -%> + <%= vname -%>: '<%= val %>' +<% end -%> +<% end -%> +<% end -%> +<% unless example.test_env_vars.nil? -%> +<% unless example.test_env_vars.empty? -%> + test_env_vars: +<% example.test_env_vars.each do |vname, val| -%> + <%= vname -%>: '<%= val %>' +<% end -%> +<% end -%> +<% end -%> +<% unless example.test_vars_overrides.nil? -%> +<% unless example.test_vars_overrides.empty? -%> + test_vars_overrides: +<% example.test_vars_overrides.each do |vname, val| -%> + '<%= vname -%>': '<%= val %>' +<% end -%> +<% end -%> +<% end -%> +<% unless example.ignore_read_extra.empty? -%> + ignore_read_extra: +<% example.ignore_read_extra.each do |irextra| -%> + '<%= irextra %>' +<% end -%> +<% end -%> +<% unless !example.pull_external -%> + pull_external: <%= example.pull_external %> +<% end -%> +<% unless example.skip_test.nil? -%> + skip_test: <%= example.skip_test %> +<% end -%> +<% unless example.skip_import_test.nil? -%> + skip_import_test: <%= example.skip_import_test %> +<% end -%> +<% unless example.skip_docs.nil? -%> + skip_docs: <%= example.skip_docs %> +<% end -%> +<% unless example.skip_vcr.nil? -%> + skip_vcr: <%= example.skip_vcr %> +<% end -%> +<% end -%> +<% end -%> +<% +#virtual fields +-%> +<% unless object.virtual_fields.empty? -%> +virtual_fields: +<% object.virtual_fields.each do |vfield| -%> + - name: '<%= vfield.name %>' + description: '<%= vfield.description %>' +<% unless vfield.type.nil? -%> + type: <%= tf_type(vfield.type) %> +<% end -%> +<% unless vfield.default_value.nil? -%> + default_value: <%= go_literal(vfield.default_value) %> +<% end -%> +<% unless vfield.immutable.nil? -%> + immutable: <%= vfield.immutable %> +<% end -%> +<% end -%> +<% end -%> +<% +#fields +-%> +<% unless object.parameters.nil? -%> +parameters: +<% object.parameters.each do |prop| -%> +<%= lines(build_newyaml_field(prop, object, pwd)) -%> +<% end -%> +<% end -%> +<% unless object.properties.nil? -%> +properties: +<% object.properties.each do |prop| -%> +<%= lines(build_newyaml_field(prop, object, pwd)) -%> +<% end -%> +<% end -%> +<%# end -%> + diff --git a/mmv1/templates/terraform/yaml_conversion_field.erb b/mmv1/templates/terraform/yaml_conversion_field.erb new file mode 100644 index 000000000000..0ce6832e4fd7 --- /dev/null +++ b/mmv1/templates/terraform/yaml_conversion_field.erb @@ -0,0 +1,210 @@ +<% indent_spaces = 2 -%> +<% 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? -%> + description: "<%= property.description.strip.gsub('"', '\'') -%>" +<% end -%> +<% unless !property.unordered_list -%> + unordered_list: <%= property.unordered_list %> +<% end -%> +<% unless !property.is_set -%> + is_set: <%= property.is_set %> +<% end -%> +<% unless !property.schema_config_mode_attr -%> + schema_config_mode_attr: <%= property.schema_config_mode_attr %> +<% end -%> +<% unless property.pattern.nil? -%> + pattern: '<%= property.pattern %>' +<% end -%> +<% unless !property.exclude -%> + exclude: <%= property.exclude %> +<% end -%> +<% unless property.__resource.nil? -%> +<% unless property.min_version.name == 'ga'-%> + min_version: '<%= property.min_version.name %>' +<% end -%> +<% end -%> +<% unless property.exact_version.nil? -%> + exact_version: '<%= property.exact_version %>' +<% end -%> +<% unless property.url_param_only.nil? -%> + url_param_only: <%= property.url_param_only %> +<% end -%> +<% unless property.required.nil? -%> + required: <%= property.required %> +<% end -%> +<% unless property.immutable.nil? -%> + immutable: <%= property.immutable %> +<% end -%> +<% unless property.ignore_read.nil? -%> + ignore_read: <%= property.ignore_read %> +<% end -%> +<% unless !property.sensitive -%> + sensitive: <%= property.sensitive %> +<% end -%> +<% unless !property.default_from_api -%> + default_from_api: <%= property.default_from_api %> +<% end -%> +<% unless !property.output -%> + output: <%= property.output %> +<% end -%> +<% unless property.send_empty_value.nil? -%> + send_empty_value: <%= property.send_empty_value %> +<% end -%> +<% unless property.allow_empty_object.nil? -%> + allow_empty_object: <%= property.allow_empty_object %> +<% end -%> +<% unless property.read_query_params.nil? -%> + read_query_params: '<%= property.read_query_params %>' +<% end -%> +<% unless property.update_url.nil? -%> + update_url: '<%= property.update_url %>' +<% end -%> +<% unless property.update_verb == property.__resource&.update_verb -%> + update_verb: '<%= property.update_verb.to_s %>' +<% end -%> +<% unless property.update_id.nil? -%> + update_id: '<%= property.update_id %>' +<% end -%> +<% unless property.update_mask_fields.nil? -%> + update_mask_fields: +<% property.update_mask_fields.each do |fname| -%> + - '<%= fname %>' +<% end -%> +<% end -%> +<% unless property.fingerprint_name.nil? -%> + fingerprint_name: '<%= property.fingerprint_name %>' +<% end -%> +<% unless property.conflicts.nil? -%> +<% unless property.conflicts.empty? -%> + conflicts: +<% property.conflicts.each do |fname| -%> + - <%= fname %> +<% end -%> +<% end -%> +<% end -%> +<% unless property.at_least_one_of.nil? -%> +<% unless property.at_least_one_of.empty? -%> + at_least_one_of: +<% property.at_least_one_of.each do |fname| -%> + - '<%= fname %>' +<% end -%> +<% end -%> +<% end -%> +<% unless property.exactly_one_of.nil? -%> +<% unless property.exactly_one_of.empty? -%> + exactly_one_of: +<% property.exactly_one_of.each do |fname| -%> + - '<%= fname %>' +<% end -%> +<% end -%> +<% end -%> +<% unless property.required_with.nil? -%> +<% unless property.required_with.empty? -%> + required_with: +<% property.required_with.each do |fname| -%> + - '<%= fname %>' +<% end -%> +<% end -%> +<% end -%> +<% unless property.key_expander == 'tpgresource.ExpandString' -%> + key_expander: '<%= property.key_expander %>' +<% end -%> +<% unless property.key_diff_suppress_func.nil? -%> + key_diff_suppress_func: '<%= property.key_diff_suppress_func %>' +<% end -%> +<% unless property.diff_suppress_func.nil? -%> + diff_suppress_func: '<%= property.diff_suppress_func %>' +<% end -%> +<% unless property.state_func.nil? -%> + state_func: '<%= property.state_func %>' +<% end -%> +<% unless property.set_hash_func.nil? -%> + set_hash_func: '<%= property.set_hash_func %>' +<% end -%> +<% unless property.custom_flatten.nil? -%> + custom_flatten: '<%= property.custom_flatten %>' +<% end -%> +<% unless property.custom_expand.nil? -%> + custom_expand: '<%= property.custom_expand %>' +<% end -%> +<% unless property.flatten_object.nil? -%> + flatten_object: '<%= property.flatten_object %>' +<% end -%> +<% unless property.validation.nil? -%> + validation: +<% unless property.validation.regex.nil? -%> + regex: '<%= property.validation.regex %>' +<% end -%> +<% unless property.validation.function.nil? -%> + function: '<%= property.validation.function %>' +<% end -%> +<% end -%> +<% unless property.default_value.nil? -%> + default_value: <%= property.default_value %> +<% end -%> +<% unless property.deprecation_message.nil? -%> + deprecation_message: '<%= property.deprecation_message %>' +<% end -%> +<% unless property.removed_message.nil? -%> + removed_message: '<%= property.removed_message %>' +<% 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 %> +<% unless property.item_type.properties.nil? -%> + properties: +<% property.item_type.properties.each do |prop| -%> +<%= lines(indent(build_newyaml_field(prop, object, pwd), 4)) -%> +<% end -%> +<% end -%> +<% else -%> + item_type: <%= property.item_type.to_s %> +<% end -%> +<% unless property.min_size.nil? -%> + min_size: <%= property.min_size %> +<% end -%> +<% unless property.max_size.nil? -%> + max_size: <%= property.max_size %> +<% end -%> +<% end -%> +<% if property.is_a?(Api::Type::ResourceRef) -%> +<% unless property.resource.nil? -%> + resource: '<%= property.resource %>' +<% end -%> +<% unless property.imports.nil? -%> + imports: '<%= property.imports.to_s %>' +<% end -%> +<% end -%> +<% if property.is_a?(Api::Type::Enum) -%> +<% unless property.values.nil? -%> + enum_values: +<% property.values.each do |enumval| -%> + - '<%= enumval %>' +<% end -%> +<% end -%> +<% unless property.skip_docs_values.nil? -%> + skip_docs_values: <%= property.skip_docs_values %> +<% 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 %>' +<% end -%> +<% end -%> +<% if property.is_a?(Api::Type::NestedObject) -%> +<% unless property.properties.nil? -%> + properties: +<% property.properties.each do |prop| -%> +<%= lines(indent(build_newyaml_field(prop, object, pwd), 4)) -%> +<% end -%> +<% end -%> +<% end -%> +<% end -%> \ No newline at end of file diff --git a/mmv1/third_party/go.mod b/mmv1/third_party/go.mod new file mode 100644 index 000000000000..8b4fa24a554a --- /dev/null +++ b/mmv1/third_party/go.mod @@ -0,0 +1,2 @@ +// This is an empty go.mod to ensure packages required +// in this folder are not considered part of tpgtools modules. \ No newline at end of file