Creates and manages a single Google Cloud Resource Manager folder (
google_folder) β the console-recognizable "folder" that groups projects and other folders under an organization for shared IAM/org-policy inheritance. Targetshashicorp/google ~> 7.0, Terraform>= 1.12.0.
- π Creates one
google_folderβ the Resource Manager primitive the GCP console shows as "Folder," used to group projects and/or other folders under an organization for shared IAM policy and org-policy inheritance. Terraform's resource name matches the console name closely here, unlike many other GCP resources (per this suite's "resource names rarely match the console name 1:1" convention) β worth stating explicitly rather than assumed. - π Preserves the provider's own secure default:
deletion_protectiondefaults totrue, blocking accidental destroys at the API level, without this module having to invent that default itself. - π·οΈ Accepts Resource Manager
tagsat creation time (a different primitive from GCP labels β this resource has nolabelsargument at all) for policy-driven governance, sourced fromtf-mod-gcp-resource-tagsin a typical composition. - π§± Standalone by design β a folder has no natural
for_each-managed child collection; the projects/folders it contains are independently lifecycled resources owned by their own modules/compositions.
π‘ Why it matters: a folder is a hierarchy and policy-inheritance boundary, not just an organizational label. Every IAM binding and org policy applied at this level cascades to every project and folder beneath it β getting
parent,display_name, and the deletion guards right here has organization-wide blast radius, not just a single-resource one.
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!
graph LR
RT["tf-mod-gcp-resource-tags"]:::keystoneSibling
F["tf-mod-gcp-folder"]:::thisModule
EC["tf-mod-gcp-essential-contacts"]:::sibling
TB["tf-mod-gcp-tag-bindings"]:::sibling
IAMB["tf-mod-gcp-project-iam-bindings"]:::sibling
RT -- "tag_value_ids + tag key id consumed as tags input" --> F
F -- "name consumed as parent input" --> EC
F -- "name consumed as parent target for a tag binding" --> TB
F -- "id/name referenced by a project created under this folder (caller composition)" --> IAMB
classDef thisModule fill:#4285F4,color:#ffffff,stroke:#174EA6,stroke-width:1px;
classDef keystoneSibling fill:#174EA6,color:#ffffff,stroke:#174EA6,stroke-width:1px;
classDef sibling fill:#E8EAED,color:#202124,stroke:#9AA0A6,stroke-width:1px;
tf-mod-gcp-resource-tags (an optional upstream) supplies the tags input at folder-creation time
only β it is not a hard dependency; an empty tags = {} call is the safe default. Downstream,
tf-mod-gcp-essential-contacts and tf-mod-gcp-tag-bindings are plausible direct consumers of this
module's name output; tf-mod-gcp-project-iam-bindings (and any project-creation tooling in the
caller's own composition) is a plausible consumer of id/name/folder_id for a project created
under this folder. None of these downstream modules are hard dependencies of this module β they are
documented as the realistic next step in a typical composition.
graph LR
subgraph Inputs
A["var.display_name"]
B["var.parent"]
C["var.deletion_protection"]
D["var.deletion_policy"]
E["var.tags"]
F["var.timeouts"]
end
R["google_folder.this"]:::thisModule
A --> R
B --> R
C --> R
D --> R
E --> R
F --> R
R --> O1["output: id"]
R --> O2["output: name"]
R --> O3["output: folder_id"]
classDef thisModule fill:#4285F4,color:#ffffff,stroke:#174EA6,stroke-width:1px;
Resource inventory: exactly one resource, google_folder.this. No nested/repeating
configuration blocks in the live schema β every input is a plain scalar β and no child resources,
so this module stays a single keystone with no composite shape.
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/google provider |
~> 7.0 |
| Provider block | None β the caller configures google (project, region/zone, auth) |
Schema notes that bite (verified against hashicorp/google v7.39.0, providerDocID 12683790,
and the resolved schema-JSON fallback):
display_namemust start and end with a letter or digit, contain only letters, digits, spaces, hyphens, and underscores, and be β€ 30 characters β enforced here viavalidation {}. It must also be unique amongst sibling folders under the sameparentβ an apply-time-only constraint this plan-only library cannot catch atvalidate/plan.parentmust befolders/{folder_id}ororganizations/{org_id}β folders cannot be parented directly under a project, an intrinsic GCP hierarchy rule, not a restriction this module invents.deletion_protectiondefaults totrueat the provider level β this module preserves that default rather than overriding it.deletion_protectionanddeletion_policyare two distinct guards β the former is API-level, the latter is a Terraform-side instruction (DELETE/ABANDON/PREVENT). See Architecture Notes.tagsis immutable and create-time-only β "modifying this field after creation will trigger recreation" per the live docs. The same provider doc page's Example Usage block shows a differently-shaped example ({"1234567/env":"staging"}) that does not match its own Argument Reference format (tagKeys/{id}=tagValues/{id}) β this module follows the Argument Reference, and the discrepancy is flagged rather than silently resolved. See variables.tf.timeoutsexists, contrary to what the resource's hand-written prose docs suggest. The live docs page has no "Timeouts" section forgoogle_folder, but the resolved schema-JSON (terraform providers schema -jsonagainst the actually-installed~> 7.0, 7.39.0) confirms a real, resource-specifictimeoutsblock withcreate/read/update/deleteall present and optional. This module models all four rather than omitting the variable β see the reconciliation note in variables.tf.- This resource has no
self_linkand nolabelsargument β both common on most GCP resources but absent here. Folders usetagsinstead oflabels. See Architecture Notes.
roles/resourcemanager.folderCreatorβ required for creation, confirmed verbatim from the live provider docs: "The service account used to run Terraform when creating agoogle_folderresource must haveroles/resourcemanager.folderCreator."roles/resourcemanager.folderAdminβ required for ongoing update/delete/move lifecycle management (renamingdisplay_name, changingparent, deleting the folder).folderCreatoralone is narrower and is not documented as sufficient for these follow-on operations β grantfolderAdmin(or hold both) for anyapplybeyond the initial creation.
cloudresourcemanager.googleapis.comAPI enabled for the applying credentials.- The parent organization or parent folder named in
var.parentmust already exist. - If
var.tagsis populated, the referenced tag key/value ids (fromtf-mod-gcp-resource-tags) must already exist at apply time βtagsis create-time-only, so there is no later opportunity to attach a not-yet-existing tag without replacing the folder.
tf-mod-gcp-folder/
βββ providers.tf # required_providers (hashicorp/google ~> 7.0) + required_version β no provider {} block
βββ variables.tf # display_name, parent, deletion_protection, deletion_policy, tags, timeouts
βββ main.tf # google_folder.this β the sole resource
βββ outputs.tf # id, name, folder_id
βββ README.md # this file
βββ SCOPE.md # lightweight cross-module contract
βββ examples/ # runnable example matching the Quick Start below
module "department_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Department 1"
parent = "organizations/123456789012"
}The caller's root module configures the google provider (project, region/zone, and
authentication via ADC, Workload Identity Federation, or a service account key supplied
out-of-band) β this module accepts none of those as variables.
Consumes
| Input | Type | Source module |
|---|---|---|
tags["tagKeys/{id}"] value (tagValues/{id}) |
string |
tf-mod-gcp-resource-tags (tag_value_ids output for the value; that module's id output supplies the tagKeys/{id} key) β optional |
parent (folders/{folder_id}) |
string |
Another tf-mod-gcp-folder instance's name output, when nesting under a folder rather than an organization |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
folders/{folder_id} (inferred β see outputs.tf) |
Any module/composition needing the Terraform-internal reference |
name |
folders/{folder_id} |
Another folder's parent; tf-mod-gcp-essential-contacts/tf-mod-gcp-tag-bindings parent; a project-creation composition's parent/folder_id argument |
folder_id |
Bare numeric folder id | Any consumer specifically wanting the bare numeric id |
This resource has no self_link β none is emitted.
1 Β· Minimal call β top-level folder under an organization
module "department1" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Department 1"
parent = "organizations/123456789012"
}π‘ The empty-beyond-required call is the safe, complete path:
deletion_protectiondefaults totrue,deletion_policydefaults toDELETE, and no tags are attached.
2 Β· Nested folder under another folder
module "team_abc" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Team ABC"
parent = module.department1.name
}βΉοΈ
parentaccepts eitherorganizations/{org_id}orfolders/{folder_id}β here it consumes a siblingtf-mod-gcp-folderinstance'snameoutput directly, no string-building required.
3 Β· Explicitly disabling deletion_protection
module "sandbox_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Sandbox"
parent = "organizations/123456789012"
deletion_protection = false
}
β οΈ deletion_protection = falseallows this folder to be destroyed by a futureterraform destroy/apply. Leave this at its default (true) for any folder containing production workloads. Recommend a human confirms this opt-out during code review, per our human-judgment standard for irreversible actions.
4 Β· Non-default deletion_policy β handing off management via ABANDON
module "migrating_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Legacy Reporting"
parent = "organizations/123456789012"
deletion_policy = "ABANDON"
}π
deletion_policy = "ABANDON"removes this folder from this Terraform state without ever calling the delete API β the folder itself is untouched in GCP. Use this to hand a folder off to a different Terraform state/module without needing to first flipdeletion_protectiontofalse(which would additionally permit an actual API-level delete).deletion_protectionremainstruethroughout β it never gates this path since no delete call is made.
5 Β· Attaching a tag at creation time (from tf-mod-gcp-resource-tags)
module "env_tags" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-resource-tags.git?ref=v1.0.0"
tag_key_short_name = "environment"
tag_key_parent = "organizations/123456789012"
tag_values = {
"production" = {}
}
}
module "production_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Production"
parent = "organizations/123456789012"
tags = {
(module.env_tags.id) = module.env_tags.tag_value_ids["production"]
}
}
β οΈ tagsis create-time-only. If you later need to change which tag value is attached, you must either accept a full folder replacement or attach the new value withtf-mod-gcp-tag-bindingsinstead and leave this field as originally set.
6 Β· Custom timeouts
module "slow_org_policy_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Regulated Workloads"
parent = "organizations/123456789012"
timeouts = {
create = "30m"
update = "15m"
}
}βΉοΈ
readanddeleteare left unset here and fall back to the provider's own internal default β this module does not fabricate a specific default duration for a resource whose prose docs don't state one.
7 Β· display_name at the minimum length boundary
module "single_char_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "X" # 1 character β trivially starts and ends with a letter
parent = "organizations/123456789012"
}8 Β· display_name at the maximum length boundary
module "long_named_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Regulatory Compliance 2026" # 26 characters β within the 30-char limit
parent = "organizations/123456789012"
}
β οΈ Adisplay_namelonger than 30 characters, or one that doesn't start/end with a letter or digit, fails thevalidation {}block at plan time, before any API call is made.
9 Β· Moving an existing folder to a new parent
module "reorganized_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Team ABC"
# Was: parent = "organizations/123456789012"
parent = "folders/987654321098" # moved under Department 1's folder
}
β οΈ Changingparenton an existing folder is not force-new β Terraform moves the folder in place. But this re-evaluates all inherited IAM bindings and org policies for every project and folder descended from it. A one-lineparentdiff interraform plancan carry organization-wide access consequences invisible to the plan output itself. Treat anyparentchange on an existing folder as requiring the same review rigor as a direct IAM policy change, not a routine rename.
10 Β· Multiple folders via root-module for_each
locals {
department_folders = {
"finance" = "Finance"
"marketing" = "Marketing"
"it" = "Information Technology"
}
}
module "department_folders" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
for_each = local.department_folders
display_name = each.value
parent = "organizations/123456789012"
}βΉοΈ This module itself has no internal
for_each(it is standalone, single-keystone) β a caller needing several folders wraps the module call in afor_eachat the root/composition level, as shown here.
11 Β· Folder-scoped Essential Contacts
module "production_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Production"
parent = "organizations/123456789012"
}
module "production_folder_contacts" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-essential-contacts.git?ref=v1.0.0"
contacts = {
"security-team" = {
email = "security@financialpartners.com"
notification_category_subscriptions = ["SECURITY"]
language_tag = "en"
}
}
parent = module.production_folder.name
}12 Β· Post-creation tag attachment via tf-mod-gcp-tag-bindings
module "production_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Production"
parent = "organizations/123456789012"
}
module "cost_center_binding" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-tag-bindings.git?ref=v1.0.0"
bindings = {
"production-folder-cost-center" = {
parent = "//cloudresourcemanager.googleapis.com/${module.production_folder.name}"
tag_value = "tagValues/234567890123"
}
}
}π‘ Unlike example 5's create-time
tagsargument, this attaches (or later changes) a tag value on an existing folder without triggering any replacement β the house pattern for any tag attached after the folder already exists.
13 Β· Explicit deletion_policy = PREVENT (belt-and-suspenders)
module "regulated_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Regulated Workloads"
parent = "organizations/123456789012"
deletion_policy = "PREVENT"
}βΉοΈ Redundant with
deletion_protection = true(the default already in effect) for the primary destroy path, but some callers prefer an explicit second Terraform-side guard for a folder they never want moved to stateABANDON/DELETEeven by a futuremain.tfrefactor. See variables.tf for why this module does not default toPREVENTthe waytf-mod-gcp-kms-keyringdoes for its crypto keys.
14 Β· ποΈ End-to-end composition
module "env_tags" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-resource-tags.git?ref=v1.0.0"
tag_key_short_name = "environment"
tag_key_parent = "organizations/123456789012"
tag_values = {
"production" = {}
}
}
module "production_folder" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-folder.git?ref=v1.0.0"
display_name = "Production"
parent = "organizations/123456789012"
tags = {
(module.env_tags.id) = module.env_tags.tag_value_ids["production"]
}
depends_on = [module.env_tags]
}
module "production_folder_contacts" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-essential-contacts.git?ref=v1.0.0"
contacts = {
"billing-team" = {
email = "billing@financialpartners.com"
notification_category_subscriptions = ["BILLING"]
language_tag = "en"
}
}
parent = module.production_folder.name
}
module "production_folder_cost_center_binding" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-tag-bindings.git?ref=v1.0.0"
bindings = {
"production-folder-cost-center" = {
parent = "//cloudresourcemanager.googleapis.com/${module.production_folder.name}"
tag_value = "tagValues/234567890123"
}
}
depends_on = [module.production_folder]
}π‘ This wires
tf-mod-gcp-resource-tagsβtf-mod-gcp-folderβ (tf-mod-gcp-essential-contactsandtf-mod-gcp-tag-bindings) in dependency order: the tag taxonomy is defined first, consumed at folder-creation time via the create-time-onlytagsargument, then the resulting folder'snameoutput feeds both a folder-scoped Essential Contacts entry and a second, independently-managed tag binding (for a tag value that was not known at folder-creation time, attached without replacement viatf-mod-gcp-tag-bindings).
β οΈ Because IAM/tag-key propagation can lag briefly after creation (the same class of delay documented for IAM elsewhere in this library), atag_valuereference in the sameapplythat also creates the tag value can occasionally need a secondapplyto resolve cleanly on first-time bring-up.
| Variable | Type | Required | Default | Notes |
|---|---|---|---|---|
display_name |
string |
Yes | β | β€ 30 chars, closed format; unique among siblings (apply-time only) |
parent |
string |
Yes | β | folders/{id} or organizations/{id}; not force-new, but see blast-radius note |
deletion_protection |
bool |
No | true |
Provider-native secure default, preserved |
deletion_policy |
string |
No | "DELETE" |
DELETE | ABANDON | PREVENT β distinct from deletion_protection |
tags |
map(string) |
No | {} |
tagKeys/{id} = tagValues/{id}; immutable/create-time-only |
timeouts |
object({ create, read, update, delete = optional(string) }) |
No | null |
All four confirmed present in the resolved schema |
Full variable schemas
variable "display_name" {
type = string
# Must start/end with a letter or digit; letters, digits, spaces, hyphens,
# underscores only; <= 30 chars β enforced via validation {}.
}
variable "parent" {
type = string
# ^(folders|organizations)/[0-9]+$ β enforced via validation {}.
}
variable "deletion_protection" {
type = bool
default = true
}
variable "deletion_policy" {
type = string
default = "DELETE"
# One of DELETE, ABANDON, PREVENT β enforced via validation {}.
}
variable "tags" {
type = map(string)
default = {}
# Keys ^tagKeys/[0-9]+$, values ^tagValues/[0-9]+$ β enforced via validation {}.
}
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
})
default = null
}No labels variable exists on this module β see Architecture Notes for why.
| Output | Description |
|---|---|
id |
folders/{folder_id} (inferred β see comment in outputs.tf) |
name |
folders/{folder_id} |
folder_id |
Bare numeric folder id |
βΉοΈ No
self_linkrow: this resource does not export one.idandnameare the only Terraform-internal reference forms available.
None of these outputs are secret-bearing; no sensitive = true is applied to any of them.
deletion_protectiondefault-true is preserved from the provider, not invented here. The live docs state its own default istrue; this module'sdefault = truesimply matches that, giving this suite's secure-by-default posture "for free" on this argument.deletion_protectionanddeletion_policyare two distinct guards.deletion_protectionis enforced by the GCP API itself and blocks the destroy call outright.deletion_policyis a Terraform-side instruction evaluated before any API call:PREVENTfails the Terraform operation directly (redundant withdeletion_protection = truefor the same path);ABANDONremoves the resource from state without ever calling the delete API (the one thingdeletion_protectioncannot do);DELETE(this module's default, matching the provider's own) allows the normal flow, still gated bydeletion_protection. This module deliberately does not defaultdeletion_policytoPREVENTthe waytf-mod-gcp-kms-keyring's crypto key entries do β that precedent exists because crypto key destruction has no other native guard; a folder already hasdeletion_protectionfor that.tagsis immutable and create-time-only. Modifying it after creation forces full folder replacement β not an in-place update. Usetf-mod-gcp-tag-bindingsto attach or change tags on an existing folder without replacing it.timeoutsmodels create/read/update/delete, reconciled against a docs/schema discrepancy. The live hand-written docs page has no "Timeouts" section forgoogle_folder; the resolved schema-JSON shows all four fields present and optional. This module treats the resolved schema as ground truth (per this suite's plan-only posture and this library's standing rule) rather than omitting the variable based on the prose gap alone.- Folder deletion is soft-delete with an undelete window. Per the live docs, "it may take a while for the attached tag bindings to be deleted after the folder is scheduled to be deleted" β a destroyed folder is not immediately, irreversibly gone, but downstream tag-binding cleanup can lag visibly.
- Moving a folder to a new parent is a real operational blast-radius event, not a routine
rename.
parentis not force-new β Terraform updates the folder in place β but the move re-evaluates all inherited IAM bindings and org policies for every project and folder descended from it. A one-lineparentdiff interraform plangives no visibility into that downstream impact. Treat anyparentchange on an existing, populated folder with the same review rigor as a direct IAM policy change. - No
labelsvariable β deliberate schema-driven omission.google_folderhas nolabelsargument in the live schema at all. Folders usetags(Resource Manager tags) instead β a different GCP primitive with IAM/org-policy implications, not informational metadata. Per this suite's universal-tail escape clause, this module documents the omission rather than inventing a variable with no effect on the underlying resource. - No
self_linkoutput β same reasoning. The resource simply does not export one. display_nameuniqueness is an apply-time-only constraint. A duplicate name among sibling folders passesvalidate/plancleanly and fails only atapply, invisible to this library's plan-only proof gate.
| Concern | Secure default | Opt-out (explicit) |
|---|---|---|
| Folder deletion | deletion_protection = true β preserved from the provider's own default, not invented by this module |
Caller sets deletion_protection = false explicitly |
| Terraform-side destroy guard | deletion_policy = "DELETE" (the provider's native default) β not upgraded to PREVENT, since deletion_protection already gates the primary path |
Caller sets deletion_policy = "PREVENT" for an explicit belt-and-suspenders guard, or "ABANDON" to hand off management without touching the live resource |
| Tag attachment at creation | tags = {} β the safe, inert default; no tag is attached unless the caller supplies one |
Caller populates tags from tf-mod-gcp-resource-tags output, understanding the create-time-only/force-new implication |
| IAM grants for this module's own use | Not managed by this module at all β grant folderCreator/folderAdmin to the applying principal outside this module, never an authoritative _iam_policy |
N/A β authoritative IAM policy is intentionally excluded from this library |
cd tf-mod-gcp-folder
terraform init -backend=false
terraform validate
terraform fmt -checkPin the module source to ?ref=v1.0.0 β never a branch. This library is plan-only from an
authoring session; a human applies from CI with valid Workload Identity Federation or ADC
credentials.
terraform validate confirms internal type and reference consistency (e.g. display_name's and
parent's validation {} blocks, correct resource/output wiring). terraform fmt -check confirms
canonical formatting. Neither can catch GCP API-level rejections β display_name uniqueness among
siblings, quota limits, or org-policy constraints all surface only at apply. If the applying
principal's roles/resourcemanager.folderCreator grant was itself just applied in the same
composition, the same class of IAM-propagation delay documented elsewhere in this library (up to
roughly a minute) can cause a transient permission-denied error on the very first apply even
though the Terraform graph ordering was correct. Only a real terraform plan/apply against a
live organization, with valid credentials, exercises those paths β that step belongs to the
consuming CI pipeline, not this authoring session.
$ terraform output
folder_id = "987654321098"
id = "folders/987654321098"
name = "folders/987654321098"
| Symptom | Cause | Fix |
|---|---|---|
apply fails with a display-name conflict error, even though plan showed no issue |
display_name must be unique among sibling folders under the same parent β an apply-time-only API constraint |
Choose a different display_name, or confirm no sibling folder already uses it |
apply fails or is refused with a deletion/destroy error |
deletion_protection is true (the default) |
Set deletion_protection = false explicitly, with team sign-off, before the destroy |
Changing a value in tags triggers a full folder replacement instead of an in-place update |
tags is immutable/create-time-only per the live schema |
Do not rely on tags for post-creation changes; use tf-mod-gcp-tag-bindings to attach/change tags on an existing folder |
A destroyed folder's tag bindings still appear briefly after the folder shows DELETE_REQUESTED |
Folder deletion is soft-delete with an undelete window; attached tag bindings can take a while to clear | Expected behavior β allow time before assuming binding cleanup has failed |
A parent change plans cleanly but downstream teams report unexpected IAM/org-policy behavior afterward |
Moving a folder re-evaluates inherited IAM/org policy for every descendant project and folder β invisible to terraform plan's single-line diff |
Review descendant IAM/org-policy inheritance before applying a parent change on a populated folder, not just the Terraform diff |
First apply right after granting roles/resourcemanager.folderCreator fails with a permission-denied error |
IAM grant propagation delay (up to ~60 seconds), same class of delay documented elsewhere in this library | Re-run apply; avoid tightly coupling a fresh IAM grant and folder creation in a single non-retrying automated step |
plan fails with a parent validation error |
Value doesn't match folders/{id} or organizations/{id} (e.g. a bare numeric id, or an attempt to parent under projects/...) |
Supply the full folders/{folder_id} or organizations/{org_id} form; remember folders cannot be parented under a project at all |
google_folderprovider resource referencetf-mod-gcp-resource-tags(optional upstream β suppliestagsinput)tf-mod-gcp-essential-contacts(downstream consumer ofname)tf-mod-gcp-tag-bindings(downstream consumer ofnamefor post-creation tag attachment)tf-mod-gcp-project-iam-bindings(a project created under this folder's IAM, in the caller's own composition)- This module's
SCOPE.md