Skip to content

microsoftexpert/tf-mod-gcp-folder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

☁️ Google Cloud Folder Terraform Module

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. Targets hashicorp/google ~> 7.0, Terraform >= 1.12.0.

Terraform Google Provider Module Version Module Type Resources Posture


🧩 Overview

  • πŸ“ 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_protection defaults to true, blocking accidental destroys at the API level, without this module having to invent that default itself.
  • 🏷️ Accepts Resource Manager tags at creation time (a different primitive from GCP labels β€” this resource has no labels argument at all) for policy-driven governance, sourced from tf-mod-gcp-resource-tags in 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.


❀️ 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

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;
Loading

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.


🧬 What this builds

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;
Loading

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.


βœ… Provider / Versions

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_name must start and end with a letter or digit, contain only letters, digits, spaces, hyphens, and underscores, and be ≀ 30 characters β€” enforced here via validation {}. It must also be unique amongst sibling folders under the same parent β€” an apply-time-only constraint this plan-only library cannot catch at validate/plan.
  • parent must be folders/{folder_id} or organizations/{org_id} β€” folders cannot be parented directly under a project, an intrinsic GCP hierarchy rule, not a restriction this module invents.
  • deletion_protection defaults to true at the provider level β€” this module preserves that default rather than overriding it.
  • deletion_protection and deletion_policy are two distinct guards β€” the former is API-level, the latter is a Terraform-side instruction (DELETE/ABANDON/PREVENT). See Architecture Notes.
  • tags is 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.
  • timeouts exists, contrary to what the resource's hand-written prose docs suggest. The live docs page has no "Timeouts" section for google_folder, but the resolved schema-JSON (terraform providers schema -json against the actually-installed ~> 7.0, 7.39.0) confirms a real, resource-specific timeouts block with create/read/update/delete all 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_link and no labels argument β€” both common on most GCP resources but absent here. Folders use tags instead of labels. See Architecture Notes.

πŸ”‘ Required IAM Roles

  • roles/resourcemanager.folderCreator β€” required for creation, confirmed verbatim from the live provider docs: "The service account used to run Terraform when creating a google_folder resource must have roles/resourcemanager.folderCreator."
  • roles/resourcemanager.folderAdmin β€” required for ongoing update/delete/move lifecycle management (renaming display_name, changing parent, deleting the folder). folderCreator alone is narrower and is not documented as sufficient for these follow-on operations β€” grant folderAdmin (or hold both) for any apply beyond the initial creation.

☁️ GCP Prerequisites

  • cloudresourcemanager.googleapis.com API enabled for the applying credentials.
  • The parent organization or parent folder named in var.parent must already exist.
  • If var.tags is populated, the referenced tag key/value ids (from tf-mod-gcp-resource-tags) must already exist at apply time β€” tags is create-time-only, so there is no later opportunity to attach a not-yet-existing tag without replacing the folder.

πŸ“ Module Structure

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

βš™οΈ Quick Start

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.


πŸ”Œ Cross-Module Contract

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.


πŸ“š Example Library

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_protection defaults to true, deletion_policy defaults to DELETE, 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
}

ℹ️ parent accepts either organizations/{org_id} or folders/{folder_id} β€” here it consumes a sibling tf-mod-gcp-folder instance's name output 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 = false allows this folder to be destroyed by a future terraform 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 flip deletion_protection to false (which would additionally permit an actual API-level delete). deletion_protection remains true throughout β€” 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"]
  }
}

⚠️ tags is 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 with tf-mod-gcp-tag-bindings instead 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"
  }
}

ℹ️ read and delete are 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"
}

⚠️ A display_name longer than 30 characters, or one that doesn't start/end with a letter or digit, fails the validation {} 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
}

⚠️ Changing parent on 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-line parent diff in terraform plan can carry organization-wide access consequences invisible to the plan output itself. Treat any parent change 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 a for_each at 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 tags argument, 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 state ABANDON/DELETE even by a future main.tf refactor. See variables.tf for why this module does not default to PREVENT the way tf-mod-gcp-kms-keyring does 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-contacts and tf-mod-gcp-tag-bindings) in dependency order: the tag taxonomy is defined first, consumed at folder-creation time via the create-time-only tags argument, then the resulting folder's name output 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 via tf-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), a tag_value reference in the same apply that also creates the tag value can occasionally need a second apply to resolve cleanly on first-time bring-up.


πŸ“₯ Inputs

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.


🧾 Outputs

Output Description
id folders/{folder_id} (inferred β€” see comment in outputs.tf)
name folders/{folder_id}
folder_id Bare numeric folder id

ℹ️ No self_link row: this resource does not export one. id and name are the only Terraform-internal reference forms available.

None of these outputs are secret-bearing; no sensitive = true is applied to any of them.


🧠 Architecture Notes

  • deletion_protection default-true is preserved from the provider, not invented here. The live docs state its own default is true; this module's default = true simply matches that, giving this suite's secure-by-default posture "for free" on this argument.
  • deletion_protection and deletion_policy are two distinct guards. deletion_protection is enforced by the GCP API itself and blocks the destroy call outright. deletion_policy is a Terraform-side instruction evaluated before any API call: PREVENT fails the Terraform operation directly (redundant with deletion_protection = true for the same path); ABANDON removes the resource from state without ever calling the delete API (the one thing deletion_protection cannot do); DELETE (this module's default, matching the provider's own) allows the normal flow, still gated by deletion_protection. This module deliberately does not default deletion_policy to PREVENT the way tf-mod-gcp-kms-keyring's crypto key entries do β€” that precedent exists because crypto key destruction has no other native guard; a folder already has deletion_protection for that.
  • tags is immutable and create-time-only. Modifying it after creation forces full folder replacement β€” not an in-place update. Use tf-mod-gcp-tag-bindings to attach or change tags on an existing folder without replacing it.
  • timeouts models create/read/update/delete, reconciled against a docs/schema discrepancy. The live hand-written docs page has no "Timeouts" section for google_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. parent is 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-line parent diff in terraform plan gives no visibility into that downstream impact. Treat any parent change on an existing, populated folder with the same review rigor as a direct IAM policy change.
  • No labels variable β€” deliberate schema-driven omission. google_folder has no labels argument in the live schema at all. Folders use tags (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_link output β€” same reasoning. The resource simply does not export one.
  • display_name uniqueness is an apply-time-only constraint. A duplicate name among sibling folders passes validate/plan cleanly and fails only at apply, invisible to this library's plan-only proof gate.

🧱 Design Principles

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

πŸš€ Runbook

cd tf-mod-gcp-folder
terraform init -backend=false
terraform validate
terraform fmt -check

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


πŸ§ͺ Testing

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.


πŸ’¬ Example Output

$ terraform output

folder_id = "987654321098"
id = "folders/987654321098"
name = "folders/987654321098"

πŸ” Troubleshooting

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

πŸ”— Related Docs

  • google_folder provider resource reference
  • tf-mod-gcp-resource-tags (optional upstream β€” supplies tags input)
  • tf-mod-gcp-essential-contacts (downstream consumer of name)
  • tf-mod-gcp-tag-bindings (downstream consumer of name for 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

Packages

 
 
 

Contributors

Languages