Terraform Engineering: Advanced Reusable GCP Modules
Scenario
Your organization creates dozens of Cloud Storage buckets a week. You want to enforce a standard where all buckets must have uniform bucket level access enabled, lifecycle policies applying after 30 days, and specific labels attached. Instead of trusting developers to configure the raw `google_storage_bucket` resource correctly every time, you will create a custom Terraform Module to abstract the complexity and enforce compliance.
- Construct a local directory ./modules/secure_bucket/.
- Inside the module, write a main.tf containing a `google_storage_bucket` with forced configurations (e.g. `uniform_bucket_level_access = true`), along with `variables.tf` and `outputs.tf`.
- In your root main.tf, invoke the module using the module { source = "./modules/secure_bucket" } block.
- Instantiate the module twice to create two differently named, but identically compliant buckets.
Additional Context
Module Structure: A module is simply a folder containing `.tf` files. The input variables in the module's `variables.tf` define the "API" or arguments a user must pass when calling the module. The `outputs.tf` define what data the module passes back to the root namespace.
Sourcing Modules: You can source modules from local directories (`./modules/x`), from GitHub repositories (`github.com/org/repo`), or from the public Terraform Registry. GCP maintains an excellent suite of official modules at `gcp/terraform-google-modules`.
modules/secure_bucket/variables.tf
variable "bucket_name" {
type = string
description = "The globally unique name of the bucket"
}
variable "location" {
type = string
default = "US"
description = "GCS location (e.g. US, EU, us-central1)"
}
variable "team_label" {
type = string
description = "Which team owns this bucket (for billing)"
}modules/secure_bucket/main.tf
# The underlying resource logic hidden from the user
resource "google_storage_bucket" "this" {
name = var.bucket_name
location = var.location
# Forcing Corporate Compliance!
uniform_bucket_level_access = true
public_access_prevention = "enforced"
labels = {
managed_by = "terraform"
team = var.team_label
}
# Enforce 30 day deletion policy
lifecycle_rule {
condition {
age = 30
}
action {
type = "Delete"
}
}
}modules/secure_bucket/outputs.tf
output "bucket_url" {
value = google_storage_bucket.this.url
}Root main.tf
provider "google" {
project = var.project_id
}
# Generate a random suffix for global uniqueness
resource "random_id" "suf" { byte_length = 4 }
# Invocation 1 (Analytics Team)
module "analytics_bucket" {
source = "./modules/secure_bucket"
bucket_name = "data-lake-prod-${random_id.suf.hex}"
location = "US"
team_label = "data-science"
}
# Invocation 2 (Web Team)
module "assets_bucket" {
source = "./modules/secure_bucket"
bucket_name = "frontend-assets-${random_id.suf.hex}"
location = "EU"
team_label = "frontend-devs"
}
output "lake_url" {
# Referencing a module's output correctly
value = module.analytics_bucket.bucket_url
}Workflow Commands
# init is crucial here because it downloads and wires up the local module
terraform init
terraform apply -var="project_id=my-gcp-project-123"
terraform destroy -var="project_id=my-gcp-project-123"
