Using Terraform Dynamic Blocks and Built-in Functions to Deploy Resources to AWS
Tags: terraform, ec2, vpc, subnet
Published: 2023-10-02 14:53:41

***Terraform*** offers a strong set of features to help optimize your Terraform code. Two really useful features are 
- ***dynamic blocks***, which allow you to generate static repeated blocks within resources in Terraform.

You can dynamically construct repeatable nested blocks like ***`ingress`*** using a special dynamic block type, which is supported inside ***`resource, data, provider, and provisioner blocks`***.

A dynamic block acts much like a ***`for expression`***, but produces nested blocks instead of a complex typed value. It iterates over a given complex value, and generates a nested block for each element of that complex value. 

- ***built-in functions***, which help you manipulate variables and data to suit your needs and help make your Terraform deployments better automated and more fault resilient.

The Terraform language includes a number of built-in functions that you can call from within expressions to transform and combine values. 

The general syntax for function calls is a function name followed by comma-separated arguments in parentheses - ***`timestamp() `***

Please visit my **[GitHub Repository for Terraform articles](https://github.com/awsmine/Terraform_projects)** on various topics being updated on constant basis.




_**Let’s get started!**_


# Objectives:

**1.** Login to AWS Management Console

**2.** Create infrastructure for application block

**3.** Under ***`terraform_files`*** application directory - Create 4 files - ***`main.tf`***, ***`apache.sh`***, ***`variables.tf`***, ***`outputs.tf`***

**4.** Initialize Terraform

**5.** To generate the action plans

**6.** Create all the resources declared in main.tf configuration file

**7.** Validate all resources created
 


# Pre-requisites:
- AWS user account with admin access, not a root account.
- Cloud9 IDE with AWS CLI.



# Resources Used:
***[Terraform documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs).***
***[Terraform documentation for AMI](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami).***
***[dynamic Blocks](https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks)***
***[Built-in Functions](https://developer.hashicorp.com/terraform/language/functions)***

# Steps for implementation to this project:

## 1. Login to AWS Management Console
- Make sure you're in the N. Virginia (us-east-1) region

## 2. Create infrastructure for application block

- Let’s create the following organizational structure as shown below.


## 3. Under ***`terraform_files`*** application directory - Create 4 files - ***`main.tf`***, ***`apache.sh`***, ***`variables.tf`***, ***`outputs.tf`***.


- Create a directory - ***`terraform-files`***

- Create 4 files - ***`main.tf`***, ***`apache.sh`***, ***`variables.tf`***, ***`outputs.tf`***. 

- 1. ***`main.tf`***

[Data Source: aws_ssm_parameter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter)
ssm_parameter public endpoint resource is to get the AMI ID of the Amazon Linux 2 image that will spin up the EC2 webserver.

[Resource: aws_security_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group)

security_group resource uses a dynamic block on the ***`ingress`*** attribute to dynamically generate as many ingress blocks as we need. The dynamic block includes the ***`var.rules`*** complex variable configured in the ***`variables.tf`*** file.

[join Function](https://developer.hashicorp.com/terraform/language/functions/join)
[for_each Meta-Argument](https://developer.hashicorp.com/terraform/language/meta-arguments/for_each)

built-in functions like the ***`join`*** function for the name attribute in the security group resource, and the ***`fileexists`*** and ***`file`*** functions for the user_data parameter in the EC2 instance resource and which then installs the Apache webserver and starts up the service.


```
provider "aws" {
  region = "us-east-1"
}

data "aws_ssm_parameter" "ami_id" {
  name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs            = ["us-east-1a"]
  public_subnets = ["10.0.1.0/24"]
}

resource "aws_security_group" "my-sg" {
  vpc_id = module.vpc.vpc_id
  name   = join("_", ["sg", module.vpc.vpc_id])
  dynamic "ingress" {
    for_each = var.rules
    content {
      from_port   = ingress.value["port"]
      to_port     = ingress.value["port"]
      protocol    = ingress.value["protocol"]
      cidr_blocks = ingress.value["cidr_blocks"]
    }
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "Dynamic-SG"
  }
}

resource "aws_instance" "my-instance" {
  ami                         = data.aws_ssm_parameter.ami_id.value
  subnet_id                   = module.vpc.public_subnets[0]
  instance_type               = "t2.micro"
  security_groups             = [aws_security_group.my-sg.id]
  associate_public_ip_address = true
  user_data  = fileexists("script.sh") ? file("script.sh") : null
}
```

- 2. ***`apache.sh`***

```
#!/bin/bash
sudo yum -y install httpd
sudo systemctl start httpd && sudo systemctl enable httpd
```

- 3. ***`variables.tf`***

[variable "rules" - Declaring an Input Variable]( 
https://developer.hashicorp.com/terraform/language/values/variables)

```
variable "rules" {
  type = list(object({
    port        = number
    protocol       = string
    cidr_blocks = list(string)
  }))
  default = [
    {
      port        = 80
      protocol       = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    },
    {
      port        = 22
      protocol      = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    },
    {
      port        = 3689
      protocol       = "tcp"
      cidr_blocks = ["101.34.81.98/32"]
    }
  ]
}
```

- 4. ***`outputs.tf`***

[timestamp Function](https://developer.hashicorp.com/terraform/language/functions/timestamp)

```
output "Web-Server-URL" {
  description = "Web-Server-URL"
  value       = join("", ["http://", aws_instance.my-instance.public_ip])
}

output "Time-Date" {
  description = "Date/Time of Execution"
  value       = timestamp()
}
```

## 4. Initialize Terraform

***`cd terraform-files`***

- ***`terraform status`*** 
```
terraform version
```
Since the Terraform version is returned, you have validated that the Terraform binary is installed and functioning properly.

#### Note:
If you receive a notification that there is a newer version of Terraform available, you can ignore it — it will run safely with the version installed on the VM.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ciho6u30k8ww96kntnkj.png)


- ***`terraform init`*** will check for all the plugin dependencies and download them if required, this will be used for creating a deployment plan.

```
terraform init
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uis25uxadlyxcbg2yf05.png)


## 5. To generate the action plans, run the below command:

```
terraform plan
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n36lsxfvan1ug30s4u2z.png)


## 6. Create all the resources declared in main.tf configuration file

#### Note: 
The --auto-approve flag will prevent Terraform from prompting you to enter yes explicitly before it deploys the code.

```
terraform apply --auto-approve
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i3fegrhptrmjw3hy9s8a.png)

```
terraform state list
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0r590regby9xr6wftdaa.png)


7. Validate all resources created

- VPC

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cnd455sh2yek6fgc08ks.png)


- EC2

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qg8pj0awtvd4v2zpb1x2.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhacpy3jepwntzn7yxo2.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vg2oey6zcaystbxar2de.png)



- Security Group and Inbound rules

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hrxh6qvsip4hz2ijgtla.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8z1wj0xv51d7it9qm2w8.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ouvhwx8i9le1am9f8es7.png)



# Cleanup

- terraform destroy

# What we have done so far

We have successfuly deployed AWS resources using Terraform Dynamic Blocks ***`ingress`*** and Built-in Functions ***`join`***, ***`fileexists`*** and ***`file`***.