diff --git a/mmv1/products/dialogflowcx/Flow.yaml b/mmv1/products/dialogflowcx/Flow.yaml index a524b799f8a6..061b2e2b222f 100644 --- a/mmv1/products/dialogflowcx/Flow.yaml +++ b/mmv1/products/dialogflowcx/Flow.yaml @@ -27,17 +27,37 @@ timeouts: !ruby/object:Api::Timeouts update_minutes: 40 custom_code: !ruby/object:Provider::Terraform::CustomCode custom_import: templates/terraform/custom_import/dialogflowcx_flow.go.erb - pre_create: templates/terraform/pre_create/dialogflow_set_location.go.erb + pre_create: templates/terraform/pre_create/dialogflowcx_set_location_skip_default_obj.go.erb pre_update: templates/terraform/pre_create/dialogflow_set_location.go.erb - pre_delete: templates/terraform/pre_create/dialogflow_set_location.go.erb + pre_delete: templates/terraform/pre_delete/dialogflowcx_set_location_skip_default_obj.go.erb pre_read: templates/terraform/pre_create/dialogflow_set_location.go.erb +virtual_fields: + - !ruby/object:Api::Type::Boolean + name: is_default_start_flow + immutable: true + description: | + Marks this as the [Default Start Flow](https://cloud.google.com/dialogflow/cx/docs/concept/flow#start) for an agent. When you create an agent, the Default Start Flow is created automatically. + The Default Start Flow cannot be deleted; deleting the `google_dialogflow_cx_flow` resource does nothing to the underlying GCP resources. + + ~> Avoid having multiple `google_dialogflow_cx_flow` resources linked to the same agent with `is_default_start_flow = true` because they will compete to control a single Default Start Flow resource in GCP. examples: + - !ruby/object:Provider::Terraform::Examples + name: 'dialogflowcx_flow_basic' + primary_resource_id: 'basic_flow' + vars: + agent_name: 'dialogflowcx-agent' - !ruby/object:Provider::Terraform::Examples name: 'dialogflowcx_flow_full' primary_resource_id: 'basic_flow' vars: agent_name: 'dialogflowcx-agent' bucket_name: 'dialogflowcx-bucket' + - !ruby/object:Provider::Terraform::Examples + skip_docs: true + name: 'dialogflowcx_flow_default_start_flow' + primary_resource_id: 'default_start_flow' + vars: + agent_name: 'dialogflowcx-agent' skip_sweeper: true id_format: '{{parent}}/flows/{{name}}' import_format: ['{{parent}}/flows/{{name}}'] diff --git a/mmv1/products/dialogflowcx/Intent.yaml b/mmv1/products/dialogflowcx/Intent.yaml index 479e7756756e..3a287a4f5b07 100644 --- a/mmv1/products/dialogflowcx/Intent.yaml +++ b/mmv1/products/dialogflowcx/Intent.yaml @@ -27,16 +27,45 @@ timeouts: !ruby/object:Api::Timeouts update_minutes: 40 custom_code: !ruby/object:Provider::Terraform::CustomCode custom_import: templates/terraform/custom_import/dialogflowcx_intent.go.erb - pre_create: templates/terraform/pre_create/dialogflow_set_location.go.erb + pre_create: templates/terraform/pre_create/dialogflowcx_set_location_skip_default_obj.go.erb pre_update: templates/terraform/pre_create/dialogflow_set_location.go.erb - pre_delete: templates/terraform/pre_create/dialogflow_set_location.go.erb + pre_delete: templates/terraform/pre_delete/dialogflowcx_set_location_skip_default_obj.go.erb pre_read: templates/terraform/pre_create/dialogflow_set_location.go.erb +virtual_fields: + - !ruby/object:Api::Type::Boolean + name: is_default_welcome_intent + immutable: true + description: | + Marks this as the [Default Welcome Intent](https://cloud.google.com/dialogflow/cx/docs/concept/intent#welcome) for an agent. When you create an agent, a Default Welcome Intent is created automatically. + The Default Welcome Intent cannot be deleted; deleting the `google_dialogflow_cx_intent` resource does nothing to the underlying GCP resources. + + ~> Avoid having multiple `google_dialogflow_cx_intent` resources linked to the same agent with `is_default_welcome_intent = true` because they will compete to control a single Default Welcome Intent resource in GCP. + - !ruby/object:Api::Type::Boolean + name: is_default_negative_intent + immutable: true + description: | + Marks this as the [Default Negative Intent](https://cloud.google.com/dialogflow/cx/docs/concept/intent#negative) for an agent. When you create an agent, a Default Negative Intent is created automatically. + The Default Negative Intent cannot be deleted; deleting the `google_dialogflow_cx_intent` resource does nothing to the underlying GCP resources. + + ~> Avoid having multiple `google_dialogflow_cx_intent` resources linked to the same agent with `is_default_negative_intent = true` because they will compete to control a single Default Negative Intent resource in GCP. examples: - !ruby/object:Provider::Terraform::Examples name: 'dialogflowcx_intent_full' primary_resource_id: 'basic_intent' vars: agent_name: 'dialogflowcx-agent' + - !ruby/object:Provider::Terraform::Examples + skip_docs: true + name: 'dialogflowcx_intent_default_negative_intent' + primary_resource_id: 'default_negative_intent' + vars: + agent_name: 'dialogflowcx-agent' + - !ruby/object:Provider::Terraform::Examples + skip_docs: true + name: 'dialogflowcx_intent_default_welcome_intent' + primary_resource_id: 'default_welcome_intent' + vars: + agent_name: 'dialogflowcx-agent' skip_sweeper: true id_format: '{{parent}}/intents/{{name}}' import_format: ['{{parent}}/intents/{{name}}'] @@ -144,6 +173,7 @@ properties: description: | Indicates whether this is a fallback intent. Currently only default fallback intent is allowed in the agent, which is added upon agent creation. Adding training phrases to fallback intent is useful in the case of requests that are mistakenly matched, since training phrases assigned to fallback intents act as negative examples that triggers no-match event. + To manage the fallback intent, set `is_default_negative_intent = true` - !ruby/object:Api::Type::KeyValueLabels name: 'labels' description: | diff --git a/mmv1/products/pubsub/Topic.yaml b/mmv1/products/pubsub/Topic.yaml index 16f25f459596..95bb9563576f 100644 --- a/mmv1/products/pubsub/Topic.yaml +++ b/mmv1/products/pubsub/Topic.yaml @@ -147,4 +147,5 @@ properties: For instance, it allows any attached subscription to seek to a timestamp that is up to messageRetentionDuration in the past. If this field is not set, message retention is controlled by settings on individual subscriptions. - Cannot be more than 31 days or less than 10 minutes. + The rotation period has the format of a decimal number, followed by the + letter `s` (seconds). Cannot be more than 31 days or less than 10 minutes. diff --git a/mmv1/products/spanner/Instance.yaml b/mmv1/products/spanner/Instance.yaml index 9c0c38c266c3..6903c10a4416 100644 --- a/mmv1/products/spanner/Instance.yaml +++ b/mmv1/products/spanner/Instance.yaml @@ -156,79 +156,3 @@ properties: values: - :READY - :CREATING - - !ruby/object:Api::Type::NestedObject - name: 'autoscalingConfig' - description: | - The autoscaling configuration. Autoscaling is enabled if this field is set. - When autoscaling is enabled, num_nodes and processing_units are treated as, - OUTPUT_ONLY fields and reflect the current compute capacity allocated to - the instance. - properties: - - !ruby/object:Api::Type::NestedObject - name: 'autoscalingLimits' - description: | - Defines scale in controls to reduce the risk of response latency - and outages due to abrupt scale-in events - properties: - - !ruby/object:Api::Type::NestedObject - name: 'minLimit' - description: | - Specifies the minimum compute capacity for the instance. - properties: - - !ruby/object:Api::Type::Integer - name: 'minNodes' - exactly_one_of: - - min_nodes - - min_processing_units - description: | - Specifies minimum number of processing units allocated to the instance. - If set, this number should be greater than or equal to 1. - - !ruby/object:Api::Type::Integer - name: 'minProcessingUnits' - exactly_one_of: - - min_nodes - - min_processing_units - description: | - Specifies minimum number of processing units allocated to the instance. - If set, this number should be multiples of 1000. - - !ruby/object:Api::Type::NestedObject - name: 'maxLimit' - description: | - Specifies the maximum compute capacity for the instance. - The maximum compute capacity should be less than or equal to 10X the minimum compute capacity. - properties: - - !ruby/object:Api::Type::Integer - name: 'maxNodes' - exactly_one_of: - - max_nodes - - max_processing_units - description: | - Specifies maximum number of nodes allocated to the instance. - If set, this number should be greater than or equal to min_nodes. - - !ruby/object:Api::Type::Integer - name: 'maxProcessingUnits' - exactly_one_of: - - max_nodes - - max_processing_units - description: | - Specifies maximum number of processing units allocated to the instance. - If set, this number should be multiples of 1000 and be greater than or equal to - min_processing_units. - - !ruby/object:Api::Type::NestedObject - name: 'autoscalingTargets' - description: | - Defines scale in controls to reduce the risk of response latency - and outages due to abrupt scale-in events - properties: - - !ruby/object:Api::Type::Integer - name: 'highPriorityCpuUtilizationPercent' - description: | - Specifies the target high priority cpu utilization percentage that the autoscaler - should be trying to achieve for the instance. - This number is on a scale from 0 (no utilization) to 100 (full utilization).. - - !ruby/object:Api::Type::Integer - name: 'storageUtilizationPercent' - description: | - Specifies the target storage utilization percentage that the autoscaler - should be trying to achieve for the instance. - This number is on a scale from 0 (no utilization) to 100 (full utilization). diff --git a/mmv1/provider/terraform/virtual_fields.rb b/mmv1/provider/terraform/virtual_fields.rb index e81bdbffe52e..66acd30b3e03 100644 --- a/mmv1/provider/terraform/virtual_fields.rb +++ b/mmv1/provider/terraform/virtual_fields.rb @@ -49,12 +49,17 @@ class VirtualFields < Api::Object # The default value for the field (defaults to false) attr_reader :default_value + # If set to true, changes in the field's value require recreating the + # resource. + attr_reader :immutable + def validate super check :name, type: String, required: true check :description, type: String, required: true check :type, type: Class, default: Api::Type::Boolean check :default_value, default: false + check :immutable, default: false end end end diff --git a/mmv1/templates/terraform/custom_import/dialogflowcx_flow.go.erb b/mmv1/templates/terraform/custom_import/dialogflowcx_flow.go.erb index 9710bd1d2b69..6deca14d0d1c 100644 --- a/mmv1/templates/terraform/custom_import/dialogflowcx_flow.go.erb +++ b/mmv1/templates/terraform/custom_import/dialogflowcx_flow.go.erb @@ -15,4 +15,9 @@ if err != nil { } d.SetId(id) +// Set is_default_start_flow if the resource is actually the Default Start Flow +if d.Get("name").(string) == "00000000-0000-0000-0000-000000000000" { + d.Set("is_default_start_flow", true) +} + return []*schema.ResourceData{d}, nil \ No newline at end of file diff --git a/mmv1/templates/terraform/custom_import/dialogflowcx_intent.go.erb b/mmv1/templates/terraform/custom_import/dialogflowcx_intent.go.erb index 9f7a00d23a7c..bf26d2f2de3d 100644 --- a/mmv1/templates/terraform/custom_import/dialogflowcx_intent.go.erb +++ b/mmv1/templates/terraform/custom_import/dialogflowcx_intent.go.erb @@ -15,4 +15,12 @@ } d.SetId(id) + // Set is_default_X if the resource is actually a Default Intent + if d.Get("name").(string) == "00000000-0000-0000-0000-000000000000" { + d.Set("is_default_welcome_intent", true) + } + if d.Get("name").(string) == "00000000-0000-0000-0000-000000000001" { + d.Set("is_default_negative_intent", true) + } + return []*schema.ResourceData{d}, nil \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/dialogflowcx_flow_default_start_flow.tf.erb b/mmv1/templates/terraform/examples/dialogflowcx_flow_default_start_flow.tf.erb new file mode 100644 index 000000000000..19bda4f341dd --- /dev/null +++ b/mmv1/templates/terraform/examples/dialogflowcx_flow_default_start_flow.tf.erb @@ -0,0 +1,76 @@ +resource "google_dialogflow_cx_agent" "agent" { + display_name = "<%= ctx[:vars]["agent_name"] %>" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "Hello" + } + repeat_count = 1 + } +} + + +resource "google_dialogflow_cx_flow" "<%= ctx[:primary_resource_id] %>" { + parent = google_dialogflow_cx_agent.agent.id + is_default_start_flow = true + display_name = "Default Start Flow" + description = "A start flow created along with the agent" + + nlu_settings { + classification_threshold = 0.3 + model_type = "MODEL_TYPE_STANDARD" + } + + transition_routes { + intent = google_dialogflow_cx_intent.default_welcome_intent.id + trigger_fulfillment { + messages { + text { + text = ["Response to default welcome intent."] + } + } + } + } + + event_handlers { + event = "custom-event" + trigger_fulfillment { + messages { + text { + text = ["This is a default flow."] + } + } + } + } + + event_handlers { + event = "sys.no-match-default" + trigger_fulfillment { + messages { + text { + text = ["We've updated the default flow no-match response!"] + } + } + } + } + + event_handlers { + event = "sys.no-input-default" + trigger_fulfillment { + messages { + text { + text = ["We've updated the default flow no-input response!"] + } + } + } + } +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/dialogflowcx_intent_default_negative_intent.tf.erb b/mmv1/templates/terraform/examples/dialogflowcx_intent_default_negative_intent.tf.erb new file mode 100644 index 000000000000..3d945d8b08c3 --- /dev/null +++ b/mmv1/templates/terraform/examples/dialogflowcx_intent_default_negative_intent.tf.erb @@ -0,0 +1,21 @@ +resource "google_dialogflow_cx_agent" "agent" { + display_name = "<%= ctx[:vars]["agent_name"] %>" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + + +resource "google_dialogflow_cx_intent" "<%= ctx[:primary_resource_id] %>" { + parent = google_dialogflow_cx_agent.agent.id + is_default_negative_intent = true + display_name = "Default Negative Intent" + priority = 1 + is_fallback = true + training_phrases { + parts { + text = "Never match this phrase" + } + repeat_count = 1 + } +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/dialogflowcx_intent_default_welcome_intent.tf.erb b/mmv1/templates/terraform/examples/dialogflowcx_intent_default_welcome_intent.tf.erb new file mode 100644 index 000000000000..0772b2bdde78 --- /dev/null +++ b/mmv1/templates/terraform/examples/dialogflowcx_intent_default_welcome_intent.tf.erb @@ -0,0 +1,20 @@ +resource "google_dialogflow_cx_agent" "agent" { + display_name = "<%= ctx[:vars]["agent_name"] %>" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + + +resource "google_dialogflow_cx_intent" "<%= ctx[:primary_resource_id] %>" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "Hello" + } + repeat_count = 1 + } +} \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/integration_connectors_connection_advanced.tf.erb b/mmv1/templates/terraform/examples/integration_connectors_connection_advanced.tf.erb index 5fc3e6e2b576..7c40d7c53f77 100644 --- a/mmv1/templates/terraform/examples/integration_connectors_connection_advanced.tf.erb +++ b/mmv1/templates/terraform/examples/integration_connectors_connection_advanced.tf.erb @@ -30,7 +30,7 @@ resource "google_integration_connectors_connection" "<%= ctx[:primary_resource_i description = "tf updated description" location = "us-central1" service_account = "${data.google_project.test_project.number}-compute@developer.gserviceaccount.com" - connector_version = "projects/connectors-example/locations/global/providers/zendesk/connectors/zendesk/versions/1" + connector_version = "projects/${data.google_project.test_project.project_id}/locations/global/providers/zendesk/connectors/zendesk/versions/1" config_variable { key = "proxy_enabled" boolean_value = false diff --git a/mmv1/templates/terraform/examples/integration_connectors_connection_basic.tf.erb b/mmv1/templates/terraform/examples/integration_connectors_connection_basic.tf.erb index f2f82328660d..a0261644a100 100644 --- a/mmv1/templates/terraform/examples/integration_connectors_connection_basic.tf.erb +++ b/mmv1/templates/terraform/examples/integration_connectors_connection_basic.tf.erb @@ -1,7 +1,10 @@ +data "google_project" "test_project" { +} + resource "google_integration_connectors_connection" "<%= ctx[:primary_resource_id] %>" { name = "<%= ctx[:vars]['connection_name'] %>" location = "us-central1" - connector_version = "projects/connectors-example/locations/global/providers/gcp/connectors/pubsub/versions/1" + connector_version = "projects/${data.google_project.test_project.project_id}/locations/global/providers/gcp/connectors/pubsub/versions/1" description = "tf created description" config_variable { key = "project_id" diff --git a/mmv1/templates/terraform/examples/integration_connectors_connection_oauth.tf.erb b/mmv1/templates/terraform/examples/integration_connectors_connection_oauth.tf.erb index 781d96e40bb2..8eba69da624f 100644 --- a/mmv1/templates/terraform/examples/integration_connectors_connection_oauth.tf.erb +++ b/mmv1/templates/terraform/examples/integration_connectors_connection_oauth.tf.erb @@ -28,7 +28,7 @@ resource "google_secret_manager_secret_iam_member" "secret_iam" { resource "google_integration_connectors_connection" "<%= ctx[:primary_resource_id] %>" { name = "<%= ctx[:vars]['connection_name'] %>" location = "us-central1" - connector_version = "projects/connectors-example/locations/global/providers/box/connectors/box/versions/1" + connector_version = "projects/${data.google_project.test_project.project_id}/locations/global/providers/box/connectors/box/versions/1" description = "tf created description" config_variable { key = "impersonate_user_mode" diff --git a/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_cc.tf.erb b/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_cc.tf.erb index 025df51a83ea..b22e04c033d5 100644 --- a/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_cc.tf.erb +++ b/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_cc.tf.erb @@ -28,7 +28,7 @@ resource "google_secret_manager_secret_iam_member" "secret_iam" { resource "google_integration_connectors_connection" "<%= ctx[:primary_resource_id] %>" { name = "<%= ctx[:vars]['connection_name'] %>" location = "us-central1" - connector_version = "projects/connectors-example/locations/global/providers/box/connectors/box/versions/1" + connector_version = "projects/${data.google_project.test_project.project_id}/locations/global/providers/box/connectors/box/versions/1" service_account = "${data.google_project.test_project.number}-compute@developer.gserviceaccount.com" description = "tf created description" config_variable { diff --git a/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_jwt.tf.erb b/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_jwt.tf.erb index 436d49733c8b..baaebee91662 100644 --- a/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_jwt.tf.erb +++ b/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_jwt.tf.erb @@ -28,7 +28,7 @@ resource "google_secret_manager_secret_iam_member" "secret_iam" { resource "google_integration_connectors_connection" "<%= ctx[:primary_resource_id] %>" { name = "<%= ctx[:vars]['connection_name'] %>" location = "us-central1" - connector_version = "projects/connectors-example/locations/global/providers/box/connectors/box/versions/1" + connector_version = "projects/${data.google_project.test_project.project_id}/locations/global/providers/box/connectors/box/versions/1" service_account = "${data.google_project.test_project.number}-compute@developer.gserviceaccount.com" description = "tf created description" config_variable { diff --git a/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_ssh.tf.erb b/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_ssh.tf.erb index ecff8facc4d0..26d602f1bf58 100644 --- a/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_ssh.tf.erb +++ b/mmv1/templates/terraform/examples/integration_connectors_connection_oauth_ssh.tf.erb @@ -28,7 +28,7 @@ resource "google_secret_manager_secret_iam_member" "secret_iam" { resource "google_integration_connectors_connection" "<%= ctx[:primary_resource_id] %>" { name = "<%= ctx[:vars]['connection_name'] %>" location = "us-central1" - connector_version = "projects/connectors-example/locations/global/providers/box/connectors/box/versions/1" + connector_version = "projects/${data.google_project.test_project.project_id}/locations/global/providers/box/connectors/box/versions/1" service_account = "${data.google_project.test_project.number}-compute@developer.gserviceaccount.com" description = "tf created description" config_variable { diff --git a/mmv1/templates/terraform/examples/integration_connectors_connection_sa.tf.erb b/mmv1/templates/terraform/examples/integration_connectors_connection_sa.tf.erb index 13e1274bef11..289648fd3699 100644 --- a/mmv1/templates/terraform/examples/integration_connectors_connection_sa.tf.erb +++ b/mmv1/templates/terraform/examples/integration_connectors_connection_sa.tf.erb @@ -30,7 +30,7 @@ resource "google_integration_connectors_connection" "<%= ctx[:primary_resource_i description = "tf updated description" location = "us-central1" service_account = "${data.google_project.test_project.number}-compute@developer.gserviceaccount.com" - connector_version = "projects/connectors-example/locations/global/providers/zendesk/connectors/zendesk/versions/1" + connector_version = "projects/${data.google_project.test_project.project_id}/locations/global/providers/box/connectors/box/versions/1" config_variable { key = "proxy_enabled" boolean_value = false diff --git a/mmv1/templates/terraform/pre_create/dialogflowcx_set_location_skip_default_obj.go.erb b/mmv1/templates/terraform/pre_create/dialogflowcx_set_location_skip_default_obj.go.erb new file mode 100644 index 000000000000..b48f3e7d1a24 --- /dev/null +++ b/mmv1/templates/terraform/pre_create/dialogflowcx_set_location_skip_default_obj.go.erb @@ -0,0 +1,43 @@ + +// extract location from the parent +location := "" + +if parts := regexp.MustCompile(`locations\/([^\/]*)\/`).FindStringSubmatch(d.Get("parent").(string)); parts != nil { + location = parts[1] +} else { + return fmt.Errorf( + "Saw %s when the parent is expected to contains location %s", + d.Get("parent"), + "projects/{{project}}/locations/{{location}}/...", + ) +} + +url = strings.Replace(url,"-dialogflow",fmt.Sprintf("%s-dialogflow",location),1) + +// if it's a default object Dialogflow creates for you, "Update" instead of "Create" +// Note: below we try to access fields that aren't present in the resource, because this custom code is reused across multiple Dialogflow resources that contain different fields. When the field isn't present, we deliberately ignore the error and the boolean is false. +isDefaultStartFlow, _ := d.Get("is_default_start_flow").(bool) +isDefaultWelcomeIntent, _ := d.Get("is_default_welcome_intent").(bool) +isDefaultNegativeIntent, _ := d.Get("is_default_negative_intent").(bool) +if isDefaultStartFlow || isDefaultWelcomeIntent || isDefaultNegativeIntent { + // hardcode the default object ID: + var defaultObjName string + if isDefaultStartFlow || isDefaultWelcomeIntent { + defaultObjName = "00000000-0000-0000-0000-000000000000" + } + if isDefaultNegativeIntent { + defaultObjName = "00000000-0000-0000-0000-000000000001" + } + + // Store the ID + d.Set("name", defaultObjName) + id, err := tpgresource.ReplaceVars(d, config, "<%= id_format(object) -%>") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // and defer to the Update method: + log.Printf("[DEBUG] Updating default <%= resource_name -%>") + return resource<%= resource_name -%>Update(d, meta) +} \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_delete/dialogflowcx_set_location_skip_default_obj.go.erb b/mmv1/templates/terraform/pre_delete/dialogflowcx_set_location_skip_default_obj.go.erb new file mode 100644 index 000000000000..520f992a5d8b --- /dev/null +++ b/mmv1/templates/terraform/pre_delete/dialogflowcx_set_location_skip_default_obj.go.erb @@ -0,0 +1,26 @@ + +// extract location from the parent +location := "" + +if parts := regexp.MustCompile(`locations\/([^\/]*)\/`).FindStringSubmatch(d.Get("parent").(string)); parts != nil { + location = parts[1] +} else { + return fmt.Errorf( + "Saw %s when the parent is expected to contains location %s", + d.Get("parent"), + "projects/{{project}}/locations/{{location}}/...", + ) +} + +url = strings.Replace(url,"-dialogflow",fmt.Sprintf("%s-dialogflow",location),1) + +// if it's a default object Dialogflow creates for you, skip deletion +// Note: below we try to access fields that aren't present in the resource, because this custom code is reused across multiple Dialogflow resources that contain different fields. When the field isn't present, we deliberately ignore the error and the boolean is false. +isDefaultStartFlow, _ := d.Get("is_default_start_flow").(bool) +isDefaultWelcomeIntent, _ := d.Get("is_default_welcome_intent").(bool) +isDefaultNegativeIntent, _ := d.Get("is_default_negative_intent").(bool) +if isDefaultStartFlow || isDefaultWelcomeIntent || isDefaultNegativeIntent { + // we can't delete these resources so do nothing + log.Printf("[DEBUG] Not deleting default <%= resource_name -%>") + return nil +} \ No newline at end of file diff --git a/mmv1/templates/terraform/resource.erb b/mmv1/templates/terraform/resource.erb index bdc6c65af96e..edeaa9a62f1c 100644 --- a/mmv1/templates/terraform/resource.erb +++ b/mmv1/templates/terraform/resource.erb @@ -155,6 +155,9 @@ func Resource<%= resource_name -%>() *schema.Resource { "<%= field.name -%>": { Type: <%= tf_type(field) -%>, Optional: true, +<% if field.immutable -%> + ForceNew: true, +<% end -%> <% unless field.default_value.nil? -%> Default: <%= go_literal(field.default_value) -%>, <% end -%> diff --git a/mmv1/third_party/terraform/go.mod.erb b/mmv1/third_party/terraform/go.mod.erb index b835277e598e..11ab70d515df 100644 --- a/mmv1/third_party/terraform/go.mod.erb +++ b/mmv1/third_party/terraform/go.mod.erb @@ -4,7 +4,7 @@ go 1.19 require ( cloud.google.com/go/bigtable v1.19.0 - github.com/GoogleCloudPlatform/declarative-resource-client-library v1.52.0 + github.com/GoogleCloudPlatform/declarative-resource-client-library v1.55.0 github.com/apparentlymart/go-cidr v1.1.0 github.com/davecgh/go-spew v1.1.1 github.com/dnaeon/go-vcr v1.0.1 diff --git a/mmv1/third_party/terraform/go.sum b/mmv1/third_party/terraform/go.sum index eff54c3e3205..40a46b2fcc1c 100644 --- a/mmv1/third_party/terraform/go.sum +++ b/mmv1/third_party/terraform/go.sum @@ -17,6 +17,8 @@ cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHS github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GoogleCloudPlatform/declarative-resource-client-library v1.52.0 h1:KswxXF4E5iWv2ggktqv265zOvwmXA3mgma3UQfYA4tU= github.com/GoogleCloudPlatform/declarative-resource-client-library v1.52.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.55.0 h1:MTP0IDIztk36l8ubHkEcL6lWMG8Enqu9AP3E4MoBFg0= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.55.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= diff --git a/mmv1/third_party/terraform/services/dialogflowcx/resource_dialogflowcx_flow_test.go b/mmv1/third_party/terraform/services/dialogflowcx/resource_dialogflowcx_flow_test.go index 13d878e7aaf5..3b75c3ae1d56 100644 --- a/mmv1/third_party/terraform/services/dialogflowcx/resource_dialogflowcx_flow_test.go +++ b/mmv1/third_party/terraform/services/dialogflowcx/resource_dialogflowcx_flow_test.go @@ -350,3 +350,203 @@ func testAccDialogflowCXFlow_full(context map[string]interface{}) string { } `, context) } + +func TestAccDialogflowCXFlow_defaultStartFlow(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{ + { + // Note: this isn't actually a "create" test; it creates a resource in the TF state, but is actually importing the default object GCP has created, then updating it. + Config: testAccDialogflowCXFlow_defaultStartFlow_create(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_dialogflow_cx_flow.default_start_flow", "name", "00000000-0000-0000-0000-000000000000"), + resource.TestCheckResourceAttrPair( + "google_dialogflow_cx_flow.default_start_flow", "id", + "google_dialogflow_cx_agent.agent", "start_flow", + ), + ), + }, + { + ResourceName: "google_dialogflow_cx_flow.default_start_flow", + ImportState: true, + ImportStateVerify: true, + }, + { + // This is testing updating the default object without having to create it in the TF state first. + Config: testAccDialogflowCXFlow_defaultStartFlow_update(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_dialogflow_cx_flow.default_start_flow", "name", "00000000-0000-0000-0000-000000000000"), + resource.TestCheckResourceAttrPair( + "google_dialogflow_cx_flow.default_start_flow", "id", + "google_dialogflow_cx_agent.agent", "start_flow", + ), + ), + }, + { + ResourceName: "google_dialogflow_cx_flow.default_start_flow", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccDialogflowCXFlow_defaultStartFlow_create(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "Hello" + } + repeat_count = 1 + } +} + + +resource "google_dialogflow_cx_flow" "default_start_flow" { + parent = google_dialogflow_cx_agent.agent.id + is_default_start_flow = true + display_name = "Default Start Flow" + description = "A start flow created along with the agent" + + nlu_settings { + classification_threshold = 0.3 + model_type = "MODEL_TYPE_STANDARD" + } + + transition_routes { + intent = google_dialogflow_cx_intent.default_welcome_intent.id + trigger_fulfillment { + messages { + text { + text = ["Response to default welcome intent."] + } + } + } + } + + event_handlers { + event = "custom-event" + trigger_fulfillment { + messages { + text { + text = ["Handle a custom event!"] + } + } + } + } + + event_handlers { + event = "sys.no-match-default" + trigger_fulfillment { + messages { + text { + text = ["This is the flow no-match response."] + } + } + } + } + + event_handlers { + event = "sys.no-input-default" + trigger_fulfillment { + messages { + text { + text = ["This is the flow no-input response."] + } + } + } + } +} +`, context) +} + +func testAccDialogflowCXFlow_defaultStartFlow_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "Hello" + } + repeat_count = 1 + } +} + + +resource "google_dialogflow_cx_flow" "default_start_flow" { + parent = google_dialogflow_cx_agent.agent.id + is_default_start_flow = true + display_name = "Default Start Flow" + description = "A start flow created along with the agent" + + nlu_settings { + classification_threshold = 0.5 + model_type = "MODEL_TYPE_STANDARD" + } + + transition_routes { + intent = google_dialogflow_cx_intent.default_welcome_intent.id + trigger_fulfillment { + messages { + text { + text = ["We can update the default welcome intent response!"] + } + } + } + } + + // delete the custom-event handler to show we can + + event_handlers { + event = "sys.no-match-default" + trigger_fulfillment { + messages { + text { + text = ["We an also update the no-match response!"] + } + } + } + } + + event_handlers { + event = "sys.no-input-default" + trigger_fulfillment { + messages { + text { + text = ["The no-input response has been updated too!"] + } + } + } + } +} +`, context) +} diff --git a/mmv1/third_party/terraform/services/dialogflowcx/resource_dialogflowcx_intent_test.go b/mmv1/third_party/terraform/services/dialogflowcx/resource_dialogflowcx_intent_test.go index 5ed1c94a0e2f..7d073bc64883 100644 --- a/mmv1/third_party/terraform/services/dialogflowcx/resource_dialogflowcx_intent_test.go +++ b/mmv1/third_party/terraform/services/dialogflowcx/resource_dialogflowcx_intent_test.go @@ -139,3 +139,134 @@ func testAccDialogflowCXIntent_full(context map[string]interface{}) string { } `, context) } + +func TestAccDialogflowCXIntent_defaultIntents(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{ + { + // Note: this isn't actually a "create" test; it creates resources in the TF state, but is actually importing the default objects GCP has created, then updating them. + Config: testAccDialogflowCXIntent_defaultIntents_create(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_dialogflow_cx_intent.default_negative_intent", "name", "00000000-0000-0000-0000-000000000001"), + resource.TestCheckResourceAttr("google_dialogflow_cx_intent.default_welcome_intent", "name", "00000000-0000-0000-0000-000000000000"), + ), + }, + { + ResourceName: "google_dialogflow_cx_intent.default_negative_intent", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, + }, + { + ResourceName: "google_dialogflow_cx_intent.default_welcome_intent", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, + }, + { + // This is testing updating the default objects without having to create them in the TF state first. + Config: testAccDialogflowCXIntent_defaultIntents_update(context), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_dialogflow_cx_intent.default_negative_intent", "name", "00000000-0000-0000-0000-000000000001"), + resource.TestCheckResourceAttr("google_dialogflow_cx_intent.default_welcome_intent", "name", "00000000-0000-0000-0000-000000000000"), + ), + }, + { + ResourceName: "google_dialogflow_cx_intent.default_negative_intent", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, + }, + { + ResourceName: "google_dialogflow_cx_intent.default_welcome_intent", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"labels", "terraform_labels"}, + }, + }, + }) +} + +func testAccDialogflowCXIntent_defaultIntents_create(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + +resource "google_dialogflow_cx_intent" "default_negative_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_negative_intent = true + display_name = "Default Negative Intent" + priority = 1 + is_fallback = true + training_phrases { + parts { + text = "Never match this phrase" + } + repeat_count = 1 + } +} + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "Hello" + } + repeat_count = 1 + } +} +`, context) +} + +func testAccDialogflowCXIntent_defaultIntents_update(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_dialogflow_cx_agent" "agent" { + display_name = "tf-test-dialogflowcx-agent%{random_suffix}" + location = "global" + default_language_code = "en" + time_zone = "America/New_York" +} + +resource "google_dialogflow_cx_intent" "default_negative_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_negative_intent = true + display_name = "Default Negative Intent" + priority = 1 + is_fallback = true + training_phrases { + parts { + text = "An updated phrase to never match." + } + repeat_count = 2 + } +} + +resource "google_dialogflow_cx_intent" "default_welcome_intent" { + parent = google_dialogflow_cx_agent.agent.id + is_default_welcome_intent = true + display_name = "Default Welcome Intent" + priority = 1 + training_phrases { + parts { + text = "An updated hello." + } + repeat_count = 2 + } +} +`, context) +} diff --git a/mmv1/third_party/terraform/services/integrationconnectors/resource_integration_connectors_connection_test.go b/mmv1/third_party/terraform/services/integrationconnectors/resource_integration_connectors_connection_test.go index e27e4db567cb..be84a69b54e5 100644 --- a/mmv1/third_party/terraform/services/integrationconnectors/resource_integration_connectors_connection_test.go +++ b/mmv1/third_party/terraform/services/integrationconnectors/resource_integration_connectors_connection_test.go @@ -76,7 +76,7 @@ func testAccIntegrationConnectorsConnection_full(context map[string]interface{}) description = "tf description" location = "us-central1" service_account = "${data.google_project.test_project.number}-compute@developer.gserviceaccount.com" - connector_version = "projects/connectors-example/locations/global/providers/zendesk/connectors/zendesk/versions/1" + connector_version = "projects/${data.google_project.test_project.project_id}/locations/global/providers/zendesk/connectors/zendesk/versions/1" config_variable { key = "proxy_enabled" boolean_value = false @@ -286,7 +286,7 @@ func testAccIntegrationConnectorsConnection_update(context map[string]interface{ description = "tf updated description" location = "us-central1" service_account = "${data.google_project.test_project.number}-compute@developer.gserviceaccount.com" - connector_version = "projects/connectors-example/locations/global/providers/zendesk/connectors/zendesk/versions/1" + connector_version = "projects/${data.google_project.test_project.project_id}/locations/global/providers/zendesk/connectors/zendesk/versions/1" config_variable { key = "proxy_enabled" boolean_value = true diff --git a/mmv1/third_party/terraform/services/logging/resource_logging_bucket_config.go b/mmv1/third_party/terraform/services/logging/resource_logging_bucket_config.go index 923d253a74d0..78258975e396 100644 --- a/mmv1/third_party/terraform/services/logging/resource_logging_bucket_config.go +++ b/mmv1/third_party/terraform/services/logging/resource_logging_bucket_config.go @@ -89,6 +89,28 @@ See [Enabling CMEK for Logging Buckets](https://cloud.google.com/logging/docs/ro }, }, }, + "index_configs": { + Type: schema.TypeSet, + MaxItems: 20, + Optional: true, + Description: `A list of indexed fields and related configuration data.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "field_path": { + Type: schema.TypeString, + Required: true, + Description: `The LogEntry field path to index.`, + }, + "type": { + Type: schema.TypeString, + Required: true, + Description: `The type of data in this index +Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation]( https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields) for details. +For example: jsonPayload.request.status`, + }, + }, + }, + }, } type loggingBucketConfigIDFunc func(d *schema.ResourceData, config *transport_tpg.Config) (string, error) @@ -207,6 +229,7 @@ func resourceLoggingBucketConfigCreate(d *schema.ResourceData, meta interface{}, obj["description"] = d.Get("description") obj["retentionDays"] = d.Get("retention_days") obj["cmekSettings"] = expandCmekSettings(d.Get("cmek_settings")) + obj["indexConfigs"] = expandIndexConfigs(d.Get("index_configs")) url, err := tpgresource.ReplaceVars(d, config, "{{LoggingBasePath}}projects/{{project}}/locations/{{location}}/buckets?bucketId={{bucket_id}}") if err != nil { @@ -291,6 +314,10 @@ func resourceLoggingBucketConfigRead(d *schema.ResourceData, meta interface{}) e return fmt.Errorf("Error setting cmek_settings: %s", err) } + if err := d.Set("index_configs", flattenIndexConfigs(res["indexConfigs"])); err != nil { + return fmt.Errorf("Error setting index_configs: %s", err) + } + return nil } @@ -311,6 +338,7 @@ func resourceLoggingBucketConfigUpdate(d *schema.ResourceData, meta interface{}) obj["retentionDays"] = d.Get("retention_days") obj["description"] = d.Get("description") obj["cmekSettings"] = expandCmekSettings(d.Get("cmek_settings")) + obj["indexConfigs"] = expandIndexConfigs(d.Get("index_configs")) updateMask := []string{} if d.HasChange("retention_days") { @@ -322,6 +350,10 @@ func resourceLoggingBucketConfigUpdate(d *schema.ResourceData, meta interface{}) if d.HasChange("cmek_settings") { updateMask = append(updateMask, "cmekSettings") } + if d.HasChange("index_configs") { + updateMask = append(updateMask, "indexConfigs") + } + url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) if err != nil { return err @@ -409,3 +441,44 @@ func flattenCmekSettings(cmekSettings interface{}) []map[string]interface{} { return []map[string]interface{}{data} } + +func expandIndexConfigs(maybeIndexConfigs interface{}) []map[string]interface{} { + if maybeIndexConfigs == nil { + return nil + } + + indexConfigs := maybeIndexConfigs.(*schema.Set).List() + transformedIndexConfigs := make([]map[string]interface{}, 0, len(indexConfigs)) + + for _, entry := range indexConfigs { + original := entry.(map[string]interface{}) + + transformed := map[string]interface{}{ + "fieldPath": original["field_path"], + "type": original["type"], + } + transformedIndexConfigs = append(transformedIndexConfigs, transformed) + } + return transformedIndexConfigs +} + +func flattenIndexConfigs(maybeIndexConfigs interface{}) []map[string]interface{} { + if maybeIndexConfigs == nil { + return nil + } + + indexConfigs := maybeIndexConfigs.([]interface{}) + flattenedIndexConfigs := make([]map[string]interface{}, 0, len(indexConfigs)) + + for _, entry := range indexConfigs { + indexConfig := entry.(map[string]interface{}) + if len(indexConfig) < 1 { + continue + } + flattenedIndexConfigs = append(flattenedIndexConfigs, map[string]interface{}{ + "field_path": indexConfig["fieldPath"], + "type": indexConfig["type"], + }) + } + return flattenedIndexConfigs +} diff --git a/mmv1/third_party/terraform/services/logging/resource_logging_bucket_config_test.go b/mmv1/third_party/terraform/services/logging/resource_logging_bucket_config_test.go index e9f8da482821..b4556484a999 100644 --- a/mmv1/third_party/terraform/services/logging/resource_logging_bucket_config_test.go +++ b/mmv1/third_party/terraform/services/logging/resource_logging_bucket_config_test.go @@ -123,42 +123,6 @@ func TestAccLoggingBucketConfigProject_analyticsEnabled(t *testing.T) { }) } -func TestAccLoggingBucketConfigProject_locked(t *testing.T) { - t.Parallel() - - context := map[string]interface{}{ - "random_suffix": acctest.RandString(t, 10), - "project_name": "tf-test-" + acctest.RandString(t, 10), - "org_id": envvar.GetTestOrgFromEnv(t), - "billing_account": envvar.GetTestBillingAccountFromEnv(t), - } - - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - Steps: []resource.TestStep{ - { - Config: testAccLoggingBucketConfigProject_locked(context, false), - }, - { - ResourceName: "google_logging_project_bucket_config.variable_locked", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"project"}, - }, - { - Config: testAccLoggingBucketConfigProject_locked(context, true), - }, - { - ResourceName: "google_logging_project_bucket_config.variable_locked", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"project"}, - }, - }, - }) -} - func TestAccLoggingBucketConfigProject_cmekSettings(t *testing.T) { t.Parallel() @@ -523,3 +487,129 @@ func getLoggingBucketConfigs(context map[string]interface{}) map[string]string { } } + +func TestAccLoggingBucketConfigOrganization_indexConfigs(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + "org_id": envvar.GetTestOrgFromEnv(t), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccLoggingBucketConfigOrganization_indexConfigs(context, "INDEX_TYPE_STRING", "INDEX_TYPE_STRING"), + }, + { + ResourceName: "google_logging_organization_bucket_config.basic", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"organization"}, + }, + { + Config: testAccLoggingBucketConfigOrganization_indexConfigs(context, "INDEX_TYPE_STRING", "INDEX_TYPE_INTEGER"), + }, + { + ResourceName: "google_logging_organization_bucket_config.basic", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"organization"}, + }, + }, + }) +} + +func testAccLoggingBucketConfigOrganization_indexConfigs(context map[string]interface{}, urlIndexType, statusIndexType string) string { + return fmt.Sprintf(acctest.Nprintf(` +data "google_organization" "default" { + organization = "%{org_id}" +} + +resource "google_logging_organization_bucket_config" "basic" { + organization = data.google_organization.default.organization + location = "global" + retention_days = 30 + description = "retention test 30 days" + bucket_id = "_Default" + + index_configs { + field_path = "jsonPayload.request.url" + type = "%s" + } + + index_configs { + field_path = "jsonPayload.response.status" + type = "%s" + } +} +`, context), urlIndexType, statusIndexType) +} + +func TestAccLoggingBucketConfigProject_indexConfigs(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project_name": "tf-test-" + acctest.RandString(t, 10), + "org_id": envvar.GetTestOrgFromEnv(t), + "billing_account": envvar.GetTestBillingAccountFromEnv(t), + "bucket_id": "tf-test-bucket-" + acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccLoggingBucketConfigProject_indexConfigs(context, "INDEX_TYPE_STRING", "INDEX_TYPE_STRING"), + }, + { + ResourceName: "google_logging_project_bucket_config.basic", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"project"}, + }, + { + Config: testAccLoggingBucketConfigProject_indexConfigs(context, "INDEX_TYPE_STRING", "INDEX_TYPE_INTEGER"), + }, + { + ResourceName: "google_logging_project_bucket_config.basic", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"project"}, + }, + }, + }) +} + +func testAccLoggingBucketConfigProject_indexConfigs(context map[string]interface{}, urlIndexType, statusIndexType string) string { + return fmt.Sprintf(acctest.Nprintf(` + +resource "google_project" "default" { + project_id = "%{project_name}" + name = "%{project_name}" + org_id = "%{org_id}" + billing_account = "%{billing_account}" +} + +resource "google_logging_project_bucket_config" "basic" { + project = google_project.default.name + location = "us-east1" + retention_days = 30 + description = "retention test 30 days" + bucket_id = "%{bucket_id}" + + index_configs { + field_path = "jsonPayload.request.url" + type = "%s" + } + + index_configs { + field_path = "jsonPayload.response.status" + type = "%s" + } +} +`, context), urlIndexType, statusIndexType) +} diff --git a/mmv1/third_party/terraform/services/logging/resource_logging_project_bucket_config.go b/mmv1/third_party/terraform/services/logging/resource_logging_project_bucket_config.go index 0397e956f4da..a2d62b6de97f 100644 --- a/mmv1/third_party/terraform/services/logging/resource_logging_project_bucket_config.go +++ b/mmv1/third_party/terraform/services/logging/resource_logging_project_bucket_config.go @@ -106,6 +106,28 @@ See [Enabling CMEK for Logging Buckets](https://cloud.google.com/logging/docs/ro }, }, }, + "index_configs": { + Type: schema.TypeSet, + MaxItems: 20, + Optional: true, + Description: `A list of indexed fields and related configuration data.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "field_path": { + Type: schema.TypeString, + Required: true, + Description: `The LogEntry field path to index.`, + }, + "type": { + Type: schema.TypeString, + Required: true, + Description: `The type of data in this index +Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation]( https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields) for details. +For example: jsonPayload.request.status`, + }, + }, + }, + }, } func projectBucketConfigID(d *schema.ResourceData, config *transport_tpg.Config) (string, error) { @@ -192,6 +214,7 @@ func resourceLoggingProjectBucketConfigCreate(d *schema.ResourceData, meta inter obj["retentionDays"] = d.Get("retention_days") obj["analyticsEnabled"] = d.Get("enable_analytics") obj["cmekSettings"] = expandCmekSettings(d.Get("cmek_settings")) + obj["indexConfigs"] = expandIndexConfigs(d.Get("index_configs")) url, err := tpgresource.ReplaceVars(d, config, "{{LoggingBasePath}}projects/{{project}}/locations/{{location}}/buckets?bucketId={{bucket_id}}") if err != nil { @@ -282,6 +305,10 @@ func resourceLoggingProjectBucketConfigRead(d *schema.ResourceData, meta interfa return fmt.Errorf("Error setting cmek_settings: %s", err) } + if err := d.Set("index_configs", flattenIndexConfigs(res["indexConfigs"])); err != nil { + return fmt.Errorf("Error setting index_configs: %s", err) + } + return nil } @@ -325,6 +352,8 @@ func resourceLoggingProjectBucketConfigUpdate(d *schema.ResourceData, meta inter obj["retentionDays"] = d.Get("retention_days") obj["description"] = d.Get("description") obj["cmekSettings"] = expandCmekSettings(d.Get("cmek_settings")) + obj["indexConfigs"] = expandIndexConfigs(d.Get("index_configs")) + updateMask := []string{} if d.HasChange("retention_days") { updateMask = append(updateMask, "retentionDays") @@ -335,6 +364,10 @@ func resourceLoggingProjectBucketConfigUpdate(d *schema.ResourceData, meta inter if d.HasChange("cmek_settings") { updateMask = append(updateMask, "cmekSettings") } + if d.HasChange("index_configs") { + updateMask = append(updateMask, "indexConfigs") + } + url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) if err != nil { return err diff --git a/mmv1/third_party/terraform/services/spanner/resource_spanner_instance_test.go b/mmv1/third_party/terraform/services/spanner/resource_spanner_instance_test.go index fe3ae91f99a3..9d753eaf7e79 100644 --- a/mmv1/third_party/terraform/services/spanner/resource_spanner_instance_test.go +++ b/mmv1/third_party/terraform/services/spanner/resource_spanner_instance_test.go @@ -142,58 +142,6 @@ func TestAccSpannerInstance_virtualUpdate(t *testing.T) { }) } -func TestAccSpannerInstance_basicWithAutoscalingUsingNodeConfig(t *testing.T) { - // Randomness - acctest.SkipIfVcr(t) - t.Parallel() - - displayName := fmt.Sprintf("spanner-test-%s-dname", acctest.RandString(t, 10)) - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - CheckDestroy: testAccCheckSpannerInstanceDestroyProducer(t), - Steps: []resource.TestStep{ - { - Config: testAccSpannerInstance_basicWithAutoscalerConfigUsingNodeAsConfigs(displayName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("google_spanner_instance.basic", "state"), - ), - }, - { - ResourceName: "google_spanner_instance.basic", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAccSpannerInstance_basicWithAutoscalingUsingProcessingUnitConfig(t *testing.T) { - // Randomness - acctest.SkipIfVcr(t) - t.Parallel() - - displayName := fmt.Sprintf("spanner-test-%s-dname", acctest.RandString(t, 10)) - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - CheckDestroy: testAccCheckSpannerInstanceDestroyProducer(t), - Steps: []resource.TestStep{ - { - Config: testAccSpannerInstance_basicWithAutoscalerConfigUsingProcessingUnitsAsConfigs(displayName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("google_spanner_instance.basic", "state"), - ), - }, - { - ResourceName: "google_spanner_instance.basic", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - func testAccSpannerInstance_basic(name string) string { return fmt.Sprintf(` resource "google_spanner_instance" "basic" { @@ -255,53 +203,3 @@ resource "google_spanner_instance" "basic" { } `, name, name, virtual) } - -func testAccSpannerInstance_basicWithAutoscalerConfigUsingNodeAsConfigs(name string) string { - return fmt.Sprintf(` -resource "google_spanner_instance" "basic" { - name = "%s" - config = "regional-us-central1" - display_name = "%s-dname" - num_nodes = 1 - autoscaling_config { - autoscaling_limits { - max_limit { - max_nodes = 2 - } - min_limit { - min_nodes = 1 - } - } - autoscaling_targets { - high_priority_cpu_utilization_percent = 65 - storage_utilization_percent = 95 - } - } -} -`, name, name) -} - -func testAccSpannerInstance_basicWithAutoscalerConfigUsingProcessingUnitsAsConfigs(name string) string { - return fmt.Sprintf(` -resource "google_spanner_instance" "basic" { - name = "%s" - config = "regional-us-central1" - display_name = "%s-dname" - num_nodes = 1 - autoscaling_config { - autoscaling_limits { - max_limit { - max_processing_units = 2000 - } - min_limit { - min_processing_units = 1000 - } - } - autoscaling_targets { - high_priority_cpu_utilization_percent = 65 - storage_utilization_percent = 95 - } - } -} -`, name, name) -} diff --git a/mmv1/third_party/terraform/website/docs/r/logging_billing_account_bucket_config.html.markdown b/mmv1/third_party/terraform/website/docs/r/logging_billing_account_bucket_config.html.markdown index 808490d87161..d782ccd2f356 100644 --- a/mmv1/third_party/terraform/website/docs/r/logging_billing_account_bucket_config.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/logging_billing_account_bucket_config.html.markdown @@ -16,14 +16,30 @@ Manages a billing account level logging bucket config. For more information see ```hcl data "google_billing_account" "default" { - billing_account = "00AA00-000AAA-00AA0A" + billing_account = "00AA00-000AAA-00AA0A" } resource "google_logging_billing_account_bucket_config" "basic" { - billing_account = data.google_billing_account.default.billing_account - location = "global" - retention_days = 30 - bucket_id = "_Default" + billing_account = data.google_billing_account.default.billing_account + location = "global" + retention_days = 30 + bucket_id = "_Default" +} +``` + +Create logging bucket with index configs + +```hcl +resource "google_logging_billing_account_bucket_config" "example-billing-account-bucket-index-configs" { + folder = data.google_billing_account.default.billing_account + location = "global" + retention_days = 30 + bucket_id = "_Default" + + index_configs = { + file_path = "jsonPayload.request.status" + type = "INDEX_TYPE_STRING" + } } ``` @@ -41,6 +57,15 @@ The following arguments are supported: * `retention_days` - (Optional) Logs will be retained by default for this amount of time, after which they will automatically be deleted. The minimum retention period is 1 day. If this value is set to zero at bucket creation time, the default time of 30 days will be used. Bucket retention can not be increased on buckets outside of projects. +* `index_configs` - (Optional) A list of indexed fields and related configuration data. Structure is [documented below](#nested_index_configs). + +The `index_configs` block supports: + +* `field_path` - The LogEntry field path to index. + Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation]( https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields) for details. + +* `type` - The type of data in this index. Allowed types include `INDEX_TYPE_UNSPECIFIED`, `INDEX_TYPE_STRING` and `INDEX_TYPE_INTEGER`. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are diff --git a/mmv1/third_party/terraform/website/docs/r/logging_folder_bucket_config.html.markdown b/mmv1/third_party/terraform/website/docs/r/logging_folder_bucket_config.html.markdown index ab32ce7925f9..8824802a392c 100644 --- a/mmv1/third_party/terraform/website/docs/r/logging_folder_bucket_config.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/logging_folder_bucket_config.html.markdown @@ -16,15 +16,20 @@ Manages a folder-level logging bucket config. For more information see ```hcl resource "google_folder" "default" { - display_name = "some-folder-name" - parent = "organizations/123456789" + display_name = "some-folder-name" + parent = "organizations/123456789" } resource "google_logging_folder_bucket_config" "basic" { - folder = google_folder.default.name - location = "global" - retention_days = 30 - bucket_id = "_Default" + folder = google_folder.default.name + location = "global" + retention_days = 30 + bucket_id = "_Default" + + index_configs = { + file_path = "jsonPayload.request.status" + type = "INDEX_TYPE_STRING" + } } ``` @@ -42,6 +47,15 @@ The following arguments are supported: * `retention_days` - (Optional) Logs will be retained by default for this amount of time, after which they will automatically be deleted. The minimum retention period is 1 day. If this value is set to zero at bucket creation time, the default time of 30 days will be used. Bucket retention can not be increased on buckets outside of projects. +* `index_configs` - (Optional) A list of indexed fields and related configuration data. Structure is [documented below](#nested_index_configs). + +The `index_configs` block supports: + +* `field_path` - The LogEntry field path to index. + Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation]( https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields) for details. + +* `type` - The type of data in this index. Allowed types include `INDEX_TYPE_UNSPECIFIED`, `INDEX_TYPE_STRING` and `INDEX_TYPE_INTEGER`. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are diff --git a/mmv1/third_party/terraform/website/docs/r/logging_organization_bucket_config.html.markdown b/mmv1/third_party/terraform/website/docs/r/logging_organization_bucket_config.html.markdown index ceb8026e7b3c..090154f9687a 100644 --- a/mmv1/third_party/terraform/website/docs/r/logging_organization_bucket_config.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/logging_organization_bucket_config.html.markdown @@ -20,10 +20,15 @@ data "google_organization" "default" { } resource "google_logging_organization_bucket_config" "basic" { - organization = data.google_organization.default.organization - location = "global" - retention_days = 30 - bucket_id = "_Default" + organization = data.google_organization.default.organization + location = "global" + retention_days = 30 + bucket_id = "_Default" + + index_configs = { + file_path = "jsonPayload.request.status" + type = "INDEX_TYPE_STRING" + } } ``` @@ -41,6 +46,15 @@ The following arguments are supported: * `retention_days` - (Optional) Logs will be retained by default for this amount of time, after which they will automatically be deleted. The minimum retention period is 1 day. If this value is set to zero at bucket creation time, the default time of 30 days will be used. Bucket retention can not be increased on buckets outside of projects. +* `index_configs` - (Optional) A list of indexed fields and related configuration data. Structure is [documented below](#nested_index_configs). + +The `index_configs` block supports: + +* `field_path` - The LogEntry field path to index. + Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation]( https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields) for details. + +* `type` - The type of data in this index. Allowed types include `INDEX_TYPE_UNSPECIFIED`, `INDEX_TYPE_STRING` and `INDEX_TYPE_INTEGER`. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are diff --git a/mmv1/third_party/terraform/website/docs/r/logging_project_bucket_config.html.markdown b/mmv1/third_party/terraform/website/docs/r/logging_project_bucket_config.html.markdown index b9d050eef717..f9781d54350e 100644 --- a/mmv1/third_party/terraform/website/docs/r/logging_project_bucket_config.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/logging_project_bucket_config.html.markdown @@ -93,6 +93,22 @@ resource "google_logging_project_bucket_config" "example-project-bucket-cmek-set } ``` +Create logging bucket with index configs + +```hcl +resource "google_logging_project_bucket_config" "example-project-bucket-index-configs" { + project = "project_id" + location = "global" + retention_days = 30 + bucket_id = "custom-bucket" + + index_configs = { + file_path = "jsonPayload.request.status" + type = "INDEX_TYPE_STRING" + } +} +``` + ## Argument Reference The following arguments are supported: @@ -113,6 +129,7 @@ The following arguments are supported: * `cmek_settings` - (Optional) The CMEK settings of the log bucket. If present, new log entries written to this log bucket are encrypted using the CMEK key provided in this configuration. If a log bucket has CMEK settings, the CMEK settings cannot be disabled later by updating the log bucket. Changing the KMS key is allowed. Structure is [documented below](#nested_cmek_settings). +* `index_configs` - (Optional) A list of indexed fields and related configuration data. Structure is [documented below](#nested_index_configs). The `cmek_settings` block supports: @@ -136,6 +153,13 @@ This is a read-only field used to convey the specific configured CryptoKeyVersio Before enabling CMEK for a logging bucket, you must first assign the cloudkms.cryptoKeyEncrypterDecrypter role to the service account associated with the project for which CMEK will apply. Use [v2.getCmekSettings](https://cloud.google.com/logging/docs/reference/v2/rest/v2/TopLevel/getCmekSettings#google.logging.v2.ConfigServiceV2.GetCmekSettings) to obtain the service account ID. See [Enabling CMEK for Logging Buckets](https://cloud.google.com/logging/docs/routing/managed-encryption-storage) for more information. +The `index_configs` block supports: + +* `field_path` - The LogEntry field path to index. +Note that some paths are automatically indexed, and other paths are not eligible for indexing. See [indexing documentation]( https://cloud.google.com/logging/docs/view/advanced-queries#indexed-fields) for details. + +* `type` - The type of data in this index. Allowed types include `INDEX_TYPE_UNSPECIFIED`, `INDEX_TYPE_STRING` and `INDEX_TYPE_INTEGER`. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are diff --git a/tools/diff-processor/README.md b/tools/diff-processor/README.md index 09f7676be083..7cd410dd854a 100644 --- a/tools/diff-processor/README.md +++ b/tools/diff-processor/README.md @@ -11,8 +11,12 @@ make clone OWNER_REPO=modular-magician/terraform-provider-google # build based on old / new dirs make build OLD_REF=branch_or_commit NEW_REF=branch_or_commit -# Run the binary -bin/diff-processor +# Run breaking change detection on the difference between OLD_REF and NEW_REF +bin/diff-processor breaking-changes + +# Add labels to a PR based on the resources changed between OLD_REF and NEW_REF +# The token used must have write access to issues +GITHUB_TOKEN=github_token bin/diff-processor add-labels PR_ID [--dry-run] ``` ## Test diff --git a/tools/diff-processor/cmd/add_labels.go b/tools/diff-processor/cmd/add_labels.go new file mode 100644 index 000000000000..9fa068a29594 --- /dev/null +++ b/tools/diff-processor/cmd/add_labels.go @@ -0,0 +1,103 @@ +package cmd + +import ( + newProvider "google/provider/new/google/provider" + oldProvider "google/provider/old/google/provider" + + "fmt" + "strconv" + "strings" + + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/labels" + "github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler/labeler" + "github.com/spf13/cobra" + "golang.org/x/exp/maps" +) + +const addLabelsDesc = `Add labels to a PR based on changed resources.` + +type addLabelsOptions struct { + rootOptions *rootOptions + computeSchemaDiff func() diff.SchemaDiff + enrolledTeamsYaml []byte + getIssue func(repository string, id uint64) (labeler.Issue, error) + updateIssues func(repository string, issueUpdates []labeler.IssueUpdate, dryRun bool) + dryRun bool +} + +func newAddLabelsCmd(rootOptions *rootOptions) *cobra.Command { + o := &addLabelsOptions{ + rootOptions: rootOptions, + computeSchemaDiff: func() diff.SchemaDiff { + return diff.ComputeSchemaDiff(oldProvider.ResourceMap(), newProvider.ResourceMap()) + }, + enrolledTeamsYaml: labeler.EnrolledTeamsYaml, + getIssue: labels.GetIssue, + updateIssues: labeler.UpdateIssues, + } + cmd := &cobra.Command{ + Use: "add-labels PR_ID [--dry-run]", + Short: addLabelsDesc, + Long: addLabelsDesc, + Args: cobra.ExactArgs(1), + RunE: func(c *cobra.Command, args []string) error { + return o.run(args) + }, + } + cmd.Flags().BoolVar(&o.dryRun, "dry-run", false, "Do a dry run without updating labels") + return cmd +} +func (o *addLabelsOptions) run(args []string) error { + prId, err := strconv.ParseUint(args[0], 10, 0) + if err != nil { + return fmt.Errorf("PR_ID must be an unsigned integer: %w", err) + } + + repository := "GoogleCloudPlatform/magic-modules" + issue, err := o.getIssue(repository, prId) + + if err != nil { + return fmt.Errorf("Error retrieving PR data: %w", err) + } + + hasServiceLabels := false + oldLabels := make(map[string]struct{}, len(issue.Labels)) + for _, label := range issue.Labels { + oldLabels[label.Name] = struct{}{} + if strings.HasPrefix(label.Name, "services/") { + hasServiceLabels = true + } + } + if hasServiceLabels { + return nil + } + + schemaDiff := o.computeSchemaDiff() + affectedResources := maps.Keys(schemaDiff) + regexpLabels, err := labeler.BuildRegexLabels(o.enrolledTeamsYaml) + if err != nil { + return fmt.Errorf("Error building regex labels: %w", err) + } + + newLabels := make(map[string]struct{}, len(oldLabels)) + for label, _ := range oldLabels { + newLabels[label] = struct{}{} + } + for _, label := range labeler.ComputeLabels(affectedResources, regexpLabels) { + newLabels[label] = struct{}{} + } + + // Only update the issue if new labels should be added + if len(newLabels) != len(oldLabels) { + issueUpdate := labeler.IssueUpdate{ + Number: prId, + Labels: maps.Keys(newLabels), + OldLabels: maps.Keys(oldLabels), + } + + o.updateIssues(repository, []labeler.IssueUpdate{issueUpdate}, o.dryRun) + } + + return nil +} diff --git a/tools/diff-processor/cmd/add_labels_test.go b/tools/diff-processor/cmd/add_labels_test.go new file mode 100644 index 000000000000..62906187dd27 --- /dev/null +++ b/tools/diff-processor/cmd/add_labels_test.go @@ -0,0 +1,294 @@ +package cmd + +import ( + _ "embed" + "errors" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" + "github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler/labeler" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "testing" +) + +var enrolledTeamsYaml = []byte(` +services/google-x: + resources: + - google_x_resource`) + +func TestAddLabelsCmdRun(t *testing.T) { + cases := map[string]struct { + args []string + oldResourceMap map[string]*schema.Resource + newResourceMap map[string]*schema.Resource + githubIssue *labeler.Issue + updateErrors bool + expectedLabels []string + expectError bool + }{ + "empty resource map": { + args: []string{"12345"}, + oldResourceMap: map[string]*schema.Resource{}, + newResourceMap: map[string]*schema.Resource{}, + githubIssue: &labeler.Issue{ + Number: 12345, + Body: "Unused", + Labels: []labeler.Label{}, + PullRequest: map[string]any{}, + }, + expectedLabels: nil, + }, + "resource changed that doesn't match mapping": { + args: []string{"12345"}, + oldResourceMap: map[string]*schema.Resource{ + "google_y_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Optional: true}, + "field_b": {Description: "beep", Optional: true}, + }, + }, + }, + newResourceMap: map[string]*schema.Resource{ + "google_y_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Required: true}, + }, + }, + }, + githubIssue: &labeler.Issue{ + Number: 12345, + Body: "Unused", + Labels: []labeler.Label{}, + PullRequest: map[string]any{}, + }, + expectedLabels: nil, + }, + "resource matches mapping but isn't changed": { + args: []string{"12345"}, + oldResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Optional: true}, + "field_b": {Description: "beep", Optional: true}, + }, + }, + }, + newResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Optional: true}, + "field_b": {Description: "beep", Optional: true}, + }, + }, + }, + githubIssue: &labeler.Issue{ + Number: 12345, + Body: "Unused", + Labels: []labeler.Label{}, + PullRequest: map[string]any{}, + }, + expectedLabels: nil, + }, + "resource changed that matches mapping": { + args: []string{"12345"}, + oldResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Optional: true}, + "field_b": {Description: "beep", Optional: true}, + }, + }, + }, + newResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Required: true}, + }, + }, + }, + githubIssue: &labeler.Issue{ + Number: 12345, + Body: "Unused", + Labels: []labeler.Label{}, + PullRequest: map[string]any{}, + }, + expectedLabels: []string{"services/google-x"}, + }, + "service labels are deduped": { + args: []string{"12345"}, + oldResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Optional: true}, + "field_b": {Description: "beep", Optional: true}, + }, + }, + "google_x_resource2": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Optional: true}, + "field_b": {Description: "beep", Optional: true}, + }, + }, + }, + newResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Required: true}, + }, + }, + "google_x_resource2": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Required: true}, + }, + }, + }, + githubIssue: &labeler.Issue{ + Number: 12345, + Body: "Unused", + Labels: []labeler.Label{}, + PullRequest: map[string]any{}, + }, + expectedLabels: []string{"services/google-x"}, + }, + "existing labels are preserved": { + args: []string{"12345"}, + oldResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Optional: true}, + "field_b": {Description: "beep", Optional: true}, + }, + }, + }, + newResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Required: true}, + }, + }, + }, + githubIssue: &labeler.Issue{ + Number: 12345, + Body: "Unused", + Labels: []labeler.Label{{Name: "override-breaking-change"}}, + PullRequest: map[string]any{}, + }, + expectedLabels: []string{"override-breaking-change", "services/google-x"}, + }, + "existing service label prevents new service labels": { + args: []string{"12345"}, + oldResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Optional: true}, + "field_b": {Description: "beep", Optional: true}, + }, + }, + }, + newResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Required: true}, + }, + }, + }, + githubIssue: &labeler.Issue{ + Number: 12345, + Body: "Unused", + Labels: []labeler.Label{{Name: "services/google-z"}}, + PullRequest: map[string]any{}, + }, + // nil indicates that the issue won't be updated at all (preserving existing labels) + expectedLabels: nil, + }, + "error fetching issue": { + args: []string{"12345"}, + oldResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Optional: true}, + "field_b": {Description: "beep", Optional: true}, + }, + }, + }, + newResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Required: true}, + }, + }, + }, + githubIssue: nil, + expectError: true, + }, + "error parsing PR id": { + args: []string{"foobar"}, + oldResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Optional: true}, + "field_b": {Description: "beep", Optional: true}, + }, + }, + }, + newResourceMap: map[string]*schema.Resource{ + "google_x_resource": { + Schema: map[string]*schema.Schema{ + "field_a": {Description: "beep", Required: true}, + }, + }, + }, + githubIssue: &labeler.Issue{ + Number: 12345, + Body: "Unused", + Labels: []labeler.Label{{Name: "services/google-z"}}, + PullRequest: map[string]any{}, + }, + expectError: true, + }, + } + + for tn, tc := range cases { + tc := tc + t.Run(tn, func(t *testing.T) { + t.Parallel() + + var gotLabels []string + o := addLabelsOptions{ + computeSchemaDiff: func() diff.SchemaDiff { + return diff.ComputeSchemaDiff(tc.oldResourceMap, tc.newResourceMap) + }, + enrolledTeamsYaml: enrolledTeamsYaml, + getIssue: func(repository string, id uint64) (labeler.Issue, error) { + if tc.githubIssue != nil { + return *tc.githubIssue, nil + } + var issue labeler.Issue + return issue, errors.New("Error getting issue") + }, + updateIssues: func(repository string, issueUpdates []labeler.IssueUpdate, dryRun bool) { + gotLabels = issueUpdates[0].Labels + }, + } + + err := o.run([]string{"1"}) + if err != nil { + if tc.expectError { + return + } + t.Errorf("Error running command: %s", err) + } + + if tc.expectedLabels == nil { + if gotLabels != nil { + t.Errorf("Expected updateIssues to not run. Got %v as new labels", gotLabels) + } + } + + less := func(a, b string) bool { return a < b } + if (len(tc.expectedLabels) > 0 || len(gotLabels) > 0) && !cmp.Equal(tc.expectedLabels, gotLabels, cmpopts.SortSlices(less)) { + t.Errorf("Unexpected final labels. Want %v, got %v", tc.expectedLabels, gotLabels) + } + }) + } +} diff --git a/tools/diff-processor/cmd/breaking_changes.go b/tools/diff-processor/cmd/breaking_changes.go index 1544f6529a00..9ac78af0c94c 100644 --- a/tools/diff-processor/cmd/breaking_changes.go +++ b/tools/diff-processor/cmd/breaking_changes.go @@ -8,8 +8,8 @@ import ( "os" "sort" - "github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor/diff" - "github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor/rules" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/rules" "github.com/spf13/cobra" ) diff --git a/tools/diff-processor/cmd/breaking_changes_test.go b/tools/diff-processor/cmd/breaking_changes_test.go index f4e25966b4b8..735dc593c623 100644 --- a/tools/diff-processor/cmd/breaking_changes_test.go +++ b/tools/diff-processor/cmd/breaking_changes_test.go @@ -2,7 +2,7 @@ package cmd import ( "bytes" - "github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor/diff" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "strings" "testing" diff --git a/tools/diff-processor/cmd/root.go b/tools/diff-processor/cmd/root.go index ffa8c6dc71b1..1a3674921745 100644 --- a/tools/diff-processor/cmd/root.go +++ b/tools/diff-processor/cmd/root.go @@ -21,6 +21,7 @@ func newRootCmd() (*cobra.Command, *rootOptions, error) { SilenceErrors: true, } cmd.AddCommand(newBreakingChangesCmd(o)) + cmd.AddCommand(newAddLabelsCmd(o)) return cmd, o, nil } diff --git a/tools/diff-processor/go.mod b/tools/diff-processor/go.mod index 88bcd44634ca..880f54da41c9 100644 --- a/tools/diff-processor/go.mod +++ b/tools/diff-processor/go.mod @@ -1,4 +1,4 @@ -module github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor +module github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor go 1.19 @@ -6,9 +6,12 @@ replace google/provider/old => ./old replace google/provider/new => ./new -replace github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor => ./ +replace github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor => ./ + +replace github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler => ../issue-labeler require ( + github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler v0.0.0-00010101000000-000000000000 github.com/davecgh/go-spew v1.1.1 github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 @@ -42,7 +45,7 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/gammazero/deque v0.0.0-20180920172122-f6adf94963e4 // indirect github.com/gammazero/workerpool v0.0.0-20181230203049-86a96b5d5d92 // indirect - github.com/golang/glog v1.1.0 // indirect + github.com/golang/glog v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cpy v0.0.0-20211218193943-a9c933c06932 // indirect @@ -106,5 +109,6 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e // indirect google.golang.org/grpc v1.57.0 // indirect google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/tools/diff-processor/go.sum b/tools/diff-processor/go.sum index 42705ad96cf3..8aea913951ea 100644 --- a/tools/diff-processor/go.sum +++ b/tools/diff-processor/go.sum @@ -98,8 +98,8 @@ github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw= +github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -469,6 +469,7 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/diff-processor/labels/get_issue.go b/tools/diff-processor/labels/get_issue.go new file mode 100644 index 000000000000..fe9b5a235d8a --- /dev/null +++ b/tools/diff-processor/labels/get_issue.go @@ -0,0 +1,46 @@ +package labels + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + + labeler "github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler/labeler" +) + +func GetIssue(repository string, id uint64) (labeler.Issue, error) { + var issue labeler.Issue + client := &http.Client{} + url := fmt.Sprintf("https://api.github.com/repos/%s/issues/%d", repository, id) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return issue, fmt.Errorf("Error creating request: %w", err) + } + req.Header.Add("Accept", "application/vnd.github+json") + req.Header.Add("Authorization", "Bearer "+os.Getenv("GITHUB_TOKEN")) + req.Header.Add("X-GitHub-Api-Version", "2022-11-28") + resp, err := client.Do(req) + if err != nil { + return issue, fmt.Errorf("Error getting issue: %w", err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return issue, fmt.Errorf("Error reading response body: %w", err) + } + + err = json.Unmarshal(body, &issue) + if err != nil { + var errorResponse labeler.ErrorResponse + err = json.Unmarshal(body, &errorResponse) + if err != nil { + return issue, fmt.Errorf("Error unmarshalling response body: %w", err) + } + return issue, fmt.Errorf("Error from API: %s", errorResponse.Message) + } + + return issue, nil +} diff --git a/tools/diff-processor/main.go b/tools/diff-processor/main.go index 2795fad34354..f7693547c404 100644 --- a/tools/diff-processor/main.go +++ b/tools/diff-processor/main.go @@ -1,8 +1,9 @@ - package main + import ( - "github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor/cmd" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/cmd" ) + func main() { cmd.Execute() -} \ No newline at end of file +} diff --git a/tools/diff-processor/rules/breaking_changes.go b/tools/diff-processor/rules/breaking_changes.go index 2adaf4192720..4a3b27b365da 100644 --- a/tools/diff-processor/rules/breaking_changes.go +++ b/tools/diff-processor/rules/breaking_changes.go @@ -1,7 +1,7 @@ package rules import ( - "github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor/diff" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" ) func ComputeBreakingChanges(schemaDiff diff.SchemaDiff) []string { diff --git a/tools/diff-processor/rules/breaking_changes_test.go b/tools/diff-processor/rules/breaking_changes_test.go index 5c11cf63287e..9c63981c3391 100644 --- a/tools/diff-processor/rules/breaking_changes_test.go +++ b/tools/diff-processor/rules/breaking_changes_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor/diff" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/tools/diff-processor/rules/rules_resource_schema.go b/tools/diff-processor/rules/rules_resource_schema.go index 5cf6ca11915a..68ce9ea43650 100644 --- a/tools/diff-processor/rules/rules_resource_schema.go +++ b/tools/diff-processor/rules/rules_resource_schema.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor/diff" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" ) // ResourceSchemaRule provides structure for diff --git a/tools/diff-processor/rules/rules_resource_schema_test.go b/tools/diff-processor/rules/rules_resource_schema_test.go index ea92c9740c6c..da8b488bf69e 100644 --- a/tools/diff-processor/rules/rules_resource_schema_test.go +++ b/tools/diff-processor/rules/rules_resource_schema_test.go @@ -3,7 +3,7 @@ package rules import ( "testing" - "github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor/diff" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/diff" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" diff --git a/tools/diff-processor/rules/utility.go b/tools/diff-processor/rules/utility.go index 43437a6e579c..78e82b63fcb8 100644 --- a/tools/diff-processor/rules/utility.go +++ b/tools/diff-processor/rules/utility.go @@ -3,7 +3,7 @@ package rules import ( "fmt" - "github.com/GoogleCloudPlatform/magic-modules/.ci/diff-processor/constants" + "github.com/GoogleCloudPlatform/magic-modules/tools/diff-processor/constants" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/tools/issue-labeler/go.mod b/tools/issue-labeler/go.mod index 4a4ba3345cd4..d0e17c929f42 100644 --- a/tools/issue-labeler/go.mod +++ b/tools/issue-labeler/go.mod @@ -1,4 +1,4 @@ -module github.com/GoogleCloudPlatform/magic-modules/issue-labeler/tools/issue-labeler +module github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler go 1.19 diff --git a/tools/issue-labeler/backfill.go b/tools/issue-labeler/labeler/backfill.go similarity index 82% rename from tools/issue-labeler/backfill.go rename to tools/issue-labeler/labeler/backfill.go index e5cb8f4b728a..dd19ebc00091 100644 --- a/tools/issue-labeler/backfill.go +++ b/tools/issue-labeler/labeler/backfill.go @@ -1,4 +1,4 @@ -package main +package labeler import ( "bytes" @@ -17,7 +17,7 @@ type ErrorResponse struct { } type Issue struct { - Number int + Number uint64 Body string Labels []Label PullRequest map[string]any `json:"pull_request"` @@ -28,7 +28,7 @@ type Label struct { } type IssueUpdate struct { - Number int + Number uint64 Labels []string OldLabels []string } @@ -37,13 +37,13 @@ type IssueUpdateBody struct { Labels []string `json:"labels"` } -func getIssues(since string) []Issue { +func GetIssues(repository, since string) []Issue { client := &http.Client{} done := false page := 1 var issues []Issue for !done { - url := fmt.Sprintf("https://api.github.com/repos/hashicorp/terraform-provider-google/issues?since=%s&per_page=100&page=%d", since, page) + url := fmt.Sprintf("https://api.github.com/repos/%s/issues?since=%s&per_page=100&page=%d", repository, since, page) req, err := http.NewRequest("GET", url, nil) if err != nil { glog.Exitf("Error creating request: %v", err) @@ -78,7 +78,7 @@ func getIssues(since string) []Issue { return issues } -func computeIssueUpdates(issues []Issue, regexpLabels []regexpLabel) []IssueUpdate { +func ComputeIssueUpdates(issues []Issue, regexpLabels []RegexpLabel) []IssueUpdate { var issueUpdates []IssueUpdate for _, issue := range issues { @@ -103,8 +103,8 @@ func computeIssueUpdates(issues []Issue, regexpLabels []regexpLabel) []IssueUpda issueUpdate.OldLabels = append(issueUpdate.OldLabels, label) } - affectedResources := extractAffectedResources(issue.Body) - for _, needed := range computeLabels(affectedResources, regexpLabels) { + affectedResources := ExtractAffectedResources(issue.Body) + for _, needed := range ComputeLabels(affectedResources, regexpLabels) { desired[needed] = struct{}{} } @@ -126,10 +126,10 @@ func computeIssueUpdates(issues []Issue, regexpLabels []regexpLabel) []IssueUpda return issueUpdates } -func updateIssues(issueUpdates []IssueUpdate, dryRun bool) { +func UpdateIssues(repository string, issueUpdates []IssueUpdate, dryRun bool) { client := &http.Client{} for _, issueUpdate := range issueUpdates { - url := fmt.Sprintf("https://api.github.com/repos/hashicorp/terraform-provider-google/issues/%d", issueUpdate.Number) + url := fmt.Sprintf("https://api.github.com/repos/%s/issues/%d", repository, issueUpdate.Number) updateBody := IssueUpdateBody{Labels: issueUpdate.Labels} body, err := json.Marshal(updateBody) if err != nil { @@ -146,7 +146,7 @@ func updateIssues(issueUpdates []IssueUpdate, dryRun bool) { } fmt.Printf("Existing labels: %v\n", issueUpdate.OldLabels) fmt.Printf("New labels: %v\n", issueUpdate.Labels) - fmt.Printf("%s %s (https://github.com/hashicorp/terraform-provider-google/issues/%d)\n", req.Method, req.URL, issueUpdate.Number) + fmt.Printf("%s %s (https://github.com/%s/issues/%d)\n", repository, req.Method, req.URL, issueUpdate.Number) b, err := json.MarshalIndent(updateBody, "", " ") if err != nil { glog.Errorf("Error marshalling json: %v", err) @@ -167,8 +167,9 @@ func updateIssues(issueUpdates []IssueUpdate, dryRun bool) { var errResp ErrorResponse json.Unmarshal(body, &errResp) if errResp.Message != "" { - glog.Infof("API error: %s", errResp.Message) + glog.Errorf("API error: %s", errResp.Message) } + } } } diff --git a/tools/issue-labeler/backfill_test.go b/tools/issue-labeler/labeler/backfill_test.go similarity index 96% rename from tools/issue-labeler/backfill_test.go rename to tools/issue-labeler/labeler/backfill_test.go index 41c00f2304cf..c58e7f14b8e6 100644 --- a/tools/issue-labeler/backfill_test.go +++ b/tools/issue-labeler/labeler/backfill_test.go @@ -1,4 +1,4 @@ -package main +package labeler import ( "fmt" @@ -20,7 +20,7 @@ func testIssueBodyWithResources(resources []string) string { } func TestComputeIssueUpdates(t *testing.T) { - defaultRegexpLabels := []regexpLabel{ + defaultRegexpLabels := []RegexpLabel{ { Regexp: regexp.MustCompile("google_service1_.*"), Label: "service/service1", @@ -36,7 +36,7 @@ func TestComputeIssueUpdates(t *testing.T) { } cases := map[string]struct { issues []Issue - regexpLabels []regexpLabel + regexpLabels []RegexpLabel expectedIssueUpdates []IssueUpdate }{ "no issues -> no updates": { @@ -160,7 +160,7 @@ func TestComputeIssueUpdates(t *testing.T) { tc := tc t.Run(tn, func(t *testing.T) { t.Parallel() - issueUpdates := computeIssueUpdates(tc.issues, tc.regexpLabels) + issueUpdates := ComputeIssueUpdates(tc.issues, tc.regexpLabels) // reflect.DeepEqual treats nil & empty slices as not equal so ignore diffs if both slices are empty. if (len(issueUpdates) > 0 || len(tc.expectedIssueUpdates) > 0) && !reflect.DeepEqual(issueUpdates, tc.expectedIssueUpdates) { t.Errorf("Expected %v, got %v", tc.expectedIssueUpdates, issueUpdates) diff --git a/tools/issue-labeler/enrolled_teams.yml b/tools/issue-labeler/labeler/enrolled_teams.yml similarity index 100% rename from tools/issue-labeler/enrolled_teams.yml rename to tools/issue-labeler/labeler/enrolled_teams.yml diff --git a/tools/issue-labeler/labels.go b/tools/issue-labeler/labeler/labels.go similarity index 83% rename from tools/issue-labeler/labels.go rename to tools/issue-labeler/labeler/labels.go index 588588f65120..697bdb6f132f 100644 --- a/tools/issue-labeler/labels.go +++ b/tools/issue-labeler/labeler/labels.go @@ -1,4 +1,4 @@ -package main +package labeler import ( "fmt" @@ -17,7 +17,7 @@ var resourceRegexp = regexp.MustCompile(`google_[\w*.]+`) var ( //go:embed enrolled_teams.yml - enrolledTeamsYaml []byte + EnrolledTeamsYaml []byte ) type labelData struct { @@ -25,14 +25,14 @@ type labelData struct { Resources []string `yaml:"resources"` } -type regexpLabel struct { +type RegexpLabel struct { Regexp *regexp.Regexp Label string } -func buildRegexLabels(teamsYaml []byte) ([]regexpLabel, error) { +func BuildRegexLabels(teamsYaml []byte) ([]RegexpLabel, error) { enrolledTeams := make(map[string]labelData) - regexpLabels := []regexpLabel{} + regexpLabels := []RegexpLabel{} if err := yaml.Unmarshal(teamsYaml, &enrolledTeams); err != nil { return regexpLabels, fmt.Errorf("Error unmarshalling enrolled teams yaml: %w", err) } @@ -40,7 +40,7 @@ func buildRegexLabels(teamsYaml []byte) ([]regexpLabel, error) { for label, data := range enrolledTeams { for _, resource := range data.Resources { exactResource := fmt.Sprintf("^%s$", resource) - regexpLabels = append(regexpLabels, regexpLabel{ + regexpLabels = append(regexpLabels, RegexpLabel{ Regexp: regexp.MustCompile(exactResource), Label: label, }) @@ -54,7 +54,7 @@ func buildRegexLabels(teamsYaml []byte) ([]regexpLabel, error) { return regexpLabels, nil } -func extractAffectedResources(body string) []string { +func ExtractAffectedResources(body string) []string { section := sectionRegexp.FindString(body) section = commentRegexp.ReplaceAllString(section, "") if section != "" { @@ -64,7 +64,7 @@ func extractAffectedResources(body string) []string { return []string{} } -func computeLabels(resources []string, regexpLabels []regexpLabel) []string { +func ComputeLabels(resources []string, regexpLabels []RegexpLabel) []string { labelSet := make(map[string]struct{}) for _, resource := range resources { for _, rl := range regexpLabels { diff --git a/tools/issue-labeler/labels_test.go b/tools/issue-labeler/labeler/labels_test.go similarity index 96% rename from tools/issue-labeler/labels_test.go rename to tools/issue-labeler/labeler/labels_test.go index 1112cd9565e2..7161969dd8e9 100644 --- a/tools/issue-labeler/labels_test.go +++ b/tools/issue-labeler/labeler/labels_test.go @@ -1,4 +1,4 @@ -package main +package labeler import ( "reflect" @@ -35,7 +35,7 @@ func TestExtractAffectedResources(t *testing.T) { tc := tc t.Run(tn, func(t *testing.T) { t.Parallel() - resources := extractAffectedResources(tc.body) + resources := ExtractAffectedResources(tc.body) if !slices.Equal(resources, tc.expectedResources) { t.Errorf("Expected %v, got %v", tc.expectedResources, resources) } @@ -45,7 +45,7 @@ func TestExtractAffectedResources(t *testing.T) { func TestEnrolledTeamsData(t *testing.T) { // Smoke test to make sure enrolled teams data can be converted to a regex -> label map - _, err := buildRegexLabels(enrolledTeamsYaml) + _, err := BuildRegexLabels(EnrolledTeamsYaml) if err != nil { t.Logf("Error converting enrolled_teams.yml to regexpLabels: %s", err) t.FailNow() @@ -55,11 +55,11 @@ func TestEnrolledTeamsData(t *testing.T) { func TestBuildRegexLabels(t *testing.T) { cases := map[string]struct { yaml []byte - expectedRegexpLabels []regexpLabel + expectedRegexpLabels []RegexpLabel }{ "empty yaml": { yaml: []byte{}, - expectedRegexpLabels: []regexpLabel{}, + expectedRegexpLabels: []RegexpLabel{}, }, "labels with resources": { yaml: []byte(` @@ -70,7 +70,7 @@ service/service2: resources: - google_service2_resource1 - google_service2_resource2`), - expectedRegexpLabels: []regexpLabel{ + expectedRegexpLabels: []RegexpLabel{ { Regexp: regexp.MustCompile("^google_service1_.*$"), Label: "service/service1", @@ -91,7 +91,7 @@ service/service1: team: service1-team resources: - google_service1_resource1`), - expectedRegexpLabels: []regexpLabel{ + expectedRegexpLabels: []RegexpLabel{ { Regexp: regexp.MustCompile("^google_service1_resource1$"), Label: "service/service1", @@ -104,7 +104,7 @@ service/service1: tc := tc t.Run(tn, func(t *testing.T) { t.Parallel() - regexpLabels, err := buildRegexLabels(tc.yaml) + regexpLabels, err := BuildRegexLabels(tc.yaml) if err != nil { t.Logf("Unable to read enrolled teams: %s", err) t.FailNow() @@ -117,7 +117,7 @@ service/service1: } func TestComputeLabels(t *testing.T) { - defaultRegexpLabels := []regexpLabel{ + defaultRegexpLabels := []RegexpLabel{ { Regexp: regexp.MustCompile("^google_service1_.*$"), Label: "service/service1", @@ -141,7 +141,7 @@ func TestComputeLabels(t *testing.T) { } cases := map[string]struct { resources []string - regexpLabels []regexpLabel + regexpLabels []RegexpLabel expectedLabels []string }{ "empty resources -> empty labels": { @@ -156,7 +156,7 @@ func TestComputeLabels(t *testing.T) { }, "empty regexpLabels -> empty labels": { resources: []string{"google_service1_resource1"}, - regexpLabels: []regexpLabel{}, + regexpLabels: []RegexpLabel{}, expectedLabels: []string{}, }, "single matched resource": { @@ -195,7 +195,7 @@ func TestComputeLabels(t *testing.T) { tc := tc t.Run(tn, func(t *testing.T) { t.Parallel() - labels := computeLabels(tc.resources, tc.regexpLabels) + labels := ComputeLabels(tc.resources, tc.regexpLabels) if !slices.Equal(labels, tc.expectedLabels) { t.Errorf("want %v; got %v", tc.expectedLabels, labels) } diff --git a/tools/issue-labeler/main.go b/tools/issue-labeler/main.go index 1239337f5a5e..d2f69f0bd63c 100644 --- a/tools/issue-labeler/main.go +++ b/tools/issue-labeler/main.go @@ -7,6 +7,7 @@ import ( "sort" "strings" + "github.com/GoogleCloudPlatform/magic-modules/tools/issue-labeler/labeler" "github.com/golang/glog" ) @@ -16,15 +17,15 @@ var flagDryRun = flag.Bool("backfill-dry-run", false, "when combined with backfi func main() { flag.Parse() - regexpLabels, err := buildRegexLabels(enrolledTeamsYaml) + regexpLabels, err := labeler.BuildRegexLabels(EnrolledTeamsYaml) if err != nil { glog.Exitf("Error building regex labels: %v", err) } if *flagBackfillDate == "" { issueBody := os.Getenv("ISSUE_BODY") - affectedResources := extractAffectedResources(issueBody) - labels := computeLabels(affectedResources, regexpLabels) + affectedResources := labeler.ExtractAffectedResources(issueBody) + labels := labeler.ComputeLabels(affectedResources, regexpLabels) if len(labels) > 0 { labels = append(labels, "forward/review") @@ -32,8 +33,9 @@ func main() { fmt.Println(`["` + strings.Join(labels, `", "`) + `"]`) } } else { - issues := getIssues(*flagBackfillDate) - issueUpdates := computeIssueUpdates(issues, regexpLabels) - updateIssues(issueUpdates, *flagDryRun) + repository := "hashicorp/terraform-provider-google" + issues := labeler.GetIssues(repository, *flagBackfillDate) + issueUpdates := labeler.ComputeIssueUpdates(issues, regexpLabels) + labeler.UpdateIssues(repository, issueUpdates, *flagDryRun) } } diff --git a/tpgtools/api/assuredworkloads/samples/basic.workload.json b/tpgtools/api/assuredworkloads/samples/basic.workload.json index 331cfbf585b9..5b280bd44047 100755 --- a/tpgtools/api/assuredworkloads/samples/basic.workload.json +++ b/tpgtools/api/assuredworkloads/samples/basic.workload.json @@ -1,20 +1,22 @@ { "organization": "{{org_id}}", "location": "{{region}}", - "displayName": "Workload Example", + "displayName": "{{display}}", "complianceRegime": "FEDRAMP_MODERATE", "billingAccount": "billingAccounts/{{billing_account}}", "labels": { "label-one": "value-one" }, "provisionedResourcesParent": "folders/519620126891", + "violationNotificationsEnabled": true, "kmsSettings": { "nextRotationTime": "9999-10-02T15:01:23Z", "rotationPeriod": "10368000s" }, "resourceSettings": [ { - "resourceType": "CONSUMER_PROJECT" + "resourceType": "CONSUMER_FOLDER", + "displayName": "folder-display-name" }, { "resourceType": "ENCRYPTION_KEYS_PROJECT" @@ -24,4 +26,4 @@ "resourceType": "KEYRING" } ] -} +} \ No newline at end of file diff --git a/tpgtools/api/assuredworkloads/samples/sovereign_controls.workload.json b/tpgtools/api/assuredworkloads/samples/sovereign_controls.workload.json new file mode 100644 index 000000000000..f75313bc7777 --- /dev/null +++ b/tpgtools/api/assuredworkloads/samples/sovereign_controls.workload.json @@ -0,0 +1,27 @@ +{ + "organization": "{{org_id}}", + "location": "europe-west9", + "displayName": "{{display}}", + "complianceRegime": "EU_REGIONS_AND_SUPPORT", + "billingAccount": "billingAccounts/{{billing_account}}", + "labels": { + "label-one": "value-one" + }, + "enableSovereignControls": true, + "kmsSettings": { + "nextRotationTime": "9999-10-02T15:01:23Z", + "rotationPeriod": "10368000s" + }, + "resourceSettings": [ + { + "resourceType": "CONSUMER_FOLDER" + }, + { + "resourceType": "ENCRYPTION_KEYS_PROJECT" + }, + { + "resourceId": "{{ring}}", + "resourceType": "KEYRING" + } + ] +} \ No newline at end of file diff --git a/tpgtools/api/assuredworkloads/samples/sovereign_controls_workload.yaml b/tpgtools/api/assuredworkloads/samples/sovereign_controls_workload.yaml new file mode 100644 index 000000000000..bcb6e5fbd7ea --- /dev/null +++ b/tpgtools/api/assuredworkloads/samples/sovereign_controls_workload.yaml @@ -0,0 +1,31 @@ +# Copyright 2023 Google LLC. All Rights Reserved. +# +# 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: sovereign_controls_workload +description: A Sovereign Controls test of the assuredworkloads api +type: workload +versions: +- beta +resource: samples/sovereign_controls.workload.json +updates: +- resource: samples/update_sovereign_controls.workload.json + dependencies: [] +variables: +- name: billing_account + type: billing_account +- name: display + type: resource_name +- name: org_id + type: org_id +- name: ring + type: resource_name \ No newline at end of file diff --git a/tpgtools/api/assuredworkloads/samples/update_sovereign_controls.workload.json b/tpgtools/api/assuredworkloads/samples/update_sovereign_controls.workload.json new file mode 100644 index 000000000000..99fa43989bb9 --- /dev/null +++ b/tpgtools/api/assuredworkloads/samples/update_sovereign_controls.workload.json @@ -0,0 +1,11 @@ +{ + "name": "{{ref:__state__:name}}", + "organization": "{{org_id}}", + "location": "europe-west9", + "displayName": "updated-example", + "billingAccount": "billingAccounts/{{billing_account}}", + "complianceRegime": "EU_REGIONS_AND_SUPPORT", + "labels": { + "label-two": "value-two-eu-regions-and-support" + } +} \ No newline at end of file diff --git a/tpgtools/api/orgpolicy/samples/organization_dry_run.policy.json b/tpgtools/api/orgpolicy/samples/organization_dry_run.policy.json new file mode 100644 index 000000000000..ef93418792e8 --- /dev/null +++ b/tpgtools/api/orgpolicy/samples/organization_dry_run.policy.json @@ -0,0 +1,13 @@ +{ + "name": "organizations/{{org_id}}/policies/gcp.resourceLocations", + "parent": "organizations/{{org_id}}", + "dryRunSpec": { + "rules": [ + { + "denyAll": true + } + ], + "reset": true, + "inheritFromParent": false + } +} \ No newline at end of file diff --git a/tpgtools/api/orgpolicy/samples/organization_dry_run_policy.yaml b/tpgtools/api/orgpolicy/samples/organization_dry_run_policy.yaml new file mode 100644 index 000000000000..809c10dde32e --- /dev/null +++ b/tpgtools/api/orgpolicy/samples/organization_dry_run_policy.yaml @@ -0,0 +1,26 @@ +# Copyright 2023 Google LLC. All Rights Reserved. +# +# 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: organization_dry_run_policy +description: A test of an dry run policy for an organization +type: policy +versions: +- ga +- beta +resource: samples/organization_dry_run.policy.json +updates: +- resource: samples/update_organization_dry_run.policy.json + dependencies: [] +variables: +- name: org_id + type: org_id \ No newline at end of file diff --git a/tpgtools/api/orgpolicy/samples/update_organization_dry_run.policy.json b/tpgtools/api/orgpolicy/samples/update_organization_dry_run.policy.json new file mode 100644 index 000000000000..fa4c032116be --- /dev/null +++ b/tpgtools/api/orgpolicy/samples/update_organization_dry_run.policy.json @@ -0,0 +1,14 @@ +{ + "name": "organizations/{{org_id}}/policies/gcp.resourceLocations", + "parent": "organizations/{{org_id}}", + "dryRunSpec": { + "rules": [ + { + "allowAll": true, + "enforce": true + } + ], + "reset": false, + "inheritFromParent": true + } +} \ No newline at end of file diff --git a/tpgtools/go.mod b/tpgtools/go.mod index 07832d3e80b4..5340271e9f1f 100644 --- a/tpgtools/go.mod +++ b/tpgtools/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( bitbucket.org/creachadair/stringset v0.0.11 - github.com/GoogleCloudPlatform/declarative-resource-client-library v1.52.0 + github.com/GoogleCloudPlatform/declarative-resource-client-library v1.55.0 github.com/golang/glog v1.1.2 github.com/hashicorp/errwrap v1.0.0 github.com/hashicorp/hcl v1.0.0 diff --git a/tpgtools/go.sum b/tpgtools/go.sum index 981174d17fd5..bab15242f741 100644 --- a/tpgtools/go.sum +++ b/tpgtools/go.sum @@ -47,6 +47,8 @@ github.com/GoogleCloudPlatform/declarative-resource-client-library v1.51.0 h1:Yh github.com/GoogleCloudPlatform/declarative-resource-client-library v1.51.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= github.com/GoogleCloudPlatform/declarative-resource-client-library v1.52.0 h1:KswxXF4E5iWv2ggktqv265zOvwmXA3mgma3UQfYA4tU= github.com/GoogleCloudPlatform/declarative-resource-client-library v1.52.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.55.0 h1:MTP0IDIztk36l8ubHkEcL6lWMG8Enqu9AP3E4MoBFg0= +github.com/GoogleCloudPlatform/declarative-resource-client-library v1.55.0/go.mod h1:pL2Qt5HT+x6xrTd806oMiM3awW6kNIXB/iiuClz6m6k= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= diff --git a/tpgtools/overrides/assuredworkloads/beta/workload.yaml b/tpgtools/overrides/assuredworkloads/beta/workload.yaml index e69de29bb2d1..b75a7c4a3f6b 100644 --- a/tpgtools/overrides/assuredworkloads/beta/workload.yaml +++ b/tpgtools/overrides/assuredworkloads/beta/workload.yaml @@ -0,0 +1,12 @@ +- type: CUSTOM_SCHEMA_VALUES + field: enable_sovereign_controls + details: + required: false + optional: true + computed: true +- type: CUSTOM_SCHEMA_VALUES + field: violation_notifications_enabled + details: + required: false + optional: true + computed: true \ No newline at end of file diff --git a/tpgtools/overrides/assuredworkloads/samples/workload/basic.tf.tmpl b/tpgtools/overrides/assuredworkloads/samples/workload/basic.tf.tmpl index fd154c19057f..8d303ef61437 100644 --- a/tpgtools/overrides/assuredworkloads/samples/workload/basic.tf.tmpl +++ b/tpgtools/overrides/assuredworkloads/samples/workload/basic.tf.tmpl @@ -8,6 +8,11 @@ resource "google_assured_workloads_workload" "primary" { provisioned_resources_parent = google_folder.folder1.name organization = "{{org_id}}" location = "us-central1" + resource_settings { + resource_type = "CONSUMER_FOLDER" + display_name = "folder-display-name" + } + violation_notifications_enabled = true } resource "google_folder" "folder1" { diff --git a/tpgtools/overrides/assuredworkloads/samples/workload/basic_update.tf.tmpl b/tpgtools/overrides/assuredworkloads/samples/workload/basic_update.tf.tmpl index 9da19474c62f..4d5fb120eb93 100644 --- a/tpgtools/overrides/assuredworkloads/samples/workload/basic_update.tf.tmpl +++ b/tpgtools/overrides/assuredworkloads/samples/workload/basic_update.tf.tmpl @@ -8,6 +8,11 @@ resource "google_assured_workloads_workload" "primary" { provisioned_resources_parent = google_folder.folder1.name organization = "{{org_id}}" location = "us-central1" + resource_settings { + resource_type = "CONSUMER_FOLDER" + display_name = "folder-display-name" + } + violation_notifications_enabled = true } resource "google_folder" "folder1" { diff --git a/tpgtools/overrides/containeraws/samples/cluster/basic.tf.tmpl b/tpgtools/overrides/containeraws/samples/cluster/basic.tf.tmpl index ffa75537965e..cba4d5fefbb9 100644 --- a/tpgtools/overrides/containeraws/samples/cluster/basic.tf.tmpl +++ b/tpgtools/overrides/containeraws/samples/cluster/basic.tf.tmpl @@ -8,6 +8,9 @@ resource "google_container_aws_cluster" "primary" { admin_users { username = "{{test_service_account}}" } + admin_groups { + group = "group@domain.com" + } } aws_region = "{{aws_region}}" diff --git a/tpgtools/overrides/containeraws/samples/cluster/basic_update.tf.tmpl b/tpgtools/overrides/containeraws/samples/cluster/basic_update.tf.tmpl index c20cc9b2345d..5273978a0121 100644 --- a/tpgtools/overrides/containeraws/samples/cluster/basic_update.tf.tmpl +++ b/tpgtools/overrides/containeraws/samples/cluster/basic_update.tf.tmpl @@ -8,6 +8,9 @@ resource "google_container_aws_cluster" "primary" { admin_users { username = "{{test_service_account}}" } + admin_groups { + group = "group@domain.com" + } } aws_region = "{{aws_region}}" diff --git a/tpgtools/overrides/containerazure/samples/cluster/basic.tf.tmpl b/tpgtools/overrides/containerazure/samples/cluster/basic.tf.tmpl index dd043e5fe5ce..712364a3b4b1 100644 --- a/tpgtools/overrides/containerazure/samples/cluster/basic.tf.tmpl +++ b/tpgtools/overrides/containerazure/samples/cluster/basic.tf.tmpl @@ -8,6 +8,9 @@ resource "google_container_azure_cluster" "primary" { admin_users { username = "mmv2@google.com" } + admin_groups { + group = "group@domain.com" + } } azure_region = "westus2" diff --git a/tpgtools/overrides/containerazure/samples/cluster/basic_update.tf.tmpl b/tpgtools/overrides/containerazure/samples/cluster/basic_update.tf.tmpl index 0c2632431f82..a010a7fa4126 100644 --- a/tpgtools/overrides/containerazure/samples/cluster/basic_update.tf.tmpl +++ b/tpgtools/overrides/containerazure/samples/cluster/basic_update.tf.tmpl @@ -8,6 +8,9 @@ resource "google_container_azure_cluster" "primary" { admin_users { username = "mmv2@google.com" } + admin_groups { + group = "group@domain.com" + } } azure_region = "westus2" diff --git a/tpgtools/overrides/orgpolicy/beta/policy.yaml b/tpgtools/overrides/orgpolicy/beta/policy.yaml index c27653ff9807..e31fa2fc2c0a 100644 --- a/tpgtools/overrides/orgpolicy/beta/policy.yaml +++ b/tpgtools/overrides/orgpolicy/beta/policy.yaml @@ -7,3 +7,5 @@ field: spec.rules.deny_all - type: ENUM_BOOL field: spec.rules.enforce +- type: EXCLUDE + field: dry_run_spec diff --git a/tpgtools/overrides/orgpolicy/policy.yaml b/tpgtools/overrides/orgpolicy/policy.yaml index c27653ff9807..08ff9e6b371f 100644 --- a/tpgtools/overrides/orgpolicy/policy.yaml +++ b/tpgtools/overrides/orgpolicy/policy.yaml @@ -7,3 +7,9 @@ field: spec.rules.deny_all - type: ENUM_BOOL field: spec.rules.enforce +- type: ENUM_BOOL + field: dry_run_spec.rules.allow_all +- type: ENUM_BOOL + field: dry_run_spec.rules.deny_all +- type: ENUM_BOOL + field: dry_run_spec.rules.enforce \ No newline at end of file diff --git a/tpgtools/overrides/orgpolicy/samples/policy/meta.yaml b/tpgtools/overrides/orgpolicy/samples/policy/meta.yaml index aecd139b9102..fe632768fe17 100644 --- a/tpgtools/overrides/orgpolicy/samples/policy/meta.yaml +++ b/tpgtools/overrides/orgpolicy/samples/policy/meta.yaml @@ -1,3 +1,8 @@ ignore_read: - name - "spec.0.rules.0.condition.0.expression" +# The feature for this sample is not ready +test_hide: +- organization_dry_run_policy.yaml +doc_hide: +- organization_dry_run_policy.yaml