Terraform: HCL (HashiCorp Configuration Language) Blocks
Tags: cloud, terraform, infrastructureascode, devops
Published: 2022-06-10 03:04:19

This will be a long article because we need to cover a lot of fundamental properties of HCL. It is important to understand how it works to fully utilize its full potential. 

HCL uses block to define resources. In this article we will go through HCL block types and what they are used for.

A block is a container for other content. Blocks have a `type` that can have zero or more required labels followed by `{ }` brackets that contain block's body. Blocks can be nested inside each other. This a general representation of a block:
```terraform
type "label_1" "label_2" {
  argument_1 = value_1
  argument_2 = value_2
}
```


Let's take the `resource` block from previous article and break it down:
```terraform
resource "aws_instance" "app_server" {
  ami           = "ami-830c94e3"
  instance_type = "t2.micro"

  tags = {
    Name = "PathToTerraformCertInstance"
  }
}
```
Here we have a block of type `resource`. Since we are using AWS provider we can check documentation [here](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance). 

`aws_instance` is the first label that points to the type of AWS resource and `app_server` is the second label that represents the name of the resource. Terraform supports accessing elements using dot notation like so: `aws_instance.app_server.tags`

Some resource may have required arguments. It is always a good idea to check [official docs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#argument-reference) for the required arguments. In our case, `aws_instance` has 2 required arguments: `ami` and `instance_type`. 

As for the `tags` block, it is a good idea to get into the habit of tagging your resources.

Now that we have a general idea of Terraform block, let's explore what kind of blocks we can use in our configurations.

# Terraform Block Types
As a part of learning HCL we will cover these block types:
1. `terraform` block
2. `provider` block
3. `resource` block
4. `variable` block
5. `locals` block
6. `data` block
7. `module` block
8. `output` block
9. `provisioner` block

We will go over basic structure and purpose of each block. Once we are familiar with these blocks we can start building infrastructure that starts resembling something useful.

## Terraform Block

Terraform block is used for setting the version of the terraform we want. It may also contain `required_providers` block inside which specifies the versions of the providers we need as well as where Terraform should download these providers from. Terraform block is often put into a separate file called `terraform.tf` as a way to separate settings into their own file. 

Here is an example of a terraform block:
```terraform
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 2.0"
    }
  }
  required_version = ">= 1.0.1"
}
```

## Provider block

Provider blocks specifies special type of module that allows Terraform to interact with various cloud-hosting platforms or data centers. Providers must be configured with proper credentials before we can use them. In previous article we exported access key into our environment and that allowed us to deploy resources. Versions and download locations of providers are often specified inside the `terraform` block, but you can also specify it inside this block as well. 

```terraform
provider "aws" {
  version = "~> 3.0"
  region = "us-east-1"
}
```


## Resource Block

Resource blocks are used to manage resources such as compute instances, virtual networks, databases, buckets, or DNS resources. This block type is the backbone of any terraform configuration because it represents actual resources with majority of other block types playing supporting role.
```terraform
resource "aws_instance" "example_resource" {
  ami           = "ami-005e54dee72cc1d00" # us-west-2
  instance_type = "t2.micro"
  credit_specification {
    cpu_credits = "unlimited"
  }
}
```

## Variable Block

This block is often called an input variable block. Variable block provides parameters for terraform modules and allow users to customize the data provided to other terraform modules without modifying the source. 

Variables are often in their own file called `variables.tf`. To use a variable it needs to be declared as a block. One block for each variable.

```terraform
variable "example_variable" {
  type = var_type
  description = var_description 
  default = value_1 
  sensitive = var_boolean_value 
} 
```

Terraform has a strict order of precedence for variable setting. Here it is, from highest to lowest:
1. Command line (`-var` and `var-file`)
2. `*.auto.tfvars` or `*auto.tfvars.json`
3. `terraform.tfvars.json`
4. `terraform.tfvars` file
5. Env variables
6. Variable defaults

## Locals Block

Often called local variables block, this block is used to keep frequently referenced values or expressions to keep the code clean and tidy.

Locals block can hold many variables inside. Expressions in local values aren not limited to literal constants. They can also reference other values in the module to transform or combine them. These variables can be accessed using `local.var_name` notation, note that it is called `local.` when used to access values inside.

```terraform
locals {
  service_name = "forum"
  owner        = "Community Team"
  instance_ids = concat(aws_instance.blue.*.id, aws_instance.green.*.id)
}
```

## Data Block

Data block's primary purpose is to load or query data from APIs other than Terraform's. It can be used to provide flexibility to your configuration or to connect different workspaces. One way we would use data block in future articles is to query AWS API to get a list of active Availability Zones to deploy resources in.

Data is then accessed using dot notation using `var` identifier. For example: `var.variable_1`

```terraform
data "data_type" "data_name" {
  variable_1 = expression
}
```

## Module Block

Modules are containers for multiple resources that are used together. A module consists of `.tf` and/or `.tf.json` files stored in a directory. It is the primary way to package and reuse resources in Terraform.

Every Terraform configuration has at least one model (root module) which contains resources defined in the `.tf` files. Test configuration we created in the third part of these series is a module. 

Modules are a great way to compartmentalize reusable collections of resources in multiple configurations.

Here is an example of a module:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/32dp7v0od58x78hsyrhi.jpeg)
  

## Output Block

This is a block which is almost always present in all configurations, along with `main.tf` and `variables.tf` block. It allows Terraform to output structured data about your configuration. This output can be used by users to see data like IPs or resources names in one convenient place. Another use case involves using this data in other Terraform workspace or sharing data between modules.

```terraform
output "test_server_public_ip" {
  description = "My test output for EC2 public IP"
  value = aws_instance.test_web_server.public_ip
  sensitive = true
}

output "public_url" {
  description = "Public URL for my web server"
  value = "https://${aws_instance.test_web_server.public_ip}:8000/index.html"
}
```

## Provisioner Block

Provisioners allows us to specify actions to be performed on local or remote machines to prepare resources for service.

There are two types of Terraform provisioners: `local-exec` and `remote-exec`.

`local-exec` invokes local executable after a resource is created. It runs the process on the machine running Terraform, meaning the machine where you run `terraform apply`. This is most likely your own computer.

`remote-exec` invokes remote executable, something like an EC2 instance on AWS. 

This is an example of a provisioner for an EC2 instance. This example contains both 'local-exec' and a `remote-exec`:

```terraform
resource "aws_instance" "web_server" {
  # ...
  
  provisioner "local-exec" {
    command = "Get-Date > completed.txt"
    interpreter = ["PowerShell", "-Command"]
  }
  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/script.sh",
      "/tmp/script.sh args",
    ]
  }
}
```

## Conclusion

This is the end of this rather lengthy article covering Terraform blocks. Now that we know what tools we have at our disposal we can start creating infrastructures that begin resembling something useful.

In the next article we will begin building an infrastructure. I will try to make use of most or all block covered here to get a feel for each of them. After that I will go back to covering topics for exam prep.

Thank you for reading and see you soon!