Grants additive, bucket-scoped IAM role bindings via
google_storage_bucket_iam_member, targetinghashicorp/google ~> 7.0.
- π Grants one IAM role to one principal per map entry, via
google_storage_bucket_iam_member.memberβ additive and non-authoritative. - π« Never creates
google_storage_bucket_iam_binding(authoritative for a role) orgoogle_storage_bucket_iam_policy(authoritative for the entire bucket policy). - πΊοΈ No keystone
thisβ this is an aggregation module. Every resource is named by role:member. - πͺ£ Attaches to an already-existing Cloud Storage bucket, referenced by bare name β this module never creates, modifies, or destroys the bucket itself.
- β±οΈ Supports an optional per-binding IAM Condition for time-bound or context-scoped access.
- π€ Emits
member_idsandmember_etags, keyed identically tovar.bindings, for composition convenience.
π‘ Why it matters:
google_storage_bucket_iam_bindingandgoogle_storage_bucket_iam_policyare authoritative β an apply that uses either one removes any grant it does not explicitly declare, including ones a teammate added out-of-band. This module only ever adds a member to a role on a specific bucket, so two teams can safely grant different roles (or even the same role to different principals) on the same bucket without a shared-state apply silently deleting the other's access.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- β Star this repository to help others discover this Terraform module.
- π€ Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
SB["terraform-google-storage-bucket"]
SA["terraform-google-service-account"]
THIS["terraform-google-storage-bucket-iam-bindings"]
BUCKET["Target Cloud Storage Bucket<br/>(IAM Policy)"]
SB -->|"name output, bare bucket name"| THIS
SA -.->|"email output, formatted 'serviceAccount:<email>' (optional)"| THIS
THIS -->|"google_storage_bucket_iam_member grants"| BUCKET
style THIS fill:#4285F4,color:#ffffff
style SB fill:#174EA6,color:#ffffff
style BUCKET fill:#174EA6,color:#ffffff
style SA fill:#E8EAED,color:#202124
terraform-google-storage-bucket is the upstream keystone whose bucket this module attaches IAM grants to β its name output feeds var.bucket directly (see π§ Architecture Notes for why the bare name, not self_link, is the expected form). terraform-google-service-account is an optional upstream source of member principal values: its email output, formatted "serviceAccount:<email>" by the caller, becomes a member value here β not every entry requires this dependency, since member may equally be a caller-supplied user:/group:/domain: principal. The "Target Cloud Storage Bucket" node represents the actual GCP bucket whose IAM policy is modified, not a module in this catalog.
flowchart LR
VB["var.bucket"]
VBI["var.bindings (map, for_each)"]
VT["var.timeouts (optional)"]
RES["google_storage_bucket_iam_member.member[each.key]"]
OID["output: member_ids"]
OET["output: member_etags"]
VB -->|"bucket"| RES
VBI -->|"role, member, condition"| RES
VT -.->|"timeouts.create"| RES
RES -->|"id"| OID
RES -->|"etag"| OET
style RES fill:#4285F4,color:#ffffff
style VB fill:#E8EAED,color:#202124
style VBI fill:#E8EAED,color:#202124
style VT fill:#E8EAED,color:#202124
style OID fill:#E8EAED,color:#202124
style OET fill:#E8EAED,color:#202124
Resource inventory:
| Resource | Cardinality | Notes |
|---|---|---|
google_storage_bucket_iam_member.member |
for_each over var.bindings (0..N) |
One resource instance per map entry; additive grant only, targets the existing bucket named by var.bucket |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/google |
~> 7.0 |
| Provider block | None β the caller configures google (project, region/zone, auth) in the root module |
Schema notes that bite:
bucketisrequiredand wants the bare bucket name β confirmed against the live schema (terraform providers schema -json) and the terraform-registry MCP's livegoogle_storage_bucket_iamdocs, whose own Example Usage passesbucket = google_storage_bucket.default.name. Passing aself_linkor aprojects/.../buckets/...-qualified id is not the expected form.conditionis part of a binding's identity, not just metadata β changingtitle/description/expressionout-of-band destroys the old binding and creates a new one (the provider's own documented behavior).- No
self_linkβ this resource family's only identity form is the resourceid("b/{{bucket}} {{role}} {{member}}"). - No
labelsargument exists on this resource's schema at all (confirmed β this resource's only attributes arebucket,etag,id,member,role). Atimeoutsblock does exist but supports onlycreate(default 20 minutes) β noupdate/delete. See π§ Architecture Notes. - This resource carries the provider's general IAM Conditions limitations warning β review it before relying on conditional bucket grants (see π Related Docs).
roles/storage.adminβ needed on the target project/bucket to read and write bucket-level IAM policy bindings (the applying principal must be able to manage IAM on the specific bucket this module targets).
storage.googleapis.comAPI enabled on the target project (viaterraform-google-project-services).- The target bucket must already exist (created by
terraform-google-storage-bucket) before this module is applied β this module does not create the bucket and will fail atapplytime against a bucket name that does not yet exist. - No quota consideration specific to IAM member bindings identified; standard project IAM API rate limits apply as with any
google_*_iam_memberresource.
terraform-google-storage-bucket-iam-bindings/
βββ providers.tf # required_providers (hashicorp/google ~> 7.0) + required_version β no provider {} block
βββ variables.tf # var.bucket (bare name, resolved) + var.bindings (map(object({role, member, condition}))) + var.timeouts (create only)
βββ main.tf # google_storage_bucket_iam_member.member, for_each over var.bindings, dynamic condition + timeouts blocks
βββ outputs.tf # member_ids, member_etags β no self_link (resource exports none)
βββ README.md # this file
βββ SCOPE.md # cross-module contract
βββ examples/ # runnable example matching the Quick Start below
The caller configures the google provider (project, region/zone, authentication) in the root module β this module never declares its own provider block.
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"app-sa-object-viewer" = {
role = "roles/storage.objectViewer"
member = "serviceAccount:app-sa@casey-prod-app-data.iam.gserviceaccount.com"
}
}
}Consumes
| Input | Type | Source module |
|---|---|---|
name (bare bucket name) |
string |
terraform-google-storage-bucket (its id output is equivalent for this resource type; never pass self_link) |
email (service account email; caller formats as "serviceAccount:<email>" before supplying it as a member value) |
string |
terraform-google-service-account |
(a member value may equally be a caller-supplied user:, group:, or domain: principal β not every entry requires a sibling-module reference) |
string |
none β caller-supplied literal |
Emits
| Output | Description | Consumed by |
|---|---|---|
member_ids |
Map (keyed identically to var.bindings) of each google_storage_bucket_iam_member.member entry's id ("b/{{bucket}} {{role}} {{member}}") |
No module in the initial catalog takes a direct reference β bindings are typically a terminal/leaf record. Included for composition convenience (e.g. an external readiness check) |
member_etags |
Map (keyed identically to var.bindings) of each binding's etag |
Same as above |
1 Β· Minimal grant to a single user
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-dev-sandbox-bucket"
bindings = {
"jane-viewer" = {
role = "roles/storage.objectViewer"
member = "user:jane@financialpartners.com"
}
}
}π‘ The empty call (
bindings = {}) grants nothing β the secure default for this module is zero grants.
2 Β· Grant a role to a Google group
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"eng-group-admin" = {
role = "roles/storage.admin"
member = "group:engineering@financialpartners.com"
}
}
}βΉοΈ Grouping principals under
group:is generally preferable to individualuser:grants β group membership changes do not require a Terraform apply.
3 Β· Grant a role to a service account
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"app-sa-object-viewer" = {
role = "roles/storage.objectViewer"
member = "serviceAccount:app-sa@casey-prod-app-data.iam.gserviceaccount.com"
}
}
}
β οΈ iam.googleapis.commust be enabled on the target project for GCP to resolve the service account principal.
4 Β· Multiple bindings in one apply
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"eng-group-viewer" = { role = "roles/storage.objectViewer", member = "group:engineering@financialpartners.com" }
"sre-group-admin" = { role = "roles/storage.admin", member = "group:sre@financialpartners.com" }
"app-sa-object-creator" = { role = "roles/storage.objectCreator", member = "serviceAccount:app-sa@casey-prod-app-data.iam.gserviceaccount.com" }
}
}π‘ Each map key is a stable, caller-chosen identifier β removing one entry never re-keys or forces replacement of any other entry (this suite's
for_each-over-countconvention).
5 Β· Time-bound access via IAM Condition
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"temp-writer-2026" = {
role = "roles/storage.objectCreator"
member = "user:jane@financialpartners.com"
condition = {
title = "expires_after_2026_12_31"
description = "Temporary write access, expires end of 2026"
expression = "request.time < timestamp(\"2027-01-01T00:00:00Z\")"
}
}
}
}
β οΈ Changingtitle,description, orexpressionafter initial apply causes Terraform to destroy this binding and create a new one β it is part of the binding's identity, not just metadata.
6 Β· Domain-wide grant
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"domain-viewer" = {
role = "roles/storage.objectViewer"
member = "domain:financialpartners.com"
}
}
}π Domain restriction org policies (
constraints/iam.allowedPolicyMemberDomains) can reject this at apply time even thoughterraform planshows no conflict β verify the org's policy set before relying on a domain-wide grant.
7 Β· Project-level convenience principal
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"project-editors-writer" = {
role = "roles/storage.objectCreator"
member = "projectEditor:casey-prod-app-data-project"
}
}
}βΉοΈ
projectOwner:/projectEditor:/projectViewer:principals grant the role to every member holding the corresponding basic project role β broader than a singleuser:/serviceAccount:grant; confirm this is genuinely intended.
8 Β· allUsers / allAuthenticatedUsers principal
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-public-demo-assets"
bindings = {
"public-viewer" = {
role = "roles/storage.objectViewer"
member = "allUsers"
}
}
}π High risk.
allUsersgrants a role to anyone on the internet, with or without a Google account. Confirm this is genuinely intended and permitted by org policy before applying β this is exactly the kind of grant a public-access-prevention or domain-restriction org policy is designed to block. Consider whetherterraform-google-storage-bucket'spublic_access_prevention = "enforced"secure default already blocks this at the bucket level before relying on it here.
9 Β· Custom role grant
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"custom-role-grant" = {
role = "projects/casey-prod-networking/roles/customBucketAuditor"
member = "group:storage-audit@financialpartners.com"
}
}
}βΉοΈ Custom role names use the full
[projects|organizations]/{parent-name}/roles/{role-name}format β novalidation{}is applied toroleprecisely because custom role names are open-ended (see variables.tf's description).
10 Β· Same role, multiple principals
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"viewer-alice" = { role = "roles/storage.objectViewer", member = "user:alice@financialpartners.com" }
"viewer-bob" = { role = "roles/storage.objectViewer", member = "user:bob@financialpartners.com" }
"viewer-carla" = { role = "roles/storage.objectViewer", member = "user:carla@financialpartners.com" }
}
}π‘ This module never accepts a
members(plural) list per entry β onegoogle_storage_bucket_iam_memberper(role, member)pair, matching the resource's own one-member-at-a-time, non-authoritative design.
11 Β· Removing a binding safely
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"viewer-alice" = { role = "roles/storage.objectViewer", member = "user:alice@financialpartners.com" }
# "viewer-bob" removed β only this entry's grant is revoked on the next apply
"viewer-carla" = { role = "roles/storage.objectViewer", member = "user:carla@financialpartners.com" }
}
}π‘ Because keys are stable strings (not derived from
role/member), deleting an entry from the middle of the map only destroys that onegoogle_storage_bucket_iam_memberresource β every other entry's resource address is untouched.
12 Β· Custom create timeout
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"app-sa-object-viewer" = {
role = "roles/storage.objectViewer"
member = "serviceAccount:app-sa@casey-prod-app-data.iam.gserviceaccount.com"
}
}
timeouts = {
create = "10m"
}
}βΉοΈ This resource's schema exposes only a
createtimeout override (provider default: 20 minutes) β there is noupdate/deletekey to set; see π§ Architecture Notes.
13 Β· Referencing binding outputs downstream
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"app-sa-object-viewer" = {
role = "roles/storage.objectViewer"
member = "serviceAccount:app-sa@casey-prod-app-data.iam.gserviceaccount.com"
}
}
}
output "app_sa_binding_id" {
value = module.storage_bucket_iam_bindings.member_ids["app-sa-object-viewer"]
}βΉοΈ No module in the initial catalog takes a direct Terraform reference to
member_ids/member_etagsβ this pattern exists for external readiness checks (e.g. a script that polls until a binding'sidappears in state before proceeding).
14 Β· Basic Role + Condition rejection (what not to do)
# This will pass `terraform plan` but be REJECTED by the API at apply time β
# GCP does not allow IAM Conditions on Basic Roles (owner/editor/viewer).
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
bucket = "casey-prod-app-data"
bindings = {
"bad-conditional-owner" = {
role = "roles/owner" # Basic Role β incompatible with `condition`
member = "user:jane@financialpartners.com"
condition = {
title = "temp"
expression = "request.time < timestamp(\"2027-01-01T00:00:00Z\")"
}
}
}
}
β οΈ Use a predefined (non-Basic) or custom role instead βroles/owner,roles/editor, androles/viewercannot carry acondition. This is invisible toterraform planper this suite's plan-only posture.
15 Β· ποΈ End-to-end composition
module "app_data_bucket" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket.git?ref=v1.0.0"
name = "casey-prod-app-data"
location = "US"
}
module "app_service_account" {
source = "git::https://github.com/microsoftexpert/terraform-google-service-account.git?ref=v1.0.0"
account_id = "app-workload"
display_name = "Application workload service account"
}
module "storage_bucket_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-storage-bucket-iam-bindings.git?ref=v1.0.0"
# Sibling module output -> this module's bucket input, per SCOPE.md's Consumes contract β
# bare bucket name, never self_link.
bucket = module.app_data_bucket.name
bindings = {
# Sibling module output -> this module's member input, formatted per SCOPE.md's Consumes contract.
"app-sa-object-viewer" = {
role = "roles/storage.objectViewer"
member = "serviceAccount:${module.app_service_account.email}"
}
# A literal group principal β not every entry requires a sibling-module reference.
"eng-group-admin" = {
role = "roles/storage.admin"
member = "group:engineering@financialpartners.com"
}
}
}π‘ This mirrors SCOPE.md's documented Consumes relationship exactly:
terraform-google-storage-bucket'snameoutput feedsvar.bucket,terraform-google-service-account's"serviceAccount:<email>") feeds one binding, and a second binding uses a caller-supplied literal principal with no sibling-module dependency.
β οΈ Per the IAM propagation delay note (π§ Architecture Notes / π Troubleshooting), if a resource created in the same apply depends on this newly-granted role β e.g. a workload immediately reading frommodule.app_data_bucketβ it can transiently fail with a permission-denied error even though Terraform's graph ordering (viamodule.app_data_bucket.nameandmodule.app_service_account.email) was correct.
| Name | Type | Default | Required | Notes |
|---|---|---|---|---|
bucket |
string |
β | Yes | Bare name of the existing Cloud Storage bucket. Validated against a conservative subset of GCS bucket-naming rules. Never pass self_link. |
bindings |
map(object({...})) |
{} |
No | Map of caller-chosen key β { role, member, condition }. Empty map grants nothing (secure default). |
timeouts |
object({ create = optional(string) }) |
null |
No | Create-timeout override only β this resource's schema has no update/delete timeout. |
Full object schemas
variable "bucket" {
type = string
# Validated: 3-222 chars, lowercase letters/digits/dashes/underscores/dots, must not
# start or end with a dot or dash.
}
variable "bindings" {
type = map(object({
role = string
member = string
condition = optional(object({
expression = string
title = string
description = optional(string)
}))
}))
default = {}
}
variable "timeouts" {
type = object({
create = optional(string)
})
default = null
}No labels variable exists on this module β google_storage_bucket_iam_member's schema exposes no labels argument. See π§ Architecture Notes.
| Output | Description | Sensitive? |
|---|---|---|
member_ids |
Map (keyed identically to var.bindings) of each binding's id ("b/{{bucket}} {{role}} {{member}}") |
No |
member_etags |
Map (keyed identically to var.bindings) of each binding's etag |
No |
This resource family exports no self_link β the resource id above is its only identity form.
bucketargument form β resolved fact, not an open item. SCOPE.md's Prompt-1 open question ("bare name vs. full id") is settled: confirmed against the live schema and the terraform-registry MCP'sgoogle_storage_bucket_iamdocs,bucketwants the bare bucket name (every provider Example Usage passesgoogle_storage_bucket.default.name), never aself_linkor a fully-qualified id.google_storage_bucket's ownidandnameresolve to the same string, so either output works identically βvariables.tfdocumentsnameas the canonical source for clarity.labels/timeoutsβ schema-confirmed partial exception.google_storage_bucket_iam_member's attributes are onlybucket,etag,id,member,role, withconditionandtimeoutsas its only block types. There is nolabelsargument at all β omitted from this module entirely, consistent withterraform-google-project-iam-bindings's identical omission (IAM grant resources are policy records, not taggable infrastructure). Thetimeoutsblock does exist but supports onlycreate(default 20 minutes) β noupdate/deleteoverride β sovariables.tf'stimeoutsvariable declares onlycreate, applied uniformly to everyfor_eachinstance via adynamic "timeouts"block inmain.tf.- IAM propagation delay.
google_storage_bucket_iam_memberchanges can take up to ~60 seconds to become effective at the API level. A composition that applies this module and then immediately creates a resource depending on the freshly-granted role in the same apply can hit a transient permission-denied error even though Terraform's graph ordering was correct. This is an operational characteristic of GCP IAM, not a bug to work around in code. conditionis part of a binding's identity. Changingtitle/description/expressionout-of-band causes Terraform to destroy the old binding and create a new one, not update in place.- No
self_link. The resourceid("b/{{bucket}} {{role}} {{member}}") is this resource family's only identity form. for_eachkey stability.var.bindingsis keyed by a caller-chosen stable string, never derived fromrole/member. Removing a middle entry destroys only that resource instance β no other binding is re-keyed or forced to replace.- No
members(plural) list. Onegoogle_storage_bucket_iam_memberper(role, member)pair, matching the resource's own one-member-at-a-time design.
| Concern | Secure default | Opt-out (explicit) |
|---|---|---|
| Authoritative IAM resources | This module only ever creates google_storage_bucket_iam_member (additive/non-authoritative) |
N/A β google_storage_bucket_iam_binding/google_storage_bucket_iam_policy are intentionally excluded from this library by design |
| Default grants | var.bindings defaults to {} β the empty call grants nothing |
Caller supplies explicit map entries |
condition support |
Optional per entry; omitted by default | Caller opts in per binding |
Broad/public principals (allUsers, allAuthenticatedUsers, domain:, projectOwner:/projectEditor:/projectViewer:) |
Not defaulted or suggested anywhere in this module β always an explicit caller choice, called out with a π warning in the Example Library | Caller supplies the principal explicitly, aware of the risk |
| Bucket targeting | var.bucket must always be supplied explicitly and passes a conservative format check β no silent fallback to an ambient default that could apply a grant to the wrong bucket |
N/A β required by the resource's own schema |
cd C:\GitHubCode\newgooglecloudmodules\terraform-google-storage-bucket-iam-bindings
terraform init -backend=false
terraform validate
terraform fmt -checkPin ?ref=v1.0.0 in every consuming composition β never a branch. This library is plan-only; a human applies from CI with valid credentials (ADC or Workload Identity Federation).
terraform init -backend=false && terraform validate && terraform fmt -check is the entire offline proof gate for this module: validate confirms internal type/reference consistency (e.g. that var.bindings' object shape is well-formed and every reference resolves), and fmt -check confirms canonical formatting. Neither can catch GCP API-level rejections β quota, org policy (domain restriction, public access prevention, IAM Condition-on-Basic-Role), or IAM propagation delay are all invisible to this proof gate and surface only at apply time in a real project, per this suite's plan-only posture convention. The examples/ directory exists so a consuming GitHub Actions workflow can run a real terraform plan against a real project as part of that pipeline's own review gate.
$ terraform output
member_ids = {
"app-sa-object-viewer" = "b/casey-prod-app-data roles/storage.objectViewer serviceAccount:app-sa@casey-prod-app-data.iam.gserviceaccount.com"
"eng-group-admin" = "b/casey-prod-app-data roles/storage.admin group:engineering@financialpartners.com"
}
member_etags = {
"app-sa-object-viewer" = "CAI="
"eng-group-admin" = "CAI="
}
| Symptom | Cause | Fix |
|---|---|---|
| A resource created in the same apply fails with a transient permission-denied error, even though it correctly referenced this module's output | IAM propagation delay β grants can take up to ~60 seconds to become effective at the API level | Re-apply, or add a manual wait step in the consuming pipeline; not a code defect to fix in this module |
terraform plan shows an unrelated binding being destroyed and recreated after only editing a condition's title/description/expression |
condition is part of the binding's identity β any change to it is a new binding, not an in-place update |
Expected behavior; review before applying, and communicate the destroy/recreate to anyone relying on the old binding's continuous existence |
| Apply is rejected with an error about IAM Conditions and Basic Roles | GCP disallows condition on roles/owner, roles/editor, roles/viewer β invisible to terraform plan |
Remove the condition, or use a predefined non-Basic role or a custom role instead |
Apply fails with a 404/not-found error referencing the bucket, even though terraform plan succeeded |
The target bucket does not yet exist, or var.bucket was given a self_link/qualified id instead of the bare name |
Confirm the bucket exists first (via terraform-google-storage-bucket), and confirm var.bucket is the bare bucket name |
terraform validate fails with a bucket validation error on what looks like a valid bucket name |
The validation regex enforces a conservative subset of GCS naming rules (lowercase letters/digits/dashes/underscores/dots, 3-222 chars, no leading/trailing dot or dash) | Confirm you passed the bucket's bare name, not its self_link |
A member value is silently rejected at apply even though plan succeeded |
A domain restriction org policy (constraints/iam.allowedPolicyMemberDomains) or the bucket's own public-access-prevention setting is blocking that identity |
Check the org's policy set and the target bucket's public_access_prevention setting; this is an org/bucket-level control, not something this module can validate offline |
terraform validate fails with "Unsupported argument: labels" |
Caller copied a labels block pattern from another module |
This resource's schema has no labels argument β remove the block; see π§ Architecture Notes |
terraform validate fails with "Unsupported argument: update" or "delete" inside a timeouts block |
Caller copied a full create/update/delete timeouts pattern from a module whose resource supports all three |
This resource's timeouts block supports only create β remove update/delete; see π§ Architecture Notes |
google_storage_bucket_iamresource docs (covers_member,_binding,_policy)- IAM Conditions overview and its limitations
- Cloud Storage bucket naming rules
- Sibling modules:
terraform-google-storage-bucket,terraform-google-service-account,terraform-google-project-iam-bindings - This module's
SCOPE.md