LAB49_GCP_Advanced HCL: Dynamic Blocks and Lifecycle Preconditions

Advanced HCL: Dynamic Blocks and Lifecycle Preconditions

☁ GCP 🔥 Advanced HCL Dynamic Blocks Preconditions Firewall

Scenario

You are writing a complex `.tf` file to rapidly spin up testing Compute Instances with specific open firewall ports. Instead of hardcoding 10 different `allow {}` blocks inside a `google_compute_firewall`, you want the user to pass a list of ports via a variable, and Terraform will generate the configurations programmatically using Dynamic Blocks. Additionally, you want to use a Lifecycle Precondition to explicitly halt execution if the user attempts to choose an invalid machine type.

Your Objectives
  • Declare a list variable open_ports defaulting to `["80", "443", "8080"]`.
  • In a google_compute_firewall, use a dynamic "allow" { for_each = var.open_ports ... } structure to generate the nested blocks automatically.
  • Deploy a google_compute_instance. Inside its lifecycle block, write a precondition evaluating the machine_type against a regex or custom logic to ensure it is part of the `e2` family.

Additional Context

Dynamic Blocks vs for_each: The meta-argument `for_each` creates entirely separate resources (e.g. 3 distinct `google_compute_firewall` resources). `dynamic` blocks create repeated nested configuration blocks inside a single resource.

Preconditions vs Variable Validation: Variable validation{} blocks can only look at the variable itself. A lifecycle { precondition {} } block can evaluate properties of other resources, local variables, or expressions available only at execution time.

variables.tf

variable "project_id" { type = string }

variable "open_ports" {
  type        = list(string)
  default     = ["22", "80", "443", "3000"]
  description = "A list of TCP ports to punch through the firewall"
}

variable "vm_size" {
  type        = string
  default     = "e2-micro"
}

main.tf

provider "google" {
  project = var.project_id
}

# 1. Advanced HCL: Dynamic Nested Blocks
resource "google_compute_firewall" "dynamic_rules" {
  name    = "web-app-dynamic-fw"
  network = "default"

  # Iterates over the list, creating one of these 'allow' blocks for each port
  dynamic "allow" {
    for_each = var.open_ports
    
    # The iterator defaults to the name of the dynamic block ("allow")
    content {
      protocol = "tcp"
      # We reference the current item in the iteration via the iterator's .value property
      ports    = [allow.value] 
    }
  }

  source_ranges = ["0.0.0.0/0"]
}

# 2. Advanced HCL: Lifecycle Preconditions
resource "google_compute_instance" "app_node" {
  name         = "testing-node"
  machine_type = var.vm_size
  zone         = "us-central1-a"

  boot_disk {
    initialize_params { image = "debian-cloud/debian-11" }
  }
  network_interface { network = "default" }

  lifecycle {
    # Preconditions evaluate during the `terraform plan` phase
    precondition {
      # Custom logic: ensure the machine string starts with "e2-"
      condition     = substr(self.machine_type, 0, 3) == "e2-"
      error_message = "Only cheap e2 family instances are allowed in this testing lab!"
    }
  }
}

Workflow Commands

terraform init
terraform apply -var="project_id=my-gcp-project-123"

# Look at the execution plan or state. Notice how the single firewall resource 
# generated 4 distinct `allow {}` blocks internally.

# Test the Precondition Failure:
terraform apply -var="project_id=my-gcp-project-123" -var="vm_size=n1-standard-1"
# Output -> Error: Resource precondition failed: Only cheap e2 family instances...

terraform destroy -var="project_id=my-gcp-project-123"