DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Terraform: HCL (HashiCorp Configuration Language) Blocks
Andrey Frol
Andrey Frol

Posted on • Updated on

Terraform: HCL (HashiCorp Configuration Language) Blocks

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:

type "label_1" "label_2" {
  argument_1 = value_1
  argument_2 = value_2
}
Enter fullscreen mode Exit fullscreen mode

Let's take the resource block from previous article and break it down:

resource "aws_instance" "app_server" {
  ami           = "ami-830c94e3"
  instance_type = "t2.micro"

  tags = {
    Name = "PathToTerraformCertInstance"
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we have a block of type resource. Since we are using AWS provider we can check documentation here.

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 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 {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 2.0"
    }
  }
  required_version = ">= 1.0.1"
}
Enter fullscreen mode Exit fullscreen mode

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.

provider "aws" {
  version = "~> 3.0"
  region = "us-east-1"
}
Enter fullscreen mode Exit fullscreen mode

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.

resource "aws_instance" "example_resource" {
  ami           = "ami-005e54dee72cc1d00" # us-west-2
  instance_type = "t2.micro"
  credit_specification {
    cpu_credits = "unlimited"
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

variable "example_variable" {
  type = var_type
  description = var_description 
  default = value_1 
  sensitive = var_boolean_value 
} 
Enter fullscreen mode Exit fullscreen mode

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.

locals {
  service_name = "forum"
  owner        = "Community Team"
  instance_ids = concat(aws_instance.blue.*.id, aws_instance.green.*.id)
}
Enter fullscreen mode Exit fullscreen mode

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

data "data_type" "data_name" {
  variable_1 = expression
}
Enter fullscreen mode Exit fullscreen mode

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

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.

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"
}
Enter fullscreen mode Exit fullscreen mode

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:

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",
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (1)

Collapse
yuri_plotnikov_19732e956f profile image
Yuri Plotnikov

Thanks, this is very informative

Head to your account's Settings to...

🌚 Enable dark mode
πŸ”  Change your default font
πŸ“š Adjust your experience level to see more relevant content