diff --git a/.ci/gcb-downstream-builder.yml b/.ci/gcb-downstream-builder.yml index add2c4108ebc..bbbee77ae4fb 100644 --- a/.ci/gcb-downstream-builder.yml +++ b/.ci/gcb-downstream-builder.yml @@ -10,4 +10,7 @@ steps: '--cache-from', 'gcr.io/$PROJECT_ID/downstream-builder:latest', './.ci/containers/downstream-builder' ] +# the downstream-builder takes around 15 minutes to build +timeout: "8000s" +# note: this container is also used for the run-rake-tests build images: ['gcr.io/$PROJECT_ID/downstream-builder:latest'] diff --git a/.ci/gcb-run-rake-tests.yml b/.ci/gcb-run-rake-tests.yml new file mode 100644 index 000000000000..27bc3b8b57b7 --- /dev/null +++ b/.ci/gcb-run-rake-tests.yml @@ -0,0 +1,9 @@ +--- +steps: +- name: 'gcr.io/$PROJECT_ID/downstream-builder' + id: run-rake-tests + entrypoint: bundle + args: + - exec + - rake + - test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1cfe76311839..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: ruby # ruby version defined in .ruby-version will be used - -script: -- bundle exec rake test - -git: - submodules: false diff --git a/products/bigquerydatatransfer/api.yaml b/products/bigquerydatatransfer/api.yaml index 0f29cb09ee17..d5211fa2abdf 100644 --- a/products/bigquerydatatransfer/api.yaml +++ b/products/bigquerydatatransfer/api.yaml @@ -138,6 +138,17 @@ objects: - schedule_options.0.disable_auto_scheduling - schedule_options.0.start_time - schedule_options.0.end_time + - !ruby/object:Api::Type::NestedObject + name: 'emailPreferences' + description: | + Email notifications will be sent according to these preferences to the + email address of the user who owns this transfer config. + properties: + - !ruby/object:Api::Type::Boolean + name: 'enableFailureEmail' + required: true + description: | + If true, email notifications will be sent on transfer run failures. - !ruby/object:Api::Type::String name: 'notificationPubsubTopic' description: | diff --git a/products/billingbudget/api.yaml b/products/billingbudget/api.yaml index 352fbfe322b1..64e5f1491a58 100644 --- a/products/billingbudget/api.yaml +++ b/products/billingbudget/api.yaml @@ -13,7 +13,8 @@ --- !ruby/object:Api::Product name: Billing -display_name: Billing Budget +# Strictly speaking it should be Billing Budget but setting it to Cloud Billing will put in the same doc section as billing accounts. +display_name: Cloud Billing versions: - !ruby/object:Api::Product::Version name: beta diff --git a/products/compute/api.yaml b/products/compute/api.yaml index 9d2e83858585..8e365eb95cef 100644 --- a/products/compute/api.yaml +++ b/products/compute/api.yaml @@ -13886,6 +13886,7 @@ objects: description: | Export filter used to define which VPC flow logs should be logged, as as CEL expression. See https://cloud.google.com/vpc/docs/flow-logs#filtering for details on how to format this field. + The default value is 'true', which evaluates to include everything. default_value: "true" references: !ruby/object:Api::Resource::ReferenceLinks guides: diff --git a/products/iambeta/api.yaml b/products/iambeta/api.yaml index 8a9ed3ffec37..ad111bf9dda5 100644 --- a/products/iambeta/api.yaml +++ b/products/iambeta/api.yaml @@ -71,14 +71,14 @@ objects: name: 'state' description: | The state of the pool. - STATE_UNSPECIFIED: State unspecified. - ACTIVE: The pool is active, and may be used in Google Cloud policies. - DELETED: The pool is soft-deleted. Soft-deleted pools are permanently deleted after - approximately 30 days. You can restore a soft-deleted pool using - UndeleteWorkloadIdentityPool. You cannot reuse the ID of a soft-deleted pool until it is - permanently deleted. While a pool is deleted, you cannot use it to exchange tokens, or - use existing tokens to access resources. If the pool is undeleted, existing tokens grant - access again. + * STATE_UNSPECIFIED: State unspecified. + * ACTIVE: The pool is active, and may be used in Google Cloud policies. + * DELETED: The pool is soft-deleted. Soft-deleted pools are permanently deleted after + approximately 30 days. You can restore a soft-deleted pool using + UndeleteWorkloadIdentityPool. You cannot reuse the ID of a soft-deleted pool until it is + permanently deleted. While a pool is deleted, you cannot use it to exchange tokens, or + use existing tokens to access resources. If the pool is undeleted, existing tokens grant + access again. output: true values: - :STATE_UNSPECIFIED @@ -94,7 +94,7 @@ objects: name: 'name' description: | The resource name of the pool as - `projects//locations/global/workloadIdentityPools/`. + `projects/{project_number}/locations/global/workloadIdentityPools/{workload_identity_pool_id}`. output: true - !ruby/object:Api::Type::Boolean name: 'disabled' @@ -102,3 +102,191 @@ objects: Whether the pool is disabled. You cannot use a disabled pool to exchange tokens, or use existing tokens to access resources. If the pool is re-enabled, existing tokens grant access again. + - !ruby/object:Api::Resource + name: 'WorkloadIdentityPoolProvider' + min_version: beta + base_url: projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/providers + self_link: projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/providers/{{workload_identity_pool_provider_id}} + create_url: projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/providers?workloadIdentityPoolProviderId={{workload_identity_pool_provider_id}} + update_verb: :PATCH + update_mask: true + description: A configuration for an external identity provider. + references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Managing workload identity providers': + 'https://cloud.google.com/iam/docs/manage-workload-identity-pools-providers#managing_workload_identity_providers' + api: 'https://cloud.google.com/iam/docs/reference/rest/v1beta/projects.locations.workloadIdentityPools.providers' + properties: + - !ruby/object:Api::Type::String + name: 'workloadIdentityPoolId' + description: | + The ID used for the pool, which is the final component of the pool resource name. This + value should be 4-32 characters, and may contain the characters [a-z0-9-]. The prefix + `gcp-` is reserved for use by Google, and may not be specified. + required: true + input: true + url_param_only: true + - !ruby/object:Api::Type::String + name: 'workloadIdentityPoolProviderId' + description: | + The ID for the provider, which becomes the final component of the resource name. This + value must be 4-32 characters, and may contain the characters [a-z0-9-]. The prefix + `gcp-` is reserved for use by Google, and may not be specified. + required: true + input: true + url_param_only: true + - !ruby/object:Api::Type::Enum + name: 'state' + description: | + The state of the provider. + * STATE_UNSPECIFIED: State unspecified. + * ACTIVE: The provider is active, and may be used to validate authentication credentials. + * DELETED: The provider is soft-deleted. Soft-deleted providers are permanently deleted + after approximately 30 days. You can restore a soft-deleted provider using + UndeleteWorkloadIdentityPoolProvider. You cannot reuse the ID of a soft-deleted provider + until it is permanently deleted. + output: true + values: + - :STATE_UNSPECIFIED + - :ACTIVE + - :DELETED + - !ruby/object:Api::Type::String + name: 'displayName' + description: A display name for the provider. Cannot exceed 32 characters. + - !ruby/object:Api::Type::String + name: 'description' + description: A description for the provider. Cannot exceed 256 characters. + - !ruby/object:Api::Type::String + name: 'name' + description: | + The resource name of the provider as + `projects/{project_number}/locations/global/workloadIdentityPools/{workload_identity_pool_id}/providers/{workload_identity_pool_provider_id}`. + output: true + - !ruby/object:Api::Type::Boolean + name: 'disabled' + description: | + Whether the provider is disabled. You cannot use a disabled provider to exchange tokens. + However, existing tokens still grant access. + - !ruby/object:Api::Type::KeyValuePairs + name: 'attributeMapping' + description: | + Maps attributes from authentication credentials issued by an external identity provider + to Google Cloud attributes, such as `subject` and `segment`. + + Each key must be a string specifying the Google Cloud IAM attribute to map to. + + The following keys are supported: + * `google.subject`: The principal IAM is authenticating. You can reference this value + in IAM bindings. This is also the subject that appears in Cloud Logging logs. + Cannot exceed 127 characters. + * `google.groups`: Groups the external identity belongs to. You can grant groups + access to resources using an IAM `principalSet` binding; access applies to all + members of the group. + + You can also provide custom attributes by specifying `attribute.{custom_attribute}`, + where `{custom_attribute}` is the name of the custom attribute to be mapped. You can + define a maximum of 50 custom attributes. The maximum length of a mapped attribute key + is 100 characters, and the key may only contain the characters [a-z0-9_]. + + You can reference these attributes in IAM policies to define fine-grained access for a + workload to Google Cloud resources. For example: + * `google.subject`: + `principal://iam.googleapis.com/projects/{project}/locations/{location}/workloadIdentityPools/{pool}/subject/{value}` + * `google.groups`: + `principalSet://iam.googleapis.com/projects/{project}/locations/{location}/workloadIdentityPools/{pool}/group/{value}` + * `attribute.{custom_attribute}`: + `principalSet://iam.googleapis.com/projects/{project}/locations/{location}/workloadIdentityPools/{pool}/attribute.{custom_attribute}/{value}` + + Each value must be a [Common Expression Language](https://opensource.google/projects/cel) + function that maps an identity provider credential to the normalized attribute specified + by the corresponding map key. + + You can use the `assertion` keyword in the expression to access a JSON representation of + the authentication credential issued by the provider. + + The maximum length of an attribute mapping expression is 2048 characters. When evaluated, + the total size of all mapped attributes must not exceed 8KB. + + For AWS providers, the following rules apply: + - If no attribute mapping is defined, the following default mapping applies: + ``` + { + "google.subject":"assertion.arn", + "attribute.aws_role": + "assertion.arn.contains('assumed-role')" + " ? assertion.arn.extract('{account_arn}assumed-role/')" + " + 'assumed-role/'" + " + assertion.arn.extract('assumed-role/{role_name}/')" + " : assertion.arn", + } + ``` + - If any custom attribute mappings are defined, they must include a mapping to the + `google.subject` attribute. + + For OIDC providers, the following rules apply: + - Custom attribute mappings must be defined, and must include a mapping to the + `google.subject` attribute. For example, the following maps the `sub` claim of the + incoming credential to the `subject` attribute on a Google token. + ``` + {"google.subject": "assertion.sub"} + ``` + - !ruby/object:Api::Type::String + name: 'attributeCondition' + description: | + [A Common Expression Language](https://opensource.google/projects/cel) expression, in + plain text, to restrict what otherwise valid authentication credentials issued by the + provider should not be accepted. + + The expression must output a boolean representing whether to allow the federation. + + The following keywords may be referenced in the expressions: + * `assertion`: JSON representing the authentication credential issued by the provider. + * `google`: The Google attributes mapped from the assertion in the `attribute_mappings`. + * `attribute`: The custom attributes mapped from the assertion in the `attribute_mappings`. + + The maximum length of the attribute condition expression is 4096 characters. If + unspecified, all valid authentication credential are accepted. + + The following example shows how to only allow credentials with a mapped `google.groups` + value of `admins`: + ``` + "'admins' in google.groups" + ``` + - !ruby/object:Api::Type::NestedObject + name: aws + description: An Amazon Web Services identity provider. Not compatible with the property oidc. + exactly_one_of: + - aws + - oidc + properties: + - !ruby/object:Api::Type::String + name: accountId + description: The AWS account ID. + required: true + - !ruby/object:Api::Type::NestedObject + name: oidc + description: An OpenId Connect 1.0 identity provider. Not compatible with the property aws. + exactly_one_of: + - aws + - oidc + properties: + - !ruby/object:Api::Type::Array + name: allowedAudiences + item_type: Api::Type::String + description: | + Acceptable values for the `aud` field (audience) in the OIDC token. Token exchange + requests are rejected if the token audience does not match one of the configured + values. Each audience may be at most 256 characters. A maximum of 10 audiences may + be configured. + + If this list is empty, the OIDC token audience must be equal to the full canonical + resource name of the WorkloadIdentityPoolProvider, with or without the HTTPS prefix. + For example: + ``` + //iam.googleapis.com/projects//locations//workloadIdentityPools//providers/ + https://iam.googleapis.com/projects//locations//workloadIdentityPools//providers/ + ``` + - !ruby/object:Api::Type::String + name: issuerUri + description: The OIDC issuer URL. + required: true diff --git a/products/iambeta/terraform.yaml b/products/iambeta/terraform.yaml index d0a01a2f3ae0..d9332f380aff 100644 --- a/products/iambeta/terraform.yaml +++ b/products/iambeta/terraform.yaml @@ -30,15 +30,54 @@ overrides: !ruby/object:Overrides::ResourceOverrides vars: workload_identity_pool_id: "example-pool" min_version: beta - docs: !ruby/object:Provider::Terraform::Docs - attributes: | - * `self_link`: The self link of the created WorkloadIdentityPool in the format `projects/{project}/locations/global/workloadIdentityPools/{workload_identity_pool_id}` custom_code: !ruby/object:Provider::Terraform::CustomCode constants: templates/terraform/constants/iam_workload_identity_pool.go.erb + decoder: templates/terraform/decoders/treat_deleted_state_as_gone.go.erb + test_check_destroy: templates/terraform/custom_check_destroy/iam_workload_identity_pool.go.erb properties: workloadIdentityPoolId: !ruby/object:Overrides::Terraform::PropertyOverride validation: !ruby/object:Provider::Terraform::Validation function: 'validateWorkloadIdentityPoolId' + WorkloadIdentityPoolProvider: !ruby/object:Overrides::Terraform::ResourceOverride + autogen_async: true + import_format: ["projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/providers/{{workload_identity_pool_provider_id}}"] + examples: + - !ruby/object:Provider::Terraform::Examples + name: "iam_workload_identity_pool_provider_aws_basic" + primary_resource_id: "example" + vars: + workload_identity_pool_id: "example-pool" + workload_identity_pool_provider_id: "example-prvdr" + min_version: beta + - !ruby/object:Provider::Terraform::Examples + name: "iam_workload_identity_pool_provider_aws_full" + primary_resource_id: "example" + vars: + workload_identity_pool_id: "example-pool" + workload_identity_pool_provider_id: "example-prvdr" + min_version: beta + - !ruby/object:Provider::Terraform::Examples + name: "iam_workload_identity_pool_provider_oidc_basic" + primary_resource_id: "example" + vars: + workload_identity_pool_id: "example-pool" + workload_identity_pool_provider_id: "example-prvdr" + min_version: beta + - !ruby/object:Provider::Terraform::Examples + name: "iam_workload_identity_pool_provider_oidc_full" + primary_resource_id: "example" + vars: + workload_identity_pool_id: "example-pool" + workload_identity_pool_provider_id: "example-prvdr" + min_version: beta + custom_code: !ruby/object:Provider::Terraform::CustomCode + constants: templates/terraform/constants/iam_workload_identity_pool_provider.go.erb + decoder: templates/terraform/decoders/treat_deleted_state_as_gone.go.erb + test_check_destroy: templates/terraform/custom_check_destroy/iam_workload_identity_pool_provider.go.erb + properties: + workloadIdentityPoolProviderId: !ruby/object:Overrides::Terraform::PropertyOverride + validation: !ruby/object:Provider::Terraform::Validation + function: 'validateWorkloadIdentityPoolProviderId' # This is for copying files over files: !ruby/object:Provider::Config::Files # These files have templating (ERB) code that will be run. diff --git a/products/monitoring/api.yaml b/products/monitoring/api.yaml index 862e883f8644..28ccc97d23b0 100644 --- a/products/monitoring/api.yaml +++ b/products/monitoring/api.yaml @@ -294,6 +294,59 @@ objects: the condition is created as part of a new or updated alerting policy. output: true + - !ruby/object:Api::Type::NestedObject + name: conditionMonitoringQueryLanguage + description: | + A Monitoring Query Language query that outputs a boolean stream + properties: + - !ruby/object:Api::Type::String + name: query + description: | + Monitoring Query Language query that outputs a boolean stream. + required: true + - !ruby/object:Api::Type::String + name: duration + required: true + description: | + The amount of time that a time series must + violate the threshold to be considered + failing. Currently, only values that are a + multiple of a minute--e.g., 0, 60, 120, or + 300 seconds--are supported. If an invalid + value is given, an error will be returned. + When choosing a duration, it is useful to + keep in mind the frequency of the underlying + time series data (which may also be affected + by any alignments specified in the + aggregations field); a good duration is long + enough so that a single outlier does not + generate spurious alerts, but short enough + that unhealthy states are detected and + alerted on quickly. + - !ruby/object:Api::Type::NestedObject + name: trigger + description: | + The number/percent of time series for which + the comparison must hold in order for the + condition to trigger. If unspecified, then + the condition will trigger if the comparison + is true for any of the time series that have + been identified by filter and aggregations, + or by the ratio, if denominator_filter and + denominator_aggregations are specified. + properties: + - !ruby/object:Api::Type::Double + name: percent + description: | + The percentage of time series that + must fail the predicate for the + condition to be triggered. + - !ruby/object:Api::Type::Integer + name: count + description: | + The absolute number of time series + that must fail the predicate for the + condition to be triggered. - !ruby/object:Api::Type::NestedObject name: conditionThreshold description: | @@ -1212,7 +1265,7 @@ objects: exclusive of max. Open ranges can be defined by setting just one of min or max. properties: - - !ruby/object:Api::Type::Integer + - !ruby/object:Api::Type::Double name: min at_least_one_of: - service_level_indicator.0.request_based_sli.0.distribution_cut.0.range.0.min @@ -1221,7 +1274,7 @@ objects: Min value for the range (inclusive). If not given, will be set to "-infinity", defining an open range "< range.max" - - !ruby/object:Api::Type::Integer + - !ruby/object:Api::Type::Double name: max at_least_one_of: - service_level_indicator.0.request_based_sli.0.distribution_cut.0.range.0.min @@ -1384,7 +1437,7 @@ objects: exclusive of max. Open ranges can be defined by setting just one of min or max. properties: - - !ruby/object:Api::Type::Integer + - !ruby/object:Api::Type::Double name: min at_least_one_of: - service_level_indicator.0.windows_based_sli.0.good_total_ratio_threshold.0.performance.0.distribution_cut.0.range.0.min @@ -1393,7 +1446,7 @@ objects: Min value for the range (inclusive). If not given, will be set to "-infinity", defining an open range "< range.max" - - !ruby/object:Api::Type::Integer + - !ruby/object:Api::Type::Double name: max at_least_one_of: - service_level_indicator.0.windows_based_sli.0.good_total_ratio_threshold.0.performance.0.distribution_cut.0.range.0.min @@ -1492,7 +1545,7 @@ objects: values should satisfy `range.min <= X < range.max` for a good service. properties: - - !ruby/object:Api::Type::Integer + - !ruby/object:Api::Type::Double name: min at_least_one_of: - service_level_indicator.0.windows_based_sli.0.metric_mean_in_range.0.range.0.min @@ -1501,7 +1554,7 @@ objects: Min value for the range (inclusive). If not given, will be set to "-infinity", defining an open range "< range.max" - - !ruby/object:Api::Type::Integer + - !ruby/object:Api::Type::Double name: max at_least_one_of: - service_level_indicator.0.windows_based_sli.0.metric_mean_in_range.0.range.0.min @@ -1550,7 +1603,7 @@ objects: just one of min or max. Summed value `X` should satisfy `range.min <= X < range.max` for a good window. properties: - - !ruby/object:Api::Type::Integer + - !ruby/object:Api::Type::Double name: min at_least_one_of: - service_level_indicator.0.windows_based_sli.0.metric_sum_in_range.0.range.0.min @@ -1559,7 +1612,7 @@ objects: Min value for the range (inclusive). If not given, will be set to "-infinity", defining an open range "< range.max" - - !ruby/object:Api::Type::Integer + - !ruby/object:Api::Type::Double name: max at_least_one_of: - service_level_indicator.0.windows_based_sli.0.metric_sum_in_range.0.range.0.min diff --git a/products/oslogin/terraform.yaml b/products/oslogin/terraform.yaml index 4a4cb6b06ee7..f96ec4d864e9 100644 --- a/products/oslogin/terraform.yaml +++ b/products/oslogin/terraform.yaml @@ -18,13 +18,8 @@ overrides: !ruby/object:Overrides::ResourceOverrides import_format: ["users/{{user}}/sshPublicKeys/{{fingerprint}}"] examples: - !ruby/object:Provider::Terraform::Examples - name: "os_login_ssh_key_provided_user" + name: "os_login_ssh_key_basic" primary_resource_id: "cache" - - !ruby/object:Provider::Terraform::Examples - name: "os_login_ssh_key_with_project" - primary_resource_id: "cache" - test_env_vars: - project: :PROJECT_NAME custom_code: !ruby/object:Provider::Terraform::CustomCode pre_create: templates/terraform/pre_create/os_login_ssh_public_key.go.erb post_create: templates/terraform/post_create/sshkeyfingerprint.go.erb diff --git a/products/privateca/api.yaml b/products/privateca/api.yaml index fb269225c3d6..52936b41fae4 100644 --- a/products/privateca/api.yaml +++ b/products/privateca/api.yaml @@ -13,13 +13,30 @@ apis_required: url: https://console.cloud.google.com/apis/library/privateca.googleapis.com/ objects: - !ruby/object:Api::Resource - name: 'CertificateAuthorities' + name: 'CertificateAuthority' base_url: 'projects/{{project}}/locations/{{location}}/certificateAuthorities' create_url: 'projects/{{project}}/locations/{{location}}/certificateAuthorities?certificateAuthorityId={{name}}' self_link: 'projects/{{project}}/locations/{{location}}/certificateAuthorities/{{name}}' input: true + async: !ruby/object:Api::OpAsync + operation: !ruby/object:Api::OpAsync::Operation + path: 'name' + base_url: '{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::OpAsync::Result + path: 'response' + resource_inside_response: true + status: !ruby/object:Api::OpAsync::Status + path: 'done' + complete: True + allowed: + - True + - False + error: !ruby/object:Api::OpAsync::Error + path: 'error' + message: 'message' description: | - A `CertificateAuthorities` is a toplevel logical grouping of `CertificateAuthority`. + A `CertificateAuthority` represents an individual Certificate Authority. A `CertificateAuthority` can be used to create Certificates. parameters: - !ruby/object:Api::Type::String name: 'location' @@ -35,6 +52,7 @@ objects: description: | Output only. The resource name for this CertificateAuthority in the format projects/*/locations/*/certificateAuthorities/*. required: true + url_param_only: true - !ruby/object:Api::Type::Enum name: 'type' required: true @@ -76,37 +94,30 @@ objects: name: 'countryCode' description: | The country code of the subject. - required: true - !ruby/object:Api::Type::String name: 'organization' description: | The organization of the subject. - required: true - !ruby/object:Api::Type::String name: 'organizationalUnit' description: | The organizationalUnit of the subject. - required: true - !ruby/object:Api::Type::String name: 'locality' description: | The locality or city of the subject. - required: true - !ruby/object:Api::Type::String name: 'province' description: | The province of the subject. - required: true - !ruby/object:Api::Type::String name: 'streetAddress' description: | The streetAddress or city of the subject. - required: true - !ruby/object:Api::Type::String name: 'postalCode' description: | The postalCode or city of the subject. - required: true - !ruby/object:Api::Type::NestedObject name: 'subjectAltName' description: | @@ -786,6 +797,7 @@ objects: name: 'crlAccessUrl' description: | The URL where this CertificateAuthority's CRLs are published. This will only be set for CAs that have been activated. + output: true - !ruby/object:Api::Type::Time name: 'createTime' description: | @@ -1483,10 +1495,6 @@ objects: - "KEY_TYPE_UNSPECIFIED" - "PEM_RSA_KEY" - "PEM_EC_KEY" - - - - references: !ruby/object:Api::Resource::ReferenceLinks guides: 'Creating a Certificate': diff --git a/products/privateca/terraform.yaml b/products/privateca/terraform.yaml index 3723c861d463..1290383e5c10 100644 --- a/products/privateca/terraform.yaml +++ b/products/privateca/terraform.yaml @@ -12,26 +12,14 @@ # limitations under the License. --- !ruby/object:Provider::Terraform::Config -# overrides: !ruby/object:Overrides::ResourceOverrides -# CertificateAuthorities: !ruby/object:Overrides::Terraform::ResourceOverride -# description: | -# {{description}} -# ~> **Note:** CertificateAuthorities cannot be deleted from Google Cloud Platform. -# Destroying a Terraform-managed KeyRing will remove it from state but -# *will not delete the resource on the server.* -# id_format: "projects/{{project}}/locations/{{location}}/certificateAuthorities/{{name}}" -# import_format: ["projects/{{project}}/locations/{{location}}/certificateAuthorities/{{name}}"] -# skip_delete: true -# properties: -# createTime: !ruby/object:Overrides::Terraform::PropertyOverride -# exclude: true -# location: !ruby/object:Overrides::Terraform::PropertyOverride -# ignore_read: true -# custom_code: !ruby/object:Provider::Terraform::CustomCode -# decoder: templates/terraform/decoders/long_name_to_self_link.go.erb -# encoder: templates/terraform/encoders/send_nil_body.go.erb - - +overrides: !ruby/object:Overrides::ResourceOverrides + CertificateAuthority: !ruby/object:Overrides::Terraform::ResourceOverride + autogen_async: true + examples: + - !ruby/object:Provider::Terraform::Examples + min_version: beta + name: "certificate_authority_basic" + primary_resource_id: "root_ca" files: !ruby/object:Provider::Config::Files # These files have templating (ERB) code that will be run. # This is usually to add licensing info, autogeneration notices, etc. diff --git a/products/pubsub/terraform.yaml b/products/pubsub/terraform.yaml index a42966f450fe..6da64155fd15 100644 --- a/products/pubsub/terraform.yaml +++ b/products/pubsub/terraform.yaml @@ -83,7 +83,6 @@ overrides: !ruby/object:Overrides::ResourceOverrides - !ruby/object:Provider::Terraform::Examples name: "pubsub_subscription_push" primary_resource_id: "example" - skip_test: true vars: topic_name: "example-topic" subscription_name: "example-subscription" @@ -127,7 +126,9 @@ overrides: !ruby/object:Overrides::ResourceOverrides retryPolicy.minimumBackoff: !ruby/object:Overrides::Terraform::PropertyOverride default_from_api: true retryPolicy.maximumBackoff: !ruby/object:Overrides::Terraform::PropertyOverride - default_from_api: true + default_from_api: true + pushConfig.attributes: !ruby/object:Overrides::Terraform::PropertyOverride + diff_suppress_func: 'ignoreMissingKeyInMap("x-goog-version")' custom_code: !ruby/object:Provider::Terraform::CustomCode constants: templates/terraform/constants/subscription.go.erb decoder: templates/terraform/decoders/pubsub_subscription.erb diff --git a/products/storage/terraform.yaml b/products/storage/terraform.yaml index 56fd4bfe903d..5fa12e75dfe9 100644 --- a/products/storage/terraform.yaml +++ b/products/storage/terraform.yaml @@ -133,7 +133,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides state: !ruby/object:Overrides::Terraform::PropertyOverride update_url: projects/{{project}}/hmacKeys/{{access_id}} custom_code: !ruby/object:Provider::Terraform::CustomCode - decoder: templates/terraform/decoders/storage_hmac_key.go.erb + decoder: templates/terraform/decoders/treat_deleted_state_as_gone.go.erb pre_delete: templates/terraform/pre_delete/storage_hmac_key.go.erb post_create: templates/terraform/post_create/storage_hmac_key.go.erb test_check_destroy: templates/terraform/custom_check_destroy/storage_hmac_key.go.erb diff --git a/provider/terraform_object_library.rb b/provider/terraform_object_library.rb index f35d2e548fcd..3db19d30a89e 100644 --- a/provider/terraform_object_library.rb +++ b/provider/terraform_object_library.rb @@ -102,6 +102,8 @@ def copy_common_files(output_folder, generate_code, _generate_docs) 'third_party/validator/folder_iam.go'], ['google/container.go', 'third_party/validator/container.go'], + ['google/project_service.go', + 'third_party/validator/project_service.go'], ['google/image.go', 'third_party/terraform/utils/image.go'], ['google/disk_type.go', @@ -141,7 +143,17 @@ def copy_common_files(output_folder, generate_code, _generate_docs) ['google/retry_transport.go', 'third_party/terraform/utils/retry_transport.go'], ['google/error_retry_predicates.go', - 'third_party/terraform/utils/error_retry_predicates.go'] + 'third_party/terraform/utils/error_retry_predicates.go'], + ['google/pubsub_utils.go', + 'third_party/terraform/utils/pubsub_utils.go'], + ['google/sqladmin_operation.go', + 'third_party/terraform/utils/sqladmin_operation.go'], + ['google/path_or_contents.go', + 'third_party/terraform/utils/path_or_contents.go'], + ['google/mutexkv.go', + 'third_party/terraform/utils/mutexkv.go'], + ['google/hashcode.go', + 'third_party/terraform/utils/hashcode.go'] ]) end diff --git a/templates/terraform/constants/iam_workload_identity_pool_provider.go.erb b/templates/terraform/constants/iam_workload_identity_pool_provider.go.erb new file mode 100644 index 000000000000..c8f6100ceaf7 --- /dev/null +++ b/templates/terraform/constants/iam_workload_identity_pool_provider.go.erb @@ -0,0 +1,27 @@ +const workloadIdentityPoolProviderIdRegexp = `^[0-9a-z-]+$` + +func validateWorkloadIdentityPoolProviderId(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if strings.HasPrefix(value, "gcp-") { + errors = append(errors, fmt.Errorf( + "%q (%q) can not start with \"gcp-\"", k, value)) + } + + if !regexp.MustCompile(workloadIdentityPoolProviderIdRegexp).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q must contain only lowercase letters (a-z), numbers (0-9), or dashes (-)", k)) + } + + if len(value) < 4 { + errors = append(errors, fmt.Errorf( + "%q cannot be smaller than 4 characters", k)) + } + + if len(value) > 32 { + errors = append(errors, fmt.Errorf( + "%q cannot be greater than 32 characters", k)) + } + + return +} diff --git a/templates/terraform/custom_check_destroy/iam_workload_identity_pool.go.erb b/templates/terraform/custom_check_destroy/iam_workload_identity_pool.go.erb new file mode 100644 index 000000000000..2f8625e11abb --- /dev/null +++ b/templates/terraform/custom_check_destroy/iam_workload_identity_pool.go.erb @@ -0,0 +1,17 @@ +config := googleProviderConfig(t) + +url, err := replaceVarsForTest(config, rs, "{{IAMBetaBasePath}}projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}") +if err != nil { + return err +} + +res, err := sendRequest(config, "GET", "", url, config.userAgent, nil) +if err != nil { + return nil +} + +if v := res["state"]; v == "DELETED" { + return nil +} + +return fmt.Errorf("IAMBetaWorkloadIdentityPool still exists at %s", url) diff --git a/templates/terraform/custom_check_destroy/iam_workload_identity_pool_provider.go.erb b/templates/terraform/custom_check_destroy/iam_workload_identity_pool_provider.go.erb new file mode 100644 index 000000000000..be95789594e7 --- /dev/null +++ b/templates/terraform/custom_check_destroy/iam_workload_identity_pool_provider.go.erb @@ -0,0 +1,17 @@ +config := googleProviderConfig(t) + +url, err := replaceVarsForTest(config, rs, "{{IAMBetaBasePath}}projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}/providers/{{workload_identity_pool_provider_id}}") +if err != nil { + return err +} + +res, err := sendRequest(config, "GET", "", url, config.userAgent, nil) +if err != nil { + return nil +} + +if v := res["state"]; v == "DELETED" { + return nil +} + +return fmt.Errorf("IAMBetaWorkloadIdentityPoolProvider still exists at %s", url) diff --git a/templates/terraform/decoders/storage_hmac_key.go.erb b/templates/terraform/decoders/treat_deleted_state_as_gone.go.erb similarity index 100% rename from templates/terraform/decoders/storage_hmac_key.go.erb rename to templates/terraform/decoders/treat_deleted_state_as_gone.go.erb diff --git a/templates/terraform/examples/certificate_authority_basic.tf.erb b/templates/terraform/examples/certificate_authority_basic.tf.erb new file mode 100644 index 000000000000..fe4cf2ce39f1 --- /dev/null +++ b/templates/terraform/examples/certificate_authority_basic.tf.erb @@ -0,0 +1,26 @@ +resource "google_private_ca_certificate_authority" "<%= ctx[:primary_resource_id] %>" { + provider = google-beta + name = "tf-test%{random_suffix}" + location = "us-central1" + lifetime = "30000s" + type = "SELF_SIGNED" + tier = "DEVOPS" + + key_spec { + algorithm = "RSA_PSS_2048_SHA256" + } + config { + subject_config { + common_name = "Example Authority G1" + subject { + organization = "Example, Org." + } + } + + reusable_config { + # TODO(drebes): Replace this by a data source. The returned project is by number + # so either we make this computed or have the datasource convert it. + reusable_config= "projects/568668481468/locations/us-central1/reusableConfigs/root-unconstrained" + } + } +} diff --git a/templates/terraform/examples/iam_workload_identity_pool_provider_aws_basic.tf.erb b/templates/terraform/examples/iam_workload_identity_pool_provider_aws_basic.tf.erb new file mode 100644 index 000000000000..d372c80425ae --- /dev/null +++ b/templates/terraform/examples/iam_workload_identity_pool_provider_aws_basic.tf.erb @@ -0,0 +1,13 @@ +resource "google_iam_workload_identity_pool" "pool" { + provider = google-beta + workload_identity_pool_id = "<%= ctx[:vars]["workload_identity_pool_id"] %>" +} + +resource "google_iam_workload_identity_pool_provider" "<%= ctx[:primary_resource_id] %>" { + provider = google-beta + workload_identity_pool_id = google_iam_workload_identity_pool.pool.workload_identity_pool_id + workload_identity_pool_provider_id = "<%= ctx[:vars]["workload_identity_pool_provider_id"] %>" + aws { + account_id = "999999999999" + } +} diff --git a/templates/terraform/examples/iam_workload_identity_pool_provider_aws_full.tf.erb b/templates/terraform/examples/iam_workload_identity_pool_provider_aws_full.tf.erb new file mode 100644 index 000000000000..eeb461223d5d --- /dev/null +++ b/templates/terraform/examples/iam_workload_identity_pool_provider_aws_full.tf.erb @@ -0,0 +1,22 @@ +resource "google_iam_workload_identity_pool" "pool" { + provider = google-beta + workload_identity_pool_id = "<%= ctx[:vars]["workload_identity_pool_id"] %>" +} + +resource "google_iam_workload_identity_pool_provider" "<%= ctx[:primary_resource_id] %>" { + provider = google-beta + workload_identity_pool_id = google_iam_workload_identity_pool.pool.workload_identity_pool_id + workload_identity_pool_provider_id = "<%= ctx[:vars]["workload_identity_pool_provider_id"] %>" + display_name = "Name of provider" + description = "AWS identity pool provider for automated test" + disabled = true + attribute_condition = "attribute.aws_role==\"arn:aws:sts::999999999999:assumed-role/stack-eu-central-1-lambdaRole\"" + attribute_mapping = { + "google.subject" = "assertion.arn" + "attribute.aws_account" = "assertion.account" + "attribute.environment" = "assertion.arn.contains(\":instance-profile/Production\") ? \"prod\" : \"test\"" + } + aws { + account_id = "999999999999" + } +} diff --git a/templates/terraform/examples/iam_workload_identity_pool_provider_oidc_basic.tf.erb b/templates/terraform/examples/iam_workload_identity_pool_provider_oidc_basic.tf.erb new file mode 100644 index 000000000000..21ccc08d18fe --- /dev/null +++ b/templates/terraform/examples/iam_workload_identity_pool_provider_oidc_basic.tf.erb @@ -0,0 +1,16 @@ +resource "google_iam_workload_identity_pool" "pool" { + provider = google-beta + workload_identity_pool_id = "<%= ctx[:vars]["workload_identity_pool_id"] %>" +} + +resource "google_iam_workload_identity_pool_provider" "<%= ctx[:primary_resource_id] %>" { + provider = google-beta + workload_identity_pool_id = google_iam_workload_identity_pool.pool.workload_identity_pool_id + workload_identity_pool_provider_id = "<%= ctx[:vars]["workload_identity_pool_provider_id"] %>" + attribute_mapping = { + "google.subject" = "assertion.sub" + } + oidc { + issuer_uri = "https://sts.windows.net/azure-tenant-id" + } +} diff --git a/templates/terraform/examples/iam_workload_identity_pool_provider_oidc_full.tf.erb b/templates/terraform/examples/iam_workload_identity_pool_provider_oidc_full.tf.erb new file mode 100644 index 000000000000..05e0a4e899e3 --- /dev/null +++ b/templates/terraform/examples/iam_workload_identity_pool_provider_oidc_full.tf.erb @@ -0,0 +1,28 @@ +resource "google_iam_workload_identity_pool" "pool" { + provider = google-beta + workload_identity_pool_id = "<%= ctx[:vars]["workload_identity_pool_id"] %>" +} + +resource "google_iam_workload_identity_pool_provider" "<%= ctx[:primary_resource_id] %>" { + provider = google-beta + workload_identity_pool_id = google_iam_workload_identity_pool.pool.workload_identity_pool_id + workload_identity_pool_provider_id = "<%= ctx[:vars]["workload_identity_pool_provider_id"] %>" + display_name = "Name of provider" + description = "OIDC identity pool provider for automated test" + disabled = true + attribute_condition = "\"e968c2ef-047c-498d-8d79-16ca1b61e77e\" in assertion.groups" + attribute_mapping = { + "google.subject" = "\"azure::\" + assertion.tid + \"::\" + assertion.sub" + "attribute.tid" = "assertion.tid" + "attribute.managed_identity_name" = <" { request_based_sli { distribution_cut { - distribution_filter = join(" AND ", [ - "metric.type=\"serviceruntime.googleapis.com/api/request_latencies\"", - "resource.type=\"consumed_api\"", - "resource.label.\"project_id\"=\"<%= ctx[:test_env_vars]['project'] -%>\"", - ]) - - range { - max = 10 - } - } + distribution_filter = "metric.type=\"serviceruntime.googleapis.com/api/request_latencies\" resource.type=\"api\" " + range { + max = 0.5 + } + } } } diff --git a/templates/terraform/examples/os_login_ssh_key_provided_user.tf.erb b/templates/terraform/examples/os_login_ssh_key_basic.tf.erb similarity index 100% rename from templates/terraform/examples/os_login_ssh_key_provided_user.tf.erb rename to templates/terraform/examples/os_login_ssh_key_basic.tf.erb diff --git a/templates/terraform/examples/os_login_ssh_key_with_project.tf.erb b/templates/terraform/examples/os_login_ssh_key_with_project.tf.erb deleted file mode 100644 index 26dca83263ab..000000000000 --- a/templates/terraform/examples/os_login_ssh_key_with_project.tf.erb +++ /dev/null @@ -1,8 +0,0 @@ -data "google_client_openid_userinfo" "me" { -} - -resource "google_os_login_ssh_public_key" "<%= ctx[:primary_resource_id] %>" { - user = data.google_client_openid_userinfo.me.email - key = file("path/to/id_rsa.pub") - project = "<%= ctx[:test_env_vars]['project'] -%>" -} diff --git a/third_party/terraform/data_sources/data_source_iam_beta_workload_identity_pool.go.erb b/third_party/terraform/data_sources/data_source_iam_beta_workload_identity_pool.go.erb new file mode 100644 index 000000000000..21b8289e000d --- /dev/null +++ b/third_party/terraform/data_sources/data_source_iam_beta_workload_identity_pool.go.erb @@ -0,0 +1,32 @@ +<% autogen_exception -%> +package google + +<% unless version == 'ga' -%> +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceIAMBetaWorkloadIdentityPool() *schema.Resource { + + dsSchema := (resourceIAMBetaWorkloadIdentityPool().Schema) + addRequiredFieldsToSchema(dsSchema, "workload_identity_pool_id") + addOptionalFieldsToSchema(dsSchema, "project") + + return &schema.Resource{ + Read: dataSourceIAMBetaWorkloadIdentityPoolRead, + Schema: dsSchema, + } +} + +func dataSourceIAMBetaWorkloadIdentityPoolRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + id, err := replaceVars(d, config, "projects/{{project}}/locations/global/workloadIdentityPools/{{workload_identity_pool_id}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + return resourceIAMBetaWorkloadIdentityPoolRead(d, meta) + +} +<% end -%> diff --git a/third_party/terraform/resources/resource_bigquery_table.go b/third_party/terraform/resources/resource_bigquery_table.go index 4ddd8c954b45..6a6f75e3ee67 100644 --- a/third_party/terraform/resources/resource_bigquery_table.go +++ b/third_party/terraform/resources/resource_bigquery_table.go @@ -153,9 +153,9 @@ func resourceBigQueryTable() *schema.Resource { "source_format": { Type: schema.TypeString, Required: true, - Description: `The data format. Supported values are: "CSV", "GOOGLE_SHEETS", "NEWLINE_DELIMITED_JSON", "AVRO", "PARQUET", and "DATSTORE_BACKUP". To use "GOOGLE_SHEETS" the scopes must include "googleapis.com/auth/drive.readonly".`, + Description: `The data format. Supported values are: "CSV", "GOOGLE_SHEETS", "NEWLINE_DELIMITED_JSON", "AVRO", "PARQUET", and "DATASTORE_BACKUP". To use "GOOGLE_SHEETS" the scopes must include "googleapis.com/auth/drive.readonly".`, ValidateFunc: validation.StringInSlice([]string{ - "CSV", "GOOGLE_SHEETS", "NEWLINE_DELIMITED_JSON", "AVRO", "DATSTORE_BACKUP", "PARQUET", + "CSV", "GOOGLE_SHEETS", "NEWLINE_DELIMITED_JSON", "AVRO", "DATASTORE_BACKUP", "PARQUET", }, false), }, // SourceURIs [Required] The fully-qualified URIs that point to your data in Google Cloud. diff --git a/third_party/terraform/resources/resource_bigtable_instance.go b/third_party/terraform/resources/resource_bigtable_instance.go index 80a729e77747..b0e97985f1df 100644 --- a/third_party/terraform/resources/resource_bigtable_instance.go +++ b/third_party/terraform/resources/resource_bigtable_instance.go @@ -52,7 +52,7 @@ func resourceBigtableInstance() *schema.Resource { Type: schema.TypeList, Optional: true, Computed: true, - Description: `A block of cluster configuration options. This can be specified at least once, and up to 4 times.`, + Description: `A block of cluster configuration options. This can be specified at least once.`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "cluster_id": { @@ -382,13 +382,10 @@ func expandBigtableClusters(clusters []interface{}, instanceID string) []bigtabl func resourceBigtableInstanceClusterReorderTypeList(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error { oldCount, newCount := diff.GetChange("cluster.#") - // simulate Required:true, MinItems:1, MaxItems:4 for "cluster" + // simulate Required:true, MinItems:1 for "cluster" if newCount.(int) < 1 { return fmt.Errorf("config is invalid: Too few cluster blocks: Should have at least 1 \"cluster\" block") } - if newCount.(int) > 4 { - return fmt.Errorf("config is invalid: Too many cluster blocks: No more than 4 \"cluster\" blocks are allowed") - } // exit early if we're in create (name's old value is nil) n, _ := diff.GetChange("name") diff --git a/third_party/terraform/resources/resource_cloudfunctions_function.go b/third_party/terraform/resources/resource_cloudfunctions_function.go index b0881faf394c..baf0d9523e36 100644 --- a/third_party/terraform/resources/resource_cloudfunctions_function.go +++ b/third_party/terraform/resources/resource_cloudfunctions_function.go @@ -21,6 +21,7 @@ var functionAllowedMemory = map[int]bool{ 512: true, 1024: true, 2048: true, + 4096: true, } var allowedIngressSettings = []string{ diff --git a/third_party/terraform/resources/resource_compute_instance.go.erb b/third_party/terraform/resources/resource_compute_instance.go.erb index 4d59dc175d5d..5842646ce039 100644 --- a/third_party/terraform/resources/resource_compute_instance.go.erb +++ b/third_party/terraform/resources/resource_compute_instance.go.erb @@ -193,7 +193,7 @@ func resourceComputeInstance() *schema.Resource { Computed: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"pd-standard", "pd-ssd", "pd-balanced"}, false), - Description: `The GCE disk type. One of pd-standard, pd-ssd or pd-balanced.`, + Description: `The Google Compute Engine disk type. One of pd-standard, pd-ssd or pd-balanced.`, }, "image": { diff --git a/third_party/terraform/resources/resource_compute_instance_template.go.erb b/third_party/terraform/resources/resource_compute_instance_template.go.erb index 849d12e431a3..01171e3fe5be 100644 --- a/third_party/terraform/resources/resource_compute_instance_template.go.erb +++ b/third_party/terraform/resources/resource_compute_instance_template.go.erb @@ -142,7 +142,7 @@ func resourceComputeInstanceTemplate() *schema.Resource { Optional: true, ForceNew: true, Computed: true, - Description: `The GCE disk type. Can be either "pd-ssd", "local-ssd", "pd-balanced" or "pd-standard".`, + Description: `The Google Compute Engine disk type. Can be either "pd-ssd", "local-ssd", "pd-balanced" or "pd-standard".`, }, "labels": { @@ -191,7 +191,7 @@ func resourceComputeInstanceTemplate() *schema.Resource { Optional: true, ForceNew: true, Computed: true, - Description: `The type of GCE disk, can be either "SCRATCH" or "PERSISTENT".`, + Description: `The type of Google Compute Engine disk, can be either "SCRATCH" or "PERSISTENT".`, }, "disk_encryption_key": { diff --git a/third_party/terraform/resources/resource_container_cluster.go.erb b/third_party/terraform/resources/resource_container_cluster.go.erb index 4bd5a658f827..335466c51f8a 100644 --- a/third_party/terraform/resources/resource_container_cluster.go.erb +++ b/third_party/terraform/resources/resource_container_cluster.go.erb @@ -602,7 +602,7 @@ func resourceContainerCluster() *schema.Resource { Type: schema.TypeList, Required: true, MaxItems: 1, - Description: `Notification config for pubsub`, + Description: `Notification config for Cloud Pub/Sub`, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "enabled": { @@ -613,7 +613,7 @@ func resourceContainerCluster() *schema.Resource { "topic": { Type: schema.TypeString, Optional: true, - Description: `The pubsub topic to send the notification to, must be in the format: projects/{project}/topics/{topic}`, + Description: `The Cloud Pub/Sub topic to send the notification to, must be in the format: projects/{project}/topics/{topic}`, }, }, }, diff --git a/third_party/terraform/resources/resource_dataflow_job.go b/third_party/terraform/resources/resource_dataflow_job.go index 805f40e9e507..39b3b552e4b2 100644 --- a/third_party/terraform/resources/resource_dataflow_job.go +++ b/third_party/terraform/resources/resource_dataflow_job.go @@ -66,13 +66,13 @@ func resourceDataflowJob() *schema.Resource { "template_gcs_path": { Type: schema.TypeString, Required: true, - Description: `The GCS path to the Dataflow job template.`, + Description: `The Google Cloud Storage path to the Dataflow job template.`, }, "temp_gcs_location": { Type: schema.TypeString, Required: true, - Description: `A writeable location on GCS for the Dataflow job to dump its temporary data.`, + Description: `A writeable location on Google Cloud Storage for the Dataflow job to dump its temporary data.`, }, "zone": { @@ -515,10 +515,7 @@ func resourceDataflowJobLaunchTemplate(config *Config, project, region, userAgen } func resourceDataflowJobSetupEnv(d *schema.ResourceData, config *Config) (dataflow.RuntimeEnvironment, error) { - zone, err := getZone(d, config) - if err != nil { - return dataflow.RuntimeEnvironment{}, err - } + zone, _ := getZone(d, config) labels := expandStringMap(d, "labels") diff --git a/third_party/terraform/resources/resource_google_organization_iam_custom_role.go b/third_party/terraform/resources/resource_google_organization_iam_custom_role.go index 0cbf5c262549..5547a4ce8bfb 100644 --- a/third_party/terraform/resources/resource_google_organization_iam_custom_role.go +++ b/third_party/terraform/resources/resource_google_organization_iam_custom_role.go @@ -84,7 +84,7 @@ func resourceGoogleOrganizationIamCustomRoleCreate(d *schema.ResourceData, meta // Look for role with given ID. // If it exists in deleted state, update to match "created" role state - // If it exists and and is enabled, return error - we should not try to recreate. + // If it exists and is enabled, return error - we should not try to recreate. r, err := config.NewIamClient(userAgent).Organizations.Roles.Get(roleId).Do() if err == nil { if r.Deleted { diff --git a/third_party/terraform/resources/resource_google_project_default_service_accounts.go b/third_party/terraform/resources/resource_google_project_default_service_accounts.go new file mode 100644 index 000000000000..618fa4714480 --- /dev/null +++ b/third_party/terraform/resources/resource_google_project_default_service_accounts.go @@ -0,0 +1,202 @@ +package google + +import ( + "fmt" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1" + "google.golang.org/api/iam/v1" +) + +// resourceGoogleProjectDefaultServiceAccounts returns a *schema.Resource that allows a customer +// to manage all the default serviceAccounts. +// It does mean that terraform tried to perform the action in the SA at some point but does not ensure that +// all defaults serviceAccounts where managed. Eg.: API was activated after project creation. +func resourceGoogleProjectDefaultServiceAccounts() *schema.Resource { + return &schema.Resource{ + Create: resourceGoogleProjectDefaultServiceAccountsCreate, + Read: schema.Noop, + Update: schema.Noop, + Delete: resourceGoogleProjectDefaultServiceAccountsDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Read: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateProjectID(), + Description: `The project ID where service accounts are created.`, + }, + "action": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"DEPRIVILEGE", "DELETE", "DISABLE"}, false), + Description: `The action to be performed in the default service accounts. Valid values are: DEPRIVILEGE, DELETE, DISABLE. + Note that DEPRIVILEGE action will ignore the REVERT configuration in the restore_policy.`, + }, + "restore_policy": { + Type: schema.TypeString, + Optional: true, + Default: "REVERT", + ValidateFunc: validation.StringInSlice([]string{"NONE", "REVERT"}, false), + Description: `The action to be performed in the default service accounts on the resource destroy. + Valid values are NONE and REVERT. If set to REVERT it will attempt to restore all default SAs but in the DEPRIVILEGE action.`, + }, + "service_accounts": { + Type: schema.TypeMap, + Computed: true, + Description: `The Service Accounts changed by this resource. It is used for revert the action on the destroy.`, + }, + }, + } +} + +func resourceGoogleProjectDefaultServiceAccountsDoAction(d *schema.ResourceData, meta interface{}, action, uniqueID, email, project string) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + serviceAccountSelfLink := fmt.Sprintf("projects/%s/serviceAccounts/%s", project, uniqueID) + switch action { + case "DELETE": + _, err := config.NewIamClient(userAgent).Projects.ServiceAccounts.Delete(serviceAccountSelfLink).Do() + if err != nil { + return fmt.Errorf("cannot delete service account %s: %v", serviceAccountSelfLink, err) + } + case "UNDELETE": + _, err := config.NewIamClient(userAgent).Projects.ServiceAccounts.Undelete(serviceAccountSelfLink, &iam.UndeleteServiceAccountRequest{}).Do() + if err != nil { + return fmt.Errorf("cannot undelete service account %s: %v", serviceAccountSelfLink, err) + } + case "DISABLE": + _, err := config.NewIamClient(userAgent).Projects.ServiceAccounts.Disable(serviceAccountSelfLink, &iam.DisableServiceAccountRequest{}).Do() + if err != nil { + return fmt.Errorf("cannot disable service account %s: %v", serviceAccountSelfLink, err) + } + case "ENABLE": + _, err := config.NewIamClient(userAgent).Projects.ServiceAccounts.Enable(serviceAccountSelfLink, &iam.EnableServiceAccountRequest{}).Do() + if err != nil { + return fmt.Errorf("cannot enable service account %s: %v", serviceAccountSelfLink, err) + } + case "DEPRIVILEGE": + iamPolicy, err := config.NewResourceManagerClient(userAgent).Projects.GetIamPolicy(project, &cloudresourcemanager.GetIamPolicyRequest{}).Do() + if err != nil { + return fmt.Errorf("cannot get IAM policy on project %s: %v", project, err) + } + + // Creates a new slice with all members but the service account + for _, bind := range iamPolicy.Bindings { + newMembers := []string{} + for _, member := range bind.Members { + if member != fmt.Sprintf("serviceAccount:%s", email) { + newMembers = append(newMembers, member) + } + } + bind.Members = newMembers + } + _, err = config.NewResourceManagerClient(userAgent).Projects.SetIamPolicy(project, &cloudresourcemanager.SetIamPolicyRequest{}).Do() + if err != nil { + return fmt.Errorf("cannot update IAM policy on project %s: %v", project, err) + } + default: + return fmt.Errorf("action %s is not a valid action", action) + } + + return nil +} + +func resourceGoogleProjectDefaultServiceAccountsCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + pid := d.Get("project").(string) + action := d.Get("action").(string) + + serviceAccounts, err := resourceGoogleProjectDefaultServiceAccountsList(config, d, userAgent) + if err != nil { + return fmt.Errorf("error listing service accounts on project %s: %v", pid, err) + } + changedServiceAccounts := make(map[string]interface{}) + for _, sa := range serviceAccounts { + // As per documentation https://cloud.google.com/iam/docs/service-accounts#default + // we have just two default SAs and the e-mail may change. So, it is been filtered + // by the Display Name + if isDefaultServiceAccount(sa.DisplayName) { + err := resourceGoogleProjectDefaultServiceAccountsDoAction(d, meta, action, sa.UniqueId, sa.Email, pid) + if err != nil { + return fmt.Errorf("error doing action %s on Service Account %s: %v", action, sa.Email, err) + } + changedServiceAccounts[sa.UniqueId] = sa.Email + } + } + if err := d.Set("service_accounts", changedServiceAccounts); err != nil { + return fmt.Errorf("error setting service_accounts: %s", err) + } + d.SetId(prefixedProject(pid)) + + return nil +} + +func resourceGoogleProjectDefaultServiceAccountsList(config *Config, d *schema.ResourceData, userAgent string) ([]*iam.ServiceAccount, error) { + pid := d.Get("project").(string) + response, err := config.NewIamClient(userAgent).Projects.ServiceAccounts.List(prefixedProject(pid)).Do() + if err != nil { + return nil, fmt.Errorf("failed to list service accounts on project %q: %v", pid, err) + } + return response.Accounts, nil +} + +func resourceGoogleProjectDefaultServiceAccountsDelete(d *schema.ResourceData, meta interface{}) error { + if d.Get("restore_policy").(string) == "NONE" { + d.SetId("") + return nil + } + + pid := d.Get("project").(string) + for saUniqueID, saEmail := range d.Get("service_accounts").(map[string]interface{}) { + origAction := d.Get("action").(string) + newAction := "" + // We agreed to not revert the DEPRIVILEGE because Morgante said it is not required. + // It may be an enhancement. https://github.com/hashicorp/terraform-provider-google/issues/4135#issuecomment-709480278 + if origAction == "DISABLE" { + newAction = "ENABLE" + } else if origAction == "DELETE" { + newAction = "UNDELETE" + } + if newAction != "" { + err := resourceGoogleProjectDefaultServiceAccountsDoAction(d, meta, newAction, saUniqueID, saEmail.(string), pid) + if err != nil { + return fmt.Errorf("error doing action %s on Service Account %s: %v", newAction, saUniqueID, err) + } + } + } + + d.SetId("") + + return nil +} + +func isDefaultServiceAccount(displayName string) bool { + gceDefaultSA := "compute engine default service account" + appEngineDefaultSA := "app engine default service account" + saDisplayName := strings.ToLower(displayName) + if saDisplayName == gceDefaultSA || saDisplayName == appEngineDefaultSA { + return true + } + + return false +} diff --git a/third_party/terraform/resources/resource_sql_database_instance.go.erb b/third_party/terraform/resources/resource_sql_database_instance.go.erb index 32f5058e2dce..192d5944af6f 100644 --- a/third_party/terraform/resources/resource_sql_database_instance.go.erb +++ b/third_party/terraform/resources/resource_sql_database_instance.go.erb @@ -137,7 +137,7 @@ func resourceSqlDatabaseInstance() *schema.Resource { Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, Deprecated: "This property is only applicable to First Generation instances, and First Generation instances are now deprecated.", - Description: `This property is only applicable to First Generation instances. First Generation instances are now deprecated, see https://cloud.google.com/sql/docs/mysql/deprecation-notice for information on how to upgrade to Second Generation instances. A list of Google App Engine (GAE) project names that are allowed to access this instance.`, + Description: `This property is only applicable to First Generation instances. First Generation instances are now deprecated, see https://cloud.google.com/sql/docs/mysql/deprecation-notice for information on how to upgrade to Second Generation instances. A list of Google App Engine project names that are allowed to access this instance.`, }, "availability_type": { Type: schema.TypeString, @@ -491,7 +491,7 @@ settings.backup_configuration.binary_log_enabled are both set to true.`, Optional: true, ForceNew: true, AtLeastOneOf: replicaConfigurationKeys, - Description: `Path to a SQL file in GCS from which slave instances are created. Format is gs://bucket/filename.`, + Description: `Path to a SQL file in Google Cloud Storage from which slave instances are created. Format is gs://bucket/filename.`, }, "failover_target": { Type: schema.TypeBool, diff --git a/third_party/terraform/resources/resource_storage_bucket.go b/third_party/terraform/resources/resource_storage_bucket.go index 5f6d5b9be975..caeb0228dfc5 100644 --- a/third_party/terraform/resources/resource_storage_bucket.go +++ b/third_party/terraform/resources/resource_storage_bucket.go @@ -87,7 +87,7 @@ func resourceStorageBucket() *schema.Resource { StateFunc: func(s interface{}) string { return strings.ToUpper(s.(string)) }, - Description: `The GCS location`, + Description: `The Google Cloud Storage location`, }, "project": { @@ -312,7 +312,7 @@ func resourceStorageBucket() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - Description: `The object prefix for log objects. If it's not provided, by default GCS sets this to this bucket's name.`, + Description: `The object prefix for log objects. If it's not provided, by default Google Cloud Storage sets this to this bucket's name.`, }, }, }, diff --git a/third_party/terraform/resources/resource_storage_notification.go b/third_party/terraform/resources/resource_storage_notification.go index 0f897b26e17a..7e8dc4fafb60 100644 --- a/third_party/terraform/resources/resource_storage_notification.go +++ b/third_party/terraform/resources/resource_storage_notification.go @@ -39,7 +39,7 @@ func resourceStorageNotification() *schema.Resource { Required: true, ForceNew: true, DiffSuppressFunc: compareSelfLinkOrResourceName, - Description: `The Cloud PubSub topic to which this subscription publishes. Expects either the topic name, assumed to belong to the default GCP provider project, or the project-level name, i.e. projects/my-gcp-project/topics/my-topic or my-topic. If the project is not set in the provider, you will need to use the project-level name.`, + Description: `The Cloud Pub/Sub topic to which this subscription publishes. Expects either the topic name, assumed to belong to the default GCP provider project, or the project-level name, i.e. projects/my-gcp-project/topics/my-topic or my-topic. If the project is not set in the provider, you will need to use the project-level name.`, }, "custom_attributes": { @@ -49,7 +49,7 @@ func resourceStorageNotification() *schema.Resource { Elem: &schema.Schema{ Type: schema.TypeString, }, - Description: ` A set of key/value attribute pairs to attach to each Cloud PubSub message published for this notification subscription`, + Description: ` A set of key/value attribute pairs to attach to each Cloud Pub/Sub message published for this notification subscription`, }, "event_types": { diff --git a/third_party/terraform/resources/resource_storage_transfer_job.go b/third_party/terraform/resources/resource_storage_transfer_job.go index 2a13425fe0b6..d3f15188ac41 100644 --- a/third_party/terraform/resources/resource_storage_transfer_job.go +++ b/third_party/terraform/resources/resource_storage_transfer_job.go @@ -2,13 +2,14 @@ package google import ( "fmt" + "log" + "strings" + "time" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "google.golang.org/api/storagetransfer/v1" - "log" - "strings" - "time" ) var ( @@ -137,7 +138,7 @@ func resourceStorageTransferJob() *schema.Resource { }, }, }, - Description: `Schedule specification defining when the Transfer Job should be scheduled to start, end and and what time to run.`, + Description: `Schedule specification defining when the Transfer Job should be scheduled to start, end and what time to run.`, }, "status": { Type: schema.TypeString, @@ -427,7 +428,7 @@ func resourceStorageTransferJobRead(d *schema.ResourceData, meta interface{}) er } name := d.Get("name").(string) - res, err := config.NewStorageTransferClient(userAgent).TransferJobs.Get(name).ProjectId(project).Do() + res, err := config.NewStorageTransferClient(userAgent).TransferJobs.Get(name, project).Do() if err != nil { return handleNotFoundError(err, d, fmt.Sprintf("Transfer Job %q", name)) } diff --git a/third_party/terraform/tests/data_source_iam_beta_workload_identity_pool_test.go.erb b/third_party/terraform/tests/data_source_iam_beta_workload_identity_pool_test.go.erb new file mode 100644 index 000000000000..ecfbfc26d410 --- /dev/null +++ b/third_party/terraform/tests/data_source_iam_beta_workload_identity_pool_test.go.erb @@ -0,0 +1,47 @@ +<% autogen_exception -%> +package google + +<% unless version == 'ga' -%> +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceIAMBetaWorkloadIdentityPool_basic(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIAMBetaWorkloadIdentityPoolDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceIAMBetaWorkloadIdentityPoolBasic(context), + Check: resource.ComposeTestCheckFunc( + checkDataSourceStateMatchesResourceState("data.google_iam_workload_identity_pool.foo", "google_iam_workload_identity_pool.bar"), + ), + }, + }, + }) +} + +func testAccDataSourceIAMBetaWorkloadIdentityPoolBasic(context map[string]interface{}) string { + return Nprintf(` +resource "google_iam_workload_identity_pool" "bar" { + workload_identity_pool_id = "bar-pool-%{random_suffix}" + display_name = "Name of pool" + description = "Identity pool for automated test" + disabled = true +} + +data "google_iam_workload_identity_pool" "foo" { + workload_identity_pool_id = google_iam_workload_identity_pool.bar.workload_identity_pool_id +} +`, context) +} +<% end -%> diff --git a/third_party/terraform/tests/resource_bigquery_data_transfer_config_test.go b/third_party/terraform/tests/resource_bigquery_data_transfer_config_test.go index 80e3cb75cd17..a9b2a7b8b5ae 100644 --- a/third_party/terraform/tests/resource_bigquery_data_transfer_config_test.go +++ b/third_party/terraform/tests/resource_bigquery_data_transfer_config_test.go @@ -191,6 +191,9 @@ resource "google_bigquery_data_transfer_config" "query_config" { } destination_dataset_id = google_bigquery_dataset.my_dataset.dataset_id notification_pubsub_topic = google_pubsub_topic.my_topic.id + email_preferences = { + enable_failure_email = true + } params = { destination_table_name_template = "my_table" write_disposition = "WRITE_APPEND" diff --git a/third_party/terraform/tests/resource_bigtable_instance_test.go b/third_party/terraform/tests/resource_bigtable_instance_test.go index 61ae1c51de63..3de97c5c338b 100644 --- a/third_party/terraform/tests/resource_bigtable_instance_test.go +++ b/third_party/terraform/tests/resource_bigtable_instance_test.go @@ -60,10 +60,6 @@ func TestAccBigtableInstance_cluster(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckBigtableInstanceDestroyProducer(t), Steps: []resource.TestStep{ - { - Config: testAccBigtableInstance_clusterMax(instanceName), - ExpectError: regexp.MustCompile("config is invalid: Too many cluster blocks: No more than 4 \"cluster\" blocks are allowed"), - }, { Config: testAccBigtableInstance_cluster(instanceName, 3), }, @@ -250,46 +246,6 @@ resource "google_bigtable_instance" "instance" { `, instanceName, instanceName, numNodes, instanceName, numNodes, instanceName, numNodes, instanceName, numNodes) } -func testAccBigtableInstance_clusterMax(instanceName string) string { - return fmt.Sprintf(` -resource "google_bigtable_instance" "instance" { - name = "%s" - cluster { - cluster_id = "%s-a" - zone = "us-central1-a" - num_nodes = 3 - storage_type = "HDD" - } - cluster { - cluster_id = "%s-b" - zone = "us-central1-b" - num_nodes = 3 - storage_type = "HDD" - } - cluster { - cluster_id = "%s-c" - zone = "us-central1-c" - num_nodes = 3 - storage_type = "HDD" - } - cluster { - cluster_id = "%s-d" - zone = "us-central1-f" - num_nodes = 3 - storage_type = "HDD" - } - cluster { - cluster_id = "%s-e" - zone = "us-east1-a" - num_nodes = 3 - storage_type = "HDD" - } - - deletion_protection = false -} -`, instanceName, instanceName, instanceName, instanceName, instanceName, instanceName) -} - func testAccBigtableInstance_clusterReordered(instanceName string, numNodes int) string { return fmt.Sprintf(` resource "google_bigtable_instance" "instance" { diff --git a/third_party/terraform/tests/resource_google_project_default_service_accounts_test.go b/third_party/terraform/tests/resource_google_project_default_service_accounts_test.go new file mode 100644 index 000000000000..10ae316a88da --- /dev/null +++ b/third_party/terraform/tests/resource_google_project_default_service_accounts_test.go @@ -0,0 +1,247 @@ +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1" +) + +func TestAccResourceGoogleProjectDefaultServiceAccountsBasic(t *testing.T) { + t.Parallel() + + resourceName := "google_project_default_service_accounts.acceptance" + org := getTestOrgFromEnv(t) + project := fmt.Sprintf("tf-project-%d", randInt(t)) + billingAccount := getTestBillingAccountFromEnv(t) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleProjectDefaultServiceAccountsBasic(org, project, billingAccount), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "id", "projects/"+project), + resource.TestCheckResourceAttrSet(resourceName, "project"), + resource.TestCheckResourceAttrSet(resourceName, "action"), + resource.TestCheckResourceAttrSet(resourceName, "restore_policy"), + ), + }, + }, + }) +} + +func testAccCheckGoogleProjectDefaultServiceAccountsBasic(org, project, billingAccount string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_default_service_accounts" "acceptance" { + project = google_project.acceptance.project_id + action = "DISABLE" +} +`, project, project, org, billingAccount) +} + +func TestAccResourceGoogleProjectDefaultServiceAccountsDisable(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + project := fmt.Sprintf("tf-project-%d", randInt(t)) + billingAccount := getTestBillingAccountFromEnv(t) + action := "DISABLE" + restorePolicy := "REVERT" + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectDefaultServiceAccountsRevert(t, project, action), + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleProjectDefaultServiceAccountsAdvanced(org, project, billingAccount, action, restorePolicy), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_project_default_service_accounts.acceptance", "id", "projects/"+project), + resource.TestCheckResourceAttrSet("google_project_default_service_accounts.acceptance", "project"), + resource.TestCheckResourceAttr("google_project_default_service_accounts.acceptance", "action", action), + resource.TestCheckResourceAttrSet("google_project_default_service_accounts.acceptance", "project"), + sleepInSecondsForTest(5), + testAccCheckGoogleProjectDefaultServiceAccountsChanges(t, project, action), + ), + }, + }, + }) +} + +func TestAccResourceGoogleProjectDefaultServiceAccountsDelete(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + project := fmt.Sprintf("tf-project-%d", randInt(t)) + billingAccount := getTestBillingAccountFromEnv(t) + action := "DELETE" + restorePolicy := "REVERT" + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectDefaultServiceAccountsRevert(t, project, action), + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleProjectDefaultServiceAccountsAdvanced(org, project, billingAccount, action, restorePolicy), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_project_default_service_accounts.acceptance", "id", "projects/"+project), + resource.TestCheckResourceAttrSet("google_project_default_service_accounts.acceptance", "project"), + resource.TestCheckResourceAttr("google_project_default_service_accounts.acceptance", "action", action), + resource.TestCheckResourceAttrSet("google_project_default_service_accounts.acceptance", "project"), + sleepInSecondsForTest(10), + testAccCheckGoogleProjectDefaultServiceAccountsChanges(t, project, action), + ), + }, + }, + }) +} + +func TestAccResourceGoogleProjectDefaultServiceAccountsDeprivilege(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + project := fmt.Sprintf("tf-project-%d", randInt(t)) + billingAccount := getTestBillingAccountFromEnv(t) + action := "DEPRIVILEGE" + restorePolicy := "REVERT" + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGoogleProjectDefaultServiceAccountsRevert(t, project, action), + Steps: []resource.TestStep{ + { + Config: testAccCheckGoogleProjectDefaultServiceAccountsAdvanced(org, project, billingAccount, action, restorePolicy), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_project_default_service_accounts.acceptance", "id", "projects/"+project), + resource.TestCheckResourceAttrSet("google_project_default_service_accounts.acceptance", "project"), + resource.TestCheckResourceAttr("google_project_default_service_accounts.acceptance", "action", action), + resource.TestCheckResourceAttrSet("google_project_default_service_accounts.acceptance", "project"), + sleepInSecondsForTest(5), + testAccCheckGoogleProjectDefaultServiceAccountsChanges(t, project, action), + ), + }, + }, + }) +} + +func testAccCheckGoogleProjectDefaultServiceAccountsAdvanced(org, project, billingAccount, action, restorePolicy string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + project_id = "%s" + name = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_service" "acceptance" { + project = google_project.acceptance.project_id + service = "compute.googleapis.com" + + disable_dependent_services = true +} + +resource "google_project_default_service_accounts" "acceptance" { + depends_on = [google_project_service.acceptance] + project = google_project.acceptance.project_id + action = "%s" + restore_policy = "%s" +} +`, project, project, org, billingAccount, action, restorePolicy) +} + +func testAccCheckGoogleProjectDefaultServiceAccountsChanges(t *testing.T, project, action string) func(s *terraform.State) error { + return func(s *terraform.State) error { + config := googleProviderConfig(t) + response, err := config.NewIamClient(config.userAgent).Projects.ServiceAccounts.List(prefixedProject(project)).Do() + if err != nil { + return fmt.Errorf("failed to list service accounts on project %q: %v", project, err) + } + for _, sa := range response.Accounts { + if testAccIsDefaultServiceAccount(sa.DisplayName) { + switch action { + case "DISABLE": + if !sa.Disabled { + return fmt.Errorf("compute engine default service account is not disabled, disable field is %t", sa.Disabled) + } + case "DELETE": + return fmt.Errorf("compute engine default service account is not deleted") + case "DEPRIVILEGE": + iamPolicy, err := config.NewResourceManagerClient(config.userAgent).Projects.GetIamPolicy(project, &cloudresourcemanager.GetIamPolicyRequest{}).Do() + if err != nil { + return fmt.Errorf("cannot get IAM policy on project %s: %v", project, err) + } + for _, bind := range iamPolicy.Bindings { + for _, member := range bind.Members { + if member == fmt.Sprintf("serviceAccount:%s", sa.Email) { + return fmt.Errorf("compute engine default service account is not deprivileged") + } + } + } + return nil + } + } + } + return nil + } +} + +// Test if actions were reverted properly +func testAccCheckGoogleProjectDefaultServiceAccountsRevert(t *testing.T, project, action string) func(s *terraform.State) error { + return func(s *terraform.State) error { + config := googleProviderConfig(t) + response, err := config.NewIamClient(config.userAgent).Projects.ServiceAccounts.List(prefixedProject(project)).Do() + if err != nil { + return fmt.Errorf("failed to list service accounts on project %q: %v", project, err) + } + for _, sa := range response.Accounts { + if testAccIsDefaultServiceAccount(sa.DisplayName) { + // We agreed to not revert the DEPRIVILEGE action because will be hard to track the roles over the time + if action == "DISABLE" { + if sa.Disabled { + return fmt.Errorf("compute engine default service account is not enabled, disable field is %t", sa.Disabled) + } + } else if action == "DELETE" { + // A deleted service account was found meaning the undelete action triggered + // on destroy worked + return nil + } + } + } + // if action is DELETE, the service account should be found in the previous loop + // due to undelete action + if action == "DELETE" { + return fmt.Errorf("service account changes were not reverted after destroy") + } + + return nil + } +} + +// testAccIsDefaultServiceAccount is a helper function to facilitate TDD when there is a need +// to update how we determine whether it's a default SA or not. +// If you follow TDD, it is going to be different from isDefaultServiceAccount func while coding +// but they must be identical before commit/push +func testAccIsDefaultServiceAccount(displayName string) bool { + gceDefaultSA := "compute engine default service account" + appEngineDefaultSA := "app engine default service account" + saDisplayName := strings.ToLower(displayName) + if saDisplayName == gceDefaultSA || saDisplayName == appEngineDefaultSA { + return true + } + + return false +} diff --git a/third_party/terraform/tests/resource_iam_beta-workload_identity_pool_id_test.go.erb b/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_id_test.go.erb similarity index 100% rename from third_party/terraform/tests/resource_iam_beta-workload_identity_pool_id_test.go.erb rename to third_party/terraform/tests/resource_iam_beta_workload_identity_pool_id_test.go.erb diff --git a/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_provider_id_test.go.erb b/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_provider_id_test.go.erb new file mode 100644 index 000000000000..7306aee220a5 --- /dev/null +++ b/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_provider_id_test.go.erb @@ -0,0 +1,34 @@ +<% autogen_exception -%> +package google + +<% unless version == 'ga' %> +import ( + "strings" + "testing" +) + +func TestValidateIAMBetaWorkloadIdentityPoolProviderId(t *testing.T) { + x := []StringValidationTestCase{ + // No errors + {TestName: "basic", Value: "foobar"}, + {TestName: "with numbers", Value: "foobar123"}, + {TestName: "short", Value: "foos"}, + {TestName: "long", Value: "12345678901234567890123456789012"}, + {TestName: "has a hyphen", Value: "foo-bar"}, + + // With errors + {TestName: "empty", Value: "", ExpectError: true}, + {TestName: "starts with a gcp-", Value: "gcp-foobar", ExpectError: true}, + {TestName: "with uppercase", Value: "fooBar", ExpectError: true}, + {TestName: "has an slash", Value: "foo/bar", ExpectError: true}, + {TestName: "has an backslash", Value: "foo\bar", ExpectError: true}, + {TestName: "too short", Value: "foo", ExpectError: true}, + {TestName: "too long", Value: strings.Repeat("f", 33), ExpectError: true}, + } + + es := testStringValidationCases(x, validateWorkloadIdentityPoolProviderId) + if len(es) > 0 { + t.Errorf("Failed to validate WorkloadIdentityPoolProvider names: %v", es) + } +} +<% end -%> diff --git a/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_provider_test.go.erb b/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_provider_test.go.erb new file mode 100644 index 000000000000..b840fd58f642 --- /dev/null +++ b/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_provider_test.go.erb @@ -0,0 +1,200 @@ +<% autogen_exception -%> +package google + +<% unless version == 'ga' %> +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccIAMBetaWorkloadIdentityPoolProvider_aws(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIAMBetaWorkloadIdentityPoolProviderDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccIAMBetaWorkloadIdentityPoolProvider_aws_full(context), + }, + { + ResourceName: "google_iam_workload_identity_pool_provider.my_provider", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccIAMBetaWorkloadIdentityPoolProvider_aws_enabled(context), + }, + { + ResourceName: "google_iam_workload_identity_pool_provider.my_provider", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccIAMBetaWorkloadIdentityPoolProvider_aws_basic(context), + }, + { + ResourceName: "google_iam_workload_identity_pool_provider.my_provider", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccIAMBetaWorkloadIdentityPoolProvider_oidc(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckIAMBetaWorkloadIdentityPoolProviderDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccIAMBetaWorkloadIdentityPoolProvider_oidc_full(context), + }, + { + ResourceName: "google_iam_workload_identity_pool_provider.my_provider", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccIAMBetaWorkloadIdentityPoolProvider_oidc_basic(context), + }, + { + ResourceName: "google_iam_workload_identity_pool_provider.my_provider", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccIAMBetaWorkloadIdentityPoolProvider_aws_full(context map[string]interface{}) string { + return Nprintf(` +resource "google_iam_workload_identity_pool" "my_pool" { + workload_identity_pool_id = "my-pool-%{random_suffix}" +} + +resource "google_iam_workload_identity_pool_provider" "my_provider" { + workload_identity_pool_id = google_iam_workload_identity_pool.my_pool.workload_identity_pool_id + workload_identity_pool_provider_id = "my-provider-%{random_suffix}" + display_name = "Name of provider" + description = "AWS identity pool provider for automated test" + disabled = true + attribute_condition = "attribute.aws_role==\"arn:aws:sts::999999999999:assumed-role/stack-eu-central-1-lambdaRole\"" + attribute_mapping = { + "google.subject" = "assertion.arn" + "attribute.aws_account" = "assertion.account" + "attribute.environment" = "assertion.arn.contains(\":instance-profile/Production\") ? \"prod\" : \"test\"" + } + aws { + account_id = "999999999999" + } +} +`, context) +} + +func testAccIAMBetaWorkloadIdentityPoolProvider_aws_enabled(context map[string]interface{}) string { + return Nprintf(` +resource "google_iam_workload_identity_pool" "my_pool" { + workload_identity_pool_id = "my-pool-%{random_suffix}" +} + +resource "google_iam_workload_identity_pool_provider" "my_provider" { + workload_identity_pool_id = google_iam_workload_identity_pool.my_pool.workload_identity_pool_id + workload_identity_pool_provider_id = "my-provider-%{random_suffix}" + display_name = "Name of provider" + description = "AWS identity pool provider for automated test" + disabled = false + attribute_condition = "attribute.aws_role==\"arn:aws:sts::999999999999:assumed-role/stack-eu-central-1-lambdaRole\"" + attribute_mapping = { + "google.subject" = "assertion.arn" + "attribute.aws_account" = "assertion.account" + "attribute.environment" = "assertion.arn.contains(\":instance-profile/Production\") ? \"prod\" : \"test\"" + } + aws { + account_id = "999999999999" + } +} +`, context) +} + +func testAccIAMBetaWorkloadIdentityPoolProvider_oidc_full(context map[string]interface{}) string { + return Nprintf(` +resource "google_iam_workload_identity_pool" "my_pool" { + workload_identity_pool_id = "my-pool-%{random_suffix}" +} + +resource "google_iam_workload_identity_pool_provider" "my_provider" { + workload_identity_pool_id = google_iam_workload_identity_pool.my_pool.workload_identity_pool_id + workload_identity_pool_provider_id = "my-provider-%{random_suffix}" + display_name = "Name of provider" + description = "OIDC identity pool provider for automated test" + disabled = true + attribute_condition = "\"e968c2ef-047c-498d-8d79-16ca1b61e77e\" in assertion.groups" + attribute_mapping = { + "google.subject" = "\"azure::\" + assertion.tid + \"::\" + assertion.sub" + "attribute.tid" = "assertion.tid" + "attribute.managed_identity_name" = < diff --git a/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_test.go.erb b/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_test.go.erb index a5458976aa90..7b90a5bc4428 100644 --- a/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_test.go.erb +++ b/third_party/terraform/tests/resource_iam_beta_workload_identity_pool_test.go.erb @@ -17,6 +17,7 @@ func TestAccIAMBetaWorkloadIdentityPool_full(t *testing.T) { vcrTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, + CheckDestroy: testAccCheckIAMBetaWorkloadIdentityPoolDestroyProducer(t), Steps: []resource.TestStep{ { Config: testAccIAMBetaWorkloadIdentityPool_full(randomSuffix), @@ -46,6 +47,7 @@ func TestAccIAMBetaWorkloadIdentityPool_minimal(t *testing.T) { vcrTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, + CheckDestroy: testAccCheckIAMBetaWorkloadIdentityPoolDestroyProducer(t), Steps: []resource.TestStep{ { Config: testAccIAMBetaWorkloadIdentityPool_minimal(randomSuffix), diff --git a/third_party/terraform/tests/resource_monitoring_alert_policy_test.go b/third_party/terraform/tests/resource_monitoring_alert_policy_test.go index 09af729168f2..b7351ceab381 100644 --- a/third_party/terraform/tests/resource_monitoring_alert_policy_test.go +++ b/third_party/terraform/tests/resource_monitoring_alert_policy_test.go @@ -16,6 +16,7 @@ func TestAccMonitoringAlertPolicy(t *testing.T) { "basic": testAccMonitoringAlertPolicy_basic, "full": testAccMonitoringAlertPolicy_full, "update": testAccMonitoringAlertPolicy_update, + "mql": testAccMonitoringAlertPolicy_mql, } for name, tc := range testCases { @@ -110,6 +111,28 @@ func testAccMonitoringAlertPolicy_full(t *testing.T) { }) } +func testAccMonitoringAlertPolicy_mql(t *testing.T) { + + alertName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + conditionName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAlertPolicyDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccMonitoringAlertPolicy_mqlCfg(alertName, conditionName), + }, + { + ResourceName: "google_monitoring_alert_policy.mql", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckAlertPolicyDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { config := googleProviderConfig(t) @@ -226,3 +249,31 @@ resource "google_monitoring_alert_policy" "full" { } `, alertName, conditionName1, conditionName2) } + +func testAccMonitoringAlertPolicy_mqlCfg(alertName, conditionName string) string { + return fmt.Sprintf(` +resource "google_monitoring_alert_policy" "mql" { + display_name = "%s" + combiner = "OR" + enabled = true + + conditions { + display_name = "%s" + + condition_monitoring_query_language { + query = "fetch gce_instance::compute.googleapis.com/instance/cpu/utilization | align mean_aligner() | window 5m | condition value.utilization > .15 '10^2.%%'" + duration = "60s" + + trigger { + count = 2 + } + } + } + + documentation { + content = "test content" + mime_type = "text/markdown" + } +} +`, alertName, conditionName) +} diff --git a/third_party/terraform/tests/resource_storage_transfer_job_test.go b/third_party/terraform/tests/resource_storage_transfer_job_test.go index c204a4c75077..dcaed8f06960 100644 --- a/third_party/terraform/tests/resource_storage_transfer_job_test.go +++ b/third_party/terraform/tests/resource_storage_transfer_job_test.go @@ -103,7 +103,7 @@ func testAccStorageTransferJobDestroyProducer(t *testing.T) func(s *terraform.St return err } - res, err := config.NewStorageTransferClient(config.userAgent).TransferJobs.Get(name).ProjectId(project).Do() + res, err := config.NewStorageTransferClient(config.userAgent).TransferJobs.Get(name, project).Do() if res.Status != "DELETED" { return fmt.Errorf("Transfer Job not set to DELETED") } diff --git a/third_party/terraform/utils/common_diff_suppress.go.erb b/third_party/terraform/utils/common_diff_suppress.go.erb index 83161a5e3a66..b16266e559f3 100644 --- a/third_party/terraform/utils/common_diff_suppress.go.erb +++ b/third_party/terraform/utils/common_diff_suppress.go.erb @@ -18,6 +18,28 @@ func optionalPrefixSuppress(prefix string) schema.SchemaDiffSuppressFunc { } } +func ignoreMissingKeyInMap(key string) schema.SchemaDiffSuppressFunc { + return func(k, old, new string, d *schema.ResourceData) bool { + log.Printf("suppressing diff %q with old %q, new %q", k, old, new) + if strings.HasSuffix(k, ".%") { + oldNum, err := strconv.Atoi(old) + if err != nil { + log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", old) + return false + } + newNum, err := strconv.Atoi(new) + if err != nil { + log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", new) + return false + } + return oldNum+1 == newNum + } else if strings.HasSuffix(k, "." + key) { + return old == "" + } + return false + } +} + func optionalSurroundingSpacesSuppress(k, old, new string, d *schema.ResourceData) bool { return strings.TrimSpace(old) == strings.TrimSpace(new) } diff --git a/third_party/terraform/utils/provider.go.erb b/third_party/terraform/utils/provider.go.erb index c56e72b18ca6..34b351501658 100644 --- a/third_party/terraform/utils/provider.go.erb +++ b/third_party/terraform/utils/provider.go.erb @@ -223,6 +223,9 @@ func Provider() *schema.Provider { "google_iam_policy": dataSourceGoogleIamPolicy(), "google_iam_role": dataSourceGoogleIamRole(), "google_iam_testable_permissions": dataSourceGoogleIamTestablePermissions(), + <% unless version == 'ga' -%> + "google_iam_workload_identity_pool": dataSourceIAMBetaWorkloadIdentityPool(), + <% end -%> "google_kms_crypto_key": dataSourceGoogleKmsCryptoKey(), "google_kms_crypto_key_version": dataSourceGoogleKmsCryptoKeyVersion(), "google_kms_key_ring": dataSourceGoogleKmsKeyRing(), @@ -433,6 +436,7 @@ end # products.each do "google_organization_iam_audit_config": ResourceIamAuditConfig(IamOrganizationSchema, NewOrganizationIamUpdater, OrgIdParseFunc), "google_organization_policy": resourceGoogleOrganizationPolicy(), "google_project": resourceGoogleProject(), + "google_project_default_service_accounts": resourceGoogleProjectDefaultServiceAccounts(), "google_project_iam_policy": ResourceIamPolicy(IamPolicyProjectSchema, NewProjectIamPolicyUpdater, ProjectIdParseFunc), "google_project_iam_binding": ResourceIamBindingWithBatching(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc, IamBatchingEnabled), "google_project_iam_member": ResourceIamMemberWithBatching(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc, IamBatchingEnabled), diff --git a/third_party/terraform/utils/provider_test.go.erb b/third_party/terraform/utils/provider_test.go.erb index 4a6b442f229f..92758d2b31fc 100644 --- a/third_party/terraform/utils/provider_test.go.erb +++ b/third_party/terraform/utils/provider_test.go.erb @@ -947,3 +947,10 @@ func skipIfVcr(t *testing.T) { t.Skipf("VCR enabled, skipping test: %s", t.Name()) } } + +func sleepInSecondsForTest(t int) resource.TestCheckFunc { + return func(s *terraform.State) error { + time.Sleep(time.Duration(t) * time.Second) + return nil + } +} diff --git a/third_party/terraform/website/docs/d/billing_account.html.markdown b/third_party/terraform/website/docs/d/billing_account.html.markdown index 84d8c8b1e19b..185ed03c6dc5 100644 --- a/third_party/terraform/website/docs/d/billing_account.html.markdown +++ b/third_party/terraform/website/docs/d/billing_account.html.markdown @@ -1,5 +1,5 @@ --- -subcategory: "Cloud Platform" +subcategory: "Cloud Billing" layout: "google" page_title: "Google: google_billing_account" sidebar_current: "docs-google-datasource-billing-account" diff --git a/third_party/terraform/website/docs/d/iam_workload_identity_pool.markdown b/third_party/terraform/website/docs/d/iam_workload_identity_pool.markdown new file mode 100644 index 000000000000..b2a9217d800f --- /dev/null +++ b/third_party/terraform/website/docs/d/iam_workload_identity_pool.markdown @@ -0,0 +1,38 @@ +--- +subcategory: "Cloud IAM" +layout: "google" +page_title: "Google: google_iam_workload_identity_pool" +sidebar_current: "docs-google-datasource-iam-workload-identity-pool" +description: |- + Get a IAM workload identity pool from Google Cloud +--- + +# google\_iam\_workload_\identity\_pool + +Get a IAM workload identity pool from Google Cloud by its id. + +~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. +See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. + +## Example Usage + +```tf +data "google_iam_workload_identity_pool" "foo" { + workload_identity_pool_id = "foo-pool" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `workload_identity_pool_id` - (Required) The id of the pool which is the + final component of the resource name. + +- - - + +* `project` - (Optional) The project in which the resource belongs. If it + is not provided, the provider project is used. + +## Attributes Reference +See [google_iam_workload_identity_pool](https://www.terraform.io/docs/providers/google/r/iam_workload_identity_pool.html) resource for details of all the available attributes. diff --git a/third_party/terraform/website/docs/index.html.markdown b/third_party/terraform/website/docs/index.html.markdown index edbf2b086ab9..7fa326cd84e3 100644 --- a/third_party/terraform/website/docs/index.html.markdown +++ b/third_party/terraform/website/docs/index.html.markdown @@ -17,7 +17,6 @@ A typical provider configuration will look something like: ```hcl provider "google" { - credentials = file("account.json") project = "my-project-id" region = "us-central1" } diff --git a/third_party/terraform/website/docs/r/bigquery_table.html.markdown b/third_party/terraform/website/docs/r/bigquery_table.html.markdown index 3e6dae8a3aae..57f57ed87e79 100644 --- a/third_party/terraform/website/docs/r/bigquery_table.html.markdown +++ b/third_party/terraform/website/docs/r/bigquery_table.html.markdown @@ -180,7 +180,7 @@ The `external_data_configuration` block supports: * `source_format` (Required) - The data format. Supported values are: "CSV", "GOOGLE_SHEETS", "NEWLINE_DELIMITED_JSON", "AVRO", "PARQUET", - and "DATSTORE_BACKUP". To use "GOOGLE_SHEETS" + and "DATASTORE_BACKUP". To use "GOOGLE_SHEETS" the `scopes` must include "https://www.googleapis.com/auth/drive.readonly". diff --git a/third_party/terraform/website/docs/r/billing_account_iam.html.markdown b/third_party/terraform/website/docs/r/billing_account_iam.html.markdown new file mode 100644 index 000000000000..64082f07e9e4 --- /dev/null +++ b/third_party/terraform/website/docs/r/billing_account_iam.html.markdown @@ -0,0 +1,106 @@ +--- +subcategory: "Cloud Billing" +layout: "google" +page_title: "Google: google_billing_account_iam" +sidebar_current: "docs-google-billing-account-iam" +description: |- + Collection of resources to manage IAM policy for a Billing Account. +--- + +# IAM policy for Billing Account + +Three different resources help you manage IAM policies on billing accounts. Each of these resources serves a different use case: + +* `google_billing_account_iam_policy`: Authoritative. Sets the IAM policy for the billing accounts and replaces any existing policy already attached. +* `google_billing_account_iam_binding`: Authoritative for a given role. Updates the IAM policy to grant a role to a list of members. Other roles within the IAM policy for the table are preserved. +* `google_billing_account_iam_member`: Non-authoritative. Updates the IAM policy to grant a role to a new member. Other members for the role of the billing accounts are preserved. + +~> **Note:** `google_billing_account_iam_policy` **cannot** be used in conjunction with `google_billing_account_iam_binding` and `google_billing_account_iam_member` or they will fight over what your policy should be. In addition, be careful not to accidentally unset ownership of the billing account as `google_billing_account_iam_policy` replaces the entire policy. + +~> **Note:** `google_billing_account_iam_binding` resources **can be** used in conjunction with `google_billing_account_iam_member` resources **only if** they do not grant privilege to the same role. + +## google\_billing_\_account\_iam\_policy + +```hcl +data "google_iam_policy" "admin" { + binding { + role = "roles/billing.viewer" + members = [ + "user:jane@example.com", + ] + } +} + +resource "google_billing_account_iam_policy" "editor" { + billing_account_id = "00AA00-000AAA-00AA0A" + policy_data = data.google_iam_policy.admin.policy_data +} +``` + +## google\_billing\_account_\_iam\_binding + +```hcl +resource "google_billing_account_iam_binding" "editor" { + billing_account_id = "00AA00-000AAA-00AA0A" + role = "roles/billing.viewer" + members = [ + "user:jane@example.com", + ] +} +``` + +## google\_billing\_account\_iam\_member + +```hcl +resource "google_billing_account_iam_member" "editor" { + billing_account_id = "00AA00-000AAA-00AA0A" + role = "roles/billing.viewer" + member = "user:jane@example.com" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `billing_account_id` - (Required) The billing account id. + +For `google_billing_account_iam_member` or `google_billing_account_iam_binding`: + +* `member/members` - (Required) Identities that will be granted the privilege in `role`. + Each entry can have one of the following values: + * **user:{emailid}**: An email address that represents a specific Google account. For example, alice@gmail.com or joe@example.com. + * **serviceAccount:{emailid}**: An email address that represents a service account. For example, my-other-app@appspot.gserviceaccount.com. + * **group:{emailid}**: An email address that represents a Google group. For example, admins@example.com. + * **domain:{domain}**: A G Suite domain (primary, instead of alias) name that represents all the users of that domain. For example, google.com or example.com. + +* `role` - (Required) The role that should be applied. Only one + `google_billing_account_iam_binding` can be used per role. Note that custom roles must be of the format + `[projects|organizations]/{parent-name}/roles/{role-name}`. Read more about roles [here](https://cloud.google.com/bigtable/docs/access-control#roles). + +`google_billing_account_iam_policy` only: +* `policy_data` - (Required) The policy data generated by a `google_iam_policy` data source. + +- - - + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `etag` - (Computed) The etag of the billing account's IAM policy. + +## Import + +Instance IAM resources can be imported using the project, table name, role and/or member. + +``` +$ terraform import google_billing_account_iam_policy.binding "your-billing-account-id" + +$ terraform import google_billing_account_iam_binding.binding "your-billing-account-id roles/billing.user" + +$ terraform import google_billing_account_iam_member.binding "your-billing-account-id roles/billing.user user:jane@example.com" +``` + +-> **Custom Roles**: If you're importing a IAM resource with a custom role, make sure to use the + full name of the custom role, e.g. `organizations/my-org-id/roles/my-custom-role`. diff --git a/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown b/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown index 18f3b85098a1..99821a492cef 100644 --- a/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown +++ b/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown @@ -113,7 +113,7 @@ Eg. `"nodejs8"`, `"nodejs10"`, `"python37"`, `"go111"`, `"go113"`. * `description` - (Optional) Description of the function. -* `available_memory_mb` - (Optional) Memory (in MB), available to the function. Default value is 256MB. Allowed values are: 128MB, 256MB, 512MB, 1024MB, and 2048MB. +* `available_memory_mb` - (Optional) Memory (in MB), available to the function. Default value is 256MB. Allowed values are: 128MB, 256MB, 512MB, 1024MB, 2048MB and 4096MB. * `timeout` - (Optional) Timeout (in seconds) for the function. Default value is 60 seconds. Cannot be more than 540 seconds. diff --git a/third_party/terraform/website/docs/r/dataproc_cluster_iam.html.markdown b/third_party/terraform/website/docs/r/dataproc_cluster_iam.html.markdown index 288da1ecd0f0..afbc46154ac1 100644 --- a/third_party/terraform/website/docs/r/dataproc_cluster_iam.html.markdown +++ b/third_party/terraform/website/docs/r/dataproc_cluster_iam.html.markdown @@ -19,7 +19,7 @@ Three different resources help you manage IAM policies on dataproc clusters. Eac ~> **Note:** `google_dataproc_cluster_iam_binding` resources **can be** used in conjunction with `google_dataproc_cluster_iam_member` resources **only if** they do not grant privilege to the same role. -## google\_pubsub\_subscription\_iam\_policy +## google\_dataproc\_cluster\_iam\_policy ```hcl data "google_iam_policy" "admin" { @@ -39,7 +39,7 @@ resource "google_dataproc_cluster_iam_policy" "editor" { } ``` -## google\_pubsub\_subscription\_iam\_binding +## google\_dataproc\_cluster\_iam\_binding ```hcl resource "google_dataproc_cluster_iam_binding" "editor" { @@ -51,7 +51,7 @@ resource "google_dataproc_cluster_iam_binding" "editor" { } ``` -## google\_pubsub\_subscription\_iam\_member +## google\_dataproc\_cluster\_iam\_member ```hcl resource "google_dataproc_cluster_iam_member" "editor" { diff --git a/third_party/terraform/website/docs/r/dataproc_job_iam.html.markdown b/third_party/terraform/website/docs/r/dataproc_job_iam.html.markdown index e8811a7b3f19..a08da44861b3 100644 --- a/third_party/terraform/website/docs/r/dataproc_job_iam.html.markdown +++ b/third_party/terraform/website/docs/r/dataproc_job_iam.html.markdown @@ -19,7 +19,7 @@ Three different resources help you manage IAM policies on dataproc jobs. Each of ~> **Note:** `google_dataproc_job_iam_binding` resources **can be** used in conjunction with `google_dataproc_job_iam_member` resources **only if** they do not grant privilege to the same role. -## google\_pubsub\_subscription\_iam\_policy +## google\_dataproc\_job\_iam\_policy ```hcl data "google_iam_policy" "admin" { @@ -39,7 +39,7 @@ resource "google_dataproc_job_iam_policy" "editor" { } ``` -## google\_pubsub\_subscription\_iam\_binding +## google\_dataproc\_job\_iam\_binding ```hcl resource "google_dataproc_job_iam_binding" "editor" { @@ -51,7 +51,7 @@ resource "google_dataproc_job_iam_binding" "editor" { } ``` -## google\_pubsub\_subscription\_iam\_member +## google\_dataproc\_job\_iam\_member ```hcl resource "google_dataproc_job_iam_member" "editor" { diff --git a/third_party/terraform/website/docs/r/google_billing_account_iam_binding.html.markdown b/third_party/terraform/website/docs/r/google_billing_account_iam_binding.html.markdown deleted file mode 100644 index 34cc5bb51f4b..000000000000 --- a/third_party/terraform/website/docs/r/google_billing_account_iam_binding.html.markdown +++ /dev/null @@ -1,59 +0,0 @@ ---- -subcategory: "Cloud Platform" -layout: "google" -page_title: "Google: google_billing_account_iam_binding" -sidebar_current: "docs-google-billing-account-iam-binding" -description: |- - Allows management of a single binding with an IAM policy for a Google Cloud Platform Billing Account. ---- - -# google\_billing\_account\_iam\_binding - -Allows creation and management of a single binding within IAM policy for -an existing Google Cloud Platform Billing Account. - -~> **Note:** This resource __must not__ be used in conjunction with - `google_billing_account_iam_member` for the __same role__ or they will fight over - what your policy should be. - -~> **Note:** On create, this resource will overwrite members of any existing roles. - Use `terraform import` and inspect the `terraform plan` output to ensure - your existing members are preserved. - -## Example Usage - -```hcl -resource "google_billing_account_iam_binding" "binding" { - billing_account_id = "00AA00-000AAA-00AA0A" - role = "roles/billing.viewer" - - members = [ - "user:alice@gmail.com", - ] -} -``` - -## Argument Reference - -The following arguments are supported: - -* `billing_account_id` - (Required) The billing account id. - -* `role` - (Required) The role that should be applied. - -* `members` - (Required) A list of users that the role should apply to. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding - -## Attributes Reference - -In addition to the arguments listed above, the following computed attributes are -exported: - -* `etag` - (Computed) The etag of the billing account's IAM policy. - -## Import - -IAM binding imports use space-delimited identifiers; first the resource in question and then the role. These bindings can be imported using the `billing_account_id` and role, e.g. - -``` -$ terraform import google_billing_account_iam_binding.binding "your-billing-account-id roles/viewer" -``` diff --git a/third_party/terraform/website/docs/r/google_billing_account_iam_member.html.markdown b/third_party/terraform/website/docs/r/google_billing_account_iam_member.html.markdown deleted file mode 100644 index 2576a765576e..000000000000 --- a/third_party/terraform/website/docs/r/google_billing_account_iam_member.html.markdown +++ /dev/null @@ -1,55 +0,0 @@ ---- -subcategory: "Cloud Platform" -layout: "google" -page_title: "Google: google_billing_account_iam_member" -sidebar_current: "docs-google-billing-account-iam-member" -description: |- - Allows management of a single member for a single binding on the IAM policy for a Google Cloud Platform Billing Account. ---- - -# google\_billing\_account\_iam\_member - -Allows creation and management of a single member for a single binding within -the IAM policy for an existing Google Cloud Platform Billing Account. - -~> **Note:** This resource __must not__ be used in conjunction with - `google_billing_account_iam_binding` for the __same role__ or they will fight over - what your policy should be. - -## Example Usage - -```hcl -resource "google_billing_account_iam_member" "binding" { - billing_account_id = "00AA00-000AAA-00AA0A" - role = "roles/billing.viewer" - member = "user:alice@gmail.com" -} -``` - -## Argument Reference - -The following arguments are supported: - -* `billing_account_id` - (Required) The billing account id. - -* `role` - (Required) The role that should be applied. - -* `member` - (Required) The user that the role should apply to. For more details on format and restrictions see https://cloud.google.com/billing/reference/rest/v1/Policy#Binding - -## Attributes Reference - -In addition to the arguments listed above, the following computed attributes are -exported: - -* `etag` - (Computed) The etag of the billing account's IAM policy. - -## Import - -IAM member imports use space-delimited identifiers; the resource in question, the role, and the account. This member resource can be imported using the `billing_account_id`, role, and member identity, e.g. - -``` -$ terraform import google_billing_account_iam_member.binding "your-billing-account-id roles/viewer user:foo@example.com" -``` - --> **Custom Roles**: If you're importing a IAM member with a custom role, make sure to use the - full name of the custom role, e.g. `[projects/my-project|organizations/my-org]/roles/my-custom-role`. diff --git a/third_party/terraform/website/docs/r/google_billing_account_iam_policy.html.markdown b/third_party/terraform/website/docs/r/google_billing_account_iam_policy.html.markdown deleted file mode 100644 index e704e910bf73..000000000000 --- a/third_party/terraform/website/docs/r/google_billing_account_iam_policy.html.markdown +++ /dev/null @@ -1,57 +0,0 @@ ---- -subcategory: "Cloud Platform" -layout: "google" -page_title: "Google: google_billing_account_iam_policy" -sidebar_current: "docs-google-billing-account-iam-policy" -description: |- - Allows management of the entire IAM policy for a Google Cloud Platform Billing Account. ---- - -# google\_billing\_account\_iam\_policy - -Allows management of the entire IAM policy for an existing Google Cloud Platform Billing Account. - -~> **Warning:** Billing accounts have a default user that can be **overwritten** -by use of this resource. The safest alternative is to use multiple `google_billing_account_iam_binding` - resources. If you do use this resource, the best way to be sure that you are - not making dangerous changes is to start by importing your existing policy, - and examining the diff very closely. - -~> **Note:** This resource __must not__ be used in conjunction with - `google_billing_account_iam_member` or `google_billing_account_iam_binding` - or they will fight over what your policy should be. - -## Example Usage - -```hcl -resource "google_billing_account_iam_policy" "policy" { - billing_account_id = "00AA00-000AAA-00AA0A" - policy_data = data.google_iam_policy.admin.policy_data -} - -data "google_iam_policy" "admin" { - binding { - role = "roles/billing.viewer" - - members = [ - "user:jane@example.com", - ] - } -} -``` - -## Argument Reference - -The following arguments are supported: - -* `billing_account_id` - (Required) The billing account id. - -* `policy_data` - (Required) The `google_iam_policy` data source that represents - the IAM policy that will be applied to the billing account. This policy overrides any existing - policy applied to the billing account. - -## Import - -``` -$ terraform import google_billing_account_iam_policy.policy billing-account-id -``` diff --git a/third_party/terraform/website/docs/r/google_project_default_service_accounts.html.markdown b/third_party/terraform/website/docs/r/google_project_default_service_accounts.html.markdown new file mode 100644 index 000000000000..5cec6f1a06a6 --- /dev/null +++ b/third_party/terraform/website/docs/r/google_project_default_service_accounts.html.markdown @@ -0,0 +1,68 @@ +--- +subcategory: "Cloud Platform" +layout: "google" +page_title: "Google: google_project_default_service_accounts" +sidebar_current: "docs-google-project-default-service-accounts-x" +description: |- + Allows management of Google Cloud Platform project default service accounts. +--- + +# google_project_default_service_accounts + +Allows management of Google Cloud Platform project default service accounts. + +When certain service APIs are enabled, Google Cloud Platform automatically creates service accounts to help get started, but +this is not recommended for production environments as per [Google's documentation](https://cloud.google.com/iam/docs/service-accounts#default). +See the [Organization documentation](https://cloud.google.com/resource-manager/docs/quickstarts) for more details. +~> This resource works on a best-effort basis, as no API formally describes the default service accounts. If the default service accounts change their name or additional service accounts are added, this resource will need to be updated. + +## Example Usage + +```hcl +resource "google_project_default_service_accounts" "my_project" { + project = "my-project-id" + action = "DELETE" +} +``` + +To enable the default service accounts on the resource destroy: + +```hcl +resource "google_project_default_service_accounts" "my_project" { + project = "my-project-id" + action = "DISABLE" + restore_policy = "REVERT" +} + +``` + +## Argument Reference + +The following arguments are supported: + +- `project` - (Required) The project ID where service accounts are created. + +- `action` - (Required) The action to be performed in the default service accounts. Valid values are: `DEPRIVILEGE`, `DELETE`, `DISABLE`. Note that `DEPRIVILEGE` action will ignore the REVERT configuration in the restore_policy + +- `restore_policy` - (Optional) The action to be performed in the default service accounts on the resource destroy. Valid values are `NONE` and `REVERT`. If set to `REVERT` it will attempt to restore all default SAs but in the `DEPRIVILEGE` action. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +- `id` - an identifier for the resource with format `projects/{{project}}` +- `service_accounts` - The Service Accounts changed by this resource. It is used for `REVERT` the `action` on the destroy. + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 10 minutes. +- `update` - Default is 10 minutes. +- `delete` - Default is 10 minutes. + +## Import + +This resource does not support import diff --git a/third_party/terraform/website/docs/r/storage_transfer_job.html.markdown b/third_party/terraform/website/docs/r/storage_transfer_job.html.markdown index b1015fe1f5b8..f299072580cb 100644 --- a/third_party/terraform/website/docs/r/storage_transfer_job.html.markdown +++ b/third_party/terraform/website/docs/r/storage_transfer_job.html.markdown @@ -97,7 +97,7 @@ The following arguments are supported: * `transfer_spec` - (Required) Transfer specification. Structure documented below. -* `schedule` - (Required) Schedule specification defining when the Transfer Job should be scheduled to start, end and and what time to run. Structure documented below. +* `schedule` - (Required) Schedule specification defining when the Transfer Job should be scheduled to start, end and what time to run. Structure documented below. - - - diff --git a/third_party/terraform/website/docs/r/usage_export_bucket.html.markdown b/third_party/terraform/website/docs/r/usage_export_bucket.html.markdown index 3ea6d2ca2d6d..59f86dde1b6f 100644 --- a/third_party/terraform/website/docs/r/usage_export_bucket.html.markdown +++ b/third_party/terraform/website/docs/r/usage_export_bucket.html.markdown @@ -1,5 +1,5 @@ --- -subcategory: "Cloud Platform" +subcategory: "Compute Engine" layout: "google" page_title: "Google: google_project_usage_export_bucket" sidebar_current: "docs-google-project-usage-export-bucket" diff --git a/third_party/validator/project_service.go b/third_party/validator/project_service.go new file mode 100644 index 000000000000..9541347c65da --- /dev/null +++ b/third_party/validator/project_service.go @@ -0,0 +1,60 @@ +package google + +import ( + "fmt" + "reflect" +) + +func GetServiceUsageCaiObject(d TerraformResourceData, config *Config) (Asset, error) { + name, err := assetName(d, config, "//serviceusage.googleapis.com/projects/{{project}}/services/{{service}}") + if err != nil { + return Asset{}, err + } + if obj, err := GetServiceUsageApiObject(d, config); err == nil { + return Asset{ + Name: name, + Type: "serviceusage.googleapis.com/Service", + Resource: &AssetResource{ + Version: "v1", + DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/serviceusage/v1/rest", + DiscoveryName: "Service", + Data: obj, + }, + }, nil + } + return Asset{}, err +} + +func GetServiceUsageApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) { + obj := make(map[string]interface{}) + parentProjectProp, err := expandServiceUsageParentProject(d.Get("project"), d, config) + if err != nil { + return nil, err + } + obj["parent"] = parentProjectProp + + serviceNameProp, err := expandServiceUsageServiceName(d.Get("service"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("service"); !isEmptyValue(reflect.ValueOf(serviceNameProp)) && (ok || !reflect.DeepEqual(v, serviceNameProp)) { + obj["name"] = serviceNameProp + } + + obj["state"] = "ENABLED" + + return obj, nil +} + +func expandServiceUsageParentProject(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + if v == nil || v.(string) == "" { + // It does not try to construct anything from empty. + return "", nil + } + // Ideally we should use project_number, but since that is generated server-side, + // we substitute project_id. + return fmt.Sprintf("projects/%s", v.(string)), nil +} + +func expandServiceUsageServiceName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/third_party/validator/project_service_test.go b/third_party/validator/project_service_test.go new file mode 100644 index 000000000000..eef3b7cc0665 --- /dev/null +++ b/third_party/validator/project_service_test.go @@ -0,0 +1,93 @@ +package google + +import ( + "testing" +) + +func TestParseServiceForExistingProject(t *testing.T) { + cases := []struct { + name string + data TerraformResourceData + expectedType string + expectedAssetName string + expectedParentProject string + expectedService string + expectedState string + }{ + { + name: "resource has service and project", + data: &mockTerraformResourceData{ + m: map[string]interface{}{ + "project": "test-project", + "service": "iamcredentials.googleapis.com", + }, + }, + expectedType: "serviceusage.googleapis.com/Service", + expectedAssetName: "//serviceusage.googleapis.com/projects/test-project/services/iamcredentials.googleapis.com", + expectedParentProject: "projects/test-project", + expectedServiceName: "iamcredentials.googleapis.com", + expectedState: "ENABLED", + }, + { + name: "resource has service but missing project", + data: &mockTerraformResourceData{ + m: map[string]interface{}{ + "service": "iamcredentials.googleapis.com", + }, + }, + expectedType: "serviceusage.googleapis.com/Service", + expectedAssetName: "//serviceusage.googleapis.com/projects/default_project/services/iamcredentials.googleapis.com", + expectedParentProject: "", + expectedServiceName: "iamcredentials.googleapis.com", + expectedState: "ENABLED", + }, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + config := &Config{} + config.Project = "default_project" + asset, err := GetServiceUsageCaiObject(tt.data, config) + if err != nil { + t.Errorf("%s failed to convert project_service resource:%q", tt.name, err) + } + if asset.Type != tt.expectedType { + t.Errorf("Case: %s. Error converting asset type. Expected: %q got: %q", tt.name, tt.expectedType, asset.Type) + } + if asset.Name != tt.expectedAssetName { + t.Errorf("Case: %s. Error converting asset name. Expected: %q got: %q", tt.name, tt.expectedAssetName, asset.Name) + } + if asset.Resource.Data["parent"] != tt.expectedParentProject { + t.Errorf("Case: %s. Error converting asset parent project. Expected: %q got: %q", tt.name, tt.expectedParentProject, asset.Resource.Data["parent"]) + } + if asset.Resource.Data["name"] != tt.expectedServiceName { + t.Errorf("Case: %s. Error converting asset service name. Expected: %q got: %q", tt.name, tt.expectedServiceName, asset.Resource.Data["name"]) + } + if asset.Resource.Data["state"] != tt.expectedState { + t.Errorf("Case: %s. Error converting asset state. Expected: %q got: %q", tt.name, tt.expectedService, asset.Resource.Data["state"]) + } + }) + } +} + +type mockTerraformResourceData struct { + m map[string]interface{} + TerraformResourceData +} + +func (d *mockTerraformResourceData) GetOkExists(k string) (interface{}, bool) { + v, ok := d.m[k] + return v, ok +} + +func (d *mockTerraformResourceData) GetOk(k string) (interface{}, bool) { + v, ok := d.m[k] + return v, ok +} + +func (d *mockTerraformResourceData) Get(k string) interface{} { + v, ok := d.m[k] + if !ok { + return nil + } + return v +}