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
}
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"
}
}
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:
-
terraform
block -
provider
block -
resource
block -
variable
block -
locals
block -
data
block -
module
block -
output
block -
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"
}
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"
}
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"
}
}
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
}
Terraform has a strict order of precedence for variable setting. Here it is, from highest to lowest:
- Command line (
-var
andvar-file
) -
*.auto.tfvars
or*auto.tfvars.json
terraform.tfvars.json
-
terraform.tfvars
file - Env variables
- 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)
}
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
}
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:
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"
}
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",
]
}
}
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 (3)
Crystal clear explanation on TF Blocks.
Thanks, this is very informative
Well written