From 15c97968975c24f797915b71fdf65120c02b7023 Mon Sep 17 00:00:00 2001 From: Ryan Oaks Date: Wed, 8 Jan 2025 12:30:26 -0500 Subject: [PATCH 01/30] Fix import used in constants/network_services_gateway.go.tmpl (#12705) --- .../terraform/constants/network_services_gateway.go.tmpl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mmv1/templates/terraform/constants/network_services_gateway.go.tmpl b/mmv1/templates/terraform/constants/network_services_gateway.go.tmpl index e1607aad1e43..8091efc46a73 100644 --- a/mmv1/templates/terraform/constants/network_services_gateway.go.tmpl +++ b/mmv1/templates/terraform/constants/network_services_gateway.go.tmpl @@ -12,11 +12,7 @@ */ -}} import ( -{{ if eq $.TargetVersionName `ga` }} - tpgcompute "github.com/hashicorp/terraform-provider-google/google/services/compute" -{{- else }} - tpgcompute "github.com/hashicorp/terraform-provider-google-beta/google-beta/services/compute" -{{- end }} + tpgcompute "{{ $.ImportPath }}/services/compute" ) // Checks if there is another gateway under the same location. From 2b00b9bdf989b2ea8902acec8ee401d0d889da22 Mon Sep 17 00:00:00 2001 From: kautikdk <144651627+kautikdk@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:35:16 +0000 Subject: [PATCH 02/30] Fixes PermaDiff Issue in `google_storage_transfer_job.aws_s3_data_source.aws_access_key` field (#12666) --- .../storagetransfer/resource_storage_transfer_job.go.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/third_party/terraform/services/storagetransfer/resource_storage_transfer_job.go.tmpl b/mmv1/third_party/terraform/services/storagetransfer/resource_storage_transfer_job.go.tmpl index 28c963496d7e..36c85745f54c 100644 --- a/mmv1/third_party/terraform/services/storagetransfer/resource_storage_transfer_job.go.tmpl +++ b/mmv1/third_party/terraform/services/storagetransfer/resource_storage_transfer_job.go.tmpl @@ -1150,7 +1150,7 @@ func flattenAwsS3Data(awsS3Data *storagetransfer.AwsS3Data, d *schema.ResourceDa "path": awsS3Data.Path, "role_arn": awsS3Data.RoleArn, } - if _, exist := d.GetOkExists("transfer_spec.0.aws_s3_data_source.0.aws_access_key"); exist{ + if _, exist := d.GetOk("transfer_spec.0.aws_s3_data_source.0.aws_access_key"); exist{ data["aws_access_key"] = flattenAwsAccessKeys(d) } return []map[string]interface{}{data} From c1ae9f3234aa4303bb3b817a7edb742c71ccb4ea Mon Sep 17 00:00:00 2001 From: karolgorc Date: Wed, 8 Jan 2025 18:39:35 +0100 Subject: [PATCH 03/30] Allow for `IPV6_ONLY` stackType configurations (#12485) Co-authored-by: Zhenhua Li --- mmv1/products/compute/Subnetwork.yaml | 13 ++++ .../subnetwork_ipv6_only_external.tf.tmpl | 12 ++++ .../subnetwork_ipv6_only_internal.tf.tmpl | 13 ++++ .../compute/resource_compute_instance.go.tmpl | 4 +- ...resource_compute_instance_template.go.tmpl | 2 +- .../resource_compute_instance_test.go.tmpl | 65 +++++++++++++++++++ ...e_compute_region_instance_template.go.tmpl | 3 +- .../docs/r/compute_instance.html.markdown | 2 +- .../r/compute_instance_template.html.markdown | 2 +- ...ute_region_instance_template.html.markdown | 2 +- 10 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 mmv1/templates/terraform/examples/subnetwork_ipv6_only_external.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/subnetwork_ipv6_only_internal.tf.tmpl diff --git a/mmv1/products/compute/Subnetwork.yaml b/mmv1/products/compute/Subnetwork.yaml index 193862d5ba51..6aa5959546ea 100644 --- a/mmv1/products/compute/Subnetwork.yaml +++ b/mmv1/products/compute/Subnetwork.yaml @@ -124,6 +124,18 @@ examples: network_name: 'network-reserved-secondary-range' primary_range_name: 'reserved-primary' secondary_range_name: 'reserved-secondary' + - name: 'subnetwork_ipv6_only_internal' + primary_resource_id: 'subnetwork-ipv6-only' + exclude_docs: true + vars: + subnetwork_name: 'subnet-ipv6-only' + network_name: 'network-ipv6-only' + - name: 'subnetwork_ipv6_only_external' + primary_resource_id: 'subnetwork-ipv6-only' + exclude_docs: true + vars: + subnetwork_name: 'subnet-ipv6-only' + network_name: 'network-ipv6-only' virtual_fields: - name: 'send_secondary_ip_range_if_empty' description: | @@ -403,6 +415,7 @@ properties: enum_values: - 'IPV4_ONLY' - 'IPV4_IPV6' + - 'IPV6_ONLY' - name: 'ipv6AccessType' type: Enum description: | diff --git a/mmv1/templates/terraform/examples/subnetwork_ipv6_only_external.tf.tmpl b/mmv1/templates/terraform/examples/subnetwork_ipv6_only_external.tf.tmpl new file mode 100644 index 000000000000..d68a2949b6b5 --- /dev/null +++ b/mmv1/templates/terraform/examples/subnetwork_ipv6_only_external.tf.tmpl @@ -0,0 +1,12 @@ +resource "google_compute_subnetwork" "subnetwork-ipv6-only" { + name = "{{index $.Vars "subnetwork_name"}}" + region = "us-central1" + network = google_compute_network.custom-test.id + stack_type = "IPV6_ONLY" + ipv6_access_type = "EXTERNAL" +} + +resource "google_compute_network" "custom-test" { + name = "{{index $.Vars "network_name"}}" + auto_create_subnetworks = false +} diff --git a/mmv1/templates/terraform/examples/subnetwork_ipv6_only_internal.tf.tmpl b/mmv1/templates/terraform/examples/subnetwork_ipv6_only_internal.tf.tmpl new file mode 100644 index 000000000000..22de9abc33a1 --- /dev/null +++ b/mmv1/templates/terraform/examples/subnetwork_ipv6_only_internal.tf.tmpl @@ -0,0 +1,13 @@ +resource "google_compute_subnetwork" "subnetwork-ipv6-only" { + name = "{{index $.Vars "subnetwork_name"}}" + region = "us-central1" + network = google_compute_network.custom-test.id + stack_type = "IPV6_ONLY" + ipv6_access_type = "INTERNAL" +} + +resource "google_compute_network" "custom-test" { + name = "{{index $.Vars "network_name"}}" + auto_create_subnetworks = false + enable_ula_internal_ipv6 = true +} diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl index c33b23338b4b..158e7fccaba6 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance.go.tmpl @@ -536,7 +536,7 @@ func ResourceComputeInstance() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - ValidateFunc: validation.StringInSlice([]string{"IPV4_ONLY", "IPV4_IPV6", ""}, false), + ValidateFunc: validation.StringInSlice([]string{"IPV4_ONLY", "IPV4_IPV6", "IPV6_ONLY", ""}, false), Description: `The stack type for this network interface to identify whether the IPv6 feature is enabled or not. If not specified, IPV4_ONLY will be used.`, }, @@ -548,7 +548,7 @@ func ResourceComputeInstance() *schema.Resource { "ipv6_access_config": { Type: schema.TypeList, - Optional: true, + Optional: true, Description: `An array of IPv6 access configurations for this interface. Currently, only one IPv6 access config, DIRECT_IPV6, is supported. If there is no ipv6AccessConfig specified, then this instance will have no external IPv6 Internet access.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.tmpl index b3dad4e706ce..6484ebc19eb1 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_template.go.tmpl @@ -556,7 +556,7 @@ Google Cloud KMS.`, Optional: true, Computed: true, ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"IPV4_ONLY", "IPV4_IPV6", ""}, false), + ValidateFunc: validation.StringInSlice([]string{"IPV4_ONLY", "IPV4_IPV6", "IPV6_ONLY", ""}, false), Description: `The stack type for this network interface to identify whether the IPv6 feature is enabled or not. If not specified, IPV4_ONLY will be used.`, }, diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_instance_test.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_instance_test.go.tmpl index 831721a741e2..47177c210383 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_instance_test.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_instance_test.go.tmpl @@ -4278,6 +4278,27 @@ func TestAccComputeInstance_NicStackTypeUpdate(t *testing.T) { }) } +func TestAccComputeInstance_NicStackType_IPV6(t *testing.T) { + t.Parallel() + context := map[string]interface{}{ + "instance_name": fmt.Sprintf("tf-test-compute-instance-%s", acctest.RandString(t, 10)), + "suffix": acctest.RandString(t, 10), + "env_region": envvar.GetTestRegionFromEnv(), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckComputeInstanceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeInstance_nicStackTypeUpdate_ipv6(context), + }, + }, + }) +} + + func testAccCheckComputeInstanceDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { config := acctest.GoogleProviderConfig(t) @@ -11368,3 +11389,47 @@ resource "google_compute_instance" "foobar" { } `, context) } + +func testAccComputeInstance_nicStackTypeUpdate_ipv6(context map[string]interface{}) string { + return acctest.Nprintf(` +data "google_compute_image" "my_image" { + family = "debian-11" + project = "debian-cloud" +} + +resource "google_compute_network" "inst-test-network" { + name = "tf-test-network-%{suffix}" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "inst-test-subnetwork" { + name = "tf-test-compute-subnet-%{suffix}" + region = "%{env_region}" + ipv6_access_type = "EXTERNAL" + stack_type = "IPV6_ONLY" + network = google_compute_network.inst-test-network.id +} + +resource "google_compute_instance" "foobar" { + name = "%{instance_name}" + machine_type = "e2-medium" + zone = "%{env_region}-a" + + boot_disk { + initialize_params { + image = data.google_compute_image.my_image.self_link + } + } + + network_interface { + network = google_compute_network.inst-test-network.id + subnetwork = google_compute_subnetwork.inst-test-subnetwork.id + stack_type = "IPV6_ONLY" + + ipv6_access_config { + network_tier = "PREMIUM" + } + } +} +`, context) +} \ No newline at end of file diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.tmpl index dad4e04a7dc9..12f5927881ce 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_region_instance_template.go.tmpl @@ -524,7 +524,7 @@ Google Cloud KMS.`, Optional: true, Computed: true, ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"IPV4_ONLY", "IPV4_IPV6", ""}, false), + ValidateFunc: validation.StringInSlice([]string{"IPV4_ONLY", "IPV4_IPV6","IPV6_ONLY", ""}, false), Description: `The stack type for this network interface to identify whether the IPv6 feature is enabled or not. If not specified, IPV4_ONLY will be used.`, }, @@ -538,6 +538,7 @@ Google Cloud KMS.`, "ipv6_access_config": { Type: schema.TypeList, Optional: true, + Computed: true, ForceNew: true, Description: `An array of IPv6 access configurations for this interface. Currently, only one IPv6 access config, DIRECT_IPV6, is supported. If there is no ipv6AccessConfig specified, then this instance will have no external IPv6 Internet access.`, Elem: &schema.Resource{ diff --git a/mmv1/third_party/terraform/website/docs/r/compute_instance.html.markdown b/mmv1/third_party/terraform/website/docs/r/compute_instance.html.markdown index a9d0efc6469f..1fddef270915 100644 --- a/mmv1/third_party/terraform/website/docs/r/compute_instance.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/compute_instance.html.markdown @@ -401,7 +401,7 @@ is desired, you will need to modify your state file manually using * `network_attachment` - (Optional) [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html) The URL of the network attachment that this interface should connect to in the following format: `projects/{projectNumber}/regions/{region_name}/networkAttachments/{network_attachment_name}`. -* `stack_type` - (Optional) The stack type for this network interface to identify whether the IPv6 feature is enabled or not. Values are IPV4_IPV6 or IPV4_ONLY. If not specified, IPV4_ONLY will be used. +* `stack_type` - (Optional) The stack type for this network interface to identify whether the IPv6 feature is enabled or not. Values are IPV4_IPV6, IPV6_ONLY or IPV4_ONLY. If not specified, IPV4_ONLY will be used. * `ipv6_access_config` - (Optional) An array of IPv6 access configurations for this interface. Currently, only one IPv6 access config, DIRECT_IPV6, is supported. If there is no ipv6AccessConfig diff --git a/mmv1/third_party/terraform/website/docs/r/compute_instance_template.html.markdown b/mmv1/third_party/terraform/website/docs/r/compute_instance_template.html.markdown index b73f4c095383..efe4969eb92a 100644 --- a/mmv1/third_party/terraform/website/docs/r/compute_instance_template.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/compute_instance_template.html.markdown @@ -563,7 +563,7 @@ The following arguments are supported: * `nic_type` - (Optional) The type of vNIC to be used on this interface. Possible values: GVNIC, VIRTIO_NET. In the beta provider the additional values of MRDMA and IRDMA are supported. -* `stack_type` - (Optional) The stack type for this network interface to identify whether the IPv6 feature is enabled or not. Values are IPV4_IPV6 or IPV4_ONLY. If not specified, IPV4_ONLY will be used. +* `stack_type` - (Optional) The stack type for this network interface to identify whether the IPv6 feature is enabled or not. Values are IPV4_IPV6, IPV6_ONLY or IPV4_ONLY. If not specified, IPV4_ONLY will be used. * `ipv6_access_config` - (Optional) An array of IPv6 access configurations for this interface. Currently, only one IPv6 access config, DIRECT_IPV6, is supported. If there is no ipv6AccessConfig diff --git a/mmv1/third_party/terraform/website/docs/r/compute_region_instance_template.html.markdown b/mmv1/third_party/terraform/website/docs/r/compute_region_instance_template.html.markdown index 5626debd1c81..908b5362a54f 100644 --- a/mmv1/third_party/terraform/website/docs/r/compute_region_instance_template.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/compute_region_instance_template.html.markdown @@ -529,7 +529,7 @@ The following arguments are supported: * `nic_type` - (Optional) The type of vNIC to be used on this interface. Possible values: GVNIC, VIRTIO_NET. In the beta provider the additional values of MRDMA and IRDMA are supported. -* `stack_type` - (Optional) The stack type for this network interface to identify whether the IPv6 feature is enabled or not. Values are IPV4_IPV6 or IPV4_ONLY. If not specified, IPV4_ONLY will be used. +* `stack_type` - (Optional) The stack type for this network interface to identify whether the IPv6 feature is enabled or not. Values are IPV4_IPV6, IPV6_ONLY or IPV4_ONLY. If not specified, IPV4_ONLY will be used. * `ipv6_access_config` - (Optional) An array of IPv6 access configurations for this interface. Currently, only one IPv6 access config, DIRECT_IPV6, is supported. If there is no ipv6AccessConfig From 716d7b69f8bb52a666ec199dc1b214535cfd8fa8 Mon Sep 17 00:00:00 2001 From: sharmajai09 Date: Wed, 8 Jan 2025 09:45:05 -0800 Subject: [PATCH 04/30] Added new Apigee resource /v1/organizations//environments//addonsConfig (#12616) Co-authored-by: Stephen Lewis (Burrows) --- .../apigee/EnvironmentAddonsConfig.yaml | 64 +++++++++++++++++ .../custom_import/apigee_env_addons.go.tmpl | 14 ++++ .../decoders/apigee_env_addons.go.tmpl | 2 + .../apigee_env_addons_analytics_test.tf.tmpl | 70 +++++++++++++++++++ ...apigee_env_addons_enable_analytics.tf.tmpl | 19 +++++ 5 files changed, 169 insertions(+) create mode 100644 mmv1/products/apigee/EnvironmentAddonsConfig.yaml create mode 100644 mmv1/templates/terraform/custom_import/apigee_env_addons.go.tmpl create mode 100644 mmv1/templates/terraform/decoders/apigee_env_addons.go.tmpl create mode 100644 mmv1/templates/terraform/examples/apigee_env_addons_analytics_test.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/apigee_env_addons_enable_analytics.tf.tmpl diff --git a/mmv1/products/apigee/EnvironmentAddonsConfig.yaml b/mmv1/products/apigee/EnvironmentAddonsConfig.yaml new file mode 100644 index 000000000000..d86d8f1fec8d --- /dev/null +++ b/mmv1/products/apigee/EnvironmentAddonsConfig.yaml @@ -0,0 +1,64 @@ +# 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. + +--- +name: 'EnvironmentAddonsConfig' +api_resource_type_kind: AddonsConfig +description: Enable/Disable add-ons for an Apigee environment. +references: + guides: + 'Enable Analytics Add-On': 'https://cloud.google.com/apigee/docs/api-platform/reference/manage-analytics-add-on' + api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.environments.addonsConfig/setAddonEnablement' +docs: +base_url: '{{env_id}}' +self_link: '{{env_id}}/addonsConfig' +create_url: '{{env_id}}/addonsConfig:setAddonEnablement' +update_url: '{{env_id}}/addonsConfig:setAddonEnablement' +update_verb: 'POST' +exclude_delete: true +timeouts: + insert_minutes: 5 + update_minutes: 5 +async: + actions: ['create', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' +import_format: + - '{{env_id}}' +id_format: '{{env_id}}' +custom_code: + custom_import: 'templates/terraform/custom_import/apigee_env_addons.go.tmpl' + decoder: 'templates/terraform/decoders/apigee_env_addons.go.tmpl' +examples: + - name: 'apigee_env_addons_analytics_test' + primary_resource_id: 'apigee_org_addons' + test_env_vars: + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + exclude_docs: true + - name: 'apigee_env_addons_enable_analytics' + exclude_test: true +parameters: + - name: 'envId' + type: String + description: | + The Apigee environment group associated with the Apigee environment, + in the format `organizations/{{org_name}}/environments/{{env_name}}`. + url_param_only: true + required: true + immutable: true +properties: + - name: 'analyticsEnabled' + type: Boolean + description: Flag to enable/disable Analytics. diff --git a/mmv1/templates/terraform/custom_import/apigee_env_addons.go.tmpl b/mmv1/templates/terraform/custom_import/apigee_env_addons.go.tmpl new file mode 100644 index 000000000000..91ab6e38115b --- /dev/null +++ b/mmv1/templates/terraform/custom_import/apigee_env_addons.go.tmpl @@ -0,0 +1,14 @@ +config := meta.(*transport_tpg.Config) + +// current import_formats cannot import fields with forward slashes in their value +if err := tpgresource.ParseImportId([]string{"(?P.+)"}, d, config); err != nil { + return nil, err +} + +id := d.Get("env_id").(string) +nameParts := strings.Split(id, "/") +if len(nameParts) != 4 { + return nil, fmt.Errorf("env is expected to have shape organizations/{{"{{"}}org_id{{"}}"}}/environments/{{"{{"}}env{{"}}"}}, got %s instead", id) +} +d.SetId(id) +return []*schema.ResourceData{d}, nil diff --git a/mmv1/templates/terraform/decoders/apigee_env_addons.go.tmpl b/mmv1/templates/terraform/decoders/apigee_env_addons.go.tmpl new file mode 100644 index 000000000000..4a59f0b283f8 --- /dev/null +++ b/mmv1/templates/terraform/decoders/apigee_env_addons.go.tmpl @@ -0,0 +1,2 @@ +res["analyticsEnabled"] = res["analyticsConfig"].(map[string]interface{})["enabled"] +return res, nil diff --git a/mmv1/templates/terraform/examples/apigee_env_addons_analytics_test.tf.tmpl b/mmv1/templates/terraform/examples/apigee_env_addons_analytics_test.tf.tmpl new file mode 100644 index 000000000000..1fcdbb43f0e8 --- /dev/null +++ b/mmv1/templates/terraform/examples/apigee_env_addons_analytics_test.tf.tmpl @@ -0,0 +1,70 @@ +resource "google_project" "project" { + project_id = "tf-test-%{random_suffix}" + name = "tf-test-%{random_suffix}" + org_id = "{{index $.TestEnvVars "org_id"}}" + billing_account = "{{index $.TestEnvVars "billing_account"}}" + deletion_policy = "DELETE" +} + +resource "google_project_service" "apigee" { + project = google_project.project.project_id + service = "apigee.googleapis.com" +} + +resource "google_project_service" "compute" { + project = google_project.project.project_id + service = "compute.googleapis.com" + depends_on = [google_project_service.servicenetworking ] +} + +resource "google_project_service" "servicenetworking" { + project = google_project.project.project_id + service = "servicenetworking.googleapis.com" + depends_on = [google_project_service.apigee] +} + +resource "google_compute_network" "apigee_network" { + name = "apigee-network" + project = google_project.project.project_id + depends_on = [google_project_service.compute] +} + +resource "google_compute_global_address" "apigee_range" { + name = "apigee-range" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.apigee_network.id + project = google_project.project.project_id +} + +resource "google_service_networking_connection" "apigee_vpc_connection" { + network = google_compute_network.apigee_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.apigee_range.name] + depends_on = [google_project_service.servicenetworking] +} + +resource "google_apigee_organization" "apigee_org" { + analytics_region = "us-central1" + project_id = google_project.project.project_id + authorized_network = google_compute_network.apigee_network.id + billing_type = "PAYG" + depends_on = [ + google_service_networking_connection.apigee_vpc_connection, + google_project_service.apigee + ] +} + +resource "google_apigee_environment" "{{$.PrimaryResourceId}}" { + org_id = google_apigee_organization.apigee_org.id + name = "tf-test%{random_suffix}" + type = "COMPREHENSIVE" + description = "Apigee Environment" + display_name = "environment-1" +} + +resource "google_apigee_environment_addons_config" "{{$.PrimaryResourceId}}" { + env_id = google_apigee_environment.{{$.PrimaryResourceId}}.id + analytics_enabled = true +} diff --git a/mmv1/templates/terraform/examples/apigee_env_addons_enable_analytics.tf.tmpl b/mmv1/templates/terraform/examples/apigee_env_addons_enable_analytics.tf.tmpl new file mode 100644 index 000000000000..943cff305453 --- /dev/null +++ b/mmv1/templates/terraform/examples/apigee_env_addons_enable_analytics.tf.tmpl @@ -0,0 +1,19 @@ +resource "google_apigee_organization" "apigee_org" { + analytics_region = "us-central1" + project_id = google_project.project.project_id + authorized_network = google_compute_network.apigee_network.id + billing_type = "PAYG" +} + +resource "google_apigee_environment" "{{$.PrimaryResourceId}}" { + org_id = google_apigee_organization.apigee_org.id + name = "tf-test%{random_suffix}" + type = "COMPREHENSIVE" + description = "Apigee Environment" + display_name = "environment-1" +} + +resource "google_apigee_environment_addons_config" "{{$.PrimaryResourceId}}" { + env_id = google_apigee_environment.{{$.PrimaryResourceId}}.id + analytics_enabled = true +} From c13527da2aa99b71b95202d8d5ac72fafa0aea5c Mon Sep 17 00:00:00 2001 From: Maksym Omelchenko Date: Wed, 8 Jan 2025 11:24:12 -0800 Subject: [PATCH 05/30] Fix ApigeeOrganization update response type. (#12413) --- mmv1/products/apigee/Organization.yaml | 2 +- ...esource_apigee_organization_update_test.go | 211 ++++++++++++++++++ 2 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 mmv1/third_party/terraform/services/apigee/resource_apigee_organization_update_test.go diff --git a/mmv1/products/apigee/Organization.yaml b/mmv1/products/apigee/Organization.yaml index 55db1fc16838..32096d648d58 100644 --- a/mmv1/products/apigee/Organization.yaml +++ b/mmv1/products/apigee/Organization.yaml @@ -30,7 +30,7 @@ timeouts: delete_minutes: 45 autogen_async: true async: - actions: ['create', 'delete', 'update'] + actions: ['create', 'delete'] type: 'OpAsync' operation: base_url: '{{op_id}}' diff --git a/mmv1/third_party/terraform/services/apigee/resource_apigee_organization_update_test.go b/mmv1/third_party/terraform/services/apigee/resource_apigee_organization_update_test.go new file mode 100644 index 000000000000..cd89a7ea6d84 --- /dev/null +++ b/mmv1/third_party/terraform/services/apigee/resource_apigee_organization_update_test.go @@ -0,0 +1,211 @@ +package apigee_test + +import ( + "maps" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestAccApigeeOrganization_update(t *testing.T) { + acctest.SkipIfVcr(t) + t.Parallel() + + default_context := map[string]interface{}{ + "billing_account": envvar.GetTestBillingAccountFromEnv(t), + "org_id": envvar.GetTestOrgFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + update_context := maps.Clone(default_context) + update_context["org_description"] = "Updated Apigee Org description." + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccApigeeOrganization_full(default_context), + }, + { + ResourceName: "google_apigee_organization.apigee_org", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"project_id", "properties", "retention"}, + }, + { + Config: testAccApigeeOrganization_update(update_context), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("google_apigee_organization.apigee_org", plancheck.ResourceActionUpdate), + }, + }, + }, + { + ResourceName: "google_apigee_organization.apigee_org", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"project_id", "properties", "retention"}, + }, + }, + }) +} + +func testAccApigeeOrganization_full(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_project" "project" { + provider = google + + project_id = "tf-test%{random_suffix}" + name = "tf-test%{random_suffix}" + org_id = "%{org_id}" + billing_account = "%{billing_account}" + deletion_policy = "DELETE" +} + +resource "google_project_service" "apigee" { + provider = google + + project = google_project.project.project_id + service = "apigee.googleapis.com" +} + +resource "google_project_service" "compute" { + provider = google + + project = google_project.project.project_id + service = "compute.googleapis.com" +} + +resource "google_project_service" "servicenetworking" { + provider = google + + project = google_project.project.project_id + service = "servicenetworking.googleapis.com" +} + +resource "google_compute_network" "apigee_network" { + provider = google + + name = "apigee-network" + project = google_project.project.project_id + depends_on = [google_project_service.compute] +} + +resource "google_compute_global_address" "apigee_range" { + provider = google + + name = "apigee-range" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 21 + network = google_compute_network.apigee_network.id + project = google_project.project.project_id +} + +resource "google_service_networking_connection" "apigee_vpc_connection" { + provider = google + + network = google_compute_network.apigee_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.apigee_range.name] + depends_on = [google_project_service.servicenetworking] +} + +resource "google_apigee_organization" "apigee_org" { + provider = google + + display_name = "apigee-org" + analytics_region = "us-central1" + description = "Terraform-managed Apigee Org" + project_id = google_project.project.project_id + authorized_network = google_compute_network.apigee_network.id + billing_type = "EVALUATION" + depends_on = [ + google_service_networking_connection.apigee_vpc_connection, + google_project_service.apigee, + ] +} +`, context) +} + +func testAccApigeeOrganization_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_project" "project" { + provider = google + + project_id = "tf-test%{random_suffix}" + name = "tf-test%{random_suffix}" + org_id = "%{org_id}" + billing_account = "%{billing_account}" + deletion_policy = "DELETE" +} + +resource "google_project_service" "apigee" { + provider = google + + project = google_project.project.project_id + service = "apigee.googleapis.com" +} + +resource "google_project_service" "compute" { + provider = google + + project = google_project.project.project_id + service = "compute.googleapis.com" +} + +resource "google_project_service" "servicenetworking" { + provider = google + + project = google_project.project.project_id + service = "servicenetworking.googleapis.com" +} + +resource "google_compute_network" "apigee_network" { + provider = google + + name = "apigee-network" + project = google_project.project.project_id + depends_on = [google_project_service.compute] +} + +resource "google_compute_global_address" "apigee_range" { + provider = google + + name = "apigee-range" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 21 + network = google_compute_network.apigee_network.id + project = google_project.project.project_id +} + +resource "google_service_networking_connection" "apigee_vpc_connection" { + provider = google + + network = google_compute_network.apigee_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.apigee_range.name] + depends_on = [google_project_service.servicenetworking] +} + +resource "google_apigee_organization" "apigee_org" { + provider = google + + display_name = "apigee-org" + analytics_region = "us-central1" + description = "%{org_description}" + project_id = google_project.project.project_id + authorized_network = google_compute_network.apigee_network.id + billing_type = "EVALUATION" + depends_on = [ + google_service_networking_connection.apigee_vpc_connection, + google_project_service.apigee, + ] +} +`, context) +} From 415b35bae8e1e92e6949d472b987105f369be650 Mon Sep 17 00:00:00 2001 From: Shuya Ma <87669292+shuyama1@users.noreply.github.com> Date: Wed, 8 Jan 2025 11:45:36 -0800 Subject: [PATCH 06/30] Add GOOGLE_VMWAREENGINE_PROJECT env var for vmwareengine tests (#12707) --- .ci/gcb-pr-downstream-generation-and-test.yml | 4 +- .ci/gcb-push-downstream.yml | 3 + .ci/gcb-vcr-nightly.yml | 4 +- .ci/magician/cmd/check_cassettes.go | 1 + .ci/magician/cmd/test_eap_vcr.go | 1 + .ci/magician/cmd/test_terraform_vcr.go | 1 + .ci/magician/cmd/vcr_cassette_update.go | 1 + .ci/magician/vcr/tester.go | 1 + mmv1/api/resource/examples.go | 28 ++++---- mmv1/provider/template_data.go | 66 ++++++++++--------- .../terraform/env_var_context.go.tmpl | 2 + .../components/builds/build_parameters.kt | 10 +++ .../terraform/.teamcity/settings.kts | 7 ++ .../terraform/.teamcity/tests/test_utils.kt | 3 + .../terraform/envvar/envvar_utils.go | 11 ++++ 15 files changed, 96 insertions(+), 47 deletions(-) diff --git a/.ci/gcb-pr-downstream-generation-and-test.yml b/.ci/gcb-pr-downstream-generation-and-test.yml index 675217116245..a1dc4555ad6b 100644 --- a/.ci/gcb-pr-downstream-generation-and-test.yml +++ b/.ci/gcb-pr-downstream-generation-and-test.yml @@ -251,7 +251,7 @@ steps: - name: 'gcr.io/graphite-docker-images/go-plus' id: gcb-tpg-vcr-test entrypoint: '/workspace/.ci/scripts/go-plus/magician/exec.sh' - secretEnv: ["GITHUB_TOKEN_DOWNSTREAMS", "GITHUB_TOKEN_MAGIC_MODULES", "GOOGLE_BILLING_ACCOUNT", "GOOGLE_CHRONICLE_INSTANCE_ID", "GOOGLE_CUST_ID", "GOOGLE_IDENTITY_USER", "GOOGLE_MASTER_BILLING_ACCOUNT", "GOOGLE_ORG", "GOOGLE_ORG_2", "GOOGLE_ORG_DOMAIN", "GOOGLE_PROJECT", "GOOGLE_PROJECT_NUMBER", "GOOGLE_SERVICE_ACCOUNT", "SA_KEY", "GOOGLE_PUBLIC_AVERTISED_PREFIX_DESCRIPTION"] + secretEnv: ["GITHUB_TOKEN_DOWNSTREAMS", "GITHUB_TOKEN_MAGIC_MODULES", "GOOGLE_BILLING_ACCOUNT", "GOOGLE_CHRONICLE_INSTANCE_ID", "GOOGLE_CUST_ID", "GOOGLE_IDENTITY_USER", "GOOGLE_MASTER_BILLING_ACCOUNT", "GOOGLE_ORG", "GOOGLE_ORG_2", "GOOGLE_ORG_DOMAIN", "GOOGLE_PROJECT", "GOOGLE_PROJECT_NUMBER", "GOOGLE_SERVICE_ACCOUNT", "SA_KEY", "GOOGLE_PUBLIC_AVERTISED_PREFIX_DESCRIPTION", "GOOGLE_VMWAREENGINE_PROJECT"] waitFor: ["diff"] env: - BASE_BRANCH=$_BASE_BRANCH @@ -314,3 +314,5 @@ availableSecrets: env: SA_KEY - versionName: projects/673497134629/secrets/ci-test-public-advertised-prefix-description/versions/latest env: GOOGLE_PUBLIC_AVERTISED_PREFIX_DESCRIPTION + - versionName: projects/673497134629/secrets/ci-test-vmwareengine-project/versions/latest + env: GOOGLE_VMWAREENGINE_PROJECT diff --git a/.ci/gcb-push-downstream.yml b/.ci/gcb-push-downstream.yml index 2249dc6d39b2..9d3066a8bc36 100644 --- a/.ci/gcb-push-downstream.yml +++ b/.ci/gcb-push-downstream.yml @@ -203,6 +203,7 @@ steps: - "GOOGLE_SERVICE_ACCOUNT" - "SA_KEY" - "GOOGLE_PUBLIC_AVERTISED_PREFIX_DESCRIPTION" + - "GOOGLE_VMWAREENGINE_PROJECT" env: - "COMMIT_SHA=$COMMIT_SHA" - "GOOGLE_REGION=us-central1" @@ -249,3 +250,5 @@ availableSecrets: env: SA_KEY - versionName: projects/673497134629/secrets/ci-test-public-advertised-prefix-description/versions/latest env: GOOGLE_PUBLIC_AVERTISED_PREFIX_DESCRIPTION + - versionName: projects/673497134629/secrets/ci-test-vmwareengine-project/versions/latest + env: GOOGLE_VMWAREENGINE_PROJECT diff --git a/.ci/gcb-vcr-nightly.yml b/.ci/gcb-vcr-nightly.yml index ee0fc58c518f..e81b744fe4fd 100644 --- a/.ci/gcb-vcr-nightly.yml +++ b/.ci/gcb-vcr-nightly.yml @@ -3,7 +3,7 @@ steps: - name: 'gcr.io/graphite-docker-images/go-plus' id: gcb-vcr-nightly entrypoint: '/workspace/.ci/scripts/go-plus/magician/exec.sh' - secretEnv: ["GOOGLE_BILLING_ACCOUNT", "GOOGLE_CHRONICLE_INSTANCE_ID", "GOOGLE_CUST_ID", "GOOGLE_IDENTITY_USER", "GOOGLE_MASTER_BILLING_ACCOUNT", "GOOGLE_ORG", "GOOGLE_ORG_2", "GOOGLE_ORG_DOMAIN", "GOOGLE_PROJECT", "GOOGLE_PROJECT_NUMBER", "GOOGLE_SERVICE_ACCOUNT", "SA_KEY", "GOOGLE_PUBLIC_AVERTISED_PREFIX_DESCRIPTION", "GITHUB_TOKEN_CLASSIC"] + secretEnv: ["GOOGLE_BILLING_ACCOUNT", "GOOGLE_CHRONICLE_INSTANCE_ID", "GOOGLE_CUST_ID", "GOOGLE_IDENTITY_USER", "GOOGLE_MASTER_BILLING_ACCOUNT", "GOOGLE_ORG", "GOOGLE_ORG_2", "GOOGLE_ORG_DOMAIN", "GOOGLE_PROJECT", "GOOGLE_PROJECT_NUMBER", "GOOGLE_SERVICE_ACCOUNT", "SA_KEY", "GOOGLE_PUBLIC_AVERTISED_PREFIX_DESCRIPTION", "GITHUB_TOKEN_CLASSIC", "GOOGLE_VMWAREENGINE_PROJECT"] env: - "GOOGLE_REGION=us-central1" - "GOOGLE_ZONE=us-central1-a" @@ -48,3 +48,5 @@ availableSecrets: env: GOOGLE_PUBLIC_AVERTISED_PREFIX_DESCRIPTION - versionName: projects/673497134629/secrets/github-classic--repo-workflow/versions/latest env: GITHUB_TOKEN_CLASSIC + - versionName: projects/673497134629/secrets/ci-test-vmwareengine-project/versions/latest + env: GOOGLE_VMWAREENGINE_PROJECT diff --git a/.ci/magician/cmd/check_cassettes.go b/.ci/magician/cmd/check_cassettes.go index 497fcd0c09e9..74b5ec3ce3d6 100644 --- a/.ci/magician/cmd/check_cassettes.go +++ b/.ci/magician/cmd/check_cassettes.go @@ -34,6 +34,7 @@ var ccRequiredEnvironmentVariables = [...]string{ var ccOptionalEnvironmentVariables = [...]string{ "GOOGLE_CHRONICLE_INSTANCE_ID", + "GOOGLE_VMWAREENGINE_PROJECT", } var checkCassettesCmd = &cobra.Command{ diff --git a/.ci/magician/cmd/test_eap_vcr.go b/.ci/magician/cmd/test_eap_vcr.go index 4bf1f272169b..867c1a7ac800 100644 --- a/.ci/magician/cmd/test_eap_vcr.go +++ b/.ci/magician/cmd/test_eap_vcr.go @@ -43,6 +43,7 @@ var tevOptionalEnvironmentVariables = [...]string{ "GOOGLE_ORG_2", "GOOGLE_PUBLIC_AVERTISED_PREFIX_DESCRIPTION", "GOOGLE_SERVICE_ACCOUNT", + "GOOGLE_VMWAREENGINE_PROJECT", } var testEAPVCRCmd = &cobra.Command{ diff --git a/.ci/magician/cmd/test_terraform_vcr.go b/.ci/magician/cmd/test_terraform_vcr.go index ce92d213b54f..3dd30d9a3d1d 100644 --- a/.ci/magician/cmd/test_terraform_vcr.go +++ b/.ci/magician/cmd/test_terraform_vcr.go @@ -57,6 +57,7 @@ var ttvRequiredEnvironmentVariables = [...]string{ var ttvOptionalEnvironmentVariables = [...]string{ "GOOGLE_CHRONICLE_INSTANCE_ID", + "GOOGLE_VMWAREENGINE_PROJECT", } type analytics struct { diff --git a/.ci/magician/cmd/vcr_cassette_update.go b/.ci/magician/cmd/vcr_cassette_update.go index f90a0c0a6541..c03ef43ce470 100644 --- a/.ci/magician/cmd/vcr_cassette_update.go +++ b/.ci/magician/cmd/vcr_cassette_update.go @@ -40,6 +40,7 @@ var vcuRequiredEnvironmentVariables = [...]string{ var vcuOptionalEnvironmentVariables = [...]string{ "GOOGLE_CHRONICLE_INSTANCE_ID", + "GOOGLE_VMWAREENGINE_PROJECT", } var ( diff --git a/.ci/magician/vcr/tester.go b/.ci/magician/vcr/tester.go index e898740080ad..2bd55a12169e 100644 --- a/.ci/magician/vcr/tester.go +++ b/.ci/magician/vcr/tester.go @@ -92,6 +92,7 @@ var safeToLog = map[string]bool{ "GOOGLE_REGION": true, "GOOGLE_SERVICE_ACCOUNT": true, "GOOGLE_TEST_DIRECTORY": true, + "GOOGLE_VMWAREENGINE_PROJECT": true, "GOOGLE_ZONE": true, "GOPATH": true, "HOME": true, diff --git a/mmv1/api/resource/examples.go b/mmv1/api/resource/examples.go index 895b63585c38..3304589a5c86 100644 --- a/mmv1/api/resource/examples.go +++ b/mmv1/api/resource/examples.go @@ -73,6 +73,7 @@ type Examples struct { // - :CUST_ID // - :IDENTITY_USER // - :CHRONICLE_ID + // - :VMWAREENGINE_PROJECT // This list corresponds to the `get*FromEnv` methods in provider_test.go. TestEnvVars map[string]string `yaml:"test_env_vars,omitempty"` @@ -215,19 +216,20 @@ func (e *Examples) SetHCLText() { originalTestEnvVars := e.TestEnvVars docTestEnvVars := make(map[string]string) docs_defaults := map[string]string{ - "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", - "CHRONICLE_ID": "00000000-0000-0000-0000-000000000000", + "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", + "CHRONICLE_ID": "00000000-0000-0000-0000-000000000000", + "VMWAREENGINE_PROJECT": "my-vmwareengine-project", } // Apply doc defaults to test_env_vars from YAML diff --git a/mmv1/provider/template_data.go b/mmv1/provider/template_data.go index 4a90da836d86..50cc1bbb0e79 100644 --- a/mmv1/provider/template_data.go +++ b/mmv1/provider/template_data.go @@ -119,22 +119,23 @@ func (td *TemplateData) GenerateTestFile(filePath string, resource api.Resource) templatePath, } tmplInput := TestInput{ - Res: resource, - ImportPath: td.ImportPath(), - PROJECT_NAME: "my-project-name", - CREDENTIALS: "my/credentials/filename.json", - REGION: "us-west1", - ORG_ID: "123456789", - ORG_DOMAIN: "example.com", - ORG_TARGET: "123456789", - PROJECT_NUMBER: "1111111111111", - 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", - CHRONICLE_ID: "00000000-0000-0000-0000-000000000000", + Res: resource, + ImportPath: td.ImportPath(), + PROJECT_NAME: "my-project-name", + CREDENTIALS: "my/credentials/filename.json", + REGION: "us-west1", + ORG_ID: "123456789", + ORG_DOMAIN: "example.com", + ORG_TARGET: "123456789", + PROJECT_NUMBER: "1111111111111", + 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", + CHRONICLE_ID: "00000000-0000-0000-0000-000000000000", + VMWAREENGINE_PROJECT: "my-vmwareengine-project", } td.GenerateFile(filePath, templatePath, tmplInput, true, templates...) @@ -283,20 +284,21 @@ func FixImports(outputPath string, dumpDiffs bool) { } type TestInput struct { - Res api.Resource - ImportPath string - PROJECT_NAME string - CREDENTIALS string - REGION string - ORG_ID string - ORG_DOMAIN string - ORG_TARGET string - PROJECT_NUMBER string - BILLING_ACCT string - MASTER_BILLING_ACCT string - SERVICE_ACCT string - CUST_ID string - IDENTITY_USER string - PAP_DESCRIPTION string - CHRONICLE_ID string + Res api.Resource + ImportPath string + PROJECT_NAME string + CREDENTIALS string + REGION string + ORG_ID string + ORG_DOMAIN string + ORG_TARGET string + PROJECT_NUMBER string + BILLING_ACCT string + MASTER_BILLING_ACCT string + SERVICE_ACCT string + CUST_ID string + IDENTITY_USER string + PAP_DESCRIPTION string + CHRONICLE_ID string + VMWAREENGINE_PROJECT string } diff --git a/mmv1/templates/terraform/env_var_context.go.tmpl b/mmv1/templates/terraform/env_var_context.go.tmpl index b21a761b48cd..faa834e9f018 100644 --- a/mmv1/templates/terraform/env_var_context.go.tmpl +++ b/mmv1/templates/terraform/env_var_context.go.tmpl @@ -28,6 +28,8 @@ "{{$varKey}}": envvar.GetTestPublicAdvertisedPrefixDescriptionFromEnv(t), {{- else if eq $varVal "CHRONICLE_ID" }} "{{$varKey}}": envvar.GetTestChronicleInstanceIdFromEnv(t), + {{- else if eq $varVal "VMWAREENGINE_PROJECT" }} + "{{$varKey}}": envvar.GetTestVmwareengineProjectFromEnv(t), {{- else if eq $varVal "ZONE" }} "{{$varKey}}": envvar.GetTestZoneFromEnv(), {{- end }} diff --git a/mmv1/third_party/terraform/.teamcity/components/builds/build_parameters.kt b/mmv1/third_party/terraform/.teamcity/components/builds/build_parameters.kt index 8b1f9ac42d0f..4acd6411e05d 100644 --- a/mmv1/third_party/terraform/.teamcity/components/builds/build_parameters.kt +++ b/mmv1/third_party/terraform/.teamcity/components/builds/build_parameters.kt @@ -61,6 +61,11 @@ class AllContextParameters( val chronicleInstanceIdBeta: String, val chronicleInstanceIdVcr: String, + // GOOGLE_VMWAREENGINE_PROJECT + val vmwareengineProjectGa: String, + val vmwareengineProjectBeta: String, + val vmwareengineProjectVcr: String, + // Values that are the same across GA, Beta, and VCR testing environments val billingAccount: String, // GOOGLE_BILLING_ACCOUNT val billingAccount2: String, // GOOGLE_BILLING_ACCOUNT_2 @@ -94,6 +99,7 @@ class AccTestConfiguration( val projectNumber: String, val region: String, val serviceAccount: String, + val vmwareengineProject: String, val zone: String, // VCR specific @@ -120,6 +126,7 @@ fun getGaAcceptanceTestConfig(allConfig: AllContextParameters): AccTestConfigura allConfig.projectNumberGa, allConfig.region, allConfig.serviceAccountGa, + allConfig.vmwareengineProjectGa, allConfig.zone, allConfig.infraProject, allConfig.vcrBucketName, @@ -143,6 +150,7 @@ fun getBetaAcceptanceTestConfig(allConfig: AllContextParameters): AccTestConfigu allConfig.projectNumberBeta, allConfig.region, allConfig.serviceAccountBeta, + allConfig.vmwareengineProjectBeta, allConfig.zone, allConfig.infraProject, allConfig.vcrBucketName, @@ -166,6 +174,7 @@ fun getVcrAcceptanceTestConfig(allConfig: AllContextParameters): AccTestConfigur allConfig.projectNumberVcr, allConfig.region, allConfig.serviceAccountVcr, + allConfig.vmwareengineProjectVcr, allConfig.zone, allConfig.infraProject, allConfig.vcrBucketName, @@ -190,6 +199,7 @@ fun ParametrizedWithType.configureGoogleSpecificTestParameters(config: AccTestCo hiddenVariable("env.GOOGLE_ZONE", config.zone, "The google zone to use") hiddenVariable("env.GOOGLE_IDENTITY_USER", config.identityUser, "The user for the identity platform") hiddenVariable("env.GOOGLE_CHRONICLE_INSTANCE_ID", config.chronicleInstanceId, "The id of the Chronicle instance") + hiddenVariable("env.GOOGLE_VMWAREENGINE_PROJECT", config.vmwareengineProject, "The project used for vmwareengine tests") hiddenPasswordVariable("env.GOOGLE_CREDENTIALS", config.credentials, "The Google credentials for this test runner") } diff --git a/mmv1/third_party/terraform/.teamcity/settings.kts b/mmv1/third_party/terraform/.teamcity/settings.kts index f9bab43f5ee5..46faab219f69 100644 --- a/mmv1/third_party/terraform/.teamcity/settings.kts +++ b/mmv1/third_party/terraform/.teamcity/settings.kts @@ -49,6 +49,10 @@ val org2Vcr = DslContext.getParameter("org2Vcr", "") val chronicleInstanceIdGa = DslContext.getParameter("chronicleInstanceIdGa", "") val chronicleInstanceIdBeta = DslContext.getParameter("chronicleInstanceIdBeta", "") val chronicleInstanceIdVcr = DslContext.getParameter("chronicleInstanceIdVcr", "") +// GOOGLE_VMWAREENGINE_PROJECT +val vmwareengineProjectGa = DslContext.getParameter("vmwareengineProjectGa", "") +val vmwareengineProjectBeta = DslContext.getParameter("vmwareengineProjectBeta", "") +val vmwareengineProjectVcr = DslContext.getParameter("vmwareengineProjectVcr", "") // Values that are the same across GA, Beta, and VCR testing environments val billingAccount = DslContext.getParameter("billingAccount", "") // GOOGLE_BILLING_ACCOUNT @@ -91,6 +95,9 @@ var allContextParams = AllContextParameters( chronicleInstanceIdGa, chronicleInstanceIdBeta, chronicleInstanceIdVcr, + vmwareengineProjectGa, + vmwareengineProjectBeta, + vmwareengineProjectVcr, billingAccount, billingAccount2, custId, diff --git a/mmv1/third_party/terraform/.teamcity/tests/test_utils.kt b/mmv1/third_party/terraform/.teamcity/tests/test_utils.kt index 71ad0bcd4a26..e2e481679b89 100644 --- a/mmv1/third_party/terraform/.teamcity/tests/test_utils.kt +++ b/mmv1/third_party/terraform/.teamcity/tests/test_utils.kt @@ -44,6 +44,9 @@ fun testContextParameters(): AllContextParameters { "chronicleInstanceIdGa", "chronicleInstanceIdBeta", "chronicleInstanceIdVcr", + "vmwareengineProjectGa", + "vmwareengineProjectBeta", + "vmwareengineProjectVcr", "billingAccount", "billingAccount2", "custId", diff --git a/mmv1/third_party/terraform/envvar/envvar_utils.go b/mmv1/third_party/terraform/envvar/envvar_utils.go index 32945dd99143..537242cef5e9 100644 --- a/mmv1/third_party/terraform/envvar/envvar_utils.go +++ b/mmv1/third_party/terraform/envvar/envvar_utils.go @@ -115,6 +115,12 @@ var ImpersonateServiceAccountEnvVars = []string{ "GOOGLE_IMPERSONATE_SERVICE_ACCOUNT", } +// This value is the project used for vmwareengine tests. A separate project is needed +// due to the limited quota allocated to each testing project +var vmwareengineProjectEnvVars = []string{ + "GOOGLE_VMWAREENGINE_PROJECT", +} + // AccTestPreCheck ensures at least one of the project env variables is set. func GetTestProjectNumberFromEnv() string { return transport_tpg.MultiEnvSearch(ProjectNumberEnvVars) @@ -215,6 +221,11 @@ func GetTestChronicleInstanceIdFromEnv(t *testing.T) string { return transport_tpg.MultiEnvSearch(ChronicleInstanceIdEnvVars) } +func GetTestVmwareengineProjectFromEnv(t *testing.T) string { + SkipIfEnvNotSet(t, vmwareengineProjectEnvVars...) + return transport_tpg.MultiEnvSearch(vmwareengineProjectEnvVars) +} + func SkipIfEnvNotSet(t *testing.T, envs ...string) { if t == nil { log.Printf("[DEBUG] Not running inside of test - skip skipping") From 6d54b5142d999bfee3a7570636c4cd322f54733b Mon Sep 17 00:00:00 2001 From: f1urps Date: Wed, 8 Jan 2025 15:12:34 -0600 Subject: [PATCH 07/30] Add edgenetwork InterconnectAttachment resource. (#12586) --- .../edgenetwork/InterconnectAttachment.yaml | 134 ++++++++++++++++++ ...dgenetwork_interconnect_attachment.tf.tmpl | 23 +++ 2 files changed, 157 insertions(+) create mode 100644 mmv1/products/edgenetwork/InterconnectAttachment.yaml create mode 100644 mmv1/templates/terraform/examples/edgenetwork_interconnect_attachment.tf.tmpl diff --git a/mmv1/products/edgenetwork/InterconnectAttachment.yaml b/mmv1/products/edgenetwork/InterconnectAttachment.yaml new file mode 100644 index 000000000000..20662e23e578 --- /dev/null +++ b/mmv1/products/edgenetwork/InterconnectAttachment.yaml @@ -0,0 +1,134 @@ +# 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. + +--- +name: 'InterconnectAttachment' +description: | + A Distributed Cloud Edge interconnect attachment, which connects routers to the northbound network. +references: + guides: + 'Create and manage interconnect attachments': 'https://cloud.google.com/distributed-cloud/edge/latest/docs/attachments#api' + api: 'https://cloud.google.com/distributed-cloud/edge/latest/docs/reference/network/rest/v1/projects.locations.zones.interconnectAttachments' +docs: +base_url: 'projects/{{project}}/locations/{{location}}/zones/{{zone}}/interconnectAttachments' +self_link: 'projects/{{project}}/locations/{{location}}/zones/{{zone}}/interconnectAttachments/{{interconnect_attachment_id}}' +create_url: 'projects/{{project}}/locations/{{location}}/zones/{{zone}}/interconnectAttachments?interconnectAttachmentId={{interconnect_attachment_id}}' +immutable: true +import_format: + - 'projects/{{project}}/locations/{{location}}/zones/{{zone}}/interconnectAttachment/{{interconnect_attachment_id}}' + - '{{name}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 30 +autogen_async: true +async: + actions: ['create', 'delete', 'update'] + type: 'OpAsync' + operation: + base_url: '{{op_id}}' + path: 'name' + wait_ms: 1000 + result: + path: 'response' + resource_inside_response: false + error: + path: 'error' + message: 'message' +examples: + - name: 'edgenetwork_interconnect_attachment' + primary_resource_id: 'example_interconnect_attachment' + vars: + edgenetwork_interconnect_attachment_id: 'example-interconnect-attachment' + edgenetwork_network_id: 'example-network' + test_env_vars: + location: 'REGION' + zone: 'ZONE' + exclude_test: true +parameters: + - name: 'location' + type: String + description: | + The Google Cloud region to which the target Distributed Cloud Edge zone belongs. + url_param_only: true + required: true + immutable: true + - name: 'zone' + type: String + description: | + The name of the target Distributed Cloud Edge zone. + url_param_only: true + required: true + immutable: true + - name: 'interconnect_attachment_id' + type: String + description: | + A unique ID that identifies this interconnect attachment. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + The canonical name of this resource, with format + `projects/{{project}}/locations/{{location}}/zones/{{zone}}/interconnectAttachments/{{interconnect_attachment_id}}` + output: true + - name: 'labels' + type: KeyValueLabels + description: | + Labels associated with this resource. + required: false + - name: 'description' + type: String + description: | + A free-text description of the resource. Max length 1024 characters. + required: false + - name: 'createTime' + type: String + description: | + The time when the resource was created. + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine + fractional digits. Examples: `2014-10-02T15:01:23Z` and `2014-10-02T15:01:23.045123456Z`. + output: true + - name: 'updateTime' + type: String + description: | + The time when the resource was last updated. + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine + fractional digits. Examples: `2014-10-02T15:01:23Z` and `2014-10-02T15:01:23.045123456Z`. + output: true + - name: 'interconnect' + type: String + description: | + The ID of the underlying interconnect that this attachment's traffic will traverse through. + required: true + - name: 'network' + type: ResourceRef + description: | + The ID of the network to which this interconnect attachment belongs. + Must be of the form: `projects/{{project}}/locations/{{location}}/zones/{{zone}}/networks/{{network_id}}` + required: true + custom_expand: 'templates/terraform/custom_expand/resourceref_with_validation.go.tmpl' + resource: 'Network' + imports: 'name' + - name: 'vlanId' + type: Integer + description: | + VLAN ID provided by user. Must be site-wise unique. + required: true + - name: 'mtu' + type: Integer + description: | + IP (L3) MTU value of the virtual edge cloud. Default value is `1500`. Possible values are: `1500`, `9000`. + default_value: 1500 diff --git a/mmv1/templates/terraform/examples/edgenetwork_interconnect_attachment.tf.tmpl b/mmv1/templates/terraform/examples/edgenetwork_interconnect_attachment.tf.tmpl new file mode 100644 index 000000000000..fc3147dc74eb --- /dev/null +++ b/mmv1/templates/terraform/examples/edgenetwork_interconnect_attachment.tf.tmpl @@ -0,0 +1,23 @@ + +resource "google_edgenetwork_interconnect_attachment" "{{$.PrimaryResourceId}}" { + interconnect_attachment_id = "{{index $.Vars "edgenetwork_interconnect_attachment_id"}}" + location = "{{index $.TestEnvVars "location"}}" + zone = "{{index $.TestEnvVars "zone"}}" + description = "Example interconnect attachment." + network = google_edgenetwork_network.example_network.id + interconnect = "11111111-2222-3333-4444-555555555555" + vlan_id = 55 + mtu = 9000 + labels = { + "environment" : "dev" + } +} + +resource "google_edgenetwork_network" "example_network" { + network_id = "{{index $.Vars "edgenetwork_network_id"}}" + location = "{{index $.TestEnvVars "location"}}" + zone = "{{index $.TestEnvVars "zone"}}" + description = "Example network." + mtu = 9000 +} + From e49e760e4b22e10a8cfa964ebd073ae7b2cb5ee4 Mon Sep 17 00:00:00 2001 From: Zhenhua Li Date: Wed, 8 Jan 2025 14:11:20 -0800 Subject: [PATCH 08/30] Add google_project to v6 cai2hcl (#12713) Co-authored-by: Cameron Thornton --- .../cai2hcl/resource_converters.go.tmpl | 7 ++ .../services/resourcemanager/project.go | 76 +++++++++++++++++++ .../tgc_v6/cai2hcl/converters/utils/utils.go | 10 +++ 3 files changed, 93 insertions(+) create mode 100644 mmv1/third_party/tgc_v6/cai2hcl/converters/services/resourcemanager/project.go diff --git a/mmv1/templates/tgc_v6/cai2hcl/resource_converters.go.tmpl b/mmv1/templates/tgc_v6/cai2hcl/resource_converters.go.tmpl index b7f5e92cc2eb..51b0ef39230a 100644 --- a/mmv1/templates/tgc_v6/cai2hcl/resource_converters.go.tmpl +++ b/mmv1/templates/tgc_v6/cai2hcl/resource_converters.go.tmpl @@ -29,8 +29,15 @@ package converters import ( "github.com/GoogleCloudPlatform/terraform-google-conversion/v6/pkg/cai2hcl/models" + "github.com/GoogleCloudPlatform/terraform-google-conversion/v6/pkg/cai2hcl/converters/services/resourcemanager" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + tpg_provider "github.com/hashicorp/terraform-provider-google-beta/google-beta/provider" ) +var provider *schema.Provider = tpg_provider.Provider() + // ConverterMap is a collection of converters instances, indexed by cai asset type. var ConverterMap = map[string]models.Converter{ + resourcemanager.ProjectAssetType: resourcemanager.NewProjectConverter(provider), } diff --git a/mmv1/third_party/tgc_v6/cai2hcl/converters/services/resourcemanager/project.go b/mmv1/third_party/tgc_v6/cai2hcl/converters/services/resourcemanager/project.go new file mode 100644 index 000000000000..b921a89b9dc1 --- /dev/null +++ b/mmv1/third_party/tgc_v6/cai2hcl/converters/services/resourcemanager/project.go @@ -0,0 +1,76 @@ +package resourcemanager + +import ( + "fmt" + "strings" + + "github.com/GoogleCloudPlatform/terraform-google-conversion/v6/pkg/cai2hcl/converters/utils" + "github.com/GoogleCloudPlatform/terraform-google-conversion/v6/pkg/cai2hcl/models" + "github.com/GoogleCloudPlatform/terraform-google-conversion/v6/pkg/caiasset" + + tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// ProjectAssetType is the CAI asset type name for project. +const ProjectAssetType string = "cloudresourcemanager.googleapis.com/Project" + +// ProjectSchemaName is the TF resource schema name for resourcemanager project. +const ProjectSchemaName string = "google_project" + +// ProjectConverter for compute project resource. +type ProjectConverter struct { + name string + schema map[string]*tfschema.Schema +} + +// NewProjectConverter returns an HCL converter for compute project. +func NewProjectConverter(provider *tfschema.Provider) models.Converter { + schema := provider.ResourcesMap[ProjectSchemaName].Schema + + return &ProjectConverter{ + name: ProjectSchemaName, + schema: schema, + } +} + +// Convert converts asset resource data. +func (c *ProjectConverter) Convert(asset *caiasset.Asset) ([]*models.TerraformResourceBlock, error) { + if asset == nil || asset.Resource == nil && asset.Resource.Data == nil { + return nil, nil + } + + var blocks []*models.TerraformResourceBlock + block, err := c.convertResourceData(asset) + if err != nil { + return nil, err + } + blocks = append(blocks, block) + return blocks, nil +} + +func (c *ProjectConverter) convertResourceData(asset *caiasset.Asset) (*models.TerraformResourceBlock, error) { + if asset == nil || asset.Resource == nil || asset.Resource.Data == nil { + return nil, fmt.Errorf("asset resource data is nil") + } + + assetResourceData := asset.Resource.Data + + hclData := make(map[string]interface{}) + hclData["name"] = assetResourceData["name"] + hclData["project_id"] = assetResourceData["projectId"] + hclData["labels"] = utils.RemoveTerraformAttributionLabel(assetResourceData["labels"]) + if strings.Contains(asset.Resource.Parent, "folders/") { + hclData["folder_id"] = utils.ParseFieldValue(asset.Resource.Parent, "folders") + } else if strings.Contains(asset.Resource.Parent, "organizations/") { + hclData["org_id"] = utils.ParseFieldValue(asset.Resource.Parent, "organizations") + } + + ctyVal, err := utils.MapToCtyValWithSchema(hclData, c.schema) + if err != nil { + return nil, err + } + return &models.TerraformResourceBlock{ + Labels: []string{c.name, assetResourceData["projectId"].(string)}, + Value: ctyVal, + }, nil +} diff --git a/mmv1/third_party/tgc_v6/cai2hcl/converters/utils/utils.go b/mmv1/third_party/tgc_v6/cai2hcl/converters/utils/utils.go index 8a5e7029d94b..15a1d0379663 100644 --- a/mmv1/third_party/tgc_v6/cai2hcl/converters/utils/utils.go +++ b/mmv1/third_party/tgc_v6/cai2hcl/converters/utils/utils.go @@ -23,6 +23,16 @@ func ParseFieldValue(url string, name string) string { return "" } +// Remove the Terraform attribution label "goog-terraform-provisioned" from labels +func RemoveTerraformAttributionLabel(raw interface{}) map[string]interface{} { + if raw == nil { + return nil + } + labels := raw.(map[string]interface{}) + delete(labels, "goog-terraform-provisioned") + return labels +} + // DecodeJSON decodes the map object into the target struct. func DecodeJSON(data map[string]interface{}, v interface{}) error { b, err := json.Marshal(data) From 555fb5cec1b94b82742918067cfc408383259b3f Mon Sep 17 00:00:00 2001 From: abheda-crest <105624942+abheda-crest@users.noreply.github.com> Date: Thu, 9 Jan 2025 04:01:13 +0530 Subject: [PATCH 09/30] Add support for parameter manager regional parameter resource `google_parameter_manager_regional_parameter` (#12631) --- .../RegionalParameter.yaml | 128 ++++++++++++++ .../parametermanagerregional/product.yaml | 23 +++ .../examples/regional_parameter_basic.tf.tmpl | 5 + .../regional_parameter_with_format.tf.tmpl | 6 + .../regional_parameter_with_labels.tf.tmpl | 13 ++ .../components/inputs/services_beta.kt | 5 + .../components/inputs/services_ga.kt | 5 + ...er_manager_regional_parameter_test.go.tmpl | 156 ++++++++++++++++++ 8 files changed, 341 insertions(+) create mode 100644 mmv1/products/parametermanagerregional/RegionalParameter.yaml create mode 100644 mmv1/products/parametermanagerregional/product.yaml create mode 100644 mmv1/templates/terraform/examples/regional_parameter_basic.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/regional_parameter_with_format.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/regional_parameter_with_labels.tf.tmpl create mode 100644 mmv1/third_party/terraform/services/parametermanagerregional/resource_parameter_manager_regional_parameter_test.go.tmpl diff --git a/mmv1/products/parametermanagerregional/RegionalParameter.yaml b/mmv1/products/parametermanagerregional/RegionalParameter.yaml new file mode 100644 index 000000000000..8ad83bd80379 --- /dev/null +++ b/mmv1/products/parametermanagerregional/RegionalParameter.yaml @@ -0,0 +1,128 @@ +# 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. + +--- +name: 'RegionalParameter' +api_resource_type_kind: Parameter +description: | + A Regional Parameter is a logical regional parameter. +min_version: 'beta' +references: + guides: + api: 'https://cloud.google.com/secret-manager/parameter-manager/docs/reference/rest/v1/projects.locations.parameters' +docs: +base_url: 'projects/{{project}}/locations/{{location}}/parameters' +self_link: 'projects/{{project}}/locations/{{location}}/parameters/{{parameter_id}}' +create_url: 'projects/{{project}}/locations/{{location}}/parameters?parameter_id={{parameter_id}}' +update_verb: 'PATCH' +update_mask: true +import_format: + - 'projects/{{project}}/locations/{{location}}/parameters/{{parameter_id}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +examples: + - name: 'regional_parameter_basic' + primary_resource_id: 'regional-parameter-basic' + min_version: 'beta' + vars: + parameter_id: 'regional_parameter' + - name: 'regional_parameter_with_format' + primary_resource_id: 'regional-parameter-with-format' + min_version: 'beta' + vars: + parameter_id: 'regional_parameter' + - name: 'regional_parameter_with_labels' + primary_resource_id: 'regional-parameter-with-labels' + min_version: 'beta' + vars: + parameter_id: 'regional_parameter' +parameters: + - name: 'location' + type: String + description: | + The location of the regional parameter. eg us-central1 + url_param_only: true + required: true + immutable: true + - name: 'parameterId' + type: String + description: | + This must be unique within the project. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + The resource name of the regional Parameter. Format: + `projects/{{project}}/locations/{{location}}/parameters/{{parameter_id}}` + output: true + - name: 'createTime' + type: String + description: | + The time at which the regional Parameter was created. + output: true + - name: 'updateTime' + type: String + description: | + The time at which the regional Parameter was updated. + output: true + - name: 'policyMember' + type: NestedObject + description: | + An object containing a unique resource identity tied to the regional parameter. + output: true + properties: + - name: 'iamPolicyUidPrincipal' + type: String + description: | + IAM policy binding member referring to a Google Cloud resource by system-assigned unique identifier. If + a resource is deleted and recreated with the same name, the binding will not be applicable to the new + resource. Format: + `principal://parametermanager.googleapis.com/projects/{{project}}/uid/locations/{{location}}/parameters/{{uid}}` + output: true + - name: 'iamPolicyNamePrincipal' + type: String + description: | + IAM policy binding member referring to a Google Cloud resource by user-assigned name. If a resource is + deleted and recreated with the same name, the binding will be applicable to the new resource. Format: + `principal://parametermanager.googleapis.com/projects/{{project}}/name/locations/{{location}}/parameters/{{parameter_id}}` + output: true + - name: 'labels' + type: KeyValueLabels + description: | + The labels assigned to this regional Parameter. + + Label keys must be between 1 and 63 characters long, have a UTF-8 encoding of maximum 128 bytes, + and must conform to the following PCRE regular expression: [\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62} + + Label values must be between 0 and 63 characters long, have a UTF-8 encoding of maximum 128 bytes, + and must conform to the following PCRE regular expression: [\p{Ll}\p{Lo}\p{N}_-]{0,63} + + No more than 64 labels can be assigned to a given resource. + + An object containing a list of "key": value pairs. Example: + { "name": "wrench", "mass": "1.3kg", "count": "3" }. + - name: 'format' + type: Enum + description: | + The format type of the regional parameter. Default value is UNFORMATTED. + default_from_api: true + immutable: true + enum_values: + - 'UNFORMATTED' + - 'YAML' + - 'JSON' diff --git a/mmv1/products/parametermanagerregional/product.yaml b/mmv1/products/parametermanagerregional/product.yaml new file mode 100644 index 000000000000..7d2eeac9ee4f --- /dev/null +++ b/mmv1/products/parametermanagerregional/product.yaml @@ -0,0 +1,23 @@ +# 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. + +--- +name: 'ParameterManagerRegional' +legacy_name: 'parameter_manager' +display_name: 'Parameter Manager' +versions: + - name: 'beta' + base_url: 'https://parametermanager.{{location}}.rep.googleapis.com/v1/' + cai_base_url: 'https://parametermanager.googleapis.com/v1/' +scopes: + - 'https://www.googleapis.com/auth/cloud-platform' diff --git a/mmv1/templates/terraform/examples/regional_parameter_basic.tf.tmpl b/mmv1/templates/terraform/examples/regional_parameter_basic.tf.tmpl new file mode 100644 index 000000000000..f60daa390a6d --- /dev/null +++ b/mmv1/templates/terraform/examples/regional_parameter_basic.tf.tmpl @@ -0,0 +1,5 @@ +resource "google_parameter_manager_regional_parameter" "{{$.PrimaryResourceId}}" { + provider = google-beta + parameter_id = "{{index $.Vars "parameter_id"}}" + location = "us-central1" +} diff --git a/mmv1/templates/terraform/examples/regional_parameter_with_format.tf.tmpl b/mmv1/templates/terraform/examples/regional_parameter_with_format.tf.tmpl new file mode 100644 index 000000000000..49c7c85024cd --- /dev/null +++ b/mmv1/templates/terraform/examples/regional_parameter_with_format.tf.tmpl @@ -0,0 +1,6 @@ +resource "google_parameter_manager_regional_parameter" "{{$.PrimaryResourceId}}" { + provider = google-beta + parameter_id = "{{index $.Vars "parameter_id"}}" + location = "us-central1" + format = "JSON" +} diff --git a/mmv1/templates/terraform/examples/regional_parameter_with_labels.tf.tmpl b/mmv1/templates/terraform/examples/regional_parameter_with_labels.tf.tmpl new file mode 100644 index 000000000000..b4c2b5df6bcb --- /dev/null +++ b/mmv1/templates/terraform/examples/regional_parameter_with_labels.tf.tmpl @@ -0,0 +1,13 @@ +resource "google_parameter_manager_regional_parameter" "{{$.PrimaryResourceId}}" { + provider = google-beta + parameter_id = "{{index $.Vars "parameter_id"}}" + location = "us-central1" + + labels = { + key1 = "val1" + key2 = "val2" + key3 = "val3" + key4 = "val4" + key5 = "val5" + } +} diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt index 24abf96b5b4f..554dc1891df6 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt @@ -591,6 +591,11 @@ var ServicesListBeta = mapOf( "displayName" to "Parallelstore", "path" to "./google/services/parallelstore" ), + "parametermanagerregional" to mapOf( + "name" to "parametermanagerregional", + "displayName" to "Parametermanagerregional", + "path" to "./google-beta/services/parametermanagerregional" + ), "privateca" to mapOf( "name" to "privateca", "displayName" to "Privateca", diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt index a5bb12bca8f1..e2310c4749d8 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt @@ -586,6 +586,11 @@ var ServicesListGa = mapOf( "displayName" to "Parallelstore", "path" to "./google/services/parallelstore" ), + "parametermanagerregional" to mapOf( + "name" to "parametermanagerregional", + "displayName" to "Parametermanagerregional", + "path" to "./google/services/parametermanagerregional" + ), "privateca" to mapOf( "name" to "privateca", "displayName" to "Privateca", diff --git a/mmv1/third_party/terraform/services/parametermanagerregional/resource_parameter_manager_regional_parameter_test.go.tmpl b/mmv1/third_party/terraform/services/parametermanagerregional/resource_parameter_manager_regional_parameter_test.go.tmpl new file mode 100644 index 000000000000..70babac42568 --- /dev/null +++ b/mmv1/third_party/terraform/services/parametermanagerregional/resource_parameter_manager_regional_parameter_test.go.tmpl @@ -0,0 +1,156 @@ +package parametermanagerregional_test +{{- if ne $.TargetVersionName "ga" }} + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccParameterManagerRegionalRegionalParameter_import(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckParameterManagerRegionalRegionalParameterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccParameterManagerRegionalRegionalParameter_import(context), + }, + { + ResourceName: "google_parameter_manager_regional_parameter.regional-parameter-import", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "parameter_id", "terraform_labels"}, + }, + }, + }) +} + +func testAccParameterManagerRegionalRegionalParameter_import(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_parameter_manager_regional_parameter" "regional-parameter-import" { + provider = google-beta + parameter_id = "tf_test_parameter%{random_suffix}" + location = "us-central1" + format = "YAML" + + labels = { + key1 = "val1" + key2 = "val2" + key3 = "val3" + key4 = "val4" + key5 = "val5" + } +} +`, context) +} + +func TestAccParameterManagerRegionalRegionalParameter_labelsUpdate(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckParameterManagerRegionalRegionalParameterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccParameterManagerRegionalRegionalParameter_withoutLabels(context), + }, + { + ResourceName: "google_parameter_manager_regional_parameter.regional-parameter-with-labels", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "parameter_id", "terraform_labels"}, + }, + { + Config: testAccParameterManagerRegionalRegionalParameter_labelsUpdate(context), + }, + { + ResourceName: "google_parameter_manager_regional_parameter.regional-parameter-with-labels", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "parameter_id", "terraform_labels"}, + }, + { + Config: testAccParameterManagerRegionalRegionalParameter_labelsUpdateOther(context), + }, + { + ResourceName: "google_parameter_manager_regional_parameter.regional-parameter-with-labels", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "parameter_id", "terraform_labels"}, + }, + { + Config: testAccParameterManagerRegionalRegionalParameter_withoutLabels(context), + }, + { + ResourceName: "google_parameter_manager_regional_parameter.regional-parameter-with-labels", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "location", "parameter_id", "terraform_labels"}, + }, + }, + }) +} + +func testAccParameterManagerRegionalRegionalParameter_withoutLabels(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_parameter_manager_regional_parameter" "regional-parameter-with-labels" { + provider = google-beta + parameter_id = "tf_test_parameter%{random_suffix}" + location = "us-central1" + format = "JSON" +} +`, context) +} + +func testAccParameterManagerRegionalRegionalParameter_labelsUpdate(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_parameter_manager_regional_parameter" "regional-parameter-with-labels" { + provider = google-beta + parameter_id = "tf_test_parameter%{random_suffix}" + location = "us-central1" + format = "JSON" + + labels = { + key1 = "val1" + key2 = "val2" + key3 = "val3" + key4 = "val4" + key5 = "val5" + } +} +`, context) +} + +func testAccParameterManagerRegionalRegionalParameter_labelsUpdateOther(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_parameter_manager_regional_parameter" "regional-parameter-with-labels" { + provider = google-beta + parameter_id = "tf_test_parameter%{random_suffix}" + location = "us-central1" + format = "JSON" + + labels = { + key1 = "val1" + key2 = "updateval2" + updatekey3 = "val3" + updatekey4 = "updateval4" + key6 = "val6" + } +} +`, context) +} + +{{ end }} From e2df63b0bcdf8f7067f0d6de291815c37815d9c7 Mon Sep 17 00:00:00 2001 From: Minoru KAWAMOTO Date: Thu, 9 Jan 2025 08:39:22 +0900 Subject: [PATCH 10/30] convert databaseVersion enum to string (#12687) Co-authored-by: Minoru, Kawamoto --- .../products/sql/SourceRepresentationInstance.yaml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/mmv1/products/sql/SourceRepresentationInstance.yaml b/mmv1/products/sql/SourceRepresentationInstance.yaml index 98cd2f038f32..5c5bf7ffef2d 100644 --- a/mmv1/products/sql/SourceRepresentationInstance.yaml +++ b/mmv1/products/sql/SourceRepresentationInstance.yaml @@ -66,20 +66,10 @@ properties: required: false default_from_api: true - name: 'databaseVersion' - type: Enum + type: String description: | - The MySQL version running on your source database server. + The MySQL, PostgreSQL or SQL Server (beta) version to use. Supported values include MYSQL_5_6, MYSQL_5_7, MYSQL_8_0, MYSQL_8_4, POSTGRES_9_6, POSTGRES_10, POSTGRES_11, POSTGRES_12, POSTGRES_13, POSTGRES_14, POSTGRES_15, POSTGRES_16, POSTGRES_17. Database Version Policies includes an up-to-date reference of supported versions. required: true - enum_values: - - 'MYSQL_5_6' - - 'MYSQL_5_7' - - 'MYSQL_8_0' - - 'POSTGRES_9_6' - - 'POSTGRES_10' - - 'POSTGRES_11' - - 'POSTGRES_12' - - 'POSTGRES_13' - - 'POSTGRES_14' - name: 'onPremisesConfiguration' type: NestedObject description: | From f06d23f60b1d026dca7e324da50d98f642637ef3 Mon Sep 17 00:00:00 2001 From: Akshat Jindal <67505646+akshat-jindal-nit@users.noreply.github.com> Date: Thu, 9 Jan 2025 23:31:22 +0530 Subject: [PATCH 11/30] Failing test(s): TestAccComputeInterconnect_computeInterconnectBasicTestExample (#12718) --- .../examples/interconnect_attachment_dedicated.tf.tmpl | 2 +- .../compute/resource_compute_interconnect_macsec_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mmv1/templates/terraform/examples/interconnect_attachment_dedicated.tf.tmpl b/mmv1/templates/terraform/examples/interconnect_attachment_dedicated.tf.tmpl index b30e6bcab8ba..26131db26241 100644 --- a/mmv1/templates/terraform/examples/interconnect_attachment_dedicated.tf.tmpl +++ b/mmv1/templates/terraform/examples/interconnect_attachment_dedicated.tf.tmpl @@ -6,7 +6,7 @@ resource "google_compute_interconnect" "foobar" { interconnect_type = "DEDICATED" link_type = "LINK_TYPE_ETHERNET_10G_LR" requested_link_count = 1 - location = "https://www.googleapis.com/compute/v1/projects/${data.google_project.project.name}/global/interconnectLocations/z2z-us-east4-zone1-lciadl-a" # Special location only available for Google testing. + location = "https://www.googleapis.com/compute/v1/projects/${data.google_project.project.name}/global/interconnectLocations/z2z-us-east4-zone1-nciadf-a" # Special location only available for Google testing. } resource "google_compute_interconnect_attachment" "{{$.PrimaryResourceId}}" { diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_interconnect_macsec_test.go b/mmv1/third_party/terraform/services/compute/resource_compute_interconnect_macsec_test.go index 9de56540ec3f..28393d99432a 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_interconnect_macsec_test.go +++ b/mmv1/third_party/terraform/services/compute/resource_compute_interconnect_macsec_test.go @@ -51,7 +51,7 @@ resource "google_compute_interconnect" "example-interconnect" { customer_name = "internal_customer" # Special customer only available for Google testing. interconnect_type = "DEDICATED" link_type = "LINK_TYPE_ETHERNET_100G_LR" - location = "https://www.googleapis.com/compute/v1/projects/${data.google_project.project.name}/global/interconnectLocations/z2z-us-east4-zone1-lciadl-a" # Special location only available for Google testing. + location = "https://www.googleapis.com/compute/v1/projects/${data.google_project.project.name}/global/interconnectLocations/z2z-us-east4-zone1-lciadl-z" # Special location only available for Google testing. requested_link_count = 1 admin_enabled = true description = "example description" @@ -74,7 +74,7 @@ resource "google_compute_interconnect" "example-interconnect" { customer_name = "internal_customer" # Special customer only available for Google testing. interconnect_type = "DEDICATED" link_type = "LINK_TYPE_ETHERNET_100G_LR" - location = "https://www.googleapis.com/compute/v1/projects/${data.google_project.project.name}/global/interconnectLocations/z2z-us-east4-zone1-lciadl-a" # Special location only available for Google testing. + location = "https://www.googleapis.com/compute/v1/projects/${data.google_project.project.name}/global/interconnectLocations/z2z-us-east4-zone1-lciadl-z" # Special location only available for Google testing. requested_link_count = 1 admin_enabled = true description = "example description" From 86536e5a65595d19c295cc5a5cfc5f83effc6272 Mon Sep 17 00:00:00 2001 From: Akshat Jindal <67505646+akshat-jindal-nit@users.noreply.github.com> Date: Thu, 9 Jan 2025 23:42:35 +0530 Subject: [PATCH 12/30] Cloud Router Standard BPS Terraform Beta to GA support (#12697) --- mmv1/products/compute/Network.yaml | 6 - mmv1/products/compute/Route.yaml | 3 - ...twork_bgp_best_path_selection_mode.tf.tmpl | 1 - ..._best_path_selection_mode_standard.tf.tmpl | 1 - ...ection_mode_standard_custom_fields.tf.tmpl | 1 - .../resource_compute_network_test.go.tmpl | 141 +++--------------- 6 files changed, 18 insertions(+), 135 deletions(-) diff --git a/mmv1/products/compute/Network.yaml b/mmv1/products/compute/Network.yaml index 4e1032b0fbd8..af93a9d88b45 100644 --- a/mmv1/products/compute/Network.yaml +++ b/mmv1/products/compute/Network.yaml @@ -60,21 +60,18 @@ examples: project: 'PROJECT_NAME' - name: 'network_bgp_best_path_selection_mode' primary_resource_id: 'vpc_network' - min_version: 'beta' vars: network_name: 'vpc-network' test_env_vars: project: 'PROJECT_NAME' - name: 'network_bgp_best_path_selection_mode_standard' primary_resource_id: 'vpc_network' - min_version: 'beta' vars: network_name: 'vpc-network' test_env_vars: project: 'PROJECT_NAME' - name: 'network_bgp_best_path_selection_mode_standard_custom_fields' primary_resource_id: 'vpc_network' - min_version: 'beta' vars: network_name: 'vpc-network' test_env_vars: @@ -168,7 +165,6 @@ properties: type: Enum description: | The BGP best selection algorithm to be employed. MODE can be LEGACY or STANDARD. - min_version: 'beta' default_from_api: true update_url: 'projects/{{project}}/global/networks/{{name}}' update_verb: 'PATCH' @@ -180,7 +176,6 @@ properties: description: | Enables/disables the comparison of MED across routes with different Neighbor ASNs. This value can only be set if the --bgp-best-path-selection-mode is STANDARD - min_version: 'beta' required: false default_from_api: true update_url: 'projects/{{project}}/global/networks/{{name}}' @@ -189,7 +184,6 @@ properties: type: Enum description: | Choice of the behavior of inter-regional cost and MED in the BPS algorithm. - min_version: 'beta' required: false default_from_api: true update_url: 'projects/{{project}}/global/networks/{{name}}' diff --git a/mmv1/products/compute/Route.yaml b/mmv1/products/compute/Route.yaml index ebdd902e21e6..bce812547f4d 100644 --- a/mmv1/products/compute/Route.yaml +++ b/mmv1/products/compute/Route.yaml @@ -225,19 +225,16 @@ properties: type: String description: | Indicates the origin of the route. Can be IGP (Interior Gateway Protocol), EGP (Exterior Gateway Protocol), or INCOMPLETE. - min_version: 'beta' output: true - name: 'nextHopMed' type: String description: | Multi-Exit Discriminator, a BGP route metric that indicates the desirability of a particular route in a network. - min_version: 'beta' output: true - name: 'nextHopInterRegionCost' type: String description: | Internal fixed region-to-region cost that Google Cloud calculates based on factors such as network performance, distance, and available bandwidth between regions. - min_version: 'beta' output: true - name: 'nextHopIlb' type: String diff --git a/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode.tf.tmpl b/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode.tf.tmpl index d6ee72505e90..82fbd48493eb 100644 --- a/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode.tf.tmpl +++ b/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode.tf.tmpl @@ -1,5 +1,4 @@ resource "google_compute_network" "{{$.PrimaryResourceId}}" { - provider = google-beta project = "{{index $.TestEnvVars "project"}}" name = "{{index $.Vars "network_name"}}" routing_mode = "GLOBAL" diff --git a/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode_standard.tf.tmpl b/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode_standard.tf.tmpl index 73b44219ef03..94dde1154519 100644 --- a/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode_standard.tf.tmpl +++ b/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode_standard.tf.tmpl @@ -1,5 +1,4 @@ resource "google_compute_network" "{{$.PrimaryResourceId}}" { - provider = google-beta project = "{{index $.TestEnvVars "project"}}" name = "{{index $.Vars "network_name"}}" routing_mode = "GLOBAL" diff --git a/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode_standard_custom_fields.tf.tmpl b/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode_standard_custom_fields.tf.tmpl index 87810b6169b0..46ff2630c8c3 100644 --- a/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode_standard_custom_fields.tf.tmpl +++ b/mmv1/templates/terraform/examples/network_bgp_best_path_selection_mode_standard_custom_fields.tf.tmpl @@ -1,5 +1,4 @@ resource "google_compute_network" "{{$.PrimaryResourceId}}" { - provider = google-beta project = "{{index $.TestEnvVars "project"}}" name = "{{index $.Vars "network_name"}}" routing_mode = "GLOBAL" diff --git a/mmv1/third_party/terraform/services/compute/resource_compute_network_test.go.tmpl b/mmv1/third_party/terraform/services/compute/resource_compute_network_test.go.tmpl index 6fe55a31bfae..d54e29ee9e3b 100644 --- a/mmv1/third_party/terraform/services/compute/resource_compute_network_test.go.tmpl +++ b/mmv1/third_party/terraform/services/compute/resource_compute_network_test.go.tmpl @@ -112,7 +112,6 @@ func TestAccComputeNetwork_routingModeAndUpdate(t *testing.T) { }) } -{{ if ne $.TargetVersionName `ga` -}} func TestAccComputeNetwork_bgpBestPathSelectionModeAndUpdate(t *testing.T) { t.Parallel() @@ -122,7 +121,7 @@ func TestAccComputeNetwork_bgpBestPathSelectionModeAndUpdate(t *testing.T) { acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccCheckComputeNetworkDestroyProducer(t), Steps: []resource.TestStep{ { @@ -130,7 +129,7 @@ func TestAccComputeNetwork_bgpBestPathSelectionModeAndUpdate(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckComputeNetworkExists( t, "google_compute_network.acc_network_bgp_best_path_selection_mode", &network), - testAccCheckComputeNetworkHasBgpBestPathSelectionMode(t, "google_compute_network.acc_network_bgp_best_path_selection_mode", &network, "LEGACY"), + resource.TestCheckResourceAttr("google_compute_network.acc_network_bgp_best_path_selection_mode", "bgp_best_path_selection_mode", "LEGACY"), ), }, // Test updating the best bgp path selection field (only updatable field). @@ -139,7 +138,7 @@ func TestAccComputeNetwork_bgpBestPathSelectionModeAndUpdate(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckComputeNetworkExists( t, "google_compute_network.acc_network_bgp_best_path_selection_mode", &network), - testAccCheckComputeNetworkHasBgpBestPathSelectionMode(t, "google_compute_network.acc_network_bgp_best_path_selection_mode", &network, "STANDARD"), + resource.TestCheckResourceAttr("google_compute_network.acc_network_bgp_best_path_selection_mode", "bgp_best_path_selection_mode", "STANDARD"), ), }, }, @@ -157,7 +156,7 @@ func TestAccComputeNetwork_bgpAlwaysCompareMedAndUpdate(t *testing.T) { acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccCheckComputeNetworkDestroyProducer(t), Steps: []resource.TestStep{ { @@ -165,8 +164,7 @@ func TestAccComputeNetwork_bgpAlwaysCompareMedAndUpdate(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckComputeNetworkExists( t, "google_compute_network.acc_network_bgp_always_compare_med", &network), - testAccCheckComputeNetworkHasBgpAlwaysCompareMed( - t, "google_compute_network.acc_network_bgp_always_compare_med", &network, false), + resource.TestCheckResourceAttr("google_compute_network.acc_network_bgp_always_compare_med", "bgp_always_compare_med", "false"), ), }, // Test updating the bgpAlwaysCompareMed field (only updatable field). @@ -175,8 +173,7 @@ func TestAccComputeNetwork_bgpAlwaysCompareMedAndUpdate(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckComputeNetworkExists( t, "google_compute_network.acc_network_bgp_always_compare_med", &network), - testAccCheckComputeNetworkHasBgpAlwaysCompareMed( - t, "google_compute_network.acc_network_bgp_always_compare_med", &network, true), + resource.TestCheckResourceAttr("google_compute_network.acc_network_bgp_always_compare_med", "bgp_always_compare_med", "true"), ), }, }, @@ -193,7 +190,7 @@ func TestAccComputeNetwork_bgpInterRegionCostAndUpdate(t *testing.T) { acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccCheckComputeNetworkDestroyProducer(t), Steps: []resource.TestStep{ { @@ -201,8 +198,7 @@ func TestAccComputeNetwork_bgpInterRegionCostAndUpdate(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckComputeNetworkExists( t, "google_compute_network.acc_network_bgp_inter_region_cost", &network), - testAccCheckComputeNetworkHasBgpInterRegionCost( - t, "google_compute_network.acc_network_bgp_inter_region_cost", &network, "DEFAULT"), + resource.TestCheckResourceAttr("google_compute_network.acc_network_bgp_inter_region_cost", "bgp_inter_region_cost", "DEFAULT"), ), }, // Test updating the bgpInterRegionCost field (only updatable field). @@ -211,14 +207,14 @@ func TestAccComputeNetwork_bgpInterRegionCostAndUpdate(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckComputeNetworkExists( t, "google_compute_network.acc_network_bgp_inter_region_cost", &network), - testAccCheckComputeNetworkHasBgpInterRegionCost( - t, "google_compute_network.acc_network_bgp_inter_region_cost", &network, "ADD_COST_TO_MED"), + resource.TestCheckResourceAttr("google_compute_network.acc_network_bgp_inter_region_cost", "bgp_inter_region_cost", "ADD_COST_TO_MED"), ), }, }, }) } +{{ if ne $.TargetVersionName `ga` -}} func TestAccComputeNetwork_networkProfile(t *testing.T) { t.Parallel() @@ -303,7 +299,6 @@ func TestAccComputeNetwork_default_routing_mode(t *testing.T) { }) } -{{ if ne $.TargetVersionName `ga` -}} func TestAccComputeNetwork_default_bgp_best_path_selection_mode(t *testing.T) { t.Parallel() @@ -315,15 +310,15 @@ func TestAccComputeNetwork_default_bgp_best_path_selection_mode(t *testing.T) { acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccCheckComputeNetworkDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccComputeBetaNetwork_basic(networkName), + Config: testAccComputeNetwork_basic(networkName), Check: resource.ComposeTestCheckFunc( testAccCheckComputeNetworkExists( t, "google_compute_network.bar", &network), - testAccCheckComputeNetworkHasBgpBestPathSelectionMode(t, "google_compute_network.bar", &network, expectedBgpBestPathSelection), + resource.TestCheckResourceAttr("google_compute_network.bar", "bgp_best_path_selection_mode", expectedBgpBestPathSelection), ), }, }, @@ -338,11 +333,11 @@ func TestAccComputeNetwork_default_bgp_always_compare_med(t *testing.T) { suffixName := acctest.RandString(t, 10) networkName := fmt.Sprintf("tf-test-bgp-always-compare-med-default-routes-%s", suffixName) - expectedBgpAlwaysCompareMed := false + expectedBgpAlwaysCompareMed := "false" acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccCheckComputeNetworkDestroyProducer(t), Steps: []resource.TestStep{ { @@ -350,14 +345,12 @@ func TestAccComputeNetwork_default_bgp_always_compare_med(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckComputeNetworkExists( t, "google_compute_network.acc_network_bgp_best_path_selection_mode", &network), - testAccCheckComputeNetworkHasBgpAlwaysCompareMed( - t, "google_compute_network.acc_network_bgp_best_path_selection_mode", &network, expectedBgpAlwaysCompareMed), + resource.TestCheckResourceAttr("google_compute_network.acc_network_bgp_best_path_selection_mode", "bgp_always_compare_med", expectedBgpAlwaysCompareMed), ), }, }, }) } -{{- end }} func TestAccComputeNetwork_networkDeleteDefaultRoute(t *testing.T) { t.Parallel() @@ -564,94 +557,8 @@ func testAccCheckComputeNetworkHasRoutingMode(t *testing.T, n string, network *c } } -{{ if ne $.TargetVersionName `ga` -}} -func testAccCheckComputeNetworkHasBgpBestPathSelectionMode(t *testing.T, n string, network *compute.Network, bgpBestPathSelectionMode string) resource.TestCheckFunc { - return func(s *terraform.State) error { - config := acctest.GoogleProviderConfig(t) - - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.Attributes["bgp_always_compare_med"] == "" { - return fmt.Errorf("BGP always compare med not found on resource") - } - - found, err := config.NewComputeClient(config.UserAgent).Networks.Get( - config.Project, network.Name).Do() - if err != nil { - return err - } - - foundBgpBestPathSelectionMode := found.RoutingConfig.BgpBestPathSelectionMode - - if bgpBestPathSelectionMode != foundBgpBestPathSelectionMode { - return fmt.Errorf("Expected BGP always compare med %s to match actual BGP always compare med %s", bgpBestPathSelectionMode, foundBgpBestPathSelectionMode) - } - - return nil - } -} - -func testAccCheckComputeNetworkHasBgpAlwaysCompareMed(t *testing.T, n string, network *compute.Network, bgpAlwaysCompareMed bool) resource.TestCheckFunc { - return func(s *terraform.State) error { - config := acctest.GoogleProviderConfig(t) - - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.Attributes["bgp_always_compare_med"] == "" { - return fmt.Errorf("BGP always compare med not found on resource") - } - - found, err := config.NewComputeClient(config.UserAgent).Networks.Get( - config.Project, network.Name).Do() - if err != nil { - return err - } - - foundBgpAlwaysCompareMed := found.RoutingConfig.BgpAlwaysCompareMed - - if foundBgpAlwaysCompareMed != bgpAlwaysCompareMed { - return fmt.Errorf("Expected BGP always compare med %t to match actual BGP always compare med %t", bgpAlwaysCompareMed, foundBgpAlwaysCompareMed) - } - - return nil - } -} - -func testAccCheckComputeNetworkHasBgpInterRegionCost(t *testing.T, n string, network *compute.Network, bgpInterRegionCost string) resource.TestCheckFunc { - return func(s *terraform.State) error { - config := acctest.GoogleProviderConfig(t) - - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.Attributes["bgp_inter_region_cost"] == "" { - return fmt.Errorf("BGP inter region cost not found on resource") - } - - found, err := config.NewComputeClient(config.UserAgent).Networks.Get( - config.Project, network.Name).Do() - if err != nil { - return err - } - - foundBgpInterRegionCost := found.RoutingConfig.BgpInterRegionCost - - if foundBgpInterRegionCost != bgpInterRegionCost { - return fmt.Errorf("Expected BGP always compare med %s to match actual BGP always compare med %s", bgpInterRegionCost, foundBgpInterRegionCost) - } - - return nil - } -} +{{ if ne $.TargetVersionName `ga` -}} func testAccCheckComputeNetworkHasNetworkProfile(t *testing.T, n string, network *compute.Network, networkProfile string) resource.TestCheckFunc { return func(s *terraform.State) error { config := acctest.GoogleProviderConfig(t) @@ -747,21 +654,10 @@ resource "google_compute_network" "acc_network_routing_mode" { `, networkName, routingMode) } -{{ if ne $.TargetVersionName `ga` -}} -func testAccComputeBetaNetwork_basic(networkName string) string { - return fmt.Sprintf(` -resource "google_compute_network" "bar" { - provider = google-beta - name = "%s" - auto_create_subnetworks = true -} -`, networkName) -} func testAccComputeNetwork_best_bgp_path_selection_mode(networkName, bgpBestPathSelection string) string { return fmt.Sprintf(` resource "google_compute_network" "acc_network_bgp_best_path_selection_mode" { - provider = google-beta name = "%s" routing_mode = "GLOBAL" bgp_best_path_selection_mode = "%s" @@ -772,7 +668,6 @@ resource "google_compute_network" "acc_network_bgp_best_path_selection_mode" { func testAccComputeNetwork_bgp_always_compare_med(networkName string, bgpAlwaysCompareMed bool) string { return fmt.Sprintf(` resource "google_compute_network" "acc_network_bgp_always_compare_med" { - provider = google-beta name = "%s" routing_mode = "GLOBAL" bgp_best_path_selection_mode = "STANDARD" @@ -784,7 +679,6 @@ resource "google_compute_network" "acc_network_bgp_always_compare_med" { func testAccComputeNetwork_bgp_inter_region_cost(networkName, bgpInterRegionCost string) string { return fmt.Sprintf(` resource "google_compute_network" "acc_network_bgp_inter_region_cost" { - provider = google-beta name = "%s" routing_mode = "GLOBAL" bgp_best_path_selection_mode = "STANDARD" @@ -793,6 +687,7 @@ resource "google_compute_network" "acc_network_bgp_inter_region_cost" { `, networkName, bgpInterRegionCost) } +{{ if ne $.TargetVersionName `ga` -}} func testAccComputeNetwork_network_profile(networkName, networkProfile string) string { return fmt.Sprintf(` resource "google_compute_network" "acc_network_network_profile" { From 9f506b9edc862aeb6c3bf280059c5539e684408c Mon Sep 17 00:00:00 2001 From: abheda-crest <105624942+abheda-crest@users.noreply.github.com> Date: Fri, 10 Jan 2025 00:15:26 +0530 Subject: [PATCH 13/30] Add support for parameter manager regional parameter datasource `google_parameter_manager_regional_parameter` (#12640) --- .../provider/provider_mmv1_resources.go.tmpl | 3 + ...rameter_manager_regional_parameter.go.tmpl | 46 ++++++++++++++ ...er_manager_regional_parameter_test.go.tmpl | 61 +++++++++++++++++++ ...r_manager_regional_parameter.html.markdown | 34 +++++++++++ 4 files changed, 144 insertions(+) create mode 100644 mmv1/third_party/terraform/services/parametermanagerregional/data_source_parameter_manager_regional_parameter.go.tmpl create mode 100644 mmv1/third_party/terraform/services/parametermanagerregional/data_source_parameter_manager_regional_parameter_test.go.tmpl create mode 100644 mmv1/third_party/terraform/website/docs/d/parameter_manager_regional_parameter.html.markdown diff --git a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl index 2da43b4a3a8e..1ebc6fa6e78e 100644 --- a/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl +++ b/mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl @@ -184,6 +184,9 @@ var handwrittenDatasources = map[string]*schema.Resource{ "google_oracle_database_cloud_vm_clusters": oracledatabase.DataSourceOracleDatabaseCloudVmClusters(), "google_oracle_database_cloud_vm_cluster": oracledatabase.DataSourceOracleDatabaseCloudVmCluster(), "google_organization": resourcemanager.DataSourceGoogleOrganization(), + {{- if ne $.TargetVersionName "ga" }} + "google_parameter_manager_regional_parameter": parametermanagerregional.DataSourceParameterManagerRegionalRegionalParameter(), + {{- end }} "google_privateca_certificate_authority": privateca.DataSourcePrivatecaCertificateAuthority(), "google_privileged_access_manager_entitlement": privilegedaccessmanager.DataSourceGooglePrivilegedAccessManagerEntitlement(), "google_project": resourcemanager.DataSourceGoogleProject(), diff --git a/mmv1/third_party/terraform/services/parametermanagerregional/data_source_parameter_manager_regional_parameter.go.tmpl b/mmv1/third_party/terraform/services/parametermanagerregional/data_source_parameter_manager_regional_parameter.go.tmpl new file mode 100644 index 000000000000..bf86da9bd24d --- /dev/null +++ b/mmv1/third_party/terraform/services/parametermanagerregional/data_source_parameter_manager_regional_parameter.go.tmpl @@ -0,0 +1,46 @@ +package parametermanagerregional +{{- if ne $.TargetVersionName "ga" }} + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-google/google/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" +) + +func DataSourceParameterManagerRegionalRegionalParameter() *schema.Resource { + + dsSchema := tpgresource.DatasourceSchemaFromResourceSchema(ResourceParameterManagerRegionalRegionalParameter().Schema) + tpgresource.AddRequiredFieldsToSchema(dsSchema, "parameter_id") + tpgresource.AddRequiredFieldsToSchema(dsSchema, "location") + tpgresource.AddOptionalFieldsToSchema(dsSchema, "project") + + return &schema.Resource{ + Read: dataSourceParameterManagerRegionalRegionalParameterRead, + Schema: dsSchema, + } +} + +func dataSourceParameterManagerRegionalRegionalParameterRead(d *schema.ResourceData, meta interface{}) error { + id, err := tpgresource.ReplaceVars(d, meta.(*transport_tpg.Config), "projects/{{"{{"}}project{{"}}"}}/locations/{{"{{"}}location{{"}}"}}/parameters/{{"{{"}}parameter_id{{"}}"}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + err = resourceParameterManagerRegionalRegionalParameterRead(d, meta) + if err != nil { + return err + } + + if err := tpgresource.SetDataSourceLabels(d); err != nil { + return err + } + + if d.Id() == "" { + return fmt.Errorf("%s not found", id) + } + return nil +} + +{{ end }} diff --git a/mmv1/third_party/terraform/services/parametermanagerregional/data_source_parameter_manager_regional_parameter_test.go.tmpl b/mmv1/third_party/terraform/services/parametermanagerregional/data_source_parameter_manager_regional_parameter_test.go.tmpl new file mode 100644 index 000000000000..4f975cf8faaa --- /dev/null +++ b/mmv1/third_party/terraform/services/parametermanagerregional/data_source_parameter_manager_regional_parameter_test.go.tmpl @@ -0,0 +1,61 @@ +package parametermanagerregional_test +{{- if ne $.TargetVersionName "ga" }} + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccDataSourceParameterManagerRegionalRegionalParameter_basic(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckParameterManagerRegionalRegionalParameterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceParameterManagerRegionalRegionalParameter_basic(context), + Check: resource.ComposeTestCheckFunc( + acctest.CheckDataSourceStateMatchesResourceState( + "data.google_parameter_manager_regional_parameter.regional-parameter-datasource", + "google_parameter_manager_regional_parameter.regional-parameter", + ), + ), + }, + }, + }) +} + +func testAccDataSourceParameterManagerRegionalRegionalParameter_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_parameter_manager_regional_parameter" "regional-parameter" { + provider = google-beta + parameter_id = "tf_test_parameter%{random_suffix}" + location = "us-central1" + format = "YAML" + + labels = { + key1 = "val1" + key2 = "val2" + key3 = "val3" + key4 = "val4" + key5 = "val5" + } +} + +data "google_parameter_manager_regional_parameter" "regional-parameter-datasource" { + provider = google-beta + parameter_id = google_parameter_manager_regional_parameter.regional-parameter.parameter_id + location = google_parameter_manager_regional_parameter.regional-parameter.location +} +`, context) +} + +{{ end }} diff --git a/mmv1/third_party/terraform/website/docs/d/parameter_manager_regional_parameter.html.markdown b/mmv1/third_party/terraform/website/docs/d/parameter_manager_regional_parameter.html.markdown new file mode 100644 index 000000000000..32be5c827f36 --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/d/parameter_manager_regional_parameter.html.markdown @@ -0,0 +1,34 @@ +--- +subcategory: "Parameter Manager" +description: |- + Get information about a Parameter Manager Regional Parameter. +--- + +# google_parameter_manager_regional_parameter + +Use this data source to get information about a Parameter Manager Regional Parameter. + +~> **Warning:** This datasource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta datasources. + +## Example Usage + +```hcl +data "google_parameter_manager_regional_parameter" "reg_parameter_datasource" { + parameter_id = "foobar" + location = "us-central1" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `parameter_id` - (required) The name of the regional parameter. + +* `location` - (required) The location of the regional parameter. eg us-central1 + +* `project` - (optional) The ID of the project in which the resource belongs. + +## Attributes Reference +See [google_parameter_manager_regional_parameter](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/parameter_manager_regional_parameter) resource for details of all the available attributes. From ae390267f2cfdc2f4f39e4fdec1f6772aa80652b Mon Sep 17 00:00:00 2001 From: martijneken Date: Thu, 9 Jan 2025 13:56:22 -0500 Subject: [PATCH 14/30] chore: Move Traffic Extensions example server image (#12710) Signed-off-by: Martijn Stevenson --- .../network_services_lb_traffic_extension_basic.tf.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/templates/terraform/examples/network_services_lb_traffic_extension_basic.tf.tmpl b/mmv1/templates/terraform/examples/network_services_lb_traffic_extension_basic.tf.tmpl index 9943d494b2f1..5d7fce26a5c2 100644 --- a/mmv1/templates/terraform/examples/network_services_lb_traffic_extension_basic.tf.tmpl +++ b/mmv1/templates/terraform/examples/network_services_lb_traffic_extension_basic.tf.tmpl @@ -262,7 +262,7 @@ resource "google_compute_instance" "callouts_instance" { # Initialize an Envoy's Ext Proc gRPC API based on a docker container metadata = { - gce-container-declaration = "# DISCLAIMER:\n# This container declaration format is not a public API and may change without\n# notice. Please use gcloud command-line tool or Google Cloud Console to run\n# Containers on Google Compute Engine.\n\nspec:\n containers:\n - image: us-docker.pkg.dev/service-extensions/ext-proc/service-callout-basic-example-python:latest\n name: callouts-vm\n securityContext:\n privileged: false\n stdin: false\n tty: false\n volumeMounts: []\n restartPolicy: Always\n volumes: []\n" + gce-container-declaration = "# DISCLAIMER:\n# This container declaration format is not a public API and may change without\n# notice. Please use gcloud command-line tool or Google Cloud Console to run\n# Containers on Google Compute Engine.\n\nspec:\n containers:\n - image: us-docker.pkg.dev/service-extensions-samples/callouts/python-example-basic:main\n name: callouts-vm\n securityContext:\n privileged: false\n stdin: false\n tty: false\n volumeMounts: []\n restartPolicy: Always\n volumes: []\n" google-logging-enabled = "true" } From b60c43026dc2f875697194bd92c32e742692a88b Mon Sep 17 00:00:00 2001 From: Ankit Goyal <51757072+ankitgoyal0301@users.noreply.github.com> Date: Fri, 10 Jan 2025 02:27:54 +0530 Subject: [PATCH 15/30] Add google_chronicle_rule resource to chronicle (#12720) --- mmv1/products/chronicle/Rule.yaml | 272 ++++++++++++++++++ .../examples/chronicle_rule_basic.tf.tmpl | 9 + ...onicle_rule_with_data_access_scope.tf.tmpl | 20 ++ ...chronicle_rule_with_force_deletion.tf.tmpl | 9 + .../post_create/chronicle_rule_id.go.tmpl | 5 + .../pre_delete/chronicle_rule.go.tmpl | 4 + .../resource_chronicle_rule_test.go.tmpl | 85 ++++++ 7 files changed, 404 insertions(+) create mode 100644 mmv1/products/chronicle/Rule.yaml create mode 100644 mmv1/templates/terraform/examples/chronicle_rule_basic.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/chronicle_rule_with_data_access_scope.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/chronicle_rule_with_force_deletion.tf.tmpl create mode 100644 mmv1/templates/terraform/post_create/chronicle_rule_id.go.tmpl create mode 100644 mmv1/templates/terraform/pre_delete/chronicle_rule.go.tmpl create mode 100644 mmv1/third_party/terraform/services/chronicle/resource_chronicle_rule_test.go.tmpl diff --git a/mmv1/products/chronicle/Rule.yaml b/mmv1/products/chronicle/Rule.yaml new file mode 100644 index 000000000000..6bb16787e225 --- /dev/null +++ b/mmv1/products/chronicle/Rule.yaml @@ -0,0 +1,272 @@ +# 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. + +--- +name: Rule +description: The Rule resource represents a user-created rule. +min_version: beta +references: + guides: + 'Google SecOps Guides': 'https://cloud.google.com/chronicle/docs/secops/secops-overview' + api: 'https://cloud.google.com/chronicle/docs/reference/rest/v1alpha/projects.locations.instances.rules' +base_url: projects/{{project}}/locations/{{location}}/instances/{{instance}}/rules +self_link: projects/{{project}}/locations/{{location}}/instances/{{instance}}/rules/{{rule_id}} +create_url: projects/{{project}}/locations/{{location}}/instances/{{instance}}/rules +id_format: projects/{{project}}/locations/{{location}}/instances/{{instance}}/rules/{{rule_id}} +import_format: + - projects/{{project}}/locations/{{location}}/instances/{{instance}}/rules/{{rule_id}} +update_verb: PATCH +update_mask: true + +examples: + - name: 'chronicle_rule_basic' + primary_resource_id: example + min_version: 'beta' + test_env_vars: + chronicle_id: 'CHRONICLE_ID' + ignore_read_extra: + - 'deletion_policy' + exclude_import_test: true + - name: 'chronicle_rule_with_force_deletion' + primary_resource_id: example + min_version: 'beta' + test_env_vars: + chronicle_id: 'CHRONICLE_ID' + ignore_read_extra: + - 'deletion_policy' + exclude_import_test: true + - name: 'chronicle_rule_with_data_access_scope' + primary_resource_id: example + min_version: 'beta' + vars: + data_access_scope_id: scope-name + test_env_vars: + chronicle_id: 'CHRONICLE_ID' + exclude_import_test: true + +custom_code: + pre_delete: 'templates/terraform/pre_delete/chronicle_rule.go.tmpl' + post_create: 'templates/terraform/post_create/chronicle_rule_id.go.tmpl' + +virtual_fields: + - name: 'deletion_policy' + description: | + Policy to determine if the rule should be deleted forcefully. + If deletion_policy = "FORCE", any retrohunts and any detections associated with the rule + will also be deleted. If deletion_policy = "DEFAULT", the call will only succeed if the + rule has no associated retrohunts, including completed retrohunts, and no + associated detections. Regardless of this field's value, the rule + deployment associated with this rule will also be deleted. + Possible values: DEFAULT, FORCE + type: String + default_value: "DEFAULT" + +parameters: + - name: location + type: String + description: The location of the resource. This is the geographical region where the Chronicle instance resides, such as "us" or "europe-west2". + immutable: true + url_param_only: true + required: true + - name: instance + type: String + description: The unique identifier for the Chronicle instance, which is the same as the customer ID. + immutable: true + url_param_only: true + required: true +properties: + - name: name + type: String + output: true + description: |- + Full resource name for the rule. This unique identifier is generated using values provided for the URL parameters. + Format: + projects/{project}/locations/{location}/instances/{instance}/rules/{rule} + - name: ruleId + type: String + output: true + immutable: true + default_from_api: true + custom_flatten: 'templates/terraform/custom_flatten/id_from_name.tmpl' + description: |- + Rule Id is the ID of the Rule. + - name: text + type: String + description: |- + The YARA-L content of the rule. + Populated in FULL view. + - name: metadata + type: KeyValuePairs + description: |- + Output only. Additional metadata specified in the meta section of text. + Populated in FULL view. + output: true + - name: scope + type: String + diff_suppress_func: 'tpgresource.ProjectNumberDiffSuppress' + description: |- + Resource name of the DataAccessScope bound to this rule. + Populated in BASIC view and FULL view. + If reference lists are used in the rule, validations will be performed + against this scope to ensure that the reference lists are compatible with + both the user's and the rule's scopes. + The scope should be in the format: + "projects/{project}/locations/{location}/instances/{instance}/dataAccessScopes/{scope}". + - name: nearRealTimeLiveRuleEligible + type: Boolean + description: |- + Output only. Indicate the rule can run in near real time live rule. + If this is true, the rule uses the near real time live rule when the run + frequency is set to LIVE. + output: true + - name: revisionId + type: String + description: |- + Output only. The revision ID of the rule. + A new revision is created whenever the rule text is changed in any way. + Format: v_{10 digits}_{9 digits} + Populated in REVISION_METADATA_ONLY view and FULL view. + output: true + - name: severity + type: NestedObject + description: Severity represents the severity level of the rule. + output: true + properties: + - name: displayName + type: String + description: |- + The display name of the severity level. Extracted from the meta section of + the rule text. + - name: revisionCreateTime + type: String + description: |- + Output only. The timestamp of when the rule revision was created. + Populated in FULL, REVISION_METADATA_ONLY views. + output: true + - name: compilationState + type: String + description: |- + Output only. The current compilation state of the rule. + Populated in FULL view. + Possible values: + COMPILATION_STATE_UNSPECIFIED + SUCCEEDED + FAILED + output: true + - name: type + type: String + output: true + description: |2- + + Possible values: + RULE_TYPE_UNSPECIFIED + SINGLE_EVENT + MULTI_EVENT + - name: referenceLists + type: Array + description: |- + Output only. Resource names of the reference lists used in this rule. + Populated in FULL view. + output: true + item_type: + type: String + - name: displayName + type: String + description: |- + Output only. Display name of the rule. + Populated in BASIC view and FULL view. + output: true + - name: createTime + type: String + description: |- + Output only. The timestamp of when the rule was created. + Populated in FULL view. + output: true + - name: author + type: String + description: |- + Output only. The author of the rule. Extracted from the meta section of text. + Populated in BASIC view and FULL view. + output: true + - name: allowedRunFrequencies + type: Array + description: |- + Output only. The run frequencies that are allowed for the rule. + Populated in BASIC view and FULL view. + output: true + item_type: + type: String + - name: etag + type: String + default_from_api: true + description: |- + The etag for this rule. + If this is provided on update, the request will succeed if and only if it + matches the server-computed value, and will fail with an ABORTED error + otherwise. + Populated in BASIC view and FULL view. + - name: compilationDiagnostics + type: Array + description: |- + Output only. A list of a rule's corresponding compilation diagnostic messages + such as compilation errors and compilation warnings. + Populated in FULL view. + output: true + item_type: + type: NestedObject + properties: + - name: message + type: String + description: Output only. The diagnostic message. + output: true + - name: position + type: NestedObject + description: |- + CompilationPosition represents the location of a compilation diagnostic in + rule text. + properties: + - name: startLine + type: Integer + description: Output only. Start line number, beginning at 1. + output: true + - name: startColumn + type: Integer + description: Output only. Start column number, beginning at 1. + output: true + - name: endLine + type: Integer + description: Output only. End line number, beginning at 1. + output: true + - name: endColumn + type: Integer + description: Output only. End column number, beginning at 1. + output: true + - name: severity + type: String + description: |- + Output only. The severity of a rule's compilation diagnostic. + Possible values: + SEVERITY_UNSPECIFIED + WARNING + ERROR + output: true + - name: uri + type: String + description: Output only. Link to documentation that describes a diagnostic in more detail. + output: true + - name: dataTables + type: Array + description: Output only. Resource names of the data tables used in this rule. + output: true + item_type: + type: String diff --git a/mmv1/templates/terraform/examples/chronicle_rule_basic.tf.tmpl b/mmv1/templates/terraform/examples/chronicle_rule_basic.tf.tmpl new file mode 100644 index 000000000000..7814670c927f --- /dev/null +++ b/mmv1/templates/terraform/examples/chronicle_rule_basic.tf.tmpl @@ -0,0 +1,9 @@ +resource "google_chronicle_rule" "{{$.PrimaryResourceId}}" { + provider = "google-beta" + location = "us" + instance = "{{index $.TestEnvVars "chronicle_id"}}" + deletion_policy = "DEFAULT" + text = <<-EOT + rule test_rule { meta: events: $userid = $e.principal.user.userid match: $userid over 10m condition: $e } + EOT +} diff --git a/mmv1/templates/terraform/examples/chronicle_rule_with_data_access_scope.tf.tmpl b/mmv1/templates/terraform/examples/chronicle_rule_with_data_access_scope.tf.tmpl new file mode 100644 index 000000000000..a42966e76357 --- /dev/null +++ b/mmv1/templates/terraform/examples/chronicle_rule_with_data_access_scope.tf.tmpl @@ -0,0 +1,20 @@ +resource "google_chronicle_data_access_scope" "data_access_scope_test" { + provider = "google-beta" + location = "us" + instance = "{{index $.TestEnvVars "chronicle_id"}}" + data_access_scope_id = "{{index $.Vars "data_access_scope_id"}}" + description = "scope-description" + allowed_data_access_labels { + log_type = "GCP_CLOUDAUDIT" + } +} + +resource "google_chronicle_rule" "{{$.PrimaryResourceId}}" { + provider = "google-beta" + location = "us" + instance = "{{index $.TestEnvVars "chronicle_id"}}" + scope = resource.google_chronicle_data_access_scope.data_access_scope_test.name + text = <<-EOT + rule test_rule { meta: events: $userid = $e.principal.user.userid match: $userid over 10m condition: $e } + EOT +} diff --git a/mmv1/templates/terraform/examples/chronicle_rule_with_force_deletion.tf.tmpl b/mmv1/templates/terraform/examples/chronicle_rule_with_force_deletion.tf.tmpl new file mode 100644 index 000000000000..48561deec81d --- /dev/null +++ b/mmv1/templates/terraform/examples/chronicle_rule_with_force_deletion.tf.tmpl @@ -0,0 +1,9 @@ +resource "google_chronicle_rule" "{{$.PrimaryResourceId}}" { + provider = "google-beta" + location = "us" + instance = "{{index $.TestEnvVars "chronicle_id"}}" + deletion_policy = "FORCE" + text = <<-EOT + rule test_rule { meta: events: $userid = $e.principal.user.userid match: $userid over 10m condition: $e } + EOT +} diff --git a/mmv1/templates/terraform/post_create/chronicle_rule_id.go.tmpl b/mmv1/templates/terraform/post_create/chronicle_rule_id.go.tmpl new file mode 100644 index 000000000000..2eca96b0ce5e --- /dev/null +++ b/mmv1/templates/terraform/post_create/chronicle_rule_id.go.tmpl @@ -0,0 +1,5 @@ +// Rule id is set by API and required to GET the connection +// it is set by reading the "name" field rather than a field in the response +if err := d.Set("rule_id", flattenChronicleRuleRuleId("", d, config)); err != nil { + return fmt.Errorf("Error reading Rule ID: %s", err) +} \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_delete/chronicle_rule.go.tmpl b/mmv1/templates/terraform/pre_delete/chronicle_rule.go.tmpl new file mode 100644 index 000000000000..9b99b3132a51 --- /dev/null +++ b/mmv1/templates/terraform/pre_delete/chronicle_rule.go.tmpl @@ -0,0 +1,4 @@ +// Forcefully delete any retrohunts and any detections associated with the rule. +if deletionPolicy := d.Get("deletion_policy"); deletionPolicy == "FORCE" { + url = url + "?force=true" +} \ No newline at end of file diff --git a/mmv1/third_party/terraform/services/chronicle/resource_chronicle_rule_test.go.tmpl b/mmv1/third_party/terraform/services/chronicle/resource_chronicle_rule_test.go.tmpl new file mode 100644 index 000000000000..887037a60273 --- /dev/null +++ b/mmv1/third_party/terraform/services/chronicle/resource_chronicle_rule_test.go.tmpl @@ -0,0 +1,85 @@ +package chronicle_test + +{{- if ne $.TargetVersionName "ga" }} + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestAccChronicleRule_chronicleRuleBasicExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "chronicle_id": envvar.GetTestChronicleInstanceIdFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckChronicleRuleDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccChronicleRule_chronicleRuleBasicExample_basic(context), + }, + { + Config: testAccChronicleRule_chronicleRuleBasicExample_update(context), + }, + }, + }) +} + +func testAccChronicleRule_chronicleRuleBasicExample_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_chronicle_data_access_scope" "data_access_scope_test" { + provider = "google-beta" + location = "us" + instance = "%{chronicle_id}" + data_access_scope_id = "tf-test-scope-name%{random_suffix}" + description = "scope-description" + allowed_data_access_labels { + log_type = "GCP_CLOUDAUDIT" + } +} + +resource "google_chronicle_rule" "example" { + provider = "google-beta" + location = "us" + instance = "%{chronicle_id}" + scope = resource.google_chronicle_data_access_scope.data_access_scope_test.name + text = <<-EOT + rule test_rule { meta: events: $userid = $e.principal.user.userid match: $userid over 10m condition: $e } + EOT +} +`, context) +} + +func testAccChronicleRule_chronicleRuleBasicExample_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_chronicle_data_access_scope" "data_access_scope_test" { + provider = "google-beta" + location = "us" + instance = "%{chronicle_id}" + data_access_scope_id = "tf-test-scope-name%{random_suffix}" + description = "scope-description" + allowed_data_access_labels { + log_type = "GCP_CLOUDAUDIT" + } +} + +resource "google_chronicle_rule" "example" { + provider = "google-beta" + location = "us" + instance = "%{chronicle_id}" + text = <<-EOT + rule test_rule { meta: events: $updated_userid = $e.principal.user.userid match: $updated_userid over 10m condition: $e } + EOT +} +`, context) +} +{{- end }} From 38b66824452ad607339253f2f1f1d714567d6b17 Mon Sep 17 00:00:00 2001 From: Samir Ribeiro <42391123+Samir-Cit@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:12:47 -0300 Subject: [PATCH 16/30] Update example of usage of Authz Policy (#12724) --- .../products/networksecurity/AuthzPolicy.yaml | 3 + ...ork_services_authz_policy_advanced.tf.tmpl | 102 ++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/mmv1/products/networksecurity/AuthzPolicy.yaml b/mmv1/products/networksecurity/AuthzPolicy.yaml index 9267467ba128..68e2f24c757c 100644 --- a/mmv1/products/networksecurity/AuthzPolicy.yaml +++ b/mmv1/products/networksecurity/AuthzPolicy.yaml @@ -58,6 +58,9 @@ examples: url_name: 'l7-ilb-map' target_proxy_name: 'l7-ilb-proxy' forwarding_rule_name: 'l7-ilb-forwarding-rule' + callouts_instance_name: 'l7-ilb-callouts-ins' + callouts_instance_group_name: 'l7-ilb-callouts-ins-group' + callouts_health_check_name: 'l7-ilb-callouts-healthcheck' backend_authz_name: 'authz-service' authz_extension_name: 'my-authz-ext' test_env_vars: diff --git a/mmv1/templates/terraform/examples/network_services_authz_policy_advanced.tf.tmpl b/mmv1/templates/terraform/examples/network_services_authz_policy_advanced.tf.tmpl index e8905548cc37..69ead6b2cb2c 100644 --- a/mmv1/templates/terraform/examples/network_services_authz_policy_advanced.tf.tmpl +++ b/mmv1/templates/terraform/examples/network_services_authz_policy_advanced.tf.tmpl @@ -22,6 +22,78 @@ resource "google_compute_subnetwork" "proxy_only" { network = google_compute_network.default.id } +resource "google_compute_instance" "callouts_instance" { + name = "{{index $.Vars "callouts_instance_name"}}" + zone = "us-west1-a" + machine_type = "e2-small" + tags = ["allow-ssh","load-balanced-backend"] + deletion_protection = false + + labels = { + "container-vm" = "cos-stable-109-17800-147-54" + } + + network_interface { + network = google_compute_network.default.id + subnetwork = google_compute_subnetwork.default.id + access_config { + # add external ip to fetch packages + } + + } + + boot_disk { + auto_delete = true + initialize_params { + type = "pd-standard" + size = 10 + image = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-stable-109-17800-147-54" + } + } + + metadata = { + gce-container-declaration = "# DISCLAIMER:\n# This container declaration format is not a public API and may change without\n# notice. Please use gcloud command-line tool or Google Cloud Console to run\n# Containers on Google Compute Engine.\n\nspec:\n containers:\n - image: us-docker.pkg.dev/service-extensions/ext-proc/service-callout-basic-example-python:latest\n name: callouts-vm\n securityContext:\n privileged: false\n stdin: false\n tty: false\n volumeMounts: []\n restartPolicy: Always\n volumes: []\n" + google-logging-enabled = "true" + } + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_instance_group" "callouts_instance_group" { + name = "{{index $.Vars "callouts_instance_group_name"}}" + description = "Terraform test instance group" + zone = "us-west1-a" + + instances = [ + google_compute_instance.callouts_instance.id, + ] + + named_port { + name = "http" + port = "80" + } + + named_port { + name = "grpc" + port = "443" + } +} + +resource "google_compute_region_health_check" "callouts_health_check" { + name = "{{index $.Vars "callouts_health_check_name"}}" + region = "us-west1" + + http_health_check { + port = 80 + } + + depends_on = [ + google_compute_region_health_check.default + ] +} + resource "google_compute_address" "default" { name = "{{index $.Vars "address_name"}}" project = "{{index $.TestEnvVars "project"}}" @@ -87,6 +159,13 @@ resource "google_compute_region_backend_service" "authz_extension" { protocol = "HTTP2" load_balancing_scheme = "INTERNAL_MANAGED" port_name = "grpc" + + health_checks = [google_compute_region_health_check.callouts_health_check.id] + backend { + group = google_compute_instance_group.callouts_instance_group.id + balancing_mode = "UTILIZATION" + capacity_scaler = 1.0 + } } resource "google_network_services_authz_extension" "default" { @@ -120,4 +199,27 @@ resource "google_network_security_authz_policy" "{{$.PrimaryResourceId}}" { resources = [ google_network_services_authz_extension.default.id ] } } + + http_rules { + from { + not_sources { + principals { + exact = "dummy-principal" + } + } + } + to { + operations { + header_set { + headers { + name = "test-header" + value { + exact = "test-value" + ignore_case = true + } + } + } + } + } + } } \ No newline at end of file From 27bceb017186f07d672778cf0f1bddb74426c55a Mon Sep 17 00:00:00 2001 From: Ryan Oaks Date: Fri, 10 Jan 2025 15:09:57 -0500 Subject: [PATCH 17/30] Update TeamCity docs to include recently added GOOGLE_CHRONICLE_INSTANCE_ID (#12709) --- .../terraform/.teamcity/USE_CONFIG_WITH_TEAMCITY.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mmv1/third_party/terraform/.teamcity/USE_CONFIG_WITH_TEAMCITY.md b/mmv1/third_party/terraform/.teamcity/USE_CONFIG_WITH_TEAMCITY.md index 17c81477f036..6cbd2b4f4b9d 100644 --- a/mmv1/third_party/terraform/.teamcity/USE_CONFIG_WITH_TEAMCITY.md +++ b/mmv1/third_party/terraform/.teamcity/USE_CONFIG_WITH_TEAMCITY.md @@ -95,6 +95,9 @@ The next step is provide some input values that the configuration needs to fully | org2Ga | Used to set the [GOOGLE_ORG_2](https://github.com/GoogleCloudPlatform/magic-modules/blob/94a3f91d75ee823c521a0d8d3984a1493fa0926a/mmv1/third_party/terraform/envvar/envvar_utils.go#L73-L75) environment variable in acceptance tests - GA specific | | org2Beta | Used to set the [GOOGLE_ORG_2](https://github.com/GoogleCloudPlatform/magic-modules/blob/94a3f91d75ee823c521a0d8d3984a1493fa0926a/mmv1/third_party/terraform/envvar/envvar_utils.go#L73-L75) environment variable in acceptance tests - Beta specific | | org2Vcr | Used to set the [GOOGLE_ORG_2](https://github.com/GoogleCloudPlatform/magic-modules/blob/94a3f91d75ee823c521a0d8d3984a1493fa0926a/mmv1/third_party/terraform/envvar/envvar_utils.go#L73-L75) environment variable in acceptance tests - VCR specific | +| chronicleInstanceIdGa | Used to set the [GOOGLE_CHRONICLE_INSTANCE_ID](https://github.com/GoogleCloudPlatform/magic-modules/blob/03a313d13c5d31f8e8fc71cb32d06157bc260f78/mmv1/third_party/terraform/envvar/envvar_utils.go#L107-L112) environment variable in acceptance tests - GA specific | +| chronicleInstanceIdBeta | Used to set the [GOOGLE_CHRONICLE_INSTANCE_ID](https://github.com/GoogleCloudPlatform/magic-modules/blob/03a313d13c5d31f8e8fc71cb32d06157bc260f78/mmv1/third_party/terraform/envvar/envvar_utils.go#L107-L112) environment variable in acceptance tests - Beta specific | +| chronicleInstanceIdVcr | Used to set the [GOOGLE_CHRONICLE_INSTANCE_ID](https://github.com/GoogleCloudPlatform/magic-modules/blob/03a313d13c5d31f8e8fc71cb32d06157bc260f78/mmv1/third_party/terraform/envvar/envvar_utils.go#L107-L112) environment variable in acceptance tests - VCR specific | | billingAccount | Used to set the [GOOGLE_BILLING_ACCOUNT](https://github.com/GoogleCloudPlatform/magic-modules/blob/94a3f91d75ee823c521a0d8d3984a1493fa0926a/mmv1/third_party/terraform/envvar/envvar_utils.go#L81-L85) ALL environment variable in acceptance tests | | billingAccount2 | Used to set the [GOOGLE_BILLING_ACCOUNT_2](https://github.com/GoogleCloudPlatform/magic-modules/blob/94a3f91d75ee823c521a0d8d3984a1493fa0926a/mmv1/third_party/terraform/services/resourcemanager/resource_google_project_test.go#L78-L79) environment variable in ALL acceptance tests | | custId | Used to set the [GOOGLE_CUST_ID](https://github.com/GoogleCloudPlatform/magic-modules/blob/94a3f91d75ee823c521a0d8d3984a1493fa0926a/mmv1/third_party/terraform/envvar/envvar_utils.go#L52-L56) environment variable in ALL acceptance tests | From 22008e516c62bcad85509e892707a4a01e36e6ea Mon Sep 17 00:00:00 2001 From: Julia Matuszewska <47438768+JumiDeluxe@users.noreply.github.com> Date: Fri, 10 Jan 2025 22:30:11 +0100 Subject: [PATCH 18/30] CodeRepositoryIndex cascade deletion (#12601) Co-authored-by: Nick Elliot --- mmv1/products/gemini/CodeRepositoryIndex.yaml | 9 ++ ...code_repository_index_force_delete.go.tmpl | 8 ++ ..._gemini_code_repository_index_test.go.tmpl | 131 ++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 mmv1/templates/terraform/pre_delete/code_repository_index_force_delete.go.tmpl diff --git a/mmv1/products/gemini/CodeRepositoryIndex.yaml b/mmv1/products/gemini/CodeRepositoryIndex.yaml index 62cb5236c845..c2730c35bfe6 100644 --- a/mmv1/products/gemini/CodeRepositoryIndex.yaml +++ b/mmv1/products/gemini/CodeRepositoryIndex.yaml @@ -51,9 +51,18 @@ async: result: resource_inside_response: true include_project: false +custom_code: + pre_delete: templates/terraform/pre_delete/code_repository_index_force_delete.go.tmpl error_retry_predicates: - 'transport_tpg.IsCodeRepositoryIndexUnreadyError' - 'transport_tpg.IsRepositoryGroupQueueError' +virtual_fields: + - name: 'force_destroy' + description: + If set to true, will allow deletion of the CodeRepositoryIndex even if there are existing + RepositoryGroups for the resource. These RepositoryGroups will also be deleted. + type: Boolean + default_value: false parameters: - name: location type: String diff --git a/mmv1/templates/terraform/pre_delete/code_repository_index_force_delete.go.tmpl b/mmv1/templates/terraform/pre_delete/code_repository_index_force_delete.go.tmpl new file mode 100644 index 000000000000..a451d1ece932 --- /dev/null +++ b/mmv1/templates/terraform/pre_delete/code_repository_index_force_delete.go.tmpl @@ -0,0 +1,8 @@ +{{- if ne $.TargetVersionName "ga" -}} +obj = make(map[string]interface{}) +if v, ok := d.GetOk("force_destroy"); ok { + if v == true { + obj["force"] = true + } +} +{{- end }} diff --git a/mmv1/third_party/terraform/services/gemini/resource_gemini_code_repository_index_test.go.tmpl b/mmv1/third_party/terraform/services/gemini/resource_gemini_code_repository_index_test.go.tmpl index ab1520dbc92b..ddbd7c17168f 100644 --- a/mmv1/third_party/terraform/services/gemini/resource_gemini_code_repository_index_test.go.tmpl +++ b/mmv1/third_party/terraform/services/gemini/resource_gemini_code_repository_index_test.go.tmpl @@ -2,6 +2,7 @@ package gemini_test {{- if ne $.TargetVersionName "ga" }} import ( + "fmt" "os" "testing" @@ -44,6 +45,136 @@ func TestAccGeminiCodeRepositoryIndex_update(t *testing.T) { }) } +// TestAccGeminiCodeRepositoryIndex_delete checks if there is no error in deleting CRI along with children resource +// note: this is an example of a bad usage, where RGs refer to the CRI using a string id, not a reference, as they +// will be force-removed upon CRI deletion, because the CRI provider uses --force option by default +// The plan after the _delete function should not be empty due to the child resource in plan +func TestAccGeminiCodeRepositoryIndex_delete(t *testing.T) { + bootstrappedKMS := acctest.BootstrapKMSKeyInLocation(t, "us-central1") + randomSuffix := acctest.RandString(t, 10) + context := map[string]interface{}{ + "random_suffix": randomSuffix, + "project_id": os.Getenv("GOOGLE_PROJECT"), + "kms_key": bootstrappedKMS.CryptoKey.Name, + "cri_id": fmt.Sprintf("tf-test-cri-index-delete-example-%s", randomSuffix), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccGeminiCodeRepositoryIndex_withChildren_basic(context), + }, + { + ResourceName: "google_gemini_code_repository_index.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"code_repository_index_id", "labels", "location", "terraform_labels", "force_destroy"}, + }, + { + Config: testAccGeminiCodeRepositoryIndex_withChildren_delete(context), + ExpectNonEmptyPlan: true, + PlanOnly: true, + }, + }, + }) +} + +func testAccGeminiCodeRepositoryIndex_withChildren_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_gemini_code_repository_index" "example" { + provider = google-beta + labels = {"ccfe_debug_note": "terraform_e2e_should_be_deleted"} + location = "us-central1" + code_repository_index_id = "%{cri_id}" + force_destroy = true +} + +resource "google_gemini_repository_group" "example" { + provider = google-beta + location = "us-central1" + code_repository_index = "%{cri_id}" + repository_group_id = "tf-test-rg-repository-group-id-%{random_suffix}" + repositories { + resource = "projects/%{project_id}/locations/us-central1/connections/${google_developer_connect_connection.github_conn.connection_id}/gitRepositoryLinks/${google_developer_connect_git_repository_link.conn.git_repository_link_id}" + branch_pattern = "main" + } + labels = {"label1": "value1"} + depends_on = [ + google_gemini_code_repository_index.example + ] +} + +resource "google_developer_connect_git_repository_link" "conn" { + provider = google-beta + git_repository_link_id = "tf-test-repository-conn-delete" + parent_connection = google_developer_connect_connection.github_conn.connection_id + clone_uri = "https://github.com/CC-R-github-robot/tf-test.git" + location = "us-central1" + annotations = {} +} + +resource "google_developer_connect_connection" "github_conn" { + provider = google-beta + location = "us-central1" + connection_id = "tf-test-cloudaicompanion-delete-%{random_suffix}" + disabled = false + + github_config { + github_app = "DEVELOPER_CONNECT" + app_installation_id = 54180648 + + authorizer_credential { + oauth_token_secret_version = "projects/502367051001/secrets/tf-test-cloudaicompanion-github-oauthtoken-c42e5c/versions/1" + } + } +} +`, context) +} + +// Removed depends_on to not break plan test +func testAccGeminiCodeRepositoryIndex_withChildren_delete(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_gemini_repository_group" "example" { + provider = google-beta + location = "us-central1" + code_repository_index = "%{cri_id}" + repository_group_id = "tf-test-rg-repository-group-id-%{random_suffix}" + repositories { + resource = "projects/%{project_id}/locations/us-central1/connections/${google_developer_connect_connection.github_conn.connection_id}/gitRepositoryLinks/${google_developer_connect_git_repository_link.conn.git_repository_link_id}" + branch_pattern = "main" + } + labels = {"label1": "value1"} +} + +resource "google_developer_connect_git_repository_link" "conn" { + provider = google-beta + git_repository_link_id = "tf-test-repository-conn-delete" + parent_connection = google_developer_connect_connection.github_conn.connection_id + clone_uri = "https://github.com/CC-R-github-robot/tf-test.git" + location = "us-central1" + annotations = {} +} + +resource "google_developer_connect_connection" "github_conn" { + provider = google-beta + location = "us-central1" + connection_id = "tf-test-cloudaicompanion-delete-%{random_suffix}" + disabled = false + + github_config { + github_app = "DEVELOPER_CONNECT" + app_installation_id = 54180648 + + authorizer_credential { + oauth_token_secret_version = "projects/502367051001/secrets/tf-test-cloudaicompanion-github-oauthtoken-c42e5c/versions/1" + } + } +} +`, context) +} + func testAccGeminiCodeRepositoryIndex_basic(context map[string]interface{}) string { return acctest.Nprintf(` resource "google_gemini_code_repository_index" "example" { From 070ddb513c32cd6120a1b6af82670e5bc6d40888 Mon Sep 17 00:00:00 2001 From: Zhenhua Li Date: Fri, 10 Jan 2025 13:32:39 -0800 Subject: [PATCH 19/30] Set the async type default to OpAsync (#12730) --- mmv1/api/async.go | 4 ++++ mmv1/templates/terraform/resource.go.tmpl | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/mmv1/api/async.go b/mmv1/api/async.go index f947c62741b3..b6ea3c7291f6 100644 --- a/mmv1/api/async.go +++ b/mmv1/api/async.go @@ -58,6 +58,7 @@ func NewOperation() *Operation { return op } +// It is only used in openapi-generate func NewAsync() *Async { oa := &Async{ Actions: []string{"create", "delete", "update"}, @@ -150,6 +151,9 @@ func (a *Async) UnmarshalYAML(unmarshal func(any) error) error { return err } + if a.Type == "" { + a.Type = "OpAsync" + } if a.Type == "PollAsync" && a.TargetOccurrences == 0 { a.TargetOccurrences = 1 } diff --git a/mmv1/templates/terraform/resource.go.tmpl b/mmv1/templates/terraform/resource.go.tmpl index 73c2d18e467a..7751309e81d4 100644 --- a/mmv1/templates/terraform/resource.go.tmpl +++ b/mmv1/templates/terraform/resource.go.tmpl @@ -314,8 +314,8 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ d.SetId(id) {{if and $.GetAsync ($.GetAsync.Allow "Create") -}} -{{if ($.GetAsync.IsA "OpAsync") -}} -{{if and $.GetAsync.Result.ResourceInsideResponse $.GetIdentity -}} +{{ if ($.GetAsync.IsA "OpAsync") -}} +{{ if and $.GetAsync.Result.ResourceInsideResponse $.GetIdentity -}} // Use the resource in the operation response to populate // identity fields and d.Id() before read var opRes map[string]interface{} @@ -330,7 +330,7 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ // The resource didn't actually create d.SetId("") -{{ end -}} +{{ end -}} return fmt.Errorf("Error waiting to create {{ $.Name -}}: %s", err) } @@ -390,8 +390,8 @@ func resource{{ $.ResourceName -}}Create(d *schema.ResourceData, meta interface{ } {{ end -}} -{{ end -}} -{{ end -}} +{{ end -}}{{/*if ($.GetAsync.IsA "OpAsync")*/}} +{{ end -}}{{/*if and $.GetAsync ($.GetAsync.Allow "Create")*/}} {{if $.CustomCode.PostCreate -}} {{- $.CustomTemplate $.CustomCode.PostCreate false -}} {{- end}} From 4f7ee52795cf5a3df2854c4ba782c2a4376fcef2 Mon Sep 17 00:00:00 2001 From: Iris Chen <10179943+iyabchen@users.noreply.github.com> Date: Fri, 10 Jan 2025 16:51:19 -0500 Subject: [PATCH 20/30] add a subcommand into diff processor to detect missing docs (#11156) --- .../diff-processor/cmd/detect_missing_docs.go | 79 +++++ .../cmd/detect_missing_docs_test.go | 91 ++++++ tools/diff-processor/detector/detector.go | 98 ++++++ .../diff-processor/detector/detector_test.go | 205 +++++++++++++ .../documentparser/document_parser.go | 202 +++++++++++++ .../documentparser/document_parser_test.go | 116 +++++++ .../testdata/resource.html.markdown | 285 ++++++++++++++++++ .../website/docs/r/a_resource.html.markdown | 18 ++ 8 files changed, 1094 insertions(+) create mode 100644 tools/diff-processor/cmd/detect_missing_docs.go create mode 100644 tools/diff-processor/cmd/detect_missing_docs_test.go create mode 100644 tools/diff-processor/documentparser/document_parser.go create mode 100644 tools/diff-processor/documentparser/document_parser_test.go create mode 100644 tools/diff-processor/testdata/resource.html.markdown create mode 100644 tools/diff-processor/testdata/website/docs/r/a_resource.html.markdown diff --git a/tools/diff-processor/cmd/detect_missing_docs.go b/tools/diff-processor/cmd/detect_missing_docs.go new file mode 100644 index 000000000000..60a5a427dac6 --- /dev/null +++ b/tools/diff-processor/cmd/detect_missing_docs.go @@ -0,0 +1,79 @@ +package cmd + +import ( + newProvider "google/provider/new/google/provider" + oldProvider "google/provider/old/google/provider" + "slices" + "sort" + + "encoding/json" + "fmt" + "io" + "os" + + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/detector" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/spf13/cobra" + "golang.org/x/exp/maps" +) + +const detectMissingDocDesc = `Compute list of fields missing documents` + +type MissingDocsInfo struct { + Name string + FilePath string + Fields []string +} + +type detectMissingDocsOptions struct { + rootOptions *rootOptions + computeSchemaDiff func() diff.SchemaDiff + newResourceSchema map[string]*schema.Resource + stdout io.Writer +} + +func newDetectMissingDocsCmd(rootOptions *rootOptions) *cobra.Command { + o := &detectMissingDocsOptions{ + rootOptions: rootOptions, + computeSchemaDiff: func() diff.SchemaDiff { + return diff.ComputeSchemaDiff(oldProvider.ResourceMap(), newProvider.ResourceMap()) + }, + stdout: os.Stdout, + } + cmd := &cobra.Command{ + Use: "detect-missing-docs", + Short: detectMissingDocDesc, + Long: detectMissingDocDesc, + Args: cobra.ExactArgs(1), + RunE: func(c *cobra.Command, args []string) error { + return o.run(args) + }, + } + return cmd +} +func (o *detectMissingDocsOptions) run(args []string) error { + schemaDiff := o.computeSchemaDiff() + detectedResources, err := detector.DetectMissingDocs(schemaDiff, args[0], o.newResourceSchema) + if err != nil { + return err + } + resources := maps.Keys(detectedResources) + slices.Sort(resources) + info := []MissingDocsInfo{} + for _, r := range resources { + details := detectedResources[r] + sort.Strings(details.Fields) + info = append(info, MissingDocsInfo{ + Name: r, + FilePath: details.FilePath, + Fields: details.Fields, + }) + } + + if err := json.NewEncoder(o.stdout).Encode(info); err != nil { + return fmt.Errorf("error encoding json: %w", err) + } + + return nil +} diff --git a/tools/diff-processor/cmd/detect_missing_docs_test.go b/tools/diff-processor/cmd/detect_missing_docs_test.go new file mode 100644 index 000000000000..e385488c04ba --- /dev/null +++ b/tools/diff-processor/cmd/detect_missing_docs_test.go @@ -0,0 +1,91 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" + "github.com/google/go-cmp/cmp" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestDetectMissingDocs(t *testing.T) { + cases := []struct { + name string + oldResourceMap map[string]*schema.Resource + newResourceMap map[string]*schema.Resource + want []MissingDocsInfo + }{ + { + name: "no new fields", + oldResourceMap: map[string]*schema.Resource{ + "google_x": { + Schema: map[string]*schema.Schema{ + "field-a": {Description: "beep", Computed: true, Optional: true}, + "field-b": {Description: "beep", Computed: true}, + }, + }, + }, + newResourceMap: map[string]*schema.Resource{ + "google_x": { + Schema: map[string]*schema.Schema{ + "field-a": {Description: "beep", Computed: true, Optional: true}, + "field-b": {Description: "beep", Computed: true}, + }, + }, + }, + want: []MissingDocsInfo{}, + }, + { + name: "multiple new fields missing doc", + oldResourceMap: map[string]*schema.Resource{}, + newResourceMap: map[string]*schema.Resource{ + "google_x": { + Schema: map[string]*schema.Schema{ + "field-a": {Description: "beep", Computed: true, Optional: true}, + "field-b": {Description: "beep", Computed: true}, + }, + }, + }, + want: []MissingDocsInfo{ + { + Name: "google_x", + FilePath: "/website/docs/r/x.html.markdown", + Fields: []string{"field-a", "field-b"}, + }, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var buf bytes.Buffer + o := detectMissingDocsOptions{ + computeSchemaDiff: func() diff.SchemaDiff { + return diff.ComputeSchemaDiff(tc.oldResourceMap, tc.newResourceMap) + }, + newResourceSchema: tc.newResourceMap, + stdout: &buf, + } + + err := o.run([]string{t.TempDir()}) + if err != nil { + t.Fatalf("Error running command: %s", err) + } + + out := make([]byte, buf.Len()) + buf.Read(out) + + var got []MissingDocsInfo + if err = json.Unmarshal(out, &got); err != nil { + t.Fatalf("Failed to unmarshall output: %s", err) + } + + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("Unexpected result. Want %+v, got %+v. ", tc.want, got) + } + }) + } +} diff --git a/tools/diff-processor/detector/detector.go b/tools/diff-processor/detector/detector.go index 8305aaac1407..05e0720f085f 100644 --- a/tools/diff-processor/detector/detector.go +++ b/tools/diff-processor/detector/detector.go @@ -1,10 +1,14 @@ package detector import ( + "fmt" + "os" + "path/filepath" "sort" "strings" "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/documentparser" "github.com/GoogleCloudPlatform/magic-modules/tools/test-reader/reader" "github.com/hashicorp/hcl/v2/hclwrite" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -33,6 +37,12 @@ type Field struct { Tested bool } +// MissingDocDetails denotes the doc file path and the fields that are not shown up in the corresponding doc. +type MissingDocDetails struct { + FilePath string + Fields []string +} + // Detect missing tests for the given resource changes map in the given slice of tests. // Return a map of resource names to missing test info about that resource. func DetectMissingTests(schemaDiff diff.SchemaDiff, allTests []*reader.Test) (map[string]*MissingTestInfo, error) { @@ -152,3 +162,91 @@ func suggestedTest(resourceName string, untested []string) string { } return strings.ReplaceAll(string(f.Bytes()), `"VALUE"`, "# value needed") } + +// DetectMissingDocs detect new fields that are missing docs given the schema diffs. +// Return a map of resource names to missing doc info. +func DetectMissingDocs(schemaDiff diff.SchemaDiff, repoPath string, resourceMap map[string]*schema.Resource) (map[string]MissingDocDetails, error) { + ret := make(map[string]MissingDocDetails) + for resource, resourceDiff := range schemaDiff { + fieldsInDoc := make(map[string]bool) + + docFilePath, err := resourceToDocFile(resource, repoPath) + if err != nil { + fmt.Printf("Warning: %s.\n", err) + } else { + content, err := os.ReadFile(docFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read resource doc %s: %w", docFilePath, err) + } + parser := documentparser.NewParser() + err = parser.Parse(content) + if err != nil { + return nil, fmt.Errorf("failed to parse document %s: %w", docFilePath, err) + } + + argumentsInDoc := listToMap(parser.Arguments()) + attributesInDoc := listToMap(parser.Attributes()) + for _, m := range []map[string]bool{argumentsInDoc, attributesInDoc} { + for k, v := range m { + fieldsInDoc[k] = v + } + } + // for iam resource + if v, ok := fieldsInDoc["member/members"]; ok { + fieldsInDoc["member"] = v + fieldsInDoc["members"] = v + } + } + details := MissingDocDetails{ + FilePath: strings.ReplaceAll(docFilePath, repoPath, ""), + } + + for field, fieldDiff := range resourceDiff.Fields { + if !isNewField(fieldDiff) { + continue + } + if !fieldsInDoc[field] { + details.Fields = append(details.Fields, field) + } + } + if len(details.Fields) > 0 { + ret[resource] = details + } + } + return ret, nil +} + +func isNewField(fieldDiff diff.FieldDiff) bool { + return fieldDiff.Old == nil && fieldDiff.New != nil +} + +func resourceToDocFile(resource string, repoPath string) (string, error) { + baseNameOptions := []string{ + strings.TrimPrefix(resource, "google_") + ".html.markdown", + resource + ".html.markdown", + } + suffix := []string{"_policy", "_binding", "_member"} + for _, s := range suffix { + if strings.HasSuffix(resource, "_iam"+s) { + iamName := strings.TrimSuffix(resource, s) + baseNameOptions = append(baseNameOptions, iamName+".html.markdown") + baseNameOptions = append(baseNameOptions, strings.TrimPrefix(iamName, "google_")+".html.markdown") + } + } + for _, baseName := range baseNameOptions { + fullPath := filepath.Join(repoPath, "website", "docs", "r", baseName) + _, err := os.ReadFile(fullPath) + if !os.IsNotExist(err) { + return fullPath, nil + } + } + return filepath.Join(repoPath, "website", "docs", "r", baseNameOptions[0]), fmt.Errorf("no document files found in %s for resource %q", baseNameOptions, resource) +} + +func listToMap(items []string) map[string]bool { + m := make(map[string]bool) + for _, item := range items { + m[item] = true + } + return m +} diff --git a/tools/diff-processor/detector/detector_test.go b/tools/diff-processor/detector/detector_test.go index 60ad7739bc7f..30f0dcc5813a 100644 --- a/tools/diff-processor/detector/detector_test.go +++ b/tools/diff-processor/detector/detector_test.go @@ -2,10 +2,12 @@ package detector import ( "reflect" + "sort" "testing" "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" "github.com/GoogleCloudPlatform/magic-modules/tools/test-reader/reader" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -201,3 +203,206 @@ func TestGetMissingTestsForChanges(t *testing.T) { } } } + +func TestDetectMissingDocs(t *testing.T) { + // top level field_one is argument, field_two is attribute. + resourceSchema := map[string]*schema.Resource{ + "a_resource": { + Schema: map[string]*schema.Schema{ + "field_one": { + Computed: true, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "a": { + Computed: true, + Optional: true, + }, + "b": { + Computed: true, + Optional: false, + }, + "c": { + Computed: true, + Optional: false, + }, + }, + }, + }, + "field_two": { + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "a": { + Computed: true, + Optional: false, + }, + "b": { + Computed: true, + Optional: false, + }, + "c": { + Computed: true, + Optional: false, + }, + }, + }, + }, + "field_three": { + Computed: true, + Optional: true, + }, + "field_four": { + Computed: true, + }, + }, + }, + } + + // If repo is not temp dir, then the doc file points to tools/diff-processor/testdata/website/docs/r/a_resource.html.markdown. + for _, test := range []struct { + name string + schemaDiff diff.SchemaDiff + repo string + want map[string]MissingDocDetails + }{ + { + name: "doc file not exist", + schemaDiff: diff.SchemaDiff{ + "a_resource": diff.ResourceDiff{ + Fields: map[string]diff.FieldDiff{ + "field_one": { + New: &schema.Schema{}, + }, + "field_one.a": { + New: &schema.Schema{}, + }, + "field_one.b": { + New: &schema.Schema{}, + }, + "field_two.a": { + New: &schema.Schema{}, + Old: &schema.Schema{}, + }, + "field_two.b": { + New: &schema.Schema{}, + }, + "field_three": { + New: &schema.Schema{ + Computed: true, + Optional: true, + }, + }, + "field_four": { + New: &schema.Schema{ + Computed: true, + }, + }, + }, + }, + }, + repo: t.TempDir(), + want: map[string]MissingDocDetails{ + "a_resource": { + FilePath: "/website/docs/r/a_resource.html.markdown", + Fields: []string{"field_one", "field_one.a", "field_one.b", "field_two.b", "field_three", "field_four"}, + }, + }, + }, + { + name: "doc file exist", + schemaDiff: diff.SchemaDiff{ + "a_resource": diff.ResourceDiff{ + Fields: map[string]diff.FieldDiff{ + "field_one": { + New: &schema.Schema{}, + }, + "field_one.a": { + New: &schema.Schema{}, + }, + "field_one.b": { + New: &schema.Schema{}, + }, + "field_two.a": { + New: &schema.Schema{}, + Old: &schema.Schema{}, + }, + "field_two.b": { + New: &schema.Schema{}, + }, + "field_three": { + New: &schema.Schema{ + Computed: true, + Optional: true, + }, + }, + "field_four": { + New: &schema.Schema{ + Computed: true, + }, + }, + }, + }, + }, + repo: "../testdata", + want: map[string]MissingDocDetails{ + "a_resource": { + FilePath: "/website/docs/r/a_resource.html.markdown", + Fields: []string{"field_one.b", "field_two.b", "field_three", "field_four"}, + }, + }, + }, + { + name: "nested new field missing doc", + schemaDiff: diff.SchemaDiff{ + "a_resource": diff.ResourceDiff{ + Fields: map[string]diff.FieldDiff{ + "field_one.c": { + New: &schema.Schema{}, + }, + }, + }, + }, + repo: "../testdata", + want: map[string]MissingDocDetails{ + "a_resource": { + FilePath: "/website/docs/r/a_resource.html.markdown", + Fields: []string{"field_one.c"}, + }, + }, + }, + { + name: "member and members is member/members in doc", + schemaDiff: diff.SchemaDiff{ + "a_resource": diff.ResourceDiff{ + Fields: map[string]diff.FieldDiff{ + "member": { + New: &schema.Schema{}, + }, + "members": { + New: &schema.Schema{}, + }, + }, + }, + }, + repo: "../testdata", + want: map[string]MissingDocDetails{}, + }, + } { + t.Run(test.name, func(t *testing.T) { + got, err := DetectMissingDocs(test.schemaDiff, test.repo, resourceSchema) + if err != nil { + t.Fatalf("DetectMissingDocs = %v, want = nil", err) + } + for r := range test.want { + sort.Strings(test.want[r].Fields) + } + for r := range got { + sort.Strings(got[r].Fields) + } + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("DetectMissingDocs = %v, want = %v", got, test.want) + } + }) + } +} diff --git a/tools/diff-processor/documentparser/document_parser.go b/tools/diff-processor/documentparser/document_parser.go new file mode 100644 index 000000000000..28969906c665 --- /dev/null +++ b/tools/diff-processor/documentparser/document_parser.go @@ -0,0 +1,202 @@ +package documentparser + +import ( + "fmt" + "regexp" + "sort" + "strings" +) + +const ( + nestedNamePattern = `\(#(nested_[a-z0-9_]+)\)` + + itemNamePattern = "\\* `([a-z0-9_\\./]+)`" + nestedLinkPattern = `` + + sectionSeparator = "## " + nestedObjectSeparator = ` 0 { + l := len(queue) + for _, cur := range queue { + // the separator should always at the beginning of the line + items := strings.Split(cur.text, "\n"+listItemSeparator) + for _, item := range items[1:] { + text := listItemSeparator + item + itemName, err := findItemName(text) + if err != nil { + return err + } + // There is a special case in some hand written resource eg. in compute_instance, where its attributes is in a.0.b.0.c format. + itemName = strings.ReplaceAll(itemName, ".0.", ".") + nestedName, err := findNestedName(text) + if err != nil { + return err + } + newNode := &node{ + name: itemName, + } + cur.children = append(cur.children, newNode) + if text, ok := nestedBlock[nestedName]; ok { + newNode.text = text + queue = append(queue, newNode) + } + } + + } + queue = queue[l:] + } + return nil +} + +func findItemName(text string) (name string, err error) { + name, err = findPattern(text, itemNamePattern) + if err != nil { + return "", err + } + if name == "" { + return "", fmt.Errorf("cannot find item name from %s", text) + } + return +} + +func findPattern(text string, pattern string) (string, error) { + re, err := regexp.Compile(pattern) + if err != nil { + return "", err + } + match := re.FindStringSubmatch(text) + + if match != nil { + return match[1], nil + } + return "", nil +} + +func findNestedName(text string) (string, error) { + s := strings.ReplaceAll(text, "\n", "") + return findPattern(s, nestedNamePattern) +} diff --git a/tools/diff-processor/documentparser/document_parser_test.go b/tools/diff-processor/documentparser/document_parser_test.go new file mode 100644 index 000000000000..d48df5f184e8 --- /dev/null +++ b/tools/diff-processor/documentparser/document_parser_test.go @@ -0,0 +1,116 @@ +package documentparser + +import ( + "os" + "sort" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestParse(t *testing.T) { + b, err := os.ReadFile("../testdata/resource.html.markdown") + if err != nil { + t.Fatal(err) + } + parser := NewParser() + if err := parser.Parse(b); err != nil { + t.Fatal(err) + } + wantArguments := []string{ + "boot_disk", + "boot_disk.auto_delete", + "boot_disk.device_name", + "boot_disk.disk_encryption_key_raw", + "boot_disk.initialize_params", + "boot_disk.initialize_params.enable_confidential_compute", + "boot_disk.initialize_params.image", + "boot_disk.initialize_params.labels", + "boot_disk.initialize_params.provisioned_iops", + "boot_disk.initialize_params.provisioned_throughput", + "boot_disk.initialize_params.resource_manager_tags", + "boot_disk.initialize_params.size", + "boot_disk.initialize_params.storage_pool", + "boot_disk.initialize_params.type", + "boot_disk.kms_key_self_link", + "boot_disk.mode", + "boot_disk.source", + "name", + "network_interface", + "network_interface.access_config", + "network_interface.access_config.nat_ip", + "network_interface.access_config.network_tier", + "network_interface.access_config.public_ptr_domain_name", + "network_interface.alias_ip_range", + "network_interface.alias_ip_range.ip_cidr_range", + "network_interface.alias_ip_range.subnetwork_range_name", + "network_interface.ipv6_access_config", + "network_interface.ipv6_access_config.external_ipv6", + "network_interface.ipv6_access_config.external_ipv6_prefix_length", + "network_interface.ipv6_access_config.name", + "network_interface.ipv6_access_config.network_tier", + "network_interface.ipv6_access_config.public_ptr_domain_name", + "network_interface.network", + "network_interface.network_attachment", + "network_interface.network_ip", + "network_interface.nic_type", + "network_interface.queue_count", + "network_interface.security_policy", + "network_interface.stack_type", + "params", + // "params.resource_manager_tags", // params text does not include a nested tag + "zone", + "labels", + "description", + "traffic_port_selector", + "traffic_port_selector.ports", + "project", + } + wantAttributes := []string{ + "id", + "network_interface.access_config.nat_ip", + "workload_identity_config", + "errors", + "workload_identity_config.identity_provider", + "workload_identity_config.issuer_uri", + "workload_identity_config.workload_pool", + "errors.message", + } + gotArguments := parser.Arguments() + gotAttributes := parser.Attributes() + for _, arr := range [][]string{gotArguments, wantArguments, gotAttributes, wantAttributes} { + sort.Strings(arr) + } + if diff := cmp.Diff(wantArguments, gotArguments); diff != "" { + t.Errorf("Parse returned diff in arguments(-want, +got): %s", diff) + } + if diff := cmp.Diff(wantAttributes, gotAttributes); diff != "" { + t.Errorf("Parse returned diff in attributes(-want, +got): %s", diff) + } +} + +func TestTraverse(t *testing.T) { + n1 := &node{name: "n1"} + n2 := &node{name: "n2"} + n3 := &node{name: "n3"} + n4 := &node{name: "n4"} + root := &node{ + children: []*node{n1, n2, n3}, + } + n1.children = []*node{n4} + n2.children = []*node{n4} + + var paths []string + traverse(&paths, "", root) + + wantPaths := []string{ + "n1", + "n1.n4", + "n2", + "n2.n4", + "n3", + } + if diff := cmp.Diff(wantPaths, paths); diff != "" { + t.Errorf("traverse returned diff(-want, +got): %s", diff) + } +} diff --git a/tools/diff-processor/testdata/resource.html.markdown b/tools/diff-processor/testdata/resource.html.markdown new file mode 100644 index 000000000000..b06fc5b13984 --- /dev/null +++ b/tools/diff-processor/testdata/resource.html.markdown @@ -0,0 +1,285 @@ +--- +subcategory: "Compute Engine" +description: |- + Manages abcdefg. +--- + +# google_test_resource + +This resource combines some sections in google_compute_instance, google_container_attached_cluster, network_services_endpoint_policy and irrelvant parts are trimmed. + +## Example Usage + +Lorem ipsum + +## Example usage - Confidential Computing + +Lorem ipsum + + +## Argument Reference + +The following arguments are supported: + +* `boot_disk` - (Required) The boot disk for the instance. + Structure is [documented below](#nested_boot_disk). + +* `name` - (Required) A unique name for the resource, required by GCE. + Changing this forces a new resource to be created. + +* `zone` - (Optional) The zone that the machine should be created in. If it is not provided, the provider zone is used. + +* `network_interface` - (Required) Networks to attach to the instance. This can + be specified multiple times. Structure is [documented below](#nested_network_interface). + +* `params` - (Optional) Additional instance parameters. + +--- + +The `boot_disk` block supports: + +* `auto_delete` - (Optional) Whether the disk will be auto-deleted when the instance + is deleted. Defaults to true. + +* `device_name` - (Optional) Name with which attached disk will be accessible. + On the instance, this device will be `/dev/disk/by-id/google-{{device_name}}`. + +* `mode` - (Optional) The mode in which to attach this disk, either `READ_WRITE` + or `READ_ONLY`. If not specified, the default is to attach the disk in `READ_WRITE` mode. + +* `disk_encryption_key_raw` - (Optional) A 256-bit [customer-supplied encryption key] + (https://cloud.google.com/compute/docs/disks/customer-supplied-encryption), + encoded in [RFC 4648 base64](https://tools.ietf.org/html/rfc4648#section-4) + to encrypt this disk. Only one of `kms_key_self_link` and `disk_encryption_key_raw` + may be set. + +* `kms_key_self_link` - (Optional) The self_link of the encryption key that is + stored in Google Cloud KMS to encrypt this disk. Only one of `kms_key_self_link` + and `disk_encryption_key_raw` may be set. + +* `initialize_params` - (Optional) Parameters for a new disk that will be created + alongside the new instance. Either `initialize_params` or `source` must be set. + Structure is [documented below](#nested_initialize_params). + +* `source` - (Optional) The name or self_link of the existing disk (such as those managed by + `google_compute_disk`) or disk image. To create an instance from a snapshot, first create a + `google_compute_disk` from a snapshot and reference it here. + +The `initialize_params` block supports: + +* `size` - (Optional) The size of the image in gigabytes. If not specified, it + will inherit the size of its base image. + +* `type` - (Optional) The GCE disk type. Such as pd-standard, pd-balanced or pd-ssd. + +* `image` - (Optional) The image from which to initialize this disk. This can be + one of: the image's `self_link`, `projects/{project}/global/images/{image}`, + `projects/{project}/global/images/family/{family}`, `global/images/{image}`, + `global/images/family/{family}`, `family/{family}`, `{project}/{family}`, + `{project}/{image}`, `{family}`, or `{image}`. If referred by family, the + images names must include the family name. If they don't, use the + [google_compute_image data source](/docs/providers/google/d/compute_image.html). + For instance, the image `centos-6-v20180104` includes its family name `centos-6`. + These images can be referred by family name here. + +* `labels` - (Optional) A set of key/value label pairs assigned to the disk. This + field is only applicable for persistent disks. + +* `resource_manager_tags` - (Optional) A tag is a key-value pair that can be attached to a Google Cloud resource. You can use tags to conditionally allow or deny policies based on whether a resource has a specific tag. This value is not returned by the API. In Terraform, this value cannot be updated and changing it will recreate the resource. + +* `provisioned_iops` - (Optional) Indicates how many IOPS to provision for the disk. + This sets the number of I/O operations per second that the disk can handle. + For more details,see the [Hyperdisk documentation](https://cloud.google.com/compute/docs/disks/hyperdisks). + Note: Updating currently is only supported for hyperdisk skus via disk update + api/gcloud without the need to delete and recreate the disk, hyperdisk allows + for an update of IOPS every 4 hours. To update your hyperdisk more frequently, + you'll need to manually delete and recreate it. + +* `provisioned_throughput` - (Optional) Indicates how much throughput to provision for the disk. + This sets the number of throughput mb per second that the disk can handle. + For more details,see the [Hyperdisk documentation](https://cloud.google.com/compute/docs/disks/hyperdisks). + Note: Updating currently is only supported for hyperdisk skus via disk update + api/gcloud without the need to delete and recreate the disk, hyperdisk allows + for an update of throughput every 4 hours. To update your hyperdisk more + frequently, you'll need to manually delete and recreate it. + +* `enable_confidential_compute` - (Optional) Whether this disk is using confidential compute mode. + Note: Only supported on hyperdisk skus, disk_encryption_key is required when setting to true. + +* `storage_pool` - (Optional) The URL of the storage pool in which the new disk is created. + For example: + * https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/storagePools/{storagePool} + * /projects/{project}/zones/{zone}/storagePools/{storagePool} + + +The `network_interface` block supports: + +* `network` - (Optional) The name or self_link of the network to attach this interface to. + Either `network` or `subnetwork` must be provided. If network isn't provided it will + be inferred from the subnetwork. + +* `subnetwork` - (Optional) The name or self_link of the subnetwork to attach this + interface to. Either `network` or `subnetwork` must be provided. If network isn't provided + it will be inferred from the subnetwork. The subnetwork must exist in the same region this + instance will be created in. If the network resource is in + [legacy](https://cloud.google.com/vpc/docs/legacy) mode, do not specify this field. If the + network is in auto subnet mode, specifying the subnetwork is optional. If the network is + in custom subnet mode, specifying the subnetwork is required. + + +* `subnetwork_project` - (Optional) The project in which the subnetwork belongs. + If the `subnetwork` is a self_link, this field is ignored in favor of the project + defined in the subnetwork self_link. If the `subnetwork` is a name and this + field is not provided, the provider project is used. + +* `network_ip` - (Optional) The private IP address to assign to the instance. If + empty, the address will be automatically assigned. + +* `access_config` - (Optional) Access configurations, i.e. IPs via which this + instance can be accessed via the Internet. Omit to ensure that the instance + is not accessible from the Internet. If omitted, ssh provisioners will not + work unless Terraform can send traffic to the instance's network (e.g. via + tunnel or because it is running on another cloud instance on that network). + This block can be repeated multiple times. Structure [documented below](#nested_access_config). + +* `alias_ip_range` - (Optional) An + array of alias IP ranges for this network interface. Can only be specified for network + interfaces on subnet-mode networks. Structure [documented below](#nested_alias_ip_range). + +* `nic_type` - (Optional) The type of vNIC to be used on this interface. Possible values: GVNIC, VIRTIO_NET. + +* `network_attachment` - (Optional) [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html) The URL of the network attachment that this interface should connect to in the following format: `projects/{projectNumber}/regions/{region_name}/networkAttachments/{network_attachment_name}`. + +* `stack_type` - (Optional) The stack type for this network interface to identify whether the IPv6 feature is enabled or not. Values are IPV4_IPV6 or IPV4_ONLY. If not specified, IPV4_ONLY will be used. + +* `ipv6_access_config` - (Optional) An array of IPv6 access configurations for this interface. +Currently, only one IPv6 access config, DIRECT_IPV6, is supported. If there is no ipv6AccessConfig +specified, then this instance will have no external IPv6 Internet access. Structure [documented below](#nested_ipv6_access_config). + +* `queue_count` - (Optional) The networking queue count that's specified by users for the network interface. Both Rx and Tx queues will be set to this number. It will be empty if not specified. + +* `security_policy` - (Optional) [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html) A full or partial URL to a security policy to add to this instance. If this field is set to an empty string it will remove the associated security policy. + +The `access_config` block supports: + +* `nat_ip` - (Optional) The IP address that will be 1:1 mapped to the instance's + network ip. If not given, one will be generated. + +* `public_ptr_domain_name` - (Optional) The DNS domain name for the public PTR record. + To set this field on an instance, you must be verified as the owner of the domain. + See [the docs](https://cloud.google.com/compute/docs/instances/create-ptr-record) for how + to become verified as a domain owner. + +* `network_tier` - (Optional) The [networking tier](https://cloud.google.com/network-tiers/docs/overview) used for configuring this instance. + This field can take the following values: PREMIUM, FIXED_STANDARD or STANDARD. If this field is + not specified, it is assumed to be PREMIUM. + +The `ipv6_access_config` block supports: + +* `external_ipv6` - (Optional) The first IPv6 address of the external IPv6 range associated + with this instance, prefix length is stored in externalIpv6PrefixLength in ipv6AccessConfig. + To use a static external IP address, it must be unused and in the same region as the instance's zone. + If not specified, Google Cloud will automatically assign an external IPv6 address from the instance's subnetwork. + +* `external_ipv6_prefix_length` - (Optional) The prefix length of the external IPv6 range. + +* `name` - (Optional) The name of this access configuration. In ipv6AccessConfigs, the recommended name + is "External IPv6". + +* `network_tier` - (Optional) The service-level to be provided for IPv6 traffic when the + subnet has an external subnet. Only PREMIUM or STANDARD tier is valid for IPv6. + +* `public_ptr_domain_name` - (Optional) The domain name to be used when creating DNSv6 + records for the external IPv6 ranges.. + +The `alias_ip_range` block supports: + +* `ip_cidr_range` - The IP CIDR range represented by this alias IP range. This IP CIDR range + must belong to the specified subnetwork and cannot contain IP addresses reserved by + system or used by other network interfaces. This range may be a single IP address + (e.g. 10.2.3.4), a netmask (e.g. /24) or a CIDR format string (e.g. 10.1.2.0/24). + +* `subnetwork_range_name` - (Optional) The subnetwork secondary range name specifying + the secondary range from which to allocate the IP CIDR range for this alias IP + range. If left unspecified, the primary range of the subnetwork will be used. + +The `params` block supports: + +* `resource_manager_tags` (Optional) - A tag is a key-value pair that can be attached to a Google Cloud resource. You can use tags to conditionally allow or deny policies based on whether a resource has a specific tag. This value is not returned by the API. In Terraform, this value cannot be updated and changing it will recreate the resource. + +- - - + + +* `labels` - + (Optional) + Set of label tags associated with the TcpRoute resource. + **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. + +* `description` - + (Optional) + A free-text description of the resource. Max length 1024 characters. + +* `traffic_port_selector` - + (Optional) + Port selector for the (matched) endpoints. If no port selector is provided, the matched config is applied to all ports. + Structure is [documented below](#nested_traffic_port_selector). + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +The `traffic_port_selector` block supports: + +* `ports` - + (Required) + List of ports. Can be port numbers or port range (example, [80-90] specifies all ports from 80 to 90, including 80 and 90) or named ports or * to specify all ports. If the list is empty, all ports are selected. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/zones/{{zone}}/instances/{{name}}` + +* `network_interface.0.access_config.0.nat_ip` - If the instance has an access config, either the given external ip (in the `nat_ip` field) or the ephemeral (generated) ip (if you didn't provide one). + +* `workload_identity_config` - + Workload Identity settings. + Structure is [documented below](#nested_workload_identity_config). + +* `errors` - + A set of errors found in the cluster. + Structure is [documented below](#nested_errors). + + +The `workload_identity_config` block contains: + +* `identity_provider` - + (Optional) + The ID of the OIDC Identity Provider (IdP) associated to + the Workload Identity Pool. + +* `issuer_uri` - + (Optional) + The OIDC issuer URL for this cluster. + +* `workload_pool` - + (Optional) + The Workload Identity Pool associated to the cluster. + +The `errors` block contains: + +* `message` - + (Optional) + Human-friendly description of the error. + +## Timeouts + +Lorem ipsum + +## Import + +Lorem ipsum + diff --git a/tools/diff-processor/testdata/website/docs/r/a_resource.html.markdown b/tools/diff-processor/testdata/website/docs/r/a_resource.html.markdown new file mode 100644 index 000000000000..9d3f5a0cde76 --- /dev/null +++ b/tools/diff-processor/testdata/website/docs/r/a_resource.html.markdown @@ -0,0 +1,18 @@ +## Some resource description + +## Argument Reference + +* `field_one` lorem ipsum. Structure is [documented below](#nested_field_one). +* `member/members` - (Required) lorem ipsum. + +The `field_one` block supports: + +* `a` - (Optional) lorem ipsum. + +## Attributes Reference + +* `field_two` lorem ipsum. Structure is [documented below](#nested_field_two). + +The `field_two` block supports: + +* `a` - (Optional) lorem ipsum. \ No newline at end of file From b4683ed865a8f8968bef228764399e82d993f44c Mon Sep 17 00:00:00 2001 From: David Karwowski Date: Fri, 10 Jan 2025 17:59:27 -0500 Subject: [PATCH 21/30] Attachment 100g (#12722) Co-authored-by: Dave Karwowski --- mmv1/products/compute/InterconnectAttachment.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/mmv1/products/compute/InterconnectAttachment.yaml b/mmv1/products/compute/InterconnectAttachment.yaml index a31185bfe08b..d3f5945096b7 100644 --- a/mmv1/products/compute/InterconnectAttachment.yaml +++ b/mmv1/products/compute/InterconnectAttachment.yaml @@ -130,6 +130,7 @@ properties: - 'BPS_10G' - 'BPS_20G' - 'BPS_50G' + - 'BPS_100G' - name: 'edgeAvailabilityDomain' type: String description: | From ef139e7f42d948fdbd9db0db64ceba6b1a8480b0 Mon Sep 17 00:00:00 2001 From: abheda-crest <105624942+abheda-crest@users.noreply.github.com> Date: Sat, 11 Jan 2025 04:49:19 +0530 Subject: [PATCH 22/30] Add support for parameter manager parameter resource `google_parameter_manager_parameter` (#12630) --- mmv1/products/parametermanager/Parameter.yaml | 121 ++++++++++++++++++ mmv1/products/parametermanager/product.yaml | 21 +++ .../RegionalParameter.yaml | 4 +- .../examples/parameter_config_basic.tf.tmpl | 4 + .../examples/parameter_with_format.tf.tmpl | 5 + .../examples/parameter_with_labels.tf.tmpl | 12 ++ .../components/inputs/services_beta.kt | 5 + .../components/inputs/services_ga.kt | 5 + ...e_parameter_manager_parameter_test.go.tmpl | 109 ++++++++++++++++ 9 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 mmv1/products/parametermanager/Parameter.yaml create mode 100644 mmv1/products/parametermanager/product.yaml create mode 100644 mmv1/templates/terraform/examples/parameter_config_basic.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/parameter_with_format.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/parameter_with_labels.tf.tmpl create mode 100644 mmv1/third_party/terraform/services/parametermanager/resource_parameter_manager_parameter_test.go.tmpl diff --git a/mmv1/products/parametermanager/Parameter.yaml b/mmv1/products/parametermanager/Parameter.yaml new file mode 100644 index 000000000000..7851e09d4729 --- /dev/null +++ b/mmv1/products/parametermanager/Parameter.yaml @@ -0,0 +1,121 @@ +# 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. + +--- +name: 'Parameter' +description: | + A Parameter resource is a logical parameter. +min_version: 'beta' +references: + guides: + api: 'https://cloud.google.com/secret-manager/parameter-manager/docs/reference/rest/v1/projects.locations.parameters' +docs: +base_url: 'projects/{{project}}/locations/global/parameters' +self_link: 'projects/{{project}}/locations/global/parameters/{{parameter_id}}' +create_url: 'projects/{{project}}/locations/global/parameters?parameter_id={{parameter_id}}' +update_verb: 'PATCH' +update_mask: true +import_format: + - 'projects/{{project}}/locations/global/parameters/{{parameter_id}}' +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 +examples: + - name: 'parameter_config_basic' + primary_resource_id: 'parameter-basic' + min_version: 'beta' + vars: + parameter_id: 'parameter' + - name: 'parameter_with_format' + primary_resource_id: 'parameter-with-format' + min_version: 'beta' + vars: + parameter_id: 'parameter' + - name: 'parameter_with_labels' + primary_resource_id: 'parameter-with-labels' + min_version: 'beta' + vars: + parameter_id: 'parameter' +parameters: + - name: 'parameterId' + type: String + description: | + This must be unique within the project. + url_param_only: true + required: true + immutable: true +properties: + - name: 'name' + type: String + description: | + The resource name of the Parameter. Format: + `projects/{{project}}/locations/global/parameters/{{parameter_id}}` + output: true + - name: 'createTime' + type: String + description: | + The time at which the Parameter was created. + output: true + - name: 'updateTime' + type: String + description: | + The time at which the Parameter was updated. + output: true + - name: 'policyMember' + type: NestedObject + description: | + Policy member strings of a Google Cloud resource. + output: true + properties: + - name: 'iamPolicyUidPrincipal' + type: String + description: | + IAM policy binding member referring to a Google Cloud resource by system-assigned unique identifier. + If a resource is deleted and recreated with the same name, the binding will not be applicable to the + new resource. Format: + `principal://parametermanager.googleapis.com/projects/{{project}}/uid/locations/global/parameters/{{uid}}` + output: true + - name: 'iamPolicyNamePrincipal' + type: String + description: | + IAM policy binding member referring to a Google Cloud resource by user-assigned name. If a + resource is deleted and recreated with the same name, the binding will be applicable to the + new resource. Format: + `principal://parametermanager.googleapis.com/projects/{{project}}/name/locations/global/parameters/{{parameter_id}}` + output: true + - name: 'labels' + type: KeyValueLabels + description: | + The labels assigned to this Parameter. + + Label keys must be between 1 and 63 characters long, have a UTF-8 encoding of maximum 128 bytes, + and must conform to the following PCRE regular expression: [\p{Ll}\p{Lo}][\p{Ll}\p{Lo}\p{N}_-]{0,62} + + Label values must be between 0 and 63 characters long, have a UTF-8 encoding of maximum 128 bytes, + and must conform to the following PCRE regular expression: [\p{Ll}\p{Lo}\p{N}_-]{0,63} + + No more than 64 labels can be assigned to a given resource. + + An object containing a list of "key": value pairs. Example: + { "name": "wrench", "mass": "1.3kg", "count": "3" }. + - name: 'format' + type: Enum + description: | + The format type of the parameter resource. + default_value: 'UNFORMATTED' + immutable: true + enum_values: + - 'UNFORMATTED' + - 'YAML' + - 'JSON' diff --git a/mmv1/products/parametermanager/product.yaml b/mmv1/products/parametermanager/product.yaml new file mode 100644 index 000000000000..4a0184ba77c7 --- /dev/null +++ b/mmv1/products/parametermanager/product.yaml @@ -0,0 +1,21 @@ +# 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. + +--- +name: 'ParameterManager' +display_name: 'Parameter Manager' +versions: + - name: 'beta' + base_url: 'https://parametermanager.googleapis.com/v1/' +scopes: + - 'https://www.googleapis.com/auth/cloud-platform' diff --git a/mmv1/products/parametermanagerregional/RegionalParameter.yaml b/mmv1/products/parametermanagerregional/RegionalParameter.yaml index 8ad83bd80379..d4036d040f90 100644 --- a/mmv1/products/parametermanagerregional/RegionalParameter.yaml +++ b/mmv1/products/parametermanagerregional/RegionalParameter.yaml @@ -119,8 +119,8 @@ properties: - name: 'format' type: Enum description: | - The format type of the regional parameter. Default value is UNFORMATTED. - default_from_api: true + The format type of the regional parameter. + default_value: 'UNFORMATTED' immutable: true enum_values: - 'UNFORMATTED' diff --git a/mmv1/templates/terraform/examples/parameter_config_basic.tf.tmpl b/mmv1/templates/terraform/examples/parameter_config_basic.tf.tmpl new file mode 100644 index 000000000000..b31380eee821 --- /dev/null +++ b/mmv1/templates/terraform/examples/parameter_config_basic.tf.tmpl @@ -0,0 +1,4 @@ +resource "google_parameter_manager_parameter" "{{$.PrimaryResourceId}}" { + provider = google-beta + parameter_id = "{{index $.Vars "parameter_id"}}" +} diff --git a/mmv1/templates/terraform/examples/parameter_with_format.tf.tmpl b/mmv1/templates/terraform/examples/parameter_with_format.tf.tmpl new file mode 100644 index 000000000000..dd78230835a4 --- /dev/null +++ b/mmv1/templates/terraform/examples/parameter_with_format.tf.tmpl @@ -0,0 +1,5 @@ +resource "google_parameter_manager_parameter" "{{$.PrimaryResourceId}}" { + provider = google-beta + parameter_id = "{{index $.Vars "parameter_id"}}" + format = "JSON" +} diff --git a/mmv1/templates/terraform/examples/parameter_with_labels.tf.tmpl b/mmv1/templates/terraform/examples/parameter_with_labels.tf.tmpl new file mode 100644 index 000000000000..8d990cd7a1a3 --- /dev/null +++ b/mmv1/templates/terraform/examples/parameter_with_labels.tf.tmpl @@ -0,0 +1,12 @@ +resource "google_parameter_manager_parameter" "{{$.PrimaryResourceId}}" { + provider = google-beta + parameter_id = "{{index $.Vars "parameter_id"}}" + + labels = { + key1 = "val1" + key2 = "val2" + key3 = "val3" + key4 = "val4" + key5 = "val5" + } +} diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt index 554dc1891df6..40b5e0b4de8b 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt @@ -591,6 +591,11 @@ var ServicesListBeta = mapOf( "displayName" to "Parallelstore", "path" to "./google/services/parallelstore" ), + "parametermanager" to mapOf( + "name" to "parametermanager", + "displayName" to "Parametermanager", + "path" to "./google-beta/services/parametermanager" + ), "parametermanagerregional" to mapOf( "name" to "parametermanagerregional", "displayName" to "Parametermanagerregional", diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt index e2310c4749d8..6c1fe32207d5 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt @@ -586,6 +586,11 @@ var ServicesListGa = mapOf( "displayName" to "Parallelstore", "path" to "./google/services/parallelstore" ), + "parametermanager" to mapOf( + "name" to "parametermanager", + "displayName" to "Parametermanager", + "path" to "./google/services/parametermanager" + ), "parametermanagerregional" to mapOf( "name" to "parametermanagerregional", "displayName" to "Parametermanagerregional", diff --git a/mmv1/third_party/terraform/services/parametermanager/resource_parameter_manager_parameter_test.go.tmpl b/mmv1/third_party/terraform/services/parametermanager/resource_parameter_manager_parameter_test.go.tmpl new file mode 100644 index 000000000000..9e35e39f7abe --- /dev/null +++ b/mmv1/third_party/terraform/services/parametermanager/resource_parameter_manager_parameter_test.go.tmpl @@ -0,0 +1,109 @@ +package parametermanager_test +{{- if ne $.TargetVersionName "ga" }} + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" +) + +func TestAccParameterManagerParameter_labelsUpdate(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + CheckDestroy: testAccCheckParameterManagerParameterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccParameterManagerParameter_withoutLabels(context), + }, + { + ResourceName: "google_parameter_manager_parameter.parameter-with-labels", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "parameter_id", "terraform_labels"}, + }, + { + Config: testAccParameterManagerParameter_labelsUpdate(context), + }, + { + ResourceName: "google_parameter_manager_parameter.parameter-with-labels", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "parameter_id", "terraform_labels"}, + }, + { + Config: testAccParameterManagerParameter_labelsUpdateOther(context), + }, + { + ResourceName: "google_parameter_manager_parameter.parameter-with-labels", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "parameter_id", "terraform_labels"}, + }, + { + Config: testAccParameterManagerParameter_withoutLabels(context), + }, + { + ResourceName: "google_parameter_manager_parameter.parameter-with-labels", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "parameter_id", "terraform_labels"}, + }, + }, + }) +} + +func testAccParameterManagerParameter_withoutLabels(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_parameter_manager_parameter" "parameter-with-labels" { + provider = google-beta + parameter_id = "tf_test_parameter%{random_suffix}" + format = "JSON" +} +`, context) +} + +func testAccParameterManagerParameter_labelsUpdate(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_parameter_manager_parameter" "parameter-with-labels" { + provider = google-beta + parameter_id = "tf_test_parameter%{random_suffix}" + format = "JSON" + + labels = { + key1 = "val1" + key2 = "val2" + key3 = "val3" + key4 = "val4" + key5 = "val5" + } +} +`, context) +} + +func testAccParameterManagerParameter_labelsUpdateOther(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_parameter_manager_parameter" "parameter-with-labels" { + provider = google-beta + parameter_id = "tf_test_parameter%{random_suffix}" + format = "JSON" + + labels = { + key1 = "val1" + key2 = "updateval2" + updatekey3 = "val3" + updatekey4 = "updateval4" + key6 = "val6" + } +} +`, context) +} + +{{ end }} From fb31f70316dfb5b648f2a5ba0412d8511d5cf983 Mon Sep 17 00:00:00 2001 From: coder-221 <185867912+coder-221@users.noreply.github.com> Date: Fri, 10 Jan 2025 16:32:01 -0800 Subject: [PATCH 23/30] Change ServicePerimeterResource to use a policy level mutex lock (#12725) --- .../accesscontextmanager/ServicePerimeterResource.yaml | 10 +++++++++- ..._context_manager_service_perimeter_resource.go.tmpl | 3 +++ ..._context_manager_service_perimeter_resource.go.tmpl | 8 ++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 mmv1/templates/terraform/encoders/access_context_manager_service_perimeter_resource.go.tmpl diff --git a/mmv1/products/accesscontextmanager/ServicePerimeterResource.yaml b/mmv1/products/accesscontextmanager/ServicePerimeterResource.yaml index e5fb2f303ba2..de7585e6ae6c 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeterResource.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeterResource.yaml @@ -43,7 +43,7 @@ create_verb: 'PATCH' update_mask: true delete_verb: 'PATCH' immutable: true -mutex: '{{perimeter_name}}' +mutex: '{{access_policy_id}}' import_format: - '{{perimeter_name}}/{{resource}}' timeouts: @@ -67,6 +67,7 @@ nested_query: is_list_of_ids: true modify_by_patch: true custom_code: + encoder: 'templates/terraform/encoders/access_context_manager_service_perimeter_resource.go.tmpl' custom_import: 'templates/terraform/custom_import/access_context_manager_service_perimeter_resource.go.tmpl' post_read: 'templates/terraform/post_read/access_context_manager_service_perimeter_resource.go.tmpl' exclude_tgc: true @@ -97,3 +98,10 @@ properties: Format: projects/{project_number} required: true immutable: true + - name: 'accessPolicyId' + type: String + description: | + The name of the Access Policy this resource belongs to. + ignore_read: true + immutable: true + output: true diff --git a/mmv1/templates/terraform/custom_import/access_context_manager_service_perimeter_resource.go.tmpl b/mmv1/templates/terraform/custom_import/access_context_manager_service_perimeter_resource.go.tmpl index 77b57820573e..075587c706a0 100644 --- a/mmv1/templates/terraform/custom_import/access_context_manager_service_perimeter_resource.go.tmpl +++ b/mmv1/templates/terraform/custom_import/access_context_manager_service_perimeter_resource.go.tmpl @@ -18,6 +18,9 @@ return nil, err } + if err := d.Set("access_policy_id", fmt.Sprintf("accessPolicies/%s", parts["accessPolicy"])); err != nil { + return nil, fmt.Errorf("Error setting access_policy_id: %s", err) + } if err := d.Set("perimeter_name", fmt.Sprintf("accessPolicies/%s/servicePerimeters/%s", parts["accessPolicy"], parts["perimeter"])); err != nil { return nil, fmt.Errorf("Error setting perimeter_name: %s", err) } diff --git a/mmv1/templates/terraform/encoders/access_context_manager_service_perimeter_resource.go.tmpl b/mmv1/templates/terraform/encoders/access_context_manager_service_perimeter_resource.go.tmpl new file mode 100644 index 000000000000..0e38e7e9dcc1 --- /dev/null +++ b/mmv1/templates/terraform/encoders/access_context_manager_service_perimeter_resource.go.tmpl @@ -0,0 +1,8 @@ +// Set the access_policy_id field from part of the perimeter_name parameter. + +// The is logic is inside the encoder since the access_policy_id field is part of +// the mutex lock and encoders run before the lock is set. +parts := strings.Split(d.Get("perimeter_name").(string), "/") +d.Set("access_policy_id", fmt.Sprintf("accessPolicies/%s", parts[1])) + +return obj, nil \ No newline at end of file From 7d808d92fa8b8aab4dbdf706b1f97caf6edae3c3 Mon Sep 17 00:00:00 2001 From: Ankit Goyal <51757072+ankitgoyal0301@users.noreply.github.com> Date: Sat, 11 Jan 2025 06:22:51 +0530 Subject: [PATCH 24/30] Add google_chronicle_rule_deployment resource to chronicle (#12729) --- mmv1/products/chronicle/RuleDeployment.yaml | 135 ++++++++++++++++++ .../chronicle_ruledeployment_basic.tf.tmpl | 19 +++ ...rce_chronicle_rule_deployment_test.go.tmpl | 95 ++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 mmv1/products/chronicle/RuleDeployment.yaml create mode 100644 mmv1/templates/terraform/examples/chronicle_ruledeployment_basic.tf.tmpl create mode 100644 mmv1/third_party/terraform/services/chronicle/resource_chronicle_rule_deployment_test.go.tmpl diff --git a/mmv1/products/chronicle/RuleDeployment.yaml b/mmv1/products/chronicle/RuleDeployment.yaml new file mode 100644 index 000000000000..d21ac17d76de --- /dev/null +++ b/mmv1/products/chronicle/RuleDeployment.yaml @@ -0,0 +1,135 @@ +# Copyright 2025 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. + +--- +name: RuleDeployment +description: The RuleDeployment resource represents the deployment state of a Rule. +min_version: 'beta' +references: + guides: + 'Google SecOps Guides': 'https://cloud.google.com/chronicle/docs/secops/secops-overview' + api: 'https://cloud.google.com/chronicle/docs/reference/rest/v1alpha/RuleDeployment' +base_url: projects/{{project}}/locations/{{location}}/instances/{{instance}}/rules/{{rules}}/deployments +self_link: projects/{{project}}/locations/{{location}}/instances/{{instance}}/rules/{{rule}}/deployment +create_url: projects/{{project}}/locations/{{location}}/instances/{{instance}}/rules/{{rule}}/deployment?updateMask=enabled,alerting,archived,runFrequency +id_format: projects/{{project}}/locations/{{location}}/instances/{{instance}}/rules/{{rule}}/deployment +import_format: + - projects/{{project}}/locations/{{location}}/instances/{{instance}}/rules/{{rule}}/deployment +create_verb: PATCH +update_verb: PATCH +update_mask: true +exclude_delete: true + +examples: + - name: 'chronicle_ruledeployment_basic' + primary_resource_id: 'example' + min_version: 'beta' + test_env_vars: + chronicle_id: 'CHRONICLE_ID' + +parameters: + - name: location + type: String + description: The location of the resource. This is the geographical region where the Chronicle instance resides, such as "us" or "europe-west2". + immutable: true + url_param_only: true + required: true + - name: instance + type: String + description: The unique identifier for the Chronicle instance, which is the same as the customer ID. + immutable: true + url_param_only: true + required: true + - name: rule + type: String + description: The Rule ID of the rule. + immutable: true + url_param_only: true + required: true +properties: + - name: name + type: String + description: |- + The resource name of the rule deployment. + Note that RuleDeployment is a child of the overall Rule, not any individual + revision, so the resource ID segment for the Rule resource must not + reference a specific revision. + Format: + projects/{project}/locations/{location}/instances/{instance}/rules/{rule}/deployment + output: true + - name: enabled + type: Boolean + description: Whether the rule is currently deployed continuously against incoming data. + - name: alerting + type: Boolean + description: |- + Whether detections resulting from this deployment should be considered + alerts. + - name: archived + type: Boolean + description: |- + The archive state of the rule deployment. + Cannot be set to true unless enabled is set to false. + If set to true, alerting will automatically be set to false. + If currently set to true, enabled, alerting, and run_frequency cannot be + updated. + - name: archiveTime + type: String + description: Output only. The timestamp when the rule deployment archive state was last set to true. + If the rule deployment's current archive state is not set to true, the field will be empty. + output: true + - name: runFrequency + type: String + description: |2- + + The run frequency of the rule deployment. + Possible values: + LIVE + HOURLY + DAILY + - name: executionState + type: String + description: |2- + + The execution state of the rule deployment. + Possible values: + DEFAULT + LIMITED + PAUSED + output: true + - name: producerRules + type: Array + description: |2- + Output only. The names of the associated/chained producer rules. Rules are considered + producers for this rule if this rule explicitly filters on their ruleid. + Format: + projects/{project}/locations/{location}/instances/{instance}/rules/{rule} + output: true + item_type: + type: String + - name: consumerRules + type: Array + description: |2- + Output only. The names of the associated/chained consumer rules. Rules are considered + consumers of this rule if their rule text explicitly filters on this rule's ruleid. + Format: + projects/{project}/locations/{location}/instances/{instance}/rules/{rule} + output: true + item_type: + type: String + - name: lastAlertStatusChangeTime + type: String + description: Output only. The timestamp when the rule deployment alert state was lastly changed. + This is filled regardless of the current alert state.E.g. if the current alert status is false, + this timestamp will be the timestamp when the alert status was changed to false. + output: true diff --git a/mmv1/templates/terraform/examples/chronicle_ruledeployment_basic.tf.tmpl b/mmv1/templates/terraform/examples/chronicle_ruledeployment_basic.tf.tmpl new file mode 100644 index 000000000000..26e6a2e4dd93 --- /dev/null +++ b/mmv1/templates/terraform/examples/chronicle_ruledeployment_basic.tf.tmpl @@ -0,0 +1,19 @@ +resource "google_chronicle_rule" "my-rule" { + provider = "google-beta" + location = "us" + instance = "{{index $.TestEnvVars "chronicle_id"}}" + text = <<-EOT + rule test_rule { meta: events: $userid = $e.principal.user.userid match: $userid over 10m condition: $e } + EOT +} + +resource "google_chronicle_rule_deployment" "{{$.PrimaryResourceId}}" { + provider = "google-beta" + location = "us" + instance = "{{index $.TestEnvVars "chronicle_id"}}" + rule = element(split("/", resource.google_chronicle_rule.my-rule.name), length(split("/", resource.google_chronicle_rule.my-rule.name)) - 1) + enabled = true + alerting = true + archived = false + run_frequency = "DAILY" +} diff --git a/mmv1/third_party/terraform/services/chronicle/resource_chronicle_rule_deployment_test.go.tmpl b/mmv1/third_party/terraform/services/chronicle/resource_chronicle_rule_deployment_test.go.tmpl new file mode 100644 index 000000000000..f2dd8cb1d059 --- /dev/null +++ b/mmv1/third_party/terraform/services/chronicle/resource_chronicle_rule_deployment_test.go.tmpl @@ -0,0 +1,95 @@ +package chronicle_test + +{{- if ne $.TargetVersionName "ga" }} + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestAccChronicleRuleDeployment_chronicleRuledeploymentBasicExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "chronicle_id": envvar.GetTestChronicleInstanceIdFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccChronicleRuleDeployment_chronicleRuledeploymentBasicExample_basic(context), + }, + { + ResourceName: "google_chronicle_rule_deployment.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"instance", "location", "rule"}, + }, + { + Config: testAccChronicleRuleDeployment_chronicleRuledeploymentBasicExample_update(context), + }, + { + ResourceName: "google_chronicle_rule_deployment.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"instance", "location", "rule"}, + }, + }, + }) +} + +func testAccChronicleRuleDeployment_chronicleRuledeploymentBasicExample_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_chronicle_rule" "my-rule" { + provider = "google-beta" + location = "us" + instance = "%{chronicle_id}" + text = <<-EOT + rule test_rule { meta: events: $userid = $e.principal.user.userid match: $userid over 10m condition: $e } + EOT +} + +resource "google_chronicle_rule_deployment" "example" { + provider = "google-beta" + location = "us" + instance = "%{chronicle_id}" + rule = element(split("/", resource.google_chronicle_rule.my-rule.name), length(split("/", resource.google_chronicle_rule.my-rule.name)) - 1) + enabled = true + alerting = true + archived = false + run_frequency = "DAILY" +} +`, context) +} + +func testAccChronicleRuleDeployment_chronicleRuledeploymentBasicExample_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_chronicle_rule" "my-rule" { + provider = "google-beta" + location = "us" + instance = "%{chronicle_id}" + text = <<-EOT + rule test_rule { meta: events: $userid = $e.principal.user.userid match: $userid over 10m condition: $e } + EOT +} + +resource "google_chronicle_rule_deployment" "example" { + provider = "google-beta" + location = "us" + instance = "%{chronicle_id}" + rule = element(split("/", resource.google_chronicle_rule.my-rule.name), length(split("/", resource.google_chronicle_rule.my-rule.name)) - 1) + enabled = false + alerting = false + archived = false + run_frequency = "HOURLY" +} +`, context) +} +{{- end }} From 06d96101ab13b27dc9a9f4a46d8f854dbebecb40 Mon Sep 17 00:00:00 2001 From: lychgoogle Date: Fri, 10 Jan 2025 17:32:24 -0800 Subject: [PATCH 25/30] Support Swithcover for MySQL and PostgreSQL (#12646) --- .../sql/data_source_sql_database_instances.go | 17 + .../resource_sql_database_instance.go.tmpl | 65 +- .../resource_sql_database_instance_test.go | 700 ++++++++++++++++++ .../sql_instance_switchover.html.markdown | 405 +++++++++- .../r/sql_database_instance.html.markdown | 26 +- 5 files changed, 1191 insertions(+), 22 deletions(-) diff --git a/mmv1/third_party/terraform/services/sql/data_source_sql_database_instances.go b/mmv1/third_party/terraform/services/sql/data_source_sql_database_instances.go index 24e4e30f66f5..8f48b4a5df94 100644 --- a/mmv1/third_party/terraform/services/sql/data_source_sql_database_instances.go +++ b/mmv1/third_party/terraform/services/sql/data_source_sql_database_instances.go @@ -153,6 +153,7 @@ func flattenDatasourceGoogleDatabaseInstancesList(fetchedInstances []*sqladmin.D } instance["replica_configuration"] = flattenReplicaConfigurationforDataSource(rawInstance.ReplicaConfiguration) + instance["replication_cluster"] = flattenReplicationClusterForDataSource(rawInstance.ReplicationCluster) ipAddresses := flattenIpAddresses(rawInstance.IpAddresses) instance["ip_address"] = ipAddresses @@ -198,3 +199,19 @@ func flattenReplicaConfigurationforDataSource(replicaConfiguration *sqladmin.Rep return rc } + +// flattenReplicationClusterForDataSource converts cloud SQL backend ReplicationCluster (proto) to +// terraform replication_cluster. We explicitly allow the case when ReplicationCluster +// is nil since replication_cluster is computed+optional. +func flattenReplicationClusterForDataSource(replicationCluster *sqladmin.ReplicationCluster) []map[string]interface{} { + data := make(map[string]interface{}) + data["failover_dr_replica_name"] = "" + if replicationCluster != nil && replicationCluster.FailoverDrReplicaName != "" { + data["failover_dr_replica_name"] = replicationCluster.FailoverDrReplicaName + } + data["dr_replica"] = false + if replicationCluster != nil { + data["dr_replica"] = replicationCluster.DrReplica + } + return []map[string]interface{}{data} +} diff --git a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl index 1ce97cf50157..27acb36fb148 100644 --- a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl +++ b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance.go.tmpl @@ -925,6 +925,27 @@ is set to true. Defaults to ZONAL.`, }, Description: `The replicas of the instance.`, }, + "replication_cluster": { + Type: schema.TypeList, + Computed: true, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "failover_dr_replica_name": { + Type: schema.TypeString, + Optional: true, + Description: fmt.Sprintf(`If the instance is a primary instance, then this field identifies the disaster recovery (DR) replica. The standard format of this field is "your-project:your-instance". You can also set this field to "your-instance", but cloud SQL backend will convert it to the aforementioned standard format.`), + }, + "dr_replica": { + Type: schema.TypeBool, + Computed: true, + Description: `Read-only field that indicates whether the replica is a DR replica.`, + }, + }, + }, + Description: "A primary instance and disaster recovery replica pair. Applicable to MySQL and PostgreSQL. This field can be set only after both the primary and replica are created.", + }, "server_ca_cert": { Type: schema.TypeList, Computed: true, @@ -1719,6 +1740,11 @@ func resourceSqlDatabaseInstanceRead(d *schema.ResourceData, meta interface{}) e if err := d.Set("replica_names", instance.ReplicaNames); err != nil { return fmt.Errorf("Error setting replica_names: %w", err) } + + // We always set replication_cluster because it is computed+optional. + if err := d.Set("replication_cluster", flattenReplicationCluster(instance.ReplicationCluster, d)); err != nil { + return fmt.Errorf("Error setting replication_cluster: %w", err) + } ipAddresses := flattenIpAddresses(instance.IpAddresses) if err := d.Set("ip_address", ipAddresses); err != nil { log.Printf("[WARN] Failed to set SQL Database Instance IP Addresses") @@ -1981,7 +2007,7 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{}) ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.IsSqlOperationInProgressError}, }) if err != nil { - return fmt.Errorf("Error, failed to promote read replica instance as primary stand-alone %s: %s", instance.Name, err) + return fmt.Errorf("Error, failed to promote read replica instance as primary stand-alone %s: %s", d.Get("name"), err) } err = SqlAdminOperationWaitTime(config, op, project, "Promote Instance", userAgent, d.Timeout(schema.TimeoutUpdate)) if err != nil { @@ -2050,6 +2076,13 @@ func resourceSqlDatabaseInstanceUpdate(d *schema.ResourceData, meta interface{}) instance.DatabaseVersion = databaseVersion } + failoverDrReplicaName := d.Get("replication_cluster.0.failover_dr_replica_name").(string) + if failoverDrReplicaName != "" { + instance.ReplicationCluster = &sqladmin.ReplicationCluster{ + FailoverDrReplicaName: failoverDrReplicaName, + } + } + err = transport_tpg.Retry(transport_tpg.RetryOptions{ RetryFunc: func() (rerr error) { op, rerr = config.NewSqlAdminClient(userAgent).Instances.Update(project, d.Get("name").(string), instance).Do() @@ -2377,6 +2410,22 @@ func flattenDatabaseFlags(databaseFlags []*sqladmin.DatabaseFlags) []map[string] return flags } +// flattenReplicationCluster converts cloud SQL backend ReplicationCluster (proto) to +// terraform replication_cluster. We explicitly allow the case when ReplicationCluster +// is nil since replication_cluster is computed+optional. +func flattenReplicationCluster(replicationCluster *sqladmin.ReplicationCluster, d *schema.ResourceData) []map[string]interface{} { + data := make(map[string]interface{}) + data["failover_dr_replica_name"] = "" + if replicationCluster != nil && replicationCluster.FailoverDrReplicaName != "" { + data["failover_dr_replica_name"] = replicationCluster.FailoverDrReplicaName + } + data["dr_replica"] = false + if replicationCluster != nil { + data["dr_replica"] = replicationCluster.DrReplica + } + return []map[string]interface{}{data} +} + func flattenIpConfiguration(ipConfiguration *sqladmin.IpConfiguration, d *schema.ResourceData) interface{} { data := map[string]interface{}{ "ipv4_enabled": ipConfiguration.Ipv4Enabled, @@ -2659,11 +2708,6 @@ func isSwitchoverRequested(d *schema.ResourceData) bool { if !slices.Contains(newReplicaNames.([]interface{}), originalPrimaryName) { return false } - dbVersion := d.Get("database_version") - if !strings.HasPrefix(dbVersion.(string), "SQLSERVER") { - log.Printf("[WARN] Switchover is only supported for SQL Server %q", dbVersion) - return false - } return true } @@ -2681,10 +2725,6 @@ func isReplicaPromoteRequested(_ context.Context, oldInstanceType interface{}, n // Check if this resource change is the manual update done on old primary after a switchover. If true, no replacement is needed. func isSwitchoverFromOldPrimarySide(d *schema.ResourceDiff) bool { dbVersion := d.Get("database_version") - if !strings.HasPrefix(dbVersion.(string), "SQLSERVER") { - log.Printf("[WARN] Switchover is only supported for SQL Server %q", dbVersion) - return false - } oldInstanceType, newInstanceType := d.GetChange("instance_type") oldReplicaNames, newReplicaNames := d.GetChange("replica_names") _, newMasterInstanceName := d.GetChange("master_instance_name") @@ -2699,11 +2739,12 @@ func isSwitchoverFromOldPrimarySide(d *schema.ResourceDiff) bool { newMasterInOldReplicaNames := slices.Contains(oldReplicaNames.([]interface{}), newMasterInstanceName) newMasterNotInNewReplicaNames := !slices.Contains(newReplicaNames.([]interface{}), newMasterInstanceName) isCascadableReplica := cascadableReplicaFieldExists && cascadableReplica.(bool) + isSQLServer := strings.HasPrefix(dbVersion.(string), "SQLSERVER") return newMasterInstanceName != nil && instanceTypeChangedFromPrimaryToReplica && - newMasterInOldReplicaNames && newMasterNotInNewReplicaNames && - isCascadableReplica + newMasterInOldReplicaNames && newMasterNotInNewReplicaNames && (!isSQLServer || + isCascadableReplica) } func checkPromoteConfigurations(d *schema.ResourceData) error { diff --git a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go index 52e84b781316..d9b5952d6d28 100644 --- a/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go +++ b/mmv1/third_party/terraform/services/sql/resource_sql_database_instance_test.go @@ -2583,6 +2583,158 @@ func TestAccSqlDatabaseInstance_SwitchoverSuccess(t *testing.T) { }) } +// Switchover for MySQL. +func TestAccSqlDatabaseInstance_MysqlSwitchoverSuccess(t *testing.T) { + t.Parallel() + primaryName := "tf-test-mysql-sw-primary-" + acctest.RandString(t, 10) + replicaName := "tf-test-mysql-sw-replica-" + acctest.RandString(t, 10) + project := envvar.GetTestProjectFromEnv() + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testGoogleSqlDatabaseInstanceConfig_mysqlEplusWithReplica(project, primaryName, replicaName), + }, + { + ResourceName: "google_sql_database_instance.original-primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: ignoredReplicaConfigurationFields, + }, + { + ResourceName: "google_sql_database_instance.original-replica", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: ignoredReplicaConfigurationFields, + }, + // Let's make sure that setting and unsetting failover replica works. + { + Config: googleSqlDatabaseInstance_mysqlSetFailoverReplica(project, primaryName, replicaName), + }, + { + Config: googleSqlDatabaseInstance_mysqlUnsetFailoverReplica(project, primaryName, replicaName), + }, + { + Config: googleSqlDatabaseInstance_mysqlSetFailoverReplica(project, primaryName, replicaName), + }, + { + // Split into two configs because current TestStep implementation checks diff before refreshing. + Config: googleSqlDatabaseInstance_mysqlSwitchoverOnReplica(project, primaryName, replicaName), + // Original primary needs to be updated at the next step. + ExpectNonEmptyPlan: true, + }, + { + Config: googleSqlDatabaseInstance_mysqlUpdatePrimaryAfterSwitchover(project, primaryName, replicaName), + }, + { + RefreshState: true, + Check: resource.ComposeTestCheckFunc(resource.TestCheckTypeSetElemAttr("google_sql_database_instance.original-replica", "replica_names.*", primaryName), checkSwitchoverOriginalReplicaConfigurations("google_sql_database_instance.original-replica"), checkSwitchoverOriginalPrimaryConfigurations("google_sql_database_instance.original-primary", replicaName)), + }, + { + ResourceName: "google_sql_database_instance.original-primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: ignoredReplicaConfigurationFields, + }, + { + ResourceName: "google_sql_database_instance.original-replica", + ImportState: true, + ImportStateVerify: true, + // original-replica is no longer a replica, but replica_configuration is O + C and cannot be unset + ImportStateVerifyIgnore: []string{"replica_configuration", "deletion_protection", "root_password"}, + }, + { + // Delete replica first so PostTestDestroy doesn't fail when deleting instances which have replicas. We've already validated switchover behavior, the remaining steps are cleanup + Config: googleSqlDatabaseInstance_mysqlDeleteReplicasAfterSwitchover(project, primaryName, replicaName), + // We delete replica, but haven't updated the master's replica_names + ExpectNonEmptyPlan: true, + }, + { + // Remove replica from primary's resource + Config: googleSqlDatabaseInstance_mysqlRemoveReplicaFromPrimaryAfterSwitchover(project, replicaName), + }, + }, + }) +} + +// Switchover for PostgreSQL. +func TestAccSqlDatabaseInstance_PostgresSwitchoverSuccess(t *testing.T) { + t.Parallel() + primaryName := "tf-test-pg-sw-primary-" + acctest.RandString(t, 10) + replicaName := "tf-test-pg-sw-replica-" + acctest.RandString(t, 10) + project := envvar.GetTestProjectFromEnv() + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccSqlDatabaseInstanceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testGoogleSqlDatabaseInstanceConfig_postgresEplusWithReplica(project, primaryName, replicaName), + }, + { + ResourceName: "google_sql_database_instance.original-primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: ignoredReplicaConfigurationFields, + }, + { + ResourceName: "google_sql_database_instance.original-replica", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: ignoredReplicaConfigurationFields, + }, + // Let's make sure that setting and unsetting failover replica works. + { + Config: googleSqlDatabaseInstance_postgresSetFailoverReplica(project, primaryName, replicaName), + }, + { + Config: googleSqlDatabaseInstance_postgresUnsetFailoverReplica(project, primaryName, replicaName), + }, + { + Config: googleSqlDatabaseInstance_postgresSetFailoverReplica(project, primaryName, replicaName), + }, + { + // Split into two configs because current TestStep implementation checks diff before refreshing. + Config: googleSqlDatabaseInstance_postgresSwitchoverOnReplica(project, primaryName, replicaName), + // Original primary needs to be updated at the next step. + ExpectNonEmptyPlan: true, + }, + { + Config: googleSqlDatabaseInstance_postgresUpdatePrimaryAfterSwitchover(project, primaryName, replicaName), + }, + { + RefreshState: true, + Check: resource.ComposeTestCheckFunc(resource.TestCheckTypeSetElemAttr("google_sql_database_instance.original-replica", "replica_names.*", primaryName), checkSwitchoverOriginalReplicaConfigurations("google_sql_database_instance.original-replica"), checkSwitchoverOriginalPrimaryConfigurations("google_sql_database_instance.original-primary", replicaName)), + }, + { + ResourceName: "google_sql_database_instance.original-primary", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: ignoredReplicaConfigurationFields, + }, + { + ResourceName: "google_sql_database_instance.original-replica", + ImportState: true, + ImportStateVerify: true, + // original-replica is no longer a replica, but replica_configuration is O + C and cannot be unset + ImportStateVerifyIgnore: []string{"replica_configuration", "deletion_protection", "root_password"}, + }, + { + // Delete replica first so PostTestDestroy doesn't fail when deleting instances which have replicas. We've already validated switchover behavior, the remaining steps are cleanup + Config: googleSqlDatabaseInstance_postgresDeleteReplicasAfterSwitchover(project, primaryName, replicaName), + // We delete replica, but haven't updated the master's replica_names + ExpectNonEmptyPlan: true, + }, + { + // Remove replica from primary's resource + Config: googleSqlDatabaseInstance_postgresRemoveReplicaFromPrimaryAfterSwitchover(project, replicaName), + }, + }, + }) +} + func TestAccSqlDatabaseInstance_updateSslOptionsForPostgreSQL(t *testing.T) { t.Parallel() @@ -3468,6 +3620,554 @@ resource "google_sql_database_instance" "original-replica" { `, replicaName) } +func testGoogleSqlDatabaseInstanceConfig_mysqlEplusWithReplica(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-primary" { + project = "%s" + name = "%s" + region = "us-east1" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + deletion_protection = false + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "MYSQL_8_0" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = google_sql_database_instance.original-primary.name + deletion_protection = false + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + } +} +`, project, primaryName, project, replicaName) +} + +func googleSqlDatabaseInstance_mysqlSetFailoverReplica(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-primary" { + project = "%s" + name = "%s" + region = "us-east1" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "%s:%s" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "MYSQL_8_0" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = "%s" + deletion_protection = false + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + } +} +`, project, primaryName, project, replicaName, project, replicaName, primaryName) +} + +func googleSqlDatabaseInstance_mysqlUnsetFailoverReplica(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-primary" { + project = "%s" + name = "%s" + region = "us-east1" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "MYSQL_8_0" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = "%s" + deletion_protection = false + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + } +} +`, project, primaryName, project, replicaName, primaryName) +} + +func googleSqlDatabaseInstance_mysqlSwitchoverOnReplica(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-primary" { + project = "%s" + name = "%s" + region = "us-east1" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "%s:%s" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + replica_names = ["%s"] + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "%s:%s" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} +`, project, primaryName, project, replicaName, project, replicaName, primaryName, project, primaryName) +} + +func googleSqlDatabaseInstance_mysqlUpdatePrimaryAfterSwitchover(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-primary" { + project = "%s" + name = "%s" + region = "us-east1" + database_version = "MYSQL_8_0" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = "%s" + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = false + binary_log_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + replica_names = ["%s"] + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "%s:%s" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} +`, project, primaryName, replicaName, project, replicaName, primaryName, project, primaryName) +} + +// After a switchover, the original-primary is now the replica and must be removed first. +func googleSqlDatabaseInstance_mysqlDeleteReplicasAfterSwitchover(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + replica_names = ["%s"] + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "%s:%s" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} +`, project, replicaName, primaryName, project, primaryName) +} + +// Update original-replica replica_names after deleting original-primary +func googleSqlDatabaseInstance_mysqlRemoveReplicaFromPrimaryAfterSwitchover(project, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + replica_names = [] + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} +`, project, replicaName) +} + +func testGoogleSqlDatabaseInstanceConfig_postgresEplusWithReplica(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-primary" { + project = "%s" + name = "%s" + region = "us-east1" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + deletion_protection = false + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "POSTGRES_12" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = google_sql_database_instance.original-primary.name + deletion_protection = false + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + } +} +`, project, primaryName, project, replicaName) +} + +func googleSqlDatabaseInstance_postgresSetFailoverReplica(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-primary" { + project = "%s" + name = "%s" + region = "us-east1" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "%s:%s" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "POSTGRES_12" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = "%s" + deletion_protection = false + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + } +} +`, project, primaryName, project, replicaName, project, replicaName, primaryName) +} + +func googleSqlDatabaseInstance_postgresUnsetFailoverReplica(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-primary" { + project = "%s" + name = "%s" + region = "us-east1" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "POSTGRES_12" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = "%s" + deletion_protection = false + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + } +} +`, project, primaryName, project, replicaName, primaryName) +} + +func googleSqlDatabaseInstance_postgresSwitchoverOnReplica(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-primary" { + project = "%s" + name = "%s" + region = "us-east1" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "%s:%s" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + replica_names = ["%s"] + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "%s:%s" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} +`, project, primaryName, project, replicaName, project, replicaName, primaryName, project, primaryName) +} + +func googleSqlDatabaseInstance_postgresUpdatePrimaryAfterSwitchover(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-primary" { + project = "%s" + name = "%s" + region = "us-east1" + database_version = "POSTGRES_12" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = "%s" + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = false + point_in_time_recovery_enabled = false + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + replica_names = ["%s"] + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "%s:%s" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} +`, project, primaryName, replicaName, project, replicaName, primaryName, project, primaryName) +} + +// After a switchover, the original-primary is now the replica and must be removed first. +func googleSqlDatabaseInstance_postgresDeleteReplicasAfterSwitchover(project, primaryName, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + replica_names = ["%s"] + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "%s:%s" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} +`, project, replicaName, primaryName, project, primaryName) +} + +// Update original-replica replica_names after deleting original-primary +func googleSqlDatabaseInstance_postgresRemoveReplicaFromPrimaryAfterSwitchover(project, replicaName string) string { + return fmt.Sprintf(` +resource "google_sql_database_instance" "original-replica" { + project = "%s" + name = "%s" + region = "us-west2" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + replica_names = [] + deletion_protection = false + + replication_cluster { + failover_dr_replica_name = "" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} +`, project, replicaName) +} + func testAccSqlDatabaseInstance_basicInstanceForPsc(instanceName string, projectId string, orgId string, billingAccount string) string { return fmt.Sprintf(` resource "google_project" "testproject" { diff --git a/mmv1/third_party/terraform/website/docs/guides/sql_instance_switchover.html.markdown b/mmv1/third_party/terraform/website/docs/guides/sql_instance_switchover.html.markdown index 07623519a9ed..eaa817f0de0c 100644 --- a/mmv1/third_party/terraform/website/docs/guides/sql_instance_switchover.html.markdown +++ b/mmv1/third_party/terraform/website/docs/guides/sql_instance_switchover.html.markdown @@ -7,7 +7,7 @@ description: |- # Performing a SQL Instance Switchover This page is a brief walkthrough of performing a switchover through terraform. - ~> **NOTE:** Only supported for SQL Server. +## SQL Server 1. Create a **cross-region** primary and cascadable replica. It is recommended to use deletion_protection to prevent accidental deletions. ``` @@ -83,4 +83,405 @@ resource "google_sql_database_instance" "original-primary" { - `terraform plan` does not say **"must be replaced"** for any resource - Every resource **"will be updated in-place"** - Only the 2 instances involved in switchover have planned changes -- (Recommended) Use `deletion_protection` on instances as a safety measure \ No newline at end of file +- (Recommended) Use `deletion_protection` on instances as a safety measure + +## MySQL + +1. Create a **cross-region, Enterprise Plus edition** primary and replica. The primary should have backup and binary log enabled. + +``` +resource "google_sql_database_instance" "original-primary" { + project = "your-project" + name = "your-original-primary" + # Can be any region. + region = "us-east1" + # Any database version that supports Enterprise Plus edition. + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + + settings { + # Any tier that supports Enterprise Plus edition. + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } + + # You can add more settings. +} + +resource "google_sql_database_instance" "original-replica" { + project = "your-project" + name = "your-original-replica" + # Can be any region, but must be different from the primary's region. + region = "us-west2" + # Must be same as the primary's database_version. + database_version = "MYSQL_8_0" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = google_sql_database_instance.original-primary.name + + settings { + # Any tier that supports Enterprise Plus edition. + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + } + + # You can add more settings. +} +``` + +2. Designate the replica as DR replica of the primary by adding `replication_cluster.failover_dr_replica_name`. +```diff +resource "google_sql_database_instance" "original-primary" { + project = "your-project" + name = "your-original-primary" + region = "us-east1" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + ++ replication_cluster { ++ # Note that the format of the name is "project:instance". ++ # If you want to unset DR replica, put empty string in this field. ++ failover_dr_replica_name = "your-project:your-original-replica" ++ } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "your-project" + name = "your-original-replica" + region = "us-west2" + database_version = "MYSQL_8_0" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = "your-original-primary" + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + } +} +``` + +3. Invoke switchover on the original replica. + +* Change `instance_type` from `READ_REPLICA_INSTANCE` to `CLOUD_SQL_INSTANCE`. +* Remove `master_instance_name`. +* Add original primary's name to the original replica's `replica_names` list and `replication_cluster.failover_dr_replica_name`. +* Enable backup and binary log for original replica. + +```diff +resource "google_sql_database_instance" "original-primary" { + project = "your-project" + name = "your-original-primary" + region = "us-east1" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + + replication_cluster { + failover_dr_replica_name = "your-project:your-original-replica" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "your-project" + name = "your-original-replica" + region = "us-west2" + database_version = "MYSQL_8_0" +- instance_type = "READ_REPLICA_INSTANCE" ++ instance_type = "CLOUD_SQL_INSTANCE" +- master_instance_name = "your-original-primary" ++ replica_names = ["your-original-primary"] + ++ replication_cluster { ++ failover_dr_replica_name = "your-project:your-original-primary" ++ } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" ++ backup_configuration { ++ enabled = true ++ binary_log_enabled = true ++ } + } +} +``` + +4. Update the original primary and run `terraform plan`. +* Change `instance_type` from `CLOUD_SQL_INSTANCE` to `READ_REPLICA_INSTANCE`. +* Set `master_instance_name` to the new primary (original replica). +* (If `replica_names` is present) Remove original replica from `replica_names`. + * **NOTE**: Do **not** delete the `replica_names` field, even if it has no replicas remaining. Set `replica_names = [ ]` to indicate it having no replicas. +* Remove original replica from `replication_cluster.failover_dr_replica_name` by setting this field to the empty string. +* Disable backup for original primary (because it became a replica). +* Run `terraform plan` and verify that your configuration matches infrastructure. You should see a message like the following: + * **`No changes. Your infrastructure matches the configuration.`** + +```diff +resource "google_sql_database_instance" "original-primary" { + project = "your-project" + name = "your-original-primary" + region = "us-east1" + database_version = "MYSQL_8_0" +- instance_type = "CLOUD_SQL_INSTANCE" ++ instance_type = "READ_REPLICA_INSTANCE" ++ master_instance_name = "your-original-replica" + + replication_cluster { +- failover_dr_replica_name = "your-project:your-original-replica" ++ failover_dr_replica_name = "" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { +- enabled = true ++ enabled = false + binary_log_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "your-project" + name = "your-original-replica" + region = "us-west2" + database_version = "MYSQL_8_0" + instance_type = "CLOUD_SQL_INSTANCE" + replica_names = ["your-original-primary"] + + replication_cluster { + failover_dr_replica_name = "your-project:your-original-primary" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + binary_log_enabled = true + } + } +} +``` + +## PostgreSQL + +1. Create a **cross-region, Enterprise Plus edition** primary and replica. The primary should have backup and PITR enabled. + +``` +resource "google_sql_database_instance" "original-primary" { + project = "your-project" + name = "your-original-primary" + # Can be any region. + region = "us-east1" + # Any database version that supports Enterprise Plus edition. + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + + settings { + # Any tier that supports Enterprise Plus edition. + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } + + # You can add more settings. +} + +resource "google_sql_database_instance" "original-replica" { + project = "your-project" + name = "your-original-replica" + # Can be any region, but must be different from the primary's region. + region = "us-west2" + # Must be same as the primary's database_version. + database_version = "POSTGRES_12" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = google_sql_database_instance.original-primary.name + + settings { + # Any tier that supports Enterprise Plus edition. + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + } + + # You can add more settings. +} +``` + +2. Designate the replica as DR replica of the primary by adding `replication_cluster.failover_dr_replica_name`. +```diff +resource "google_sql_database_instance" "original-primary" { + project = "your-project" + name = "your-original-primary" + region = "us-east1" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + ++ replication_cluster { ++ # Note that the format of the name is "project:instance". ++ # If you want to unset DR replica, put empty string in this field. ++ failover_dr_replica_name = "your-project:your-original-replica" ++ } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "your-project" + name = "your-original-replica" + region = "us-west2" + database_version = "POSTGRES_12" + instance_type = "READ_REPLICA_INSTANCE" + master_instance_name = "your-original-primary" + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + } +} +``` + +3. Invoke switchover on the original replica. + +* Change `instance_type` from `READ_REPLICA_INSTANCE` to `CLOUD_SQL_INSTANCE`. +* Remove `master_instance_name`. +* Add original primary's name to the original replica's `replica_names` list and `replication_cluster.failover_dr_replica_name`. +* Enable backup and PITR for original replica. + +```diff +resource "google_sql_database_instance" "original-primary" { + project = "your-project" + name = "your-original-primary" + region = "us-east1" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + + replication_cluster { + failover_dr_replica_name = "your-project:your-original-replica" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "your-project" + name = "your-original-replica" + region = "us-west2" + database_version = "POSTGRES_12" +- instance_type = "READ_REPLICA_INSTANCE" ++ instance_type = "CLOUD_SQL_INSTANCE" +- master_instance_name = "your-original-primary" ++ replica_names = ["your-original-primary"] + ++ replication_cluster { ++ failover_dr_replica_name = "your-project:your-original-primary" ++ } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" ++ backup_configuration { ++ enabled = true ++ point_in_time_recovery_enabled = true ++ } + } +} +``` + +4. Update the original primary and run `terraform plan`. +* Change `instance_type` from `CLOUD_SQL_INSTANCE` to `READ_REPLICA_INSTANCE`. +* Set `master_instance_name` to the new primary (original replica). +* (If `replica_names` is present) Remove original replica from `replica_names`. + * **NOTE**: Do **not** delete the `replica_names` field, even if it has no replicas remaining. Set `replica_names = [ ]` to indicate it having no replicas. +* Remove original replica from `replication_cluster.failover_dr_replica_name` by setting this field to the empty string. +* Disable backup and PITR for original primary (because it became a replica). +* Run `terraform plan` and verify that your configuration matches infrastructure. You should see a message like the following: + * **`No changes. Your infrastructure matches the configuration.`** + +```diff +resource "google_sql_database_instance" "original-primary" { + project = "your-project" + name = "your-original-primary" + region = "us-east1" + database_version = "POSTGRES_12" +- instance_type = "CLOUD_SQL_INSTANCE" ++ instance_type = "READ_REPLICA_INSTANCE" ++ master_instance_name = "your-original-replica" + + replication_cluster { +- failover_dr_replica_name = "your-project:your-original-replica" ++ failover_dr_replica_name = "" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { +- enabled = true ++ enabled = false +- point_in_time_recovery_enabled = true ++ point_in_time_recovery_enabled = false + } + } +} + +resource "google_sql_database_instance" "original-replica" { + project = "your-project" + name = "your-original-replica" + region = "us-west2" + database_version = "POSTGRES_12" + instance_type = "CLOUD_SQL_INSTANCE" + replica_names = ["your-original-primary"] + + replication_cluster { + failover_dr_replica_name = "your-project:your-original-primary" + } + + settings { + tier = "db-perf-optimized-N-2" + edition = "ENTERPRISE_PLUS" + backup_configuration { + enabled = true + point_in_time_recovery_enabled = true + } + } +} +``` diff --git a/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown b/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown index 6f6e2e668bd3..8992cfeadf09 100644 --- a/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/sql_database_instance.html.markdown @@ -557,6 +557,12 @@ block during resource creation/update will trigger the restore action after the * `project` - (Optional) The full project ID of the source instance.` +The optional, computed `replication_cluster` block represents a primary instance and disaster recovery replica pair. Applicable to MySQL and PostgreSQL. This field can be set only after both the primary and replica are created. This block supports: + +* `failover_dr_replica_name`: (Optional) If the instance is a primary instance, then this field identifies the disaster recovery (DR) replica. The standard format of this field is "your-project:your-instance". You can also set this field to "your-instance", but cloud SQL backend will convert it to the aforementioned standard format. + +* `dr_replica`: Read-only field that indicates whether the replica is a DR replica. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are @@ -620,8 +626,8 @@ performing filtering in a Terraform config. * `server_ca_cert.0.sha1_fingerprint` - SHA Fingerprint of the CA Cert. -## Switchover (SQL Server Only) -Users can perform a switchover on any direct `cascadable` replica by following the steps below. +## Switchover +Users can perform a switchover on a replica by following the steps below. ~>**WARNING:** Failure to follow these steps can lead to data loss (You will be warned during plan stage). To prevent data loss during a switchover, please verify your plan with the checklist below. @@ -629,22 +635,26 @@ For a more in-depth walkthrough with example code, see the [Switchover Guide](.. ### Steps to Invoke Switchover -Create a `cascadable` replica in a different region from the primary (`cascadable_replica` is set to true in `replica_configuration`) +MySQL/PostgreSQL: Create a cross-region, Enterprise Plus edition primary and replica pair, then set the value of primary's `replication_cluster.failover_dr_replica_name` as the replica. + +SQL Server: Create a `cascadable` replica in a different region from the primary (`cascadable_replica` is set to true in `replica_configuration`) #### Invoking switchover in the replica resource: 1. Change instance_type from `READ_REPLICA_INSTANCE` to `CLOUD_SQL_INSTANCE` 2. Remove `master_instance_name` -3. Remove `replica_configuration` +3. (SQL Server) Remove `replica_configuration` 4. Add current primary's name to the replica's `replica_names` list +5. (MySQL/PostgreSQL) Add current primary's name to the replica's `replication_cluster.failover_dr_replica_name`. +6. (MySQL/PostgreSQL) Adjust `backup_configuration`. See [Switchover Guide](../guides/sql_instance_switchover.html.markdown) for details. #### Updating the primary resource: 1. Change `instance_type` from `CLOUD_SQL_INSTANCE` to `READ_REPLICA_INSTANCE` 2. Set `master_instance_name` to the original replica (which will be primary after switchover) -3. Set `replica_configuration` and set `cascadable_replica` to `true` +3. (SQL Server) Set `replica_configuration` and set `cascadable_replica` to `true` 4. Remove original replica from `replica_names` - - ~> **NOTE**: Do **not** delete the replica_names field, even if it has no replicas remaining. Set replica_names = [ ] to indicate it having no replicas. - + * **NOTE**: Do **not** delete the replica_names field, even if it has no replicas remaining. Set replica_names = [ ] to indicate it having no replicas. +5. (MySQL/PostgreSQL) Set `replication_cluster.failover_dr_replica_name` as the empty string. +6. (MySQL/PostgreSQL) Adjust `backup_configuration`. See [Switchover Guide](../guides/sql_instance_switchover.html.markdown) for details. #### Plan and verify that: - `terraform plan` outputs **"0 to add, 0 to destroy"** - `terraform plan` does not say **"must be replaced"** for any resource From 16b37875bc1fc09edf77f284f31bc5a2df9607e4 Mon Sep 17 00:00:00 2001 From: Ankit Goyal <51757072+ankitgoyal0301@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:24:44 +0530 Subject: [PATCH 26/30] =?UTF-8?q?Updated=20data=5Faccess=5Flabel=5Fid=20de?= =?UTF-8?q?scription=20for=20google=5Fchronicle=5Fdata=5Fac=E2=80=A6=20(#1?= =?UTF-8?q?2738)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mmv1/products/chronicle/DataAccessLabel.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mmv1/products/chronicle/DataAccessLabel.yaml b/mmv1/products/chronicle/DataAccessLabel.yaml index 36428850ff45..64f27da66e7d 100644 --- a/mmv1/products/chronicle/DataAccessLabel.yaml +++ b/mmv1/products/chronicle/DataAccessLabel.yaml @@ -55,9 +55,9 @@ parameters: type: String description: |- Required. The ID to use for the data access label, which will become the label's - display name and the final component of the label's resource name. It must - only contain ASCII lowercase letters, numbers, and dashes; it must begin - with a letter, and it must not exceed 1000 characters. + display name and the final component of the label's resource name. The + maximum number of characters should be 63. Regex pattern is as per AIP: + https://google.aip.dev/122#resource-id-segments immutable: true url_param_only: true required: true From 692b686bf772d2e0ff847c492600dafd88faaf28 Mon Sep 17 00:00:00 2001 From: bcreddy-gcp <123543489+bcreddy-gcp@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:12:38 -0800 Subject: [PATCH 27/30] Add support for Updating Container Image (#12607) --- .../pre_update/workbench_instance.go.tmpl | 4 ++ .../resource_workbench_instance_test.go.tmpl | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/mmv1/templates/terraform/pre_update/workbench_instance.go.tmpl b/mmv1/templates/terraform/pre_update/workbench_instance.go.tmpl index f33e2afefa96..c74166b07abe 100644 --- a/mmv1/templates/terraform/pre_update/workbench_instance.go.tmpl +++ b/mmv1/templates/terraform/pre_update/workbench_instance.go.tmpl @@ -27,6 +27,10 @@ if d.HasChange("gce_setup.0.metadata") { if d.HasChange("effective_labels") { newUpdateMask = append(newUpdateMask, "labels") } +if d.HasChange("gce_setup.0.container_image") { + newUpdateMask = append(newUpdateMask, "gce_setup.container_image") + stopInstance = true +} updateMask = newUpdateMask // Overwrite the previously set mask. url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(newUpdateMask, ",")}) diff --git a/mmv1/third_party/terraform/services/workbench/resource_workbench_instance_test.go.tmpl b/mmv1/third_party/terraform/services/workbench/resource_workbench_instance_test.go.tmpl index 9df865d0e4b5..f031aa885e59 100644 --- a/mmv1/third_party/terraform/services/workbench/resource_workbench_instance_test.go.tmpl +++ b/mmv1/third_party/terraform/services/workbench/resource_workbench_instance_test.go.tmpl @@ -690,3 +690,75 @@ resource "google_workbench_instance" "instance" { } `, context) } + + +func TestAccWorkbenchInstance_updateCustomContainers(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccWorkbenchInstance_customcontainer(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "google_workbench_instance.instance", "state", "ACTIVE"), + ), + }, + { + ResourceName: "google_workbench_instance.instance", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "instance_owners", "location", "instance_id", "request_id", "labels", "terraform_labels","desired_state"}, + }, + { + Config: testAccWorkbenchInstance_updatedcustomcontainer(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "google_workbench_instance.instance", "state", "ACTIVE"), + ), + }, + { + ResourceName: "google_workbench_instance.instance", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "instance_owners", "location", "instance_id", "request_id", "labels", "terraform_labels","desired_state"}, + }, + }, + }) +} + +func testAccWorkbenchInstance_customcontainer(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_workbench_instance" "instance" { + name = "tf-test-workbench-instance%{random_suffix}" + location = "us-central1-a" + gce_setup { + container_image { + repository = "us-docker.pkg.dev/deeplearning-platform-release/gcr.io/base-cu113.py310" + tag = "latest" + } + } +} +`, context) +} + +func testAccWorkbenchInstance_updatedcustomcontainer(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_workbench_instance" "instance" { + name = "tf-test-workbench-instance%{random_suffix}" + location = "us-central1-a" + gce_setup { + container_image { + repository = "gcr.io/deeplearning-platform-release/workbench-container" + tag = "20241117-2200-rc0" + } + } +} +`, context) +} From d0f8efaf8b363e53e099e9a4f7211f06750fce76 Mon Sep 17 00:00:00 2001 From: Ankit Goyal <51757072+ankitgoyal0301@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:59:12 +0530 Subject: [PATCH 28/30] Add google_chronicle_reference_list resource to chronicle (#12717) --- mmv1/products/chronicle/ReferenceList.yaml | 141 ++++++++++++++++++ .../chronicle_referencelist_basic.tf.tmpl | 11 ++ ...urce_chronicle_reference_list_test.go.tmpl | 79 ++++++++++ 3 files changed, 231 insertions(+) create mode 100644 mmv1/products/chronicle/ReferenceList.yaml create mode 100644 mmv1/templates/terraform/examples/chronicle_referencelist_basic.tf.tmpl create mode 100644 mmv1/third_party/terraform/services/chronicle/resource_chronicle_reference_list_test.go.tmpl diff --git a/mmv1/products/chronicle/ReferenceList.yaml b/mmv1/products/chronicle/ReferenceList.yaml new file mode 100644 index 000000000000..bc79d1796945 --- /dev/null +++ b/mmv1/products/chronicle/ReferenceList.yaml @@ -0,0 +1,141 @@ +# Copyright 2025 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. + +--- +name: ReferenceList +description: Reference lists are user-defined lists of values which users can use in multiple Rules. +min_version: 'beta' +references: + guides: + 'Google SecOps Guides': 'https://cloud.google.com/chronicle/docs/secops/secops-overview' + api: 'https://cloud.google.com/chronicle/docs/reference/rest/v1alpha/projects.locations.instances.referenceLists' +base_url: projects/{{project}}/locations/{{location}}/instances/{{instance}}/referenceLists +self_link: projects/{{project}}/locations/{{location}}/instances/{{instance}}/referenceLists/{{reference_list_id}} +create_url: projects/{{project}}/locations/{{location}}/instances/{{instance}}/referenceLists?referenceListId={{reference_list_id}} +id_format: projects/{{project}}/locations/{{location}}/instances/{{instance}}/referenceLists/{{reference_list_id}} +import_format: + - projects/{{project}}/locations/{{location}}/instances/{{instance}}/referenceLists/{{reference_list_id}} +update_verb: PATCH +update_mask: true +exclude_delete: true + +examples: + - name: 'chronicle_referencelist_basic' + primary_resource_id: 'example' + min_version: 'beta' + vars: + reference_list_id: reference_list_id + test_env_vars: + chronicle_id: 'CHRONICLE_ID' + +parameters: + - name: location + type: String + description: The location of the resource. This is the geographical region where the Chronicle instance resides, such as "us" or "europe-west2". + immutable: true + url_param_only: true + required: true + - name: instance + type: String + description: The unique identifier for the Chronicle instance, which is the same as the customer ID. + immutable: true + url_param_only: true + required: true + - name: referenceListId + type: String + description: |- + Required. The ID to use for the reference list. This is also the display name for + the reference list. It must satisfy the following requirements: + - Starts with letter. + - Contains only letters, numbers and underscore. + - Has length < 256. + - Must be unique. + immutable: true + url_param_only: true + required: true +properties: + - name: name + type: String + description: |- + Output only. The resource name of the reference list. + Format: + projects/{project}/locations/{location}/instances/{instance}/referenceLists/{reference_list} + output: true + - name: description + type: String + description: Required. A user-provided description of the reference list. + required: true + - name: entries + type: Array + description: |- + Required. The entries of the reference list. + When listed, they are returned in the order that was specified at creation + or update. The combined size of the values of the reference list may not + exceed 6MB. + This is returned only when the view is REFERENCE_LIST_VIEW_FULL. + required: true + item_type: + type: NestedObject + properties: + - name: value + type: String + description: Required. The value of the entry. Maximum length is 512 characters. + required: true + - name: scopeInfo + type: NestedObject + output: true + description: ScopeInfo specifies the scope info of the reference list. + properties: + - name: referenceListScope + type: NestedObject + description: ReferenceListScope specifies the list of scope names of the reference list. + required: true + properties: + - name: scopeNames + type: Array + description: |- + Optional. The list of scope names of the reference list. The scope names should be + full resource names and should be of the format: + "projects/{project}/locations/{location}/instances/{instance}/dataAccessScopes/{scope_name}". + item_type: + type: String + - name: displayName + type: String + description: Output only. The unique display name of the reference list. + output: true + - name: revisionCreateTime + type: String + description: Output only. The timestamp when the reference list was last updated. + output: true + - name: rules + type: Array + description: |- + Output only. The resource names for the associated self-authored Rules that use this + reference list. + This is returned only when the view is REFERENCE_LIST_VIEW_FULL. + output: true + item_type: + type: String + - name: syntaxType + type: String + description: |2- + + Possible values: + REFERENCE_LIST_SYNTAX_TYPE_PLAIN_TEXT_STRING + REFERENCE_LIST_SYNTAX_TYPE_REGEX + REFERENCE_LIST_SYNTAX_TYPE_CIDR + required: true + - name: ruleAssociationsCount + type: Integer + description: Output only. The count of self-authored rules using the reference list. + output: true diff --git a/mmv1/templates/terraform/examples/chronicle_referencelist_basic.tf.tmpl b/mmv1/templates/terraform/examples/chronicle_referencelist_basic.tf.tmpl new file mode 100644 index 000000000000..5899e5a34ca5 --- /dev/null +++ b/mmv1/templates/terraform/examples/chronicle_referencelist_basic.tf.tmpl @@ -0,0 +1,11 @@ +resource "google_chronicle_reference_list" "{{$.PrimaryResourceId}}" { + provider = "google-beta" + location = "us" + instance = "{{index $.TestEnvVars "chronicle_id"}}" + reference_list_id = "{{index $.Vars "reference_list_id"}}" + description = "referencelist-description" + entries { + value = "referencelist-entry-value" + } + syntax_type = "REFERENCE_LIST_SYNTAX_TYPE_PLAIN_TEXT_STRING" +} diff --git a/mmv1/third_party/terraform/services/chronicle/resource_chronicle_reference_list_test.go.tmpl b/mmv1/third_party/terraform/services/chronicle/resource_chronicle_reference_list_test.go.tmpl new file mode 100644 index 000000000000..c3896d61594e --- /dev/null +++ b/mmv1/third_party/terraform/services/chronicle/resource_chronicle_reference_list_test.go.tmpl @@ -0,0 +1,79 @@ +package chronicle_test + +{{- if ne $.TargetVersionName "ga" }} + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestAccChronicleReferenceList_chronicleReferencelistBasicExample_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "chronicle_id": envvar.GetTestChronicleInstanceIdFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccChronicleReferenceList_chronicleReferencelistBasicExample_basic(context), + }, + { + ResourceName: "google_chronicle_reference_list.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"instance", "location", "reference_list_id"}, + }, + { + Config: testAccChronicleReferenceList_chronicleReferencelistBasicExample_update(context), + }, + { + ResourceName: "google_chronicle_reference_list.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"instance", "location", "reference_list_id"}, + }, + }, + }) +} + +func testAccChronicleReferenceList_chronicleReferencelistBasicExample_basic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_chronicle_reference_list" "example" { + provider = "google-beta" + location = "us" + instance = "%{chronicle_id}" + reference_list_id = "tf_test_reference_list_id%{random_suffix}" + description = "referencelist-description" + entries { + value = "referencelist-entry-value" + } + syntax_type = "REFERENCE_LIST_SYNTAX_TYPE_PLAIN_TEXT_STRING" +} +`, context) +} + +func testAccChronicleReferenceList_chronicleReferencelistBasicExample_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_chronicle_reference_list" "example" { + provider = "google-beta" + location = "us" + instance = "%{chronicle_id}" + reference_list_id = "tf_test_reference_list_id%{random_suffix}" + description = "referencelist-description-updated" + entries { + value = "referencelist-entry-value-updated" + } + syntax_type = "REFERENCE_LIST_SYNTAX_TYPE_REGEX" +} +`, context) +} +{{- end }} From 993a0cff0348a36c7013ffb580a7ae3ee93c713e Mon Sep 17 00:00:00 2001 From: Rin Arakaki Date: Tue, 14 Jan 2025 03:26:59 +0900 Subject: [PATCH 29/30] Add official documentation link for URL map (#12739) --- mmv1/products/compute/UrlMap.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/mmv1/products/compute/UrlMap.yaml b/mmv1/products/compute/UrlMap.yaml index 36371f2de68d..c1a3e5ca7111 100644 --- a/mmv1/products/compute/UrlMap.yaml +++ b/mmv1/products/compute/UrlMap.yaml @@ -19,6 +19,7 @@ description: | that you define for the host and path of an incoming URL. references: guides: + 'Official Documentation': 'https://cloud.google.com/load-balancing/docs/url-map-concepts' api: 'https://cloud.google.com/compute/docs/reference/rest/v1/urlMaps' docs: base_url: 'projects/{{project}}/global/urlMaps' From 9549e70b89fd49a2f06603be2ca855e911172676 Mon Sep 17 00:00:00 2001 From: bcreddy-gcp <123543489+bcreddy-gcp@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:18:51 -0800 Subject: [PATCH 30/30] Colab runtime template (#12706) --- mmv1/products/colab/RuntimeTemplate.yaml | 172 ++++++++++++++++++ mmv1/products/colab/product.yaml | 25 +++ .../colab_runtime_template_basic.tf.tmpl | 13 ++ .../colab_runtime_template_full.tf.tmpl | 56 ++++++ .../colab_runtime_template_no_name.tf.tmpl | 12 ++ .../post_create/colab_runtime_template.tmpl | 10 + .../components/inputs/services_beta.kt | 5 + .../components/inputs/services_ga.kt | 5 + 8 files changed, 298 insertions(+) create mode 100644 mmv1/products/colab/RuntimeTemplate.yaml create mode 100644 mmv1/products/colab/product.yaml create mode 100644 mmv1/templates/terraform/examples/colab_runtime_template_basic.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/colab_runtime_template_full.tf.tmpl create mode 100644 mmv1/templates/terraform/examples/colab_runtime_template_no_name.tf.tmpl create mode 100644 mmv1/templates/terraform/post_create/colab_runtime_template.tmpl diff --git a/mmv1/products/colab/RuntimeTemplate.yaml b/mmv1/products/colab/RuntimeTemplate.yaml new file mode 100644 index 000000000000..3919c18e21dc --- /dev/null +++ b/mmv1/products/colab/RuntimeTemplate.yaml @@ -0,0 +1,172 @@ +# Copyright 2025 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. + +--- + +name: 'RuntimeTemplate' +description: | + 'A runtime template is a VM configuration that specifies a machine type and other characteristics of the VM, + as well as common settings such as the network and whether public internet access is enabled. When you create + a runtime, its VM is created according to the specifications of a runtime template.' + +references: + guides: + 'Create a runtime template': 'https://cloud.google.com/colab/docs/create-runtime-template' + api: 'https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.notebookRuntimeTemplates' +base_url: 'projects/{{project}}/locations/{{location}}/notebookRuntimeTemplates' +self_link: 'projects/{{project}}/locations/{{location}}/notebookRuntimeTemplates/{{name}}' +immutable: true +create_url: 'projects/{{project}}/locations/{{location}}/notebookRuntimeTemplates?notebook_runtime_template_id={{name}}' +autogen_async: true +async: + operation: + base_url: '{{op_id}}' +custom_code: + post_create: 'templates/terraform/post_create/colab_runtime_template.tmpl' +examples: + - name: 'colab_runtime_template_basic' + primary_resource_id: 'runtime-template' + primary_resource_name: 'fmt.Sprintf("tf-test-colab-runtime-template%s", context["random_suffix"])' + vars: + runtime_template_name: 'colab-runtime-template' + - name: 'colab_runtime_template_no_name' + primary_resource_id: 'runtime-template' + exclude_import_test: true + - name: 'colab_runtime_template_full' + primary_resource_id: 'runtime-template' + primary_resource_name: 'fmt.Sprintf("tf-test-colab-runtime-template%s", context["random_suffix"])' + vars: + runtime_template_name: 'colab-runtime-template' + network_name: 'colab-test-default' + key_name: 'my-crypto-key' + test_vars_overrides: + key_name: 'acctest.BootstrapKMSKeyInLocation(t, "us-central1").CryptoKey.Name' +parameters: + - name: 'location' + type: String + required: true + url_param_only: true + description: 'The location for the resource: https://cloud.google.com/colab/docs/locations' +properties: + - name: 'name' + type: String + default_from_api: true + custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.tmpl' + description: 'The resource name of the Runtime Template' + - name: 'displayName' + type: String + description: + Required. The display name of the Runtime Template. + required: true + - name: description + type: String + description: 'The description of the Runtime Template.' + - name: machineSpec + type: NestedObject + default_from_api: true + description: | + 'The machine configuration of the runtime.' + properties: + - name: 'machineType' + type: string + default_from_api: true + description: | + The Compute Engine machine type selected for the runtime. + - name: 'acceleratorType' + type: enum + description: | + The type of hardware accelerator used by the runtime. If specified, acceleratorCount must also be specified. + enum_values: + - 'NVIDIA_TESLA_V100' + - 'NVIDIA_TESLA_T4' + - 'NVIDIA_TESLA_A100' + - 'NVIDIA_A100_80GB' + - 'NVIDIA_L4' + - name: 'acceleratorCount' + type: Integer + default_from_api: true + description: 'The number of accelerators used by the runtime.' + - name: dataPersistentDiskSpec + default_from_api: true + type: NestedObject + description: 'The configuration for the data disk of the runtime.' + properties: + - name: 'diskType' + type: enum + description: 'The type of the persistent disk.' + default_from_api: true + enum_values: + - 'pd-standard' + - 'pd-ssd' + - 'pd-balanced' + - 'pd-extreme' + - name: 'diskSizeGb' + type: int + default_from_api: true + description: | + The disk size of the runtime in GB. If specified, the diskType must also be specified. The minimum size is 10GB and the maximum is 65536GB. + - name: networkSpec + type: NestedObject + default_from_api: true + description: 'The network configuration for the runtime.' + properties: + - name: 'enableInternetAccess' + type: Boolean + description: Enable public internet access for the runtime. + - name: 'network' + default_from_api: true + type: String + description: 'The name of the VPC that this runtime is in.' + diff_suppress_func: 'tpgresource.CompareSelfLinkRelativePaths' + - name: 'subnetwork' + type: String + description: 'The name of the subnetwork that this runtime is in.' + diff_suppress_func: 'tpgresource.CompareSelfLinkRelativePaths' + - name: 'labels' + type: KeyValueLabels + description: 'Labels to identify and group the runtime template.' + - name: idleShutdownConfig + type: NestedObject + default_from_api: true + description: 'Notebook Idle Shutdown configuration for the runtime.' + properties: + - name: 'idleTimeout' + default_from_api: true + type: String + description: 'The duration after which the runtime is automatically shut down. An input of 0s disables the idle shutdown feature, and a valid range is [10m, 24h].' + - name: eucConfig + type: NestedObject + description: 'EUC configuration of the NotebookRuntimeTemplate.' + properties: + - name: 'eucDisabled' + type: Boolean + description: 'Disable end user credential access for the runtime.' + - name: shieldedVmConfig + type: NestedObject + description: 'Runtime Shielded VM spec.' + properties: + - name: 'enableSecureBoot' + type: Boolean + description: 'Enables secure boot for the runtime.' + - name: 'networkTags' + type: Array + item_type: + type: String + description: 'Applies the given Compute Engine tags to the runtime.' + - name: encryptionSpec + type: NestedObject + description: 'Customer-managed encryption key spec for the notebook runtime.' + properties: + - name: 'kmsKeyName' + type: String + description: 'The Cloud KMS encryption key (customer-managed encryption key) used to protect the runtime.' diff --git a/mmv1/products/colab/product.yaml b/mmv1/products/colab/product.yaml new file mode 100644 index 000000000000..0d588605b43d --- /dev/null +++ b/mmv1/products/colab/product.yaml @@ -0,0 +1,25 @@ +# Copyright 2025 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. + +--- +name: 'Colab' +display_name: 'Colab Enterprise' +versions: + - name: 'ga' + base_url: 'https://{{region}}-aiplatform.googleapis.com/v1/' + cai_base_url: 'https://aiplatform.googleapis.com/v1/' + - name: 'beta' + base_url: 'https://{{region}}-aiplatform.googleapis.com/v1beta1/' + cai_base_url: 'https://aiplatform.googleapis.com/v1beta1/' +scopes: + - 'https://www.googleapis.com/auth/cloud-platform' diff --git a/mmv1/templates/terraform/examples/colab_runtime_template_basic.tf.tmpl b/mmv1/templates/terraform/examples/colab_runtime_template_basic.tf.tmpl new file mode 100644 index 000000000000..7ffb08187732 --- /dev/null +++ b/mmv1/templates/terraform/examples/colab_runtime_template_basic.tf.tmpl @@ -0,0 +1,13 @@ +resource "google_colab_runtime_template" "{{$.PrimaryResourceId}}" { + name = "{{index $.Vars "runtime_template_name"}}" + display_name = "Runtime template basic" + location = "us-central1" + + machine_spec { + machine_type = "e2-standard-4" + } + + network_spec { + enable_internet_access = true + } +} diff --git a/mmv1/templates/terraform/examples/colab_runtime_template_full.tf.tmpl b/mmv1/templates/terraform/examples/colab_runtime_template_full.tf.tmpl new file mode 100644 index 000000000000..3078041236d9 --- /dev/null +++ b/mmv1/templates/terraform/examples/colab_runtime_template_full.tf.tmpl @@ -0,0 +1,56 @@ +resource "google_compute_network" "my_network" { + name = "{{index $.Vars "network_name"}}" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "my_subnetwork" { + name = "{{index $.Vars "network_name"}}" + network = google_compute_network.my_network.id + region = "us-central1" + ip_cidr_range = "10.0.1.0/24" +} + +resource "google_colab_runtime_template" "{{$.PrimaryResourceId}}" { + name = "{{index $.Vars "runtime_template_name"}}" + display_name = "Runtime template full" + location = "us-central1" + description = "Full runtime template" + machine_spec { + machine_type = "n1-standard-2" + accelerator_type = "NVIDIA_TESLA_T4" + accelerator_count = "1" + } + + data_persistent_disk_spec { + disk_type = "pd-standard" + disk_size_gb = 200 + } + + network_spec { + enable_internet_access = true + network = google_compute_network.my_network.id + subnetwork = google_compute_subnetwork.my_subnetwork.id + } + + labels = { + k = "val" + } + + idle_shutdown_config { + idle_timeout = "3600s" + } + + euc_config { + euc_disabled = true + } + + shielded_vm_config { + enable_secure_boot = true + } + + network_tags = ["abc", "def"] + + encryption_spec { + kms_key_name = "{{index $.Vars "key_name"}}" + } +} diff --git a/mmv1/templates/terraform/examples/colab_runtime_template_no_name.tf.tmpl b/mmv1/templates/terraform/examples/colab_runtime_template_no_name.tf.tmpl new file mode 100644 index 000000000000..51dff7ab02cc --- /dev/null +++ b/mmv1/templates/terraform/examples/colab_runtime_template_no_name.tf.tmpl @@ -0,0 +1,12 @@ +resource "google_colab_runtime_template" "{{$.PrimaryResourceId}}" { + display_name = "Runtime template no name" + location = "us-central1" + + machine_spec { + machine_type = "e2-standard-4" + } + + network_spec { + enable_internet_access = true + } +} diff --git a/mmv1/templates/terraform/post_create/colab_runtime_template.tmpl b/mmv1/templates/terraform/post_create/colab_runtime_template.tmpl new file mode 100644 index 000000000000..c959ee745e0b --- /dev/null +++ b/mmv1/templates/terraform/post_create/colab_runtime_template.tmpl @@ -0,0 +1,10 @@ +// The operation for this resource contains the generated name that we need +// in order to perform a READ. We need to access the object inside of it as +// a map[string]interface, so let's do that. + +resp := res["response"].(map[string]interface{}) +name := tpgresource.GetResourceNameFromSelfLink(resp["name"].(string)) +log.Printf("[DEBUG] Setting resource name, id to %s", name) +if err := d.Set("name", name); err != nil { + return fmt.Errorf("Error setting name: %s", err) +} diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt index 40b5e0b4de8b..efd68430b38a 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_beta.kt @@ -216,6 +216,11 @@ var ServicesListBeta = mapOf( "displayName" to "Cloudtasks", "path" to "./google-beta/services/cloudtasks" ), + "colab" to mapOf( + "name" to "colab", + "displayName" to "Colab", + "path" to "./google-beta/services/colab" + ), "composer" to mapOf( "name" to "composer", "displayName" to "Composer", diff --git a/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt b/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt index 6c1fe32207d5..efa053fd771e 100644 --- a/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt +++ b/mmv1/third_party/terraform/.teamcity/components/inputs/services_ga.kt @@ -216,6 +216,11 @@ var ServicesListGa = mapOf( "displayName" to "Cloudtasks", "path" to "./google/services/cloudtasks" ), + "colab" to mapOf( + "name" to "colab", + "displayName" to "Colab", + "path" to "./google/services/colab" + ), "composer" to mapOf( "name" to "composer", "displayName" to "Composer",