Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

☁️ Google Cloud Storage Bucket IAM Bindings Terraform Module

Grants additive, bucket-scoped IAM role bindings via google_storage_bucket_iam_member, targeting hashicorp/google ~> 7.0.

Terraform Google Provider Module Version Module Type Resources Posture


🧩 Overview

  • πŸ”‘ 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) or google_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_ids and member_etags, keyed identically to var.bindings, for composition convenience.

πŸ’‘ Why it matters: google_storage_bucket_iam_binding and google_storage_bucket_iam_policy are 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.


❀️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

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!


πŸ—ΊοΈ Where this fits

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:&lt;email&gt;' (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
Loading

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.


🧬 What this builds

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
Loading

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

βœ… Provider / Versions

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:

  • bucket is required and wants the bare bucket name β€” confirmed against the live schema (terraform providers schema -json) and the terraform-registry MCP's live google_storage_bucket_iam docs, whose own Example Usage passes bucket = google_storage_bucket.default.name. Passing a self_link or a projects/.../buckets/...-qualified id is not the expected form.
  • condition is part of a binding's identity, not just metadata β€” changing title/description/expression out-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 resource id ("b/{{bucket}} {{role}} {{member}}").
  • No labels argument exists on this resource's schema at all (confirmed β€” this resource's only attributes are bucket, etag, id, member, role). A timeouts block does exist but supports only create (default 20 minutes) β€” no update/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).

πŸ”‘ Required IAM Roles

  • 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).

☁️ GCP Prerequisites

  • storage.googleapis.com API enabled on the target project (via terraform-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 at apply time 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_member resource.

πŸ“ Module Structure

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

βš™οΈ Quick Start

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"
    }
  }
}

πŸ”Œ Cross-Module Contract

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

πŸ“š Example Library

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 individual user: 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.com must 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-count convention).

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\")"
      }
    }
  }
}

⚠️ Changing title, description, or expression after 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 though terraform plan shows 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 single user:/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. allUsers grants 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 whether terraform-google-storage-bucket's public_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 β€” no validation{} is applied to role precisely 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 β€” one google_storage_bucket_iam_member per (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 one google_storage_bucket_iam_member resource β€” 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 create timeout override (provider default: 20 minutes) β€” there is no update/delete key 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's id appears 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, and roles/viewer cannot carry a condition. This is invisible to terraform plan per 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's name output feeds var.bucket, terraform-google-service-account's email output (formatted "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 from module.app_data_bucket β€” it can transiently fail with a permission-denied error even though Terraform's graph ordering (via module.app_data_bucket.name and module.app_service_account.email) was correct.


πŸ“₯ Inputs

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.


🧾 Outputs

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.


🧠 Architecture Notes

  • bucket argument 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's google_storage_bucket_iam docs, bucket wants the bare bucket name (every provider Example Usage passes google_storage_bucket.default.name), never a self_link or a fully-qualified id. google_storage_bucket's own id and name resolve to the same string, so either output works identically β€” variables.tf documents name as the canonical source for clarity.
  • labels/timeouts β€” schema-confirmed partial exception. google_storage_bucket_iam_member's attributes are only bucket, etag, id, member, role, with condition and timeouts as its only block types. There is no labels argument at all β€” omitted from this module entirely, consistent with terraform-google-project-iam-bindings's identical omission (IAM grant resources are policy records, not taggable infrastructure). The timeouts block does exist but supports only create (default 20 minutes) β€” no update/delete override β€” so variables.tf's timeouts variable declares only create, applied uniformly to every for_each instance via a dynamic "timeouts" block in main.tf.
  • IAM propagation delay. google_storage_bucket_iam_member changes 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.
  • condition is part of a binding's identity. Changing title/description/expression out-of-band causes Terraform to destroy the old binding and create a new one, not update in place.
  • No self_link. The resource id ("b/{{bucket}} {{role}} {{member}}") is this resource family's only identity form.
  • for_each key stability. var.bindings is keyed by a caller-chosen stable string, never derived from role/member. Removing a middle entry destroys only that resource instance β€” no other binding is re-keyed or forced to replace.
  • No members (plural) list. One google_storage_bucket_iam_member per (role, member) pair, matching the resource's own one-member-at-a-time design.

🧱 Design Principles

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

πŸš€ Runbook

cd C:\GitHubCode\newgooglecloudmodules\terraform-google-storage-bucket-iam-bindings
terraform init -backend=false
terraform validate
terraform fmt -check

Pin ?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).


πŸ§ͺ Testing

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.


πŸ’¬ Example Output

$ 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="
}

πŸ” Troubleshooting

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

πŸ”— Related Docs

About

Terraform module: terraform-google-storage-bucket-iam-bindings

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages