From def2f476d1d6a65010053398dcffc16523c226e7 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Tue, 15 Aug 2023 16:28:23 +0200 Subject: [PATCH] Add support for conditions to `iam_members` module variables (#1594) * project * data-catalog-policy-tag * dataproc * folder * iam-service-account * kms * net-vpc * organization * source-repository * dataplex-datascan --- modules/data-catalog-policy-tag/README.md | 12 ++-- modules/data-catalog-policy-tag/iam.tf | 8 +++ modules/data-catalog-policy-tag/variables.tf | 5 ++ modules/dataplex-datascan/README.md | 16 ++--- modules/dataplex-datascan/iam.tf | 8 +++ modules/dataplex-datascan/variables.tf | 5 ++ modules/dataproc/README.md | 14 ++-- modules/dataproc/iam.tf | 8 +++ modules/dataproc/variables.tf | 5 ++ modules/folder/README.md | 22 +++--- modules/folder/iam.tf | 8 +++ modules/folder/variables.tf | 5 ++ modules/iam-service-account/README.md | 20 +++--- modules/iam-service-account/iam.tf | 8 +++ modules/iam-service-account/variables.tf | 5 ++ modules/kms/README.md | 22 +++--- modules/kms/iam.tf | 16 +++++ modules/kms/variables.tf | 10 +++ modules/net-vpc/README.md | 10 +-- modules/net-vpc/subnets.tf | 8 +++ modules/net-vpc/variables.tf | 5 ++ modules/organization/README.md | 26 +++---- modules/organization/iam.tf | 8 +++ modules/organization/variables.tf | 5 ++ modules/project/README.md | 71 +++++++++++-------- modules/project/iam.tf | 8 +++ modules/project/variables.tf | 5 ++ modules/source-repository/README.md | 8 +-- modules/source-repository/iam.tf | 8 +++ modules/source-repository/variables.tf | 5 ++ .../modules/project/examples/iam-members.yaml | 14 +++- 31 files changed, 272 insertions(+), 106 deletions(-) diff --git a/modules/data-catalog-policy-tag/README.md b/modules/data-catalog-policy-tag/README.md index 6f53fcbd3..5a1c2055e 100644 --- a/modules/data-catalog-policy-tag/README.md +++ b/modules/data-catalog-policy-tag/README.md @@ -66,18 +66,18 @@ module "cmn-dc" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L69) | Name of this taxonomy. | string | ✓ | | -| [project_id](variables.tf#L84) | GCP project id. | | ✓ | | +| [name](variables.tf#L74) | Name of this taxonomy. | string | ✓ | | +| [project_id](variables.tf#L89) | GCP project id. | | ✓ | | | [activated_policy_types](variables.tf#L17) | A list of policy types that are activated for this taxonomy. | list(string) | | ["FINE_GRAINED_ACCESS_CONTROL"] | | [description](variables.tf#L23) | Description of this taxonomy. | string | | "Taxonomy - Terraform managed" | | [group_iam](variables.tf#L29) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} | | [iam](variables.tf#L35) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L41) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive_members](variables.tf#L47) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [iam_members](variables.tf#L53) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [location](variables.tf#L63) | Data Catalog Taxonomy location. | string | | "eu" | -| [prefix](variables.tf#L74) | Optional prefix used to generate project id and name. | string | | null | -| [tags](variables.tf#L88) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(object({…})) | | {} | +| [iam_members](variables.tf#L53) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [location](variables.tf#L68) | Data Catalog Taxonomy location. | string | | "eu" | +| [prefix](variables.tf#L79) | Optional prefix used to generate project id and name. | string | | null | +| [tags](variables.tf#L93) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(object({…})) | | {} | ## Outputs diff --git a/modules/data-catalog-policy-tag/iam.tf b/modules/data-catalog-policy-tag/iam.tf index 6f79aebff..9e7f3a093 100644 --- a/modules/data-catalog-policy-tag/iam.tf +++ b/modules/data-catalog-policy-tag/iam.tf @@ -80,6 +80,14 @@ resource "google_data_catalog_taxonomy_iam_member" "members" { taxonomy = google_data_catalog_taxonomy.default.id role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } } resource "google_data_catalog_policy_tag_iam_binding" "authoritative" { diff --git a/modules/data-catalog-policy-tag/variables.tf b/modules/data-catalog-policy-tag/variables.tf index c7c8eb072..2b799a961 100644 --- a/modules/data-catalog-policy-tag/variables.tf +++ b/modules/data-catalog-policy-tag/variables.tf @@ -55,6 +55,11 @@ variable "iam_members" { type = map(object({ member = string role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} diff --git a/modules/dataplex-datascan/README.md b/modules/dataplex-datascan/README.md index 65436af68..ca8afca7c 100644 --- a/modules/dataplex-datascan/README.md +++ b/modules/dataplex-datascan/README.md @@ -433,9 +433,9 @@ module "dataplex-datascan" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| | [data](variables.tf#L17) | The data source for DataScan. The source can be either a Dataplex `entity` or a BigQuery `resource`. | object({…}) | ✓ | | -| [name](variables.tf#L156) | Name of Dataplex Scan. | string | ✓ | | -| [project_id](variables.tf#L167) | The ID of the project where the Dataplex DataScan will be created. | string | ✓ | | -| [region](variables.tf#L172) | Region for the Dataplex DataScan. | string | ✓ | | +| [name](variables.tf#L161) | Name of Dataplex Scan. | string | ✓ | | +| [project_id](variables.tf#L172) | The ID of the project where the Dataplex DataScan will be created. | string | ✓ | | +| [region](variables.tf#L177) | Region for the Dataplex DataScan. | string | ✓ | | | [data_profile_spec](variables.tf#L29) | DataProfileScan related setting. Variable descriptions are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataProfileSpec. | object({…}) | | null | | [data_quality_spec](variables.tf#L38) | DataQualityScan related setting. Variable descriptions are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec. | object({…}) | | null | | [data_quality_spec_file](variables.tf#L80) | Path to a YAML file containing DataQualityScan related setting. Input content can use either camelCase or snake_case. Variables description are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec. | object({…}) | | null | @@ -445,11 +445,11 @@ module "dataplex-datascan" { | [iam](variables.tf#L107) | Dataplex DataScan IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L114) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive_members](variables.tf#L121) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [iam_members](variables.tf#L127) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [iam_policy](variables.tf#L137) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | map(list(string)) | | null | -| [incremental_field](variables.tf#L143) | The unnested field (of type Date or Timestamp) that contains values which monotonically increase over time. If not specified, a data scan will run for all data in the table. | string | | null | -| [labels](variables.tf#L149) | Resource labels. | map(string) | | {} | -| [prefix](variables.tf#L161) | Optional prefix used to generate Dataplex DataScan ID. | string | | null | +| [iam_members](variables.tf#L127) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [iam_policy](variables.tf#L142) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | map(list(string)) | | null | +| [incremental_field](variables.tf#L148) | The unnested field (of type Date or Timestamp) that contains values which monotonically increase over time. If not specified, a data scan will run for all data in the table. | string | | null | +| [labels](variables.tf#L154) | Resource labels. | map(string) | | {} | +| [prefix](variables.tf#L166) | Optional prefix used to generate Dataplex DataScan ID. | string | | null | ## Outputs diff --git a/modules/dataplex-datascan/iam.tf b/modules/dataplex-datascan/iam.tf index e1a7c057c..2a6c4c781 100644 --- a/modules/dataplex-datascan/iam.tf +++ b/modules/dataplex-datascan/iam.tf @@ -76,6 +76,14 @@ resource "google_dataplex_datascan_iam_member" "members" { data_scan_id = google_dataplex_datascan.datascan.data_scan_id role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } } resource "google_dataplex_datascan_iam_policy" "authoritative_for_resource" { diff --git a/modules/dataplex-datascan/variables.tf b/modules/dataplex-datascan/variables.tf index 47ca7332e..f40ca4ec1 100644 --- a/modules/dataplex-datascan/variables.tf +++ b/modules/dataplex-datascan/variables.tf @@ -129,6 +129,11 @@ variable "iam_members" { type = map(object({ member = string role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} diff --git a/modules/dataproc/README.md b/modules/dataproc/README.md index 3e454e504..b9dfe3d0e 100644 --- a/modules/dataproc/README.md +++ b/modules/dataproc/README.md @@ -165,17 +165,17 @@ module "processing-dp-cluster" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L222) | Cluster name. | string | ✓ | | -| [project_id](variables.tf#L237) | Project ID. | string | ✓ | | -| [region](variables.tf#L242) | Dataproc region. | string | ✓ | | +| [name](variables.tf#L227) | Cluster name. | string | ✓ | | +| [project_id](variables.tf#L242) | Project ID. | string | ✓ | | +| [region](variables.tf#L247) | Dataproc region. | string | ✓ | | | [dataproc_config](variables.tf#L17) | Dataproc cluster config. | object({…}) | | {} | | [group_iam](variables.tf#L185) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} | | [iam](variables.tf#L192) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L199) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iam_members](variables.tf#L206) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [labels](variables.tf#L216) | The resource labels for instance to use to annotate any related underlying resources, such as Compute Engine VMs. | map(string) | | {} | -| [prefix](variables.tf#L227) | Optional prefix used to generate project id and name. | string | | null | -| [service_account](variables.tf#L247) | Service account to set on the Dataproc cluster. | string | | null | +| [iam_members](variables.tf#L206) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [labels](variables.tf#L221) | The resource labels for instance to use to annotate any related underlying resources, such as Compute Engine VMs. | map(string) | | {} | +| [prefix](variables.tf#L232) | Optional prefix used to generate project id and name. | string | | null | +| [service_account](variables.tf#L252) | Service account to set on the Dataproc cluster. | string | | null | ## Outputs diff --git a/modules/dataproc/iam.tf b/modules/dataproc/iam.tf index 84d49f357..047561920 100644 --- a/modules/dataproc/iam.tf +++ b/modules/dataproc/iam.tf @@ -72,4 +72,12 @@ resource "google_dataproc_cluster_iam_member" "members" { cluster = google_dataproc_cluster.cluster.name role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } } diff --git a/modules/dataproc/variables.tf b/modules/dataproc/variables.tf index b28b9c279..37aad39fc 100644 --- a/modules/dataproc/variables.tf +++ b/modules/dataproc/variables.tf @@ -208,6 +208,11 @@ variable "iam_members" { type = map(object({ member = string role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} diff --git a/modules/folder/README.md b/modules/folder/README.md index b46c5e8b9..03c2a7554 100644 --- a/modules/folder/README.md +++ b/modules/folder/README.md @@ -325,17 +325,17 @@ module "folder" { | [iam](variables.tf#L44) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L51) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive_members](variables.tf#L58) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [iam_members](variables.tf#L65) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [iam_policy](variables.tf#L75) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | map(list(string)) | | null | -| [id](variables.tf#L81) | Folder ID in case you use folder_create=false. | string | | null | -| [logging_data_access](variables.tf#L87) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | -| [logging_exclusions](variables.tf#L102) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} | -| [logging_sinks](variables.tf#L109) | Logging sinks to create for the organization. | map(object({…})) | | {} | -| [name](variables.tf#L139) | Folder name. | string | | null | -| [org_policies](variables.tf#L145) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} | -| [org_policies_data_path](variables.tf#L172) | Path containing org policies in YAML format. | string | | null | -| [parent](variables.tf#L178) | Parent in folders/folder_id or organizations/org_id format. | string | | null | -| [tag_bindings](variables.tf#L188) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null | +| [iam_members](variables.tf#L65) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [iam_policy](variables.tf#L80) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | map(list(string)) | | null | +| [id](variables.tf#L86) | Folder ID in case you use folder_create=false. | string | | null | +| [logging_data_access](variables.tf#L92) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | +| [logging_exclusions](variables.tf#L107) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} | +| [logging_sinks](variables.tf#L114) | Logging sinks to create for the organization. | map(object({…})) | | {} | +| [name](variables.tf#L144) | Folder name. | string | | null | +| [org_policies](variables.tf#L150) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} | +| [org_policies_data_path](variables.tf#L177) | Path containing org policies in YAML format. | string | | null | +| [parent](variables.tf#L183) | Parent in folders/folder_id or organizations/org_id format. | string | | null | +| [tag_bindings](variables.tf#L193) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null | ## Outputs diff --git a/modules/folder/iam.tf b/modules/folder/iam.tf index 6b8fc1b18..cc457e289 100644 --- a/modules/folder/iam.tf +++ b/modules/folder/iam.tf @@ -69,6 +69,14 @@ resource "google_folder_iam_member" "members" { folder = local.folder.name role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } } resource "google_folder_iam_policy" "authoritative" { diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf index be7aad32a..df0579955 100644 --- a/modules/folder/variables.tf +++ b/modules/folder/variables.tf @@ -67,6 +67,11 @@ variable "iam_members" { type = map(object({ member = string role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} diff --git a/modules/iam-service-account/README.md b/modules/iam-service-account/README.md index 061b651e7..6c03b8249 100644 --- a/modules/iam-service-account/README.md +++ b/modules/iam-service-account/README.md @@ -41,8 +41,8 @@ module "myproject-default-service-accounts" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L101) | Name of the service account to create. | string | ✓ | | -| [project_id](variables.tf#L116) | Project id where service account will be created. | string | ✓ | | +| [name](variables.tf#L106) | Name of the service account to create. | string | ✓ | | +| [project_id](variables.tf#L121) | Project id where service account will be created. | string | ✓ | | | [description](variables.tf#L17) | Optional description. | string | | null | | [display_name](variables.tf#L23) | Display name of the service account to create. | string | | "Terraform-managed." | | [generate_key](variables.tf#L29) | Generate a key for service account. | bool | | false | @@ -50,14 +50,14 @@ module "myproject-default-service-accounts" { | [iam_additive](variables.tf#L42) | IAM additive bindings on the service account in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_billing_roles](variables.tf#L49) | Billing account roles granted to this service account, by billing account id. Non-authoritative. | map(list(string)) | | {} | | [iam_folder_roles](variables.tf#L56) | Folder roles granted to this service account, by folder id. Non-authoritative. | map(list(string)) | | {} | -| [iam_members](variables.tf#L63) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [iam_organization_roles](variables.tf#L73) | Organization roles granted to this service account, by organization id. Non-authoritative. | map(list(string)) | | {} | -| [iam_project_roles](variables.tf#L80) | Project roles granted to this service account, by project id. | map(list(string)) | | {} | -| [iam_sa_roles](variables.tf#L87) | Service account roles granted to this service account, by service account name. | map(list(string)) | | {} | -| [iam_storage_roles](variables.tf#L94) | Storage roles granted to this service account, by bucket name. | map(list(string)) | | {} | -| [prefix](variables.tf#L106) | Prefix applied to service account names. | string | | null | -| [public_keys_directory](variables.tf#L121) | Path to public keys data files to upload to the service account (should have `.pem` extension). | string | | "" | -| [service_account_create](variables.tf#L127) | Create service account. When set to false, uses a data source to reference an existing service account. | bool | | true | +| [iam_members](variables.tf#L63) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [iam_organization_roles](variables.tf#L78) | Organization roles granted to this service account, by organization id. Non-authoritative. | map(list(string)) | | {} | +| [iam_project_roles](variables.tf#L85) | Project roles granted to this service account, by project id. | map(list(string)) | | {} | +| [iam_sa_roles](variables.tf#L92) | Service account roles granted to this service account, by service account name. | map(list(string)) | | {} | +| [iam_storage_roles](variables.tf#L99) | Storage roles granted to this service account, by bucket name. | map(list(string)) | | {} | +| [prefix](variables.tf#L111) | Prefix applied to service account names. | string | | null | +| [public_keys_directory](variables.tf#L126) | Path to public keys data files to upload to the service account (should have `.pem` extension). | string | | "" | +| [service_account_create](variables.tf#L132) | Create service account. When set to false, uses a data source to reference an existing service account. | bool | | true | ## Outputs diff --git a/modules/iam-service-account/iam.tf b/modules/iam-service-account/iam.tf index ae388c8e3..a92d5e2ad 100644 --- a/modules/iam-service-account/iam.tf +++ b/modules/iam-service-account/iam.tf @@ -139,6 +139,14 @@ resource "google_service_account_iam_member" "members" { service_account_id = each.value.entity role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } } resource "google_storage_bucket_iam_member" "bucket-roles" { diff --git a/modules/iam-service-account/variables.tf b/modules/iam-service-account/variables.tf index 3594fbbcd..87c350b69 100644 --- a/modules/iam-service-account/variables.tf +++ b/modules/iam-service-account/variables.tf @@ -65,6 +65,11 @@ variable "iam_members" { type = map(object({ member = string role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} diff --git a/modules/kms/README.md b/modules/kms/README.md index 7b8eb487c..5bd82d5f1 100644 --- a/modules/kms/README.md +++ b/modules/kms/README.md @@ -101,19 +101,19 @@ module "kms" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [keyring](variables.tf#L91) | Keyring attributes. | object({…}) | ✓ | | -| [project_id](variables.tf#L114) | Project id where the keyring will be created. | string | ✓ | | +| [keyring](variables.tf#L101) | Keyring attributes. | object({…}) | ✓ | | +| [project_id](variables.tf#L124) | Project id where the keyring will be created. | string | ✓ | | | [iam](variables.tf#L17) | Keyring IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L23) | Keyring IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iam_members](variables.tf#L29) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [key_iam](variables.tf#L39) | Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | -| [key_iam_additive](variables.tf#L45) | Key IAM additive bindings in {KEY => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | -| [key_iam_members](variables.tf#L51) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [key_purpose](variables.tf#L62) | Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | map(object({…})) | | {} | -| [key_purpose_defaults](variables.tf#L74) | Defaults used for key purpose when not defined at the key level. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | object({…}) | | {…} | -| [keyring_create](variables.tf#L99) | Set to false to manage keys and IAM bindings in an existing keyring. | bool | | true | -| [keys](variables.tf#L105) | Key names and base attributes. Set attributes to null if not needed. | map(object({…})) | | {} | -| [tag_bindings](variables.tf#L119) | Tag bindings for this keyring, in key => tag value id format. | map(string) | | null | +| [iam_members](variables.tf#L29) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [key_iam](variables.tf#L44) | Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | +| [key_iam_additive](variables.tf#L50) | Key IAM additive bindings in {KEY => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | +| [key_iam_members](variables.tf#L56) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [key_purpose](variables.tf#L72) | Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | map(object({…})) | | {} | +| [key_purpose_defaults](variables.tf#L84) | Defaults used for key purpose when not defined at the key level. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | object({…}) | | {…} | +| [keyring_create](variables.tf#L109) | Set to false to manage keys and IAM bindings in an existing keyring. | bool | | true | +| [keys](variables.tf#L115) | Key names and base attributes. Set attributes to null if not needed. | map(object({…})) | | {} | +| [tag_bindings](variables.tf#L129) | Tag bindings for this keyring, in key => tag value id format. | map(string) | | null | ## Outputs diff --git a/modules/kms/iam.tf b/modules/kms/iam.tf index 3da20d083..9289a4e83 100644 --- a/modules/kms/iam.tf +++ b/modules/kms/iam.tf @@ -67,6 +67,14 @@ resource "google_kms_key_ring_iam_member" "members" { key_ring_id = local.keyring.id role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } } resource "google_kms_crypto_key_iam_binding" "default" { @@ -94,4 +102,12 @@ resource "google_kms_crypto_key_iam_member" "members" { crypto_key_id = google_kms_crypto_key.default[each.value.key].id role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } } diff --git a/modules/kms/variables.tf b/modules/kms/variables.tf index 01e96c04a..c34aaaacc 100644 --- a/modules/kms/variables.tf +++ b/modules/kms/variables.tf @@ -31,6 +31,11 @@ variable "iam_members" { type = map(object({ member = string role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} @@ -54,6 +59,11 @@ variable "key_iam_members" { key = string member = string role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 8106e1d97..0ccfeb866 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -541,11 +541,11 @@ module "vpc" { | [shared_vpc_service_projects](variables.tf#L161) | Shared VPC service projects to register with this host. | list(string) | | [] | | [subnet_iam](variables.tf#L167) | Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format. | map(map(list(string))) | | {} | | [subnet_iam_additive](variables.tf#L173) | Subnet IAM additive bindings in {REGION/NAME => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | -| [subnet_iam_members](variables.tf#L180) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [subnets](variables.tf#L191) | Subnet configuration. | list(object({…})) | | [] | -| [subnets_proxy_only](variables.tf#L217) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] | -| [subnets_psc](variables.tf#L229) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | -| [vpc_create](variables.tf#L240) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true | +| [subnet_iam_members](variables.tf#L180) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [subnets](variables.tf#L196) | Subnet configuration. | list(object({…})) | | [] | +| [subnets_proxy_only](variables.tf#L222) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] | +| [subnets_psc](variables.tf#L234) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | +| [vpc_create](variables.tf#L245) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true | ## Outputs diff --git a/modules/net-vpc/subnets.tf b/modules/net-vpc/subnets.tf index 16f6398c7..b6eab1821 100644 --- a/modules/net-vpc/subnets.tf +++ b/modules/net-vpc/subnets.tf @@ -197,4 +197,12 @@ resource "google_compute_subnetwork_iam_member" "members" { region = google_compute_subnetwork.subnetwork[each.value.subnet].region role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } } diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf index 4e114dfe5..c49455fab 100644 --- a/modules/net-vpc/variables.tf +++ b/modules/net-vpc/variables.tf @@ -183,6 +183,11 @@ variable "subnet_iam_members" { member = string role = string subnet = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} diff --git a/modules/organization/README.md b/modules/organization/README.md index d232dfd43..4984d2a83 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -475,7 +475,7 @@ module "org" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [organization_id](variables.tf#L209) | Organization id in organizations/nnnnnn format. | string | ✓ | | +| [organization_id](variables.tf#L214) | Organization id in organizations/nnnnnn format. | string | ✓ | | | [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} | | [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | | [firewall_policy_associations](variables.tf#L31) | Hierarchical firewall policies to associate to this folder, in association name => policy id format. | map(string) | | {} | @@ -483,18 +483,18 @@ module "org" { | [iam](variables.tf#L45) | IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L52) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive_members](variables.tf#L59) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [iam_members](variables.tf#L66) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [iam_policy](variables.tf#L76) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | map(list(string)) | | null | -| [logging_data_access](variables.tf#L82) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | -| [logging_exclusions](variables.tf#L97) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} | -| [logging_sinks](variables.tf#L104) | Logging sinks to create for the organization. | map(object({…})) | | {} | -| [network_tags](variables.tf#L134) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | -| [org_policies](variables.tf#L156) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | -| [org_policies_data_path](variables.tf#L183) | Path containing org policies in YAML format. | string | | null | -| [org_policy_custom_constraints](variables.tf#L189) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} | -| [org_policy_custom_constraints_data_path](variables.tf#L203) | Path containing org policy custom constraints in YAML format. | string | | null | -| [tag_bindings](variables.tf#L218) | Tag bindings for this organization, in key => tag value id format. | map(string) | | null | -| [tags](variables.tf#L224) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | +| [iam_members](variables.tf#L66) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [iam_policy](variables.tf#L81) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | map(list(string)) | | null | +| [logging_data_access](variables.tf#L87) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | +| [logging_exclusions](variables.tf#L102) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} | +| [logging_sinks](variables.tf#L109) | Logging sinks to create for the organization. | map(object({…})) | | {} | +| [network_tags](variables.tf#L139) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | +| [org_policies](variables.tf#L161) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | +| [org_policies_data_path](variables.tf#L188) | Path containing org policies in YAML format. | string | | null | +| [org_policy_custom_constraints](variables.tf#L194) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} | +| [org_policy_custom_constraints_data_path](variables.tf#L208) | Path containing org policy custom constraints in YAML format. | string | | null | +| [tag_bindings](variables.tf#L223) | Tag bindings for this organization, in key => tag value id format. | map(string) | | null | +| [tags](variables.tf#L229) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | ## Outputs diff --git a/modules/organization/iam.tf b/modules/organization/iam.tf index f5d3b2d7c..e5ba3771b 100644 --- a/modules/organization/iam.tf +++ b/modules/organization/iam.tf @@ -78,6 +78,14 @@ resource "google_organization_iam_member" "members" { org_id = local.organization_id_numeric role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } } resource "google_organization_iam_policy" "authoritative" { diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index 65f0672be..2426f8e91 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -68,6 +68,11 @@ variable "iam_members" { type = map(object({ member = string role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} diff --git a/modules/project/README.md b/modules/project/README.md index 8a867b519..8ec483fb2 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -154,7 +154,7 @@ module "project" { #### Additive IAM by Binding -When the above approaches to additive IAM are unworkable due to dynamically generated principals, the `iam_members` variable allows specifying individual role/principal pairs using arbitrary keys: +When the above approaches to additive IAM are unworkable due to dynamically generated principals, the `iam_members` variable allows specifying individual role/principal pairs using arbitrary keys. This IAM variable also supports conditions. ```hcl module "project" { @@ -173,10 +173,23 @@ module "project" { member = "user:two@example.org" role = "roles/compute.admin" } + one-delegated-grant = { + member = "user:one@example.org" + role = "roles/resourcemanager.projectIamAdmin" + condition = { + title = "delegated_network_user_one" + expression = <<-END + api.getAttribute( + 'iam.googleapis.com/modifiedGrantsByRole', [] + ).hasOnly([ + 'roles/compute.networkAdmin' + ]) + END + } + } } - } -# tftest modules=1 resources=4 inventory=iam-members.yaml +# tftest modules=1 resources=5 inventory=iam-members.yaml ``` ### Service Identities and Authoritative IAM @@ -658,7 +671,7 @@ output "compute_robot" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L171) | Project name and id suffix. | string | ✓ | | +| [name](variables.tf#L176) | Project name and id suffix. | string | ✓ | | | [auto_create_network](variables.tf#L17) | Whether to create the default network for the project. | bool | | false | | [billing_account](variables.tf#L23) | Billing account id. | string | | null | | [contacts](variables.tf#L29) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | map(list(string)) | | {} | @@ -669,31 +682,31 @@ output "compute_robot" { | [iam](variables.tf#L62) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L69) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive_members](variables.tf#L76) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [iam_members](variables.tf#L82) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [iam_policy](variables.tf#L92) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | map(list(string)) | | null | -| [labels](variables.tf#L98) | Resource labels. | map(string) | | {} | -| [lien_reason](variables.tf#L105) | If non-empty, creates a project lien with this description. | string | | "" | -| [logging_data_access](variables.tf#L111) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | -| [logging_exclusions](variables.tf#L126) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string) | | {} | -| [logging_sinks](variables.tf#L133) | Logging sinks to create for this project. | map(object({…})) | | {} | -| [metric_scopes](variables.tf#L164) | List of projects that will act as metric scopes for this project. | list(string) | | [] | -| [org_policies](variables.tf#L176) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} | -| [org_policies_data_path](variables.tf#L203) | Path containing org policies in YAML format. | string | | null | -| [oslogin](variables.tf#L209) | Enable OS Login. | bool | | false | -| [oslogin_admins](variables.tf#L215) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string) | | [] | -| [oslogin_users](variables.tf#L223) | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string) | | [] | -| [parent](variables.tf#L230) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null | -| [prefix](variables.tf#L240) | Optional prefix used to generate project id and name. | string | | null | -| [project_create](variables.tf#L250) | Create project. When set to false, uses a data source to reference existing project. | bool | | true | -| [service_config](variables.tf#L256) | Configure service API activation. | object({…}) | | {…} | -| [service_encryption_key_ids](variables.tf#L268) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string)) | | {} | -| [service_perimeter_bridges](variables.tf#L275) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string) | | null | -| [service_perimeter_standard](variables.tf#L282) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string | | null | -| [services](variables.tf#L288) | Service APIs to enable. | list(string) | | [] | -| [shared_vpc_host_config](variables.tf#L294) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null | -| [shared_vpc_service_config](variables.tf#L303) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} | -| [skip_delete](variables.tf#L325) | Allows the underlying resources to be destroyed without destroying the project itself. | bool | | false | -| [tag_bindings](variables.tf#L331) | Tag bindings for this project, in key => tag value id format. | map(string) | | null | +| [iam_members](variables.tf#L82) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [iam_policy](variables.tf#L97) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | map(list(string)) | | null | +| [labels](variables.tf#L103) | Resource labels. | map(string) | | {} | +| [lien_reason](variables.tf#L110) | If non-empty, creates a project lien with this description. | string | | "" | +| [logging_data_access](variables.tf#L116) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string))) | | {} | +| [logging_exclusions](variables.tf#L131) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string) | | {} | +| [logging_sinks](variables.tf#L138) | Logging sinks to create for this project. | map(object({…})) | | {} | +| [metric_scopes](variables.tf#L169) | List of projects that will act as metric scopes for this project. | list(string) | | [] | +| [org_policies](variables.tf#L181) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} | +| [org_policies_data_path](variables.tf#L208) | Path containing org policies in YAML format. | string | | null | +| [oslogin](variables.tf#L214) | Enable OS Login. | bool | | false | +| [oslogin_admins](variables.tf#L220) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string) | | [] | +| [oslogin_users](variables.tf#L228) | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string) | | [] | +| [parent](variables.tf#L235) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null | +| [prefix](variables.tf#L245) | Optional prefix used to generate project id and name. | string | | null | +| [project_create](variables.tf#L255) | Create project. When set to false, uses a data source to reference existing project. | bool | | true | +| [service_config](variables.tf#L261) | Configure service API activation. | object({…}) | | {…} | +| [service_encryption_key_ids](variables.tf#L273) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string)) | | {} | +| [service_perimeter_bridges](variables.tf#L280) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string) | | null | +| [service_perimeter_standard](variables.tf#L287) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string | | null | +| [services](variables.tf#L293) | Service APIs to enable. | list(string) | | [] | +| [shared_vpc_host_config](variables.tf#L299) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null | +| [shared_vpc_service_config](variables.tf#L308) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} | +| [skip_delete](variables.tf#L330) | Allows the underlying resources to be destroyed without destroying the project itself. | bool | | false | +| [tag_bindings](variables.tf#L336) | Tag bindings for this project, in key => tag value id format. | map(string) | | null | ## Outputs diff --git a/modules/project/iam.tf b/modules/project/iam.tf index 7b67d895c..0918b5529 100644 --- a/modules/project/iam.tf +++ b/modules/project/iam.tf @@ -102,6 +102,14 @@ resource "google_project_iam_member" "members" { project = local.project.project_id role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } depends_on = [ google_project_service.project_services, google_project_iam_custom_role.roles diff --git a/modules/project/variables.tf b/modules/project/variables.tf index 4ab510626..18f55886b 100644 --- a/modules/project/variables.tf +++ b/modules/project/variables.tf @@ -84,6 +84,11 @@ variable "iam_members" { type = map(object({ member = string role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} diff --git a/modules/source-repository/README.md b/modules/source-repository/README.md index 4cfae8eae..01fff2d8f 100644 --- a/modules/source-repository/README.md +++ b/modules/source-repository/README.md @@ -75,14 +75,14 @@ module "repo" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L54) | Repository name. | string | ✓ | | -| [project_id](variables.tf#L59) | Project used for resources. | string | ✓ | | +| [name](variables.tf#L59) | Repository name. | string | ✓ | | +| [project_id](variables.tf#L64) | Project used for resources. | string | ✓ | | | [group_iam](variables.tf#L17) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string)) | | {} | | [iam](variables.tf#L24) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L31) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive_members](variables.tf#L38) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [iam_members](variables.tf#L44) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | -| [triggers](variables.tf#L64) | Cloud Build triggers. | map(object({…})) | | {} | +| [iam_members](variables.tf#L44) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | map(object({…})) | | {} | +| [triggers](variables.tf#L69) | Cloud Build triggers. | map(object({…})) | | {} | ## Outputs diff --git a/modules/source-repository/iam.tf b/modules/source-repository/iam.tf index f1ec3ebca..75d00fd18 100644 --- a/modules/source-repository/iam.tf +++ b/modules/source-repository/iam.tf @@ -72,4 +72,12 @@ resource "google_sourcerepo_repository_iam_member" "members" { repository = google_sourcerepo_repository.default.name role = each.value.role member = each.value.member + dynamic "condition" { + for_each = each.value.condition == null ? [] : [""] + content { + expression = each.value.condition.expression + title = each.value.condition.title + description = each.value.condition.description + } + } } diff --git a/modules/source-repository/variables.tf b/modules/source-repository/variables.tf index 74e44aa45..61975fb94 100644 --- a/modules/source-repository/variables.tf +++ b/modules/source-repository/variables.tf @@ -46,6 +46,11 @@ variable "iam_members" { type = map(object({ member = string role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) })) nullable = false default = {} diff --git a/tests/modules/project/examples/iam-members.yaml b/tests/modules/project/examples/iam-members.yaml index b80fc2bf1..41f48a562 100644 --- a/tests/modules/project/examples/iam-members.yaml +++ b/tests/modules/project/examples/iam-members.yaml @@ -23,6 +23,15 @@ values: project_id: project-example skip_delete: false timeouts: null + module.project.google_project_iam_member.members["one-delegated-grant"]: + condition: + - description: null + expression: "api.getAttribute(\n 'iam.googleapis.com/modifiedGrantsByRole',\ + \ []\n).hasOnly([\n 'roles/compute.networkAdmin'\n])\n" + title: delegated_network_user_one + member: user:one@example.org + project: project-example + role: roles/resourcemanager.projectIamAdmin module.project.google_project_iam_member.members["one-owner"]: condition: [] member: user:one@example.org @@ -41,8 +50,9 @@ values: counts: google_project: 1 - google_project_iam_member: 3 + google_project_iam_member: 4 modules: 1 - resources: 4 + resources: 5 outputs: {} +