Terraform module that provisions one or more Google Cloud projects from a single
map(object) input. For each entry it:
- creates the project with a generated project ID (
<name>-<random suffix>), - enables the requested APIs,
- applies your labels, always merging
managed = "terraform".
Prior versions keyed the google_project_service resources with a positional index
("<project>-<api>-<index>"). Because the index was derived from the flattened
project/API list, adding or removing any project or API shifted the index of every
subsequent entry, causing Terraform to destroy and recreate unrelated
google_project_service resources — and, with disable_on_destroy = true, to
disable and re-enable those APIs on live projects.
v2.0.0 drops the index: keys are now "<project>-<api>", which is stable and unique.
Duplicate entries in an apis list are now deduplicated.
The key change means existing state entries no longer match the new resource addresses. Do not apply the upgrade blindly — an unmigrated plan will propose destroying and recreating every enabled API. Instead, move the state entries first:
# After bumping the module version, before plan/apply.
# Strips the trailing "-<index>" from every google_project_service key.
terraform state list | grep 'google_project_service\.main\[' | while read -r addr; do
new=$(echo "$addr" | sed -E 's/-[0-9]+"\]$/"]/')
terraform state mv "$addr" "$new"
doneA subsequent terraform plan should show no changes for google_project_service
resources. (moved blocks cannot express this migration because the addresses are
dynamic for_each keys.)
module "project" {
source = "FFerrinho/project/google"
version = "~> 2.0"
projects = {
"my-project" = {
org_id = "1234567890"
billing_account = "ABCD-EFGH-IJKL-MNOP"
deletion_policy = "DELETE"
apis = [
"compute.googleapis.com",
"container.googleapis.com",
]
labels = {
environment = "development"
}
}
}
}See examples/ for a minimal (simple) and a multi-project (full) configuration.
- Project ID generation: the project ID is
<map key>-<random decimal suffix>. Onerandom_id(2 bytes) is shared by all projects in the map, so all generated IDs carry the same suffix. Keep map keys ≤ 24 characters so the resulting ID stays within GCP's 30-character limit. - Parenting: set
org_idorfolder_idper project to choose where it lives. - API management: enabled services use
disable_dependent_services = trueanddisable_on_destroy = true— removing an API from the list (or destroying the project resource) actively disables that API. Treat API removals as deliberate, reviewed changes. - Labels:
managed = "terraform"is always merged into the labels you provide.
The module ships a native terraform test
suite in tests/ that runs entirely against mocked providers — no GCP
credentials or real infrastructure required:
terraform init -backend=false
terraform testCI runs fmt, validate, the test suite, and example validation on every push and
pull request. See CONTRIBUTING.md for the full development
workflow and CHANGELOG.md for release history.
| Name | Version |
|---|---|
| terraform | >= 1.5 |
| ~> 6 | |
| random | ~> 3 |
| Name | Version |
|---|---|
| 6.50.0 | |
| random | 3.9.0 |
No modules.
| Name | Type |
|---|---|
| google_project.main | resource |
| google_project_service.main | resource |
| random_id.random | resource |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| projects | A map of projects to create, keyed by project name. Per project: org_id or folder_id (parent), billing_account, deletion_policy (PREVENT, ABANDON or DELETE), labels, auto_create_network, and apis (list of service endpoints to enable). |
map(object({ |
n/a | yes |
| Name | Description |
|---|---|
| project_info | Map of created projects, keyed by project name, with the generated project ID, display name and project number. |