Grants additive, environment-scoped IAM role bindings via
google_apigee_environment_iam_member, targetinghashicorp/google ~> 7.0.
- Grants one IAM role to one principal per map entry, via
google_apigee_environment_iam_member.memberβ additive and non-authoritative. - Never creates
google_apigee_environment_iam_binding(authoritative for a role) orgoogle_apigee_environment_iam_policy(authoritative for the entire environment policy). - No keystone
thisβ this is an aggregation module. Every resource is named by role:member. - Supports an optional per-binding IAM Condition for time-bound or context-scoped access.
- Mirrors the exact pattern already established by
terraform-google-project-iam-bindings.
π‘ Why it matters: environment-level access delegation is a real, common need in Apigee β a dev team frequently needs deploy/manage rights scoped to just their environment (
dev,staging) without touching prod. This module's additive form lets multiple teams grant different roles independently without a shared-state apply silently deleting each other's access, unlike the authoritative_iam_binding/_iam_policyforms.
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
ORG["terraform-google-apigee-organization<br/>(same batch, required)"]:::keystone
ENV["terraform-google-apigee-environment<br/>(same batch, required)"]:::keystone
THIS["terraform-google-apigee-environment-iam-bindings<br/>(this module β aggregation)"]:::thismodule
ORG -.->|"id -> org_id"| THIS
ENV -.->|"id -> env_id"| THIS
classDef thismodule fill:#4285F4,color:#FFFFFF,stroke:#174EA6,stroke-width:1px;
classDef keystone fill:#174EA6,color:#FFFFFF,stroke:#174EA6,stroke-width:1px;
flowchart TB
subgraph Inputs
IDS["var.org_id / var.env_id"]
BIND["var.bindings"]
end
KEYSTONE["google_apigee_environment_iam_member.member<br/>(for_each, no keystone this)"]:::keystone
subgraph Outputs
BID["binding_ids"]
BET["binding_etags"]
end
IDS --> KEYSTONE
BIND --> KEYSTONE
KEYSTONE --> BID
KEYSTONE --> BET
classDef keystone fill:#174EA6,color:#FFFFFF,stroke:#174EA6,stroke-width:1px;
Resource inventory
| Resource | Cardinality | Notes |
|---|---|---|
google_apigee_environment_iam_member.member |
for_each over var.bindings (0..N) |
One resource instance per map entry; additive grant only |
| Item | Value |
|---|---|
| Terraform | >= 1.12.0 |
google provider |
~> 7.0 |
| Provider block | None β the caller configures google |
Schema notes that bite:
org_idandenv_idare bothrequiredwith no fallback β an environment IAM binding needs both the organization and environment context explicitly.conditionis part of a binding's identity, not just metadata β changingtitle/description/expressionout-of-band destroys the old binding and creates a new one.- No
self_linkβ this resource family's only identity form is the compositeid. - No
labelsargument and notimeoutsblock exist on this resource's schema at all β both deliberately absent fromvariables.tf.
roles/apigee.adminβ or a scoped role grantingapigee.environments.setIamPolicy/apigee.environments.getIamPolicy, required to create/update/deletegoogle_apigee_environment_iam_memberbindings.
apigee.googleapis.comenabled.- Both the target organization and environment must already exist.
- No quota concerns beyond standard IAM policy rate limits.
terraform-google-apigee-environment-iam-bindings/
βββ providers.tf # required_providers + required_version
βββ variables.tf # org_id, env_id, bindings (map(object({role, member, condition})))
βββ main.tf # google_apigee_environment_iam_member.member, for_each over var.bindings
βββ outputs.tf # binding_ids, binding_etags
βββ README.md
βββ SCOPE.md
βββ examples/
βββ basic/
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"dev-team-env-admin" = {
role = "roles/apigee.environmentAdmin"
member = "group:api-platform-dev@financialpartners.com"
}
}
}βΉοΈ The caller configures the
Consumes
| Input | Type | Source module |
|---|---|---|
org_id |
string |
terraform-google-apigee-organization (required β consumes its .id output) |
env_id |
string |
terraform-google-apigee-environment (required β consumes its .id output) |
(a member value may equally be a caller-supplied user:, group:, or domain: principal) |
string |
none β caller-supplied literal |
Emits
| Output | Description | Consumed by |
|---|---|---|
binding_ids |
Map (keyed identically to var.bindings) of each binding's composite id |
None in this batch |
binding_etags |
Map (keyed identically to var.bindings) of each binding's etag |
None |
No self_link.
1 Β· Minimal grant to a single user
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"jane-env-viewer" = {
role = "roles/apigee.readOnlyAdmin"
member = "user:jane@financialpartners.com"
}
}
}π‘ The empty call (
bindings = {}) grants nothing β the secure default is zero grants.
2 Β· Grant environment admin to a dev team group
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment_dev.id
bindings = {
"dev-team-env-admin" = {
role = "roles/apigee.environmentAdmin"
member = "group:api-platform-dev@financialpartners.com"
}
}
}3 Β· Grant deployer role to a CI/CD service account
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"ci-deployer" = {
role = "roles/apigee.deployer"
member = "serviceAccount:apigee-ci@casey-prod-apigee.iam.gserviceaccount.com"
}
}
}4 Β· Multiple bindings in one apply
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"dev-team-env-admin" = { role = "roles/apigee.environmentAdmin", member = "group:api-platform-dev@financialpartners.com" }
"sre-readonly" = { role = "roles/apigee.readOnlyAdmin", member = "group:sre@financialpartners.com" }
"ci-deployer" = { role = "roles/apigee.deployer", member = "serviceAccount:apigee-ci@casey-prod-apigee.iam.gserviceaccount.com" }
}
}5 Β· Time-bound access via IAM Condition
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"temp-deployer-2026" = {
role = "roles/apigee.deployer"
member = "user:jane@financialpartners.com"
condition = {
title = "expires_after_2026_12_31"
description = "Temporary elevated 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.
6 Β· Domain-wide grant
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"domain-readonly" = {
role = "roles/apigee.readOnlyAdmin"
member = "domain:financialpartners.com"
}
}
}π Domain restriction org policies can reject this at apply time even though
terraform planshows no conflict.
7 Β· Workload Identity Federation principal
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"gha-deployer" = {
role = "roles/apigee.deployer"
member = "principal://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/gha-pool/subject/repo:FinancialPartnerscasey/apigee-proxies:ref:refs/heads/main"
}
}
}π‘ The preferred CI/CD authentication pattern β no long-lived service account key material.
8 Β· Same role, multiple principals
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"viewer-alice" = { role = "roles/apigee.readOnlyAdmin", member = "user:alice@financialpartners.com" }
"viewer-bob" = { role = "roles/apigee.readOnlyAdmin", member = "user:bob@financialpartners.com" }
}
}π‘ One
google_apigee_environment_iam_memberper(role, member)pair β a role granted to five principals means five map entries, not one entry with a list.
9 Β· Removing a binding safely
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"viewer-alice" = { role = "roles/apigee.readOnlyAdmin", member = "user:alice@financialpartners.com" }
# "viewer-bob" removed β only this entry's grant is revoked on the next apply
}
}10 Β· Referencing binding outputs downstream
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"ci-deployer" = {
role = "roles/apigee.deployer"
member = "serviceAccount:apigee-ci@casey-prod-apigee.iam.gserviceaccount.com"
}
}
}
output "ci_deployer_binding_id" {
value = module.apigee_environment_iam_bindings.binding_ids["ci-deployer"]
}11 Β· Reusing the same bindings map across environments
locals {
standard_readonly_bindings = {
"sre-readonly" = { role = "roles/apigee.readOnlyAdmin", member = "group:sre@financialpartners.com" }
}
}
module "apigee_environment_iam_bindings_dev" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment_dev.id
bindings = local.standard_readonly_bindings
}
module "apigee_environment_iam_bindings_prod" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment_prod.id
bindings = local.standard_readonly_bindings
}12 Β· Custom role grant
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"custom-role-grant" = {
role = "organizations/123456789012/roles/customApigeeAuditor"
member = "group:api-audit@financialpartners.com"
}
}
}13 Β· 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 "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"bad-conditional-editor" = {
role = "roles/editor"
member = "user:jane@financialpartners.com"
condition = {
title = "temp"
expression = "request.time < timestamp(\"2027-01-01T00:00:00Z\")"
}
}
}
}14 Β· Staging environment scoped grant
module "apigee_environment_iam_bindings_staging" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment_staging.id
bindings = {
"qa-team-deployer" = {
role = "roles/apigee.deployer"
member = "group:qa@financialpartners.com"
}
}
}15 Β· ποΈ End-to-end composition
module "apigee_org" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-organization.git?ref=v1.0.0"
project_id = "casey-prod-apigee"
analytics_region = "us-central1"
disable_vpc_peering = true
}
module "apigee_environment" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment.git?ref=v1.0.0"
org_id = module.apigee_org.id
name = "prod"
}
module "apigee_environment_iam_bindings" {
source = "git::https://github.com/microsoftexpert/terraform-google-apigee-environment-iam-bindings.git?ref=v1.0.0"
org_id = module.apigee_org.id
env_id = module.apigee_environment.id
bindings = {
"dev-team-env-admin" = {
role = "roles/apigee.environmentAdmin"
member = "group:api-platform-dev@financialpartners.com"
}
"ci-deployer" = {
role = "roles/apigee.deployer"
member = "serviceAccount:apigee-ci@casey-prod-apigee.iam.gserviceaccount.com"
}
}
}| Name | Type | Default | Required | Notes |
|---|---|---|---|---|
org_id |
string |
β | Yes | Consumes the organization's .id output |
env_id |
string |
β | Yes | Consumes the environment's .id output |
bindings |
map(object({...})) |
{} |
No | Empty map grants nothing (secure default) |
Full object schemas
variable "org_id" {
type = string
}
variable "env_id" {
type = string
}
variable "bindings" {
type = map(object({
role = string
member = string
condition = optional(object({
expression = string
title = string
description = optional(string)
}))
}))
default = {}
}No labels or timeouts variable exists on this module β the resource's schema exposes neither.
| Output | Description |
|---|---|
binding_ids |
Map (keyed identically to var.bindings) of each binding's composite id |
binding_etags |
Map (keyed identically to var.bindings) of each binding's etag |
No self_link output β confirmed absent from this resource.
org_id/env_idare both required together β an environment IAM binding needs both parent contexts explicitly, unlike most Apigee resources which take only one_id-suffixed reference.- IAM propagation delay: changes can take up to ~60 seconds to become effective.
conditionis part of a binding's identity β changing it out-of-band destroys/recreates.- No
labels/timeoutsβ confirmed absent from this resource's schema.
| Concern | Secure default | Opt-out (explicit) |
|---|---|---|
| Authoritative IAM resources | This module only ever creates google_apigee_environment_iam_member (additive) |
N/A β _iam_binding/_iam_policy intentionally excluded |
| Default grants | var.bindings defaults to {} |
Caller supplies explicit map entries |
| Broad/public principals | Not defaulted or suggested anywhere | Caller supplies explicitly, aware of the risk |
terraform init -backend=false
terraform validate
terraform fmt -checkPin ?ref=v1.0.0 β never a branch. This library is plan-only; a human applies from CI with valid
credentials.
validate/fmt -check confirm structural and syntactic correctness only. They cannot catch
GCP-side rejections β e.g. an IAM Condition on a Basic Role, or a domain restriction org policy
blocking a member value, surface only at apply time against a real project.
binding_ids = {
"dev-team-env-admin" = "organizations/casey-prod-apigee/environments/prod roles/apigee.environmentAdmin group:api-platform-dev@financialpartners.com"
}
binding_etags = {
"dev-team-env-admin" = "BwYzX1abcde="
}
| Symptom | Cause | Fix |
|---|---|---|
| A resource in the same apply fails with transient permission-denied | IAM propagation delay | Re-apply, or add a wait step |
An unrelated binding is destroyed/recreated after editing condition |
condition is part of the binding's identity |
Expected behavior |
| Apply rejected with an IAM Condition / Basic Role error | GCP disallows condition on Basic Roles |
Use a predefined non-Basic or custom role instead |
google_apigee_environment_iam_memberresource docsterraform-google-project-iam-bindingsβ the pattern this module mirrors- This module's
SCOPE.md