Add iam_by_principals_additive to project, organization and folder modules (#2814)

* First attempt at iam_by_principals_additive

* Remove validation

* Update IAM ADR

* Apply to organization and project modules

* Update READMEs

* Add tests

* Remove "cycle errors"
This commit is contained in:
Julio Castillo
2025-01-14 13:32:19 +01:00
committed by GitHub
parent 0cea946ced
commit 7eff7b19dc
20 changed files with 275 additions and 26 deletions

View File

@@ -1,13 +1,14 @@
# Refactor IAM interface
**authors:** [Ludo](https://github.com/ludoo), [Julio](https://github.com/juliocc)
**last modified:** February 12, 2024
**last modified:** January 14, 2025
## Status
- Implemented in [#1595](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1595).
- Authoritative bindings type changed as per [#1622](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/issues/1622).
- Extended by [#2064](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/issues/2064).
- Extended by #2805 and #2814 to include `iam_by_principals_additive`
## Context
@@ -20,6 +21,7 @@ We currently support, with uneven coverage across modules:
- legacy additive `iam_additive` in `ROLE => [PRINCIPALS]` format which breaks for dynamic values
- legacy additive `iam_additive_members` in `PRINCIPAL => [ROLES]` format which breaks for dynamic values
- new additive `iam_members` in `KEY => {role: ROLE, member: MEMBER, condition: CONDITION}` format which works with dynamic values and supports conditions
- new additive `iam_by_principals_additive` in `PRINCIPAL => [ROLES]` format
- policy authoritative `iam_policy`
- specific support for third party resource bindings in the service account module
@@ -122,7 +124,7 @@ The **proposal** is to remove the IAM policy variable and resources, as its cove
```hcl
variable "iam_by_principals" {
description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable."
description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable."
type = map(list(string))
default = {}
nullable = false
@@ -132,6 +134,20 @@ variable "iam_by_principals" {
See #2064 and [this ADR](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/blob/ludo/iam-changes/fast/docs/0-domainless-iam.md) for more details.
### IAM by Principals Additive
> [!NOTE]
> This section was added on 2025-01-14
#2805 and #2814 introduced an additive version of `iam_by_principals`. The new variable format is shown below
```hcl
variable "iam_by_principals_additive" {
description = "Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable."
type = map(list(string))
default = {}
nullable = false
}
```
## Decision
@@ -202,8 +218,15 @@ variable "iam_bindings_additive" {
nullable = false
}
variable "iam_by_principals_additive" {
description = "Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable."
type = map(list(string))
default = {}
nullable = false
}
variable "iam_by_principals" {
description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable."
description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable."
type = map(list(string))
default = {}
nullable = false
@@ -228,6 +251,19 @@ locals {
try(local._iam_principals[role], [])
)
}
iam_bindings_additive = merge(
var.iam_bindings_additive,
[
for principal, roles in var.iam_by_principals_additive : {
for role in roles :
"iam-bpa:${principal}-${role}" => {
member = principal
role = role
condition = null
}
}
]...
)
}
resource "google_RESOURCE_TYPE_iam_binding" "authoritative" {
for_each = local.iam
@@ -253,7 +289,7 @@ resource "google_RESOURCE_TYPE_iam_binding" "bindings" {
}
resource "google_RESOURCE_TYPE_iam_member" "bindings" {
for_each = var.iam_bindings_additive
for_each = local.iam_bindings_additive
role = each.value.role
member = each.value.member
// add extra attributes (e.g. resource id)

View File

@@ -403,7 +403,8 @@ module "folder" {
| [iam](variables-iam.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_by_principals](variables-iam.tf#L54) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_by_principals](variables-iam.tf#L61) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_by_principals_additive](variables-iam.tf#L54) | Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [id](variables.tf#L107) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> |
| [logging_data_access](variables-logging.tf#L17) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_exclusions](variables-logging.tf#L32) | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,19 @@ locals {
try(local._iam_principals[role], [])
)
}
iam_bindings_additive = merge(
var.iam_bindings_additive,
[
for principal, roles in var.iam_by_principals_additive : {
for role in roles :
"iam-bpa:${principal}-${role}" => {
member = principal
role = role
condition = null
}
}
]...
)
}
resource "google_folder_iam_binding" "authoritative" {
@@ -56,7 +69,7 @@ resource "google_folder_iam_binding" "bindings" {
}
resource "google_folder_iam_member" "bindings" {
for_each = var.iam_bindings_additive
for_each = local.iam_bindings_additive
folder = local.folder_id
role = each.value.role
member = each.value.member

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,8 +51,15 @@ variable "iam_bindings_additive" {
default = {}
}
variable "iam_by_principals" {
description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable."
variable "iam_by_principals_additive" {
description = "Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable."
type = map(list(string))
default = {}
nullable = false
}
variable "iam_by_principals" {
description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable."
type = map(list(string))
default = {}
nullable = false

View File

@@ -542,7 +542,8 @@ module "org" {
| [iam](variables-iam.tf#L17) | IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_by_principals](variables-iam.tf#L54) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_by_principals](variables-iam.tf#L61) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_by_principals_additive](variables-iam.tf#L54) | Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_data_access](variables-logging.tf#L17) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_exclusions](variables-logging.tf#L32) | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_settings](variables-logging.tf#L39) | Default settings for logging resources. | <code title="object&#40;&#123;&#10; disable_default_sink &#61; optional&#40;bool&#41;&#10; storage_location &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,6 +51,19 @@ locals {
try(local._iam_principals[role], [])
)
}
iam_bindings_additive = merge(
var.iam_bindings_additive,
[
for principal, roles in var.iam_by_principals_additive : {
for role in roles :
"iam-bpa:${principal}-${role}" => {
member = principal
role = role
condition = null
}
}
]...
)
}
# we use a different key for custom roles to allow referring to the role alias
@@ -101,7 +114,7 @@ resource "google_organization_iam_binding" "bindings" {
}
resource "google_organization_iam_member" "bindings" {
for_each = var.iam_bindings_additive
for_each = local.iam_bindings_additive
org_id = local.organization_id_numeric
role = each.value.role
member = each.value.member

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,8 +51,15 @@ variable "iam_bindings_additive" {
default = {}
}
variable "iam_by_principals" {
description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable."
variable "iam_by_principals_additive" {
description = "Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable."
type = map(list(string))
default = {}
nullable = false
}
variable "iam_by_principals" {
description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable."
type = map(list(string))
default = {}
nullable = false

View File

@@ -1593,7 +1593,8 @@ alerts:
| [iam](variables-iam.tf#L17) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; members &#61; list&#40;string&#41;&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; condition &#61; optional&#40;object&#40;&#123;&#10; expression &#61; string&#10; title &#61; string&#10; description &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_by_principals](variables-iam.tf#L54) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_by_principals](variables-iam.tf#L61) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_by_principals_additive](variables-iam.tf#L54) | Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [labels](variables.tf#L96) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [lien_reason](variables.tf#L103) | If non-empty, creates a project lien with this description. | <code>string</code> | | <code>null</code> |
| [log_scopes](variables-observability.tf#L117) | Log scopes under this project. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; resource_names &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,6 +54,19 @@ locals {
try(local._iam_principals[role], [])
)
}
iam_bindings_additive = merge(
var.iam_bindings_additive,
[
for principal, roles in var.iam_by_principals_additive : {
for role in roles :
"iam-bpa:${principal}-${role}" => {
member = principal
role = role
condition = null
}
}
]...
)
}
# we use a different key for custom roles to allow referring to the role alias
@@ -110,7 +123,7 @@ resource "google_project_iam_binding" "bindings" {
}
resource "google_project_iam_member" "bindings" {
for_each = var.iam_bindings_additive
for_each = local.iam_bindings_additive
project = local.project.project_id
role = each.value.role
member = each.value.member

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2024 Google LLC
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -51,8 +51,15 @@ variable "iam_bindings_additive" {
default = {}
}
variable "iam_by_principals" {
description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable."
variable "iam_by_principals_additive" {
description = "Additive IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam_bindings_additive` variable."
type = map(list(string))
default = {}
nullable = false
}
variable "iam_by_principals" {
description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid errors. Merged internally with the `iam` variable."
type = map(list(string))
default = {}
nullable = false

View File

@@ -0,0 +1,4 @@
iam_by_principals_additive = {
"user:user1@example.com" = ["role1", "role2"]
"user:user2@example.com" = ["role2", "role3"]
}

View File

@@ -0,0 +1,35 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
values:
google_folder_iam_member.bindings["iam-bpa:user:user1@example.com-role1"]:
condition: []
member: user:user1@example.com
role: role1
google_folder_iam_member.bindings["iam-bpa:user:user1@example.com-role2"]:
condition: []
member: user:user1@example.com
role: role2
google_folder_iam_member.bindings["iam-bpa:user:user2@example.com-role2"]:
condition: []
member: user:user2@example.com
role: role2
google_folder_iam_member.bindings["iam-bpa:user:user2@example.com-role3"]:
condition: []
member: user:user2@example.com
role: role3
counts:
google_folder: 1
google_folder_iam_member: 4

View File

@@ -0,0 +1,21 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module: modules/folder
common_tfvars:
- common.tfvars
tests:
iam_by_principals_additive:

View File

@@ -0,0 +1,4 @@
iam_by_principals_additive = {
"user:user1@example.com" = ["role1", "role2"]
"user:user2@example.com" = ["role2", "role3"]
}

View File

@@ -0,0 +1,39 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
values:
google_organization_iam_member.bindings["iam-bpa:user:user1@example.com-role1"]:
condition: []
member: user:user1@example.com
org_id: '1234567890'
role: role1
google_organization_iam_member.bindings["iam-bpa:user:user1@example.com-role2"]:
condition: []
member: user:user1@example.com
org_id: '1234567890'
role: role2
google_organization_iam_member.bindings["iam-bpa:user:user2@example.com-role2"]:
condition: []
member: user:user2@example.com
org_id: '1234567890'
role: role2
google_organization_iam_member.bindings["iam-bpa:user:user2@example.com-role3"]:
condition: []
member: user:user2@example.com
org_id: '1234567890'
role: role3
counts:
google_organization_iam_member: 4
resources: 4

View File

@@ -1,4 +1,4 @@
# Copyright 2023 Google LLC
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -22,3 +22,4 @@ tests:
org_policies_boolean:
org_policies_custom_constraints:
tags:
iam_by_principals_additive:

View File

@@ -0,0 +1,4 @@
iam_by_principals_additive = {
"user:user1@example.com" = ["role1", "role2"]
"user:user2@example.com" = ["role2", "role3"]
}

View File

@@ -0,0 +1,41 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
values:
google_project.project[0]:
name: my-project
google_project_iam_member.bindings["iam-bpa:user:user1@example.com-role1"]:
condition: []
member: user:user1@example.com
project: my-project
role: role1
google_project_iam_member.bindings["iam-bpa:user:user1@example.com-role2"]:
condition: []
member: user:user1@example.com
project: my-project
role: role2
google_project_iam_member.bindings["iam-bpa:user:user2@example.com-role2"]:
condition: []
member: user:user2@example.com
project: my-project
role: role2
google_project_iam_member.bindings["iam-bpa:user:user2@example.com-role3"]:
condition: []
member: user:user2@example.com
project: my-project
role: role3
counts:
google_project: 1
google_project_iam_member: 4

View File

@@ -1,4 +1,4 @@
# Copyright 2023 Google LLC
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -26,3 +26,4 @@ tests:
service_encryption_keys:
org_policies_list:
org_policies_boolean:
iam_by_principals_additive:

View File

@@ -1,6 +1,6 @@
#!/bin/bash
# Copyright 2023 Google LLC
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -40,4 +40,4 @@ echo -- Blueprint metadata --
python3 tools/validate_metadata.py -v blueprints --verbose --failed-only
echo -- Version checks --
find . -type f -name 'versions.tf' -exec diff -ub default-versions.tf {} \;
find . -type f -name 'versions.tf' -exec diff -I '[[:space:]]*module_name' -ub default-versions.tf {} \;