# Terraform Dynamic Blocks

## Introduction

In this part, we’ll explore some **advanced features of Terraform**. You’ll learn about variable types, constructs like `for_each`, and how to use **dynamic blocks** to make your Terraform code cleaner and less verbose.

---

## Introduction to Variables

Just like other programming languages, Terraform’s **HCL (HashiCorp Configuration Language)** supports variables and data types.

Modules and resource blocks often accept arbitrary user inputs. The best practice is to define variables in a `terraform.tfvars` file, which supplies values to Terraform during runtime.

Let’s start with a simple example.

---

## Example Usage

We’ll create a Terraform project that provisions an **S3 bucket**. This project will include the following files:

```bash
.
├── backend.tf
├── main.tf
├── terraform.tfvars
└── variables.tf
```

### `main.tf`

This file defines the S3 bucket and uses variables via the syntax `var.variable_name`.

```plaintext
provider "aws" {
  profile = var.profile
  region  = var.region
}

resource "aws_s3_bucket" "testbucket" {
  bucket = var.bucket_name
}
```

### `variables.tf`

Here we declare the variables used in the project:

```plaintext
variable "region" {
  type    = string
  default = "us-east-1"
}

variable "profile" {
  type    = string
  default = "profile-name-to-use"
}

variable "bucket_name" {
  type        = string
  description = "Name of S3 bucket to create"
}
```

You can also mark variables as `sensitive` or add validation rules for stricter control.

### `terraform.tfvars`

This file assigns values to the declared variables:

```plaintext
bucket_name = "test_bucket"
```

### Terraform Commands

Here’s a typical Terraform workflow:

```bash
terraform init       # Initialize backend and providers
terraform fmt        # Format code
terraform validate   # Validate for syntax errors
terraform plan       # Preview changes
terraform apply      # Apply changes
terraform destroy    # Destroy resources
```

---

## Types of Variables

Terraform supports two broad categories of variables:

**1\. Primitives:**`string`, `boolean`, `number`

**2\. Complex:**`set`, `map`, `object`, `tuple`, `list`, `any`

Here’s an example of a complex variable definition:

```plaintext
variable "igress_params" {
  type = map(object({
    port = number
    proto = string
    cidr  = list(string)
  }))

  default = {
    "rule1" = {
      port  = 22
      proto = "tcp"
      cidr  = ["0.0.0.0/0"]
    },
    "rule2" = {
      port  = 80
      proto = "tcp"
      cidr  = ["1.2.3.4/32"]
    }
  }
}
```

This `map(object)` variable defines ingress rules that you can later reference while creating security groups or VPCs.

---

## Introduction to `for_each`

The `for_each` construct is useful when you want to create **multiple resources of the same type** with varying values. For example, creating three S3 buckets for `dev`, `uat`, and `prod`.

```plaintext
provider "aws" {
  profile = "aws-profile-to-use"
  region  = "us-east-1"
}

variable "s3_buckets" {
  type        = set(string)
  description = "Name of S3 buckets to create"
  default     = ["test-dev", "test-uat", "test-prod"]
}

resource "aws_s3_bucket" "testbucket" {
  for_each = var.s3_buckets
  bucket   = each.value
}
```

Terraform will iterate through `s3_buckets` and create three separate S3 buckets.

---

## The `dynamic` Keyword

When defining resources like **security groups**, you might need multiple ingress or egress rules. Manually declaring them can get verbose and hard to maintain.

### Without Dynamic Blocks

```plaintext
resource "aws_security_group" "my-sg" {
  vpc_id = aws_vpc.my-vpc.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
```

This approach quickly becomes messy for many rules.

---

### With Dynamic Blocks

We can make this clean and reusable using the `dynamic` keyword.

```plaintext
variable "igress_params" {
  type = map(object({
    port  = number
    proto = string
    cidr  = list(string)
  }))

  default = {
    "rule1" = {
      port  = 22
      proto = "tcp"
      cidr  = ["0.0.0.0/0"]
    },
    "rule2" = {
      port  = 80
      proto = "tcp"
      cidr  = ["1.2.3.4/32"]
    }
  }
}

resource "aws_vpc" "my-vpc" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_security_group" "my-sg" {
  vpc_id = aws_vpc.my-vpc.id

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  dynamic "ingress" {
    for_each = var.igress_params
    content {
      from_port   = ingress.value["port"]
      to_port     = ingress.value["port"]
      protocol    = ingress.value["proto"]
      cidr_blocks = ingress.value["cidr"]
    }
  }
}
```

Terraform will iterate through each map entry in `igress_params` and dynamically create multiple `ingress` blocks — one for each rule.

> **Key benefit:** Your Terraform files stay concise, and you can easily scale your configuration by just editing variable definitions.

---

## In Summary

Dynamic blocks and constructs like `for_each` bring **programmability and flexibility** into Terraform — reducing code repetition while improving readability and scalability.
