LAB48_GCP_Terraform Engineering: Advanced Reusable GCP Modules

Terraform Engineering: Advanced Reusable GCP Modules

☁ GCP 🔥 Advanced Modules Cloud Storage DRY

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.

Your Objectives
  • 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"