HashiCorp Terraform is an open-source infrastructure as code (IaC) tool that enables organizations to manage, version, and automate the provisioning of their infrastructure. The purpose of Terraform is to provide a common language to describe and provision infrastructure resources in a safe, predictable, and reusable manner. Terraform Configuration Syntax is the language used to describe the desired state of infrastructure resources in Terraform.

Terraform configuration is written in the HashiCorp Configuration Language (HCL), which is a declarative language specifically designed for Terraform. The syntax of HCL is human-readable, making it easy for users to understand the configuration of infrastructure resources. Additionally, HCL has powerful features for resource orchestration, such as input variables, outputs, data sources, and modules, which can be used to build reusable, composable infrastructure definitions.

In order to effectively work with Terraform, it is beneficial to have a strong understanding of cloud computing and infrastructure as a service (IaaS) platforms, such as Amazon Web Services (AWS), Microsoft Azure, and Google Cloud Platform (GCP). Knowledge of programming concepts and a background in a programming language, such as Go, Python, or Ruby, can also be helpful in working with Terraform; however this is not required. Additionally, a solid understanding of version control systems, such as Git, is essential for managing and collaborating on Terraform configurations when working on a team, or in a DevOps or Site Reliability Engineer role.

Related: This article is a general overview of the most commonly used features of the Terraform HCL language syntax. If you are looking to learn HashiCorp Terraform and get started using it for your own Infrastructure as Code (IaC) project, you’ll want to read the “Get Started with Terraform on Azure” article written by Chris Pietschmann.

Basic Terraform Configuration Structure

HashiCorp Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. It uses a configuration language, also called Terraform language, to describe the infrastructure components you want to create and manage, and how they should be connected and configured.

The HashiCorp Terraform Configuration Structure refers to the overall layout and organization of a Terraform configuration file. Terraform configuration files are written in the Terraform language and are used to define and manage the infrastructure you want to create, update, or manage.

The Terraform language is a domain-specific language that uses a specific syntax to describe the components you want to create and how they should be connected and configured. The language is used to define the infrastructure components, specify the provider that Terraform should use to manage the infrastructure, and create and manage Terraform resources.

Terraform Configuration Blocks are the building blocks of Terraform configurations. They define the infrastructure components and how they should be connected and configured. The Terraform language provides several types of blocks, such as provider, resource, module, and data, to describe the components you want to create and manage.

Terraform Configuration Files are the files in which Terraform configurations are written. They are used to define the infrastructure components, specify the provider that Terraform should use to manage the infrastructure, and create and manage Terraform resources. Terraform configuration files have a .tf file extension.

The HashiCorp Terraform configuration structure, language syntax, configuration blocks, and configuration files provide a simple and organized way to define and manage your infrastructure components. By using the Terraform language and Terraform configuration files, you can define your infrastructure in a clear and concise way, allowing you to automate the creation, update, and management of your infrastructure components.

Here’s an example of a Terraform configuration file that creates a virtual network and a subnet in Microsoft Azure:

provider "azurerm" {
  version = "2.27.0"
}

resource "azurerm_virtual_network" "b59_network" {
  name                = "b59-network"
  address_space       = ["10.0.0.0/16"]
  location            = "westeurope"
}

resource "azurerm_subnet" "b59_subnet" {
  name                 = "b59-subnet"
  resource_group_name  = azurerm_virtual_network.b59_network.name
  virtual_network_name = azurerm_virtual_network.b59_network.name
  address_prefix       = "10.0.1.0/24"
}

In this example, the provider block specifies that we are using the Azure Resource Manager provider, version 2.27.0.

The resource blocks create an Azure virtual network and a subnet. The azurerm_virtual_network block creates the virtual network with a name of b59-network and an address space of 10.0.0.0/16. The azurerm_subnet block creates a subnet with a name of b59-subnet, in the same resource group and virtual network as the virtual network defined earlier, with an address prefix of 10.0.1.0/24.

This is just a basic example, but it demonstrates the Terraform language syntax and how to use Terraform configuration blocks and files to define and manage Azure infrastructure components.

Terraform Configuration Syntax Overview

The HashiCorp Configuration Language (HCL) is the syntax used in Terraform configuration files. The syntax is designed to be human-readable and easy to write, with a focus on consistency and simplicity.

Here’s an example of a basic Terraform configuration in HCL syntax that creates an Azure resource group:

provider "azurerm" {
  version = "2.0"
}

resource "azurerm_resource_group" "b59_rg" {
  name     = "b59-rg"
  location = "eastus"
}

In this example, the first line defines the provider as azurerm, which is the Azure Resource Manager provider for Terraform. The version specified is 2.0.

The next section is a resource block that creates an Azure resource group. The resource block is followed by the resource type, in this case azurerm_resource_group, and the resource name, in this case b59_rg. Within the block, the name and location of the resource group are specified.

This is just a simple example, but it illustrates the basic structure of Terraform configurations in HCL syntax. The use of blocks and variables make it easy to manage infrastructure resources in a consistent, repeatable manner.

Input and Output Variables

Terraform variables provide a way to store values that can be reused throughout a Terraform configuration. There are two types of Terraform variables: input variables and output variables.

Input variables are used as parameters when you apply your Terraform configuration. They allow you to pass values into your Terraform code that can be used to dynamically control the creation of your infrastructure.

Here’s an example of an input variable in Terraform HCL syntax:

variable "location" {
  type        = string
  description = "The Azure region where the resource group will be created."
}

provider "azurerm" {
  version = "2.0"
}

resource "azurerm_resource_group" "b59_group" {
  name     = "b59-group"
  location = var.location
}

In this example, a variable named location is declared with a type of string and a description. The variable is then used in the location argument of the azurerm_resource_group resource block.

Output variables, on the other hand, are used to return values from your Terraform code back to the command line interface. They can be used to pass data between Terraform modules or to return information about your infrastructure for use in other tools and scripts.

Here’s an example of an output variable in Terraform HCL syntax:

provider "azurerm" {
  version = "2.0"
}

resource "azurerm_resource_group" "b59_group" {
  name     = "b59-group"
  location = "eastus"
}

output "resource_group_name" {
  value = azurerm_resource_group.b59_group.name
}

In this example, an output variable named resource_group_name is declared and its value is set to the name attribute of the azurerm_resource_group resource block.

By using input and output variables in your Terraform code, you can create more flexible and reusable configurations that can be customized to meet your specific needs.

Locals

In Terraform, locals are used to define values within a module, which can then be used elsewhere within that same module. They allow you to simplify your code and improve readability by assigning complex expressions to a local variable and then using that local variable wherever that expression is needed. Here’s an example of using locals in Terraform configuration to set up a Microsoft Azure virtual machine:

locals {
  vm_name = "b59-vm"
  vm_size = "Standard_B2s"
}

resource "azurerm_virtual_machine" "b59_vm" {
  name                  = local.vm_name
  location             = "northcentralus"
  resource_group_name  = "b59_rg"
  network_interface_ids = [azurerm_network_interface.b59_nic.id]
  vm_size               = local.vm_size

  storage_os_disk {
    name              = "myosdisk1"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
  }

  os_profile {
    computer_name  = local.vm_name
    admin_username = "adminuser"
    admin_password = "Password1234!"
  }

  os_profile_linux_config {
    disable_password_authentication = false
  }
}

In the example, we first define two locals, vm_name and vm_size, that hold the name of the virtual machine and the size of the virtual machine respectively. Later in the azurerm_virtual_machine resource block, we reference these locals with local.vm_name and local.vm_size to set the name and size of the virtual machine. This allows us to easily reuse and modify these values if needed in the future without having to make changes throughout the entire configuration file.

Supported Data Types

In Terraform, data types are used to specify the format of the values assigned to variables. There are several data types supported in Terraform, including:

  1. string
    Used to represent text-based values. For example: "Hello World"
  2. number
    Used to represent numerical values, either integer or floating-point. For example: 42 or 3.14
  3. boolean
    Used to represent true/false values. For example: true or false
  4. list
    Used to represent a list of values of any data type. For example: [1, 2, 3, 4]
  5. map
    Used to represent a collection of key-value pairs, where the keys and values can be of any data type. For example: {key1 = "value1", key2 = "value2"}
  6. object
    Used to represent a complex data structure, typically containing multiple keys and values of different data types.

When defining variables in Terraform, you can specify the data type either explicitly or let Terraform infer the type based on the value assigned to the variable. For example, the following code defines a variable message of type string and assigns it a value of Hello World:

variable "message" {
  type = string
}

locals {
  message = "Hello World"
}

In the code example above, Terraform will automatically infer the data type of the variable “message” as string based on the value assigned to it.

Terraform Provider Syntax

Terraform Providers are external services that provide Terraform with the necessary functionality to manage and interact with specific resources. In order to use a Terraform Provider, you need to define and configure it in your Terraform configuration file.

Here is an example of defining and using a Terraform Provider for Microsoft Azure in Terraform configuration:

provider "azurerm" {
  version = "2.27.0"
  features {}
}

In the example above, we define a Terraform Provider named azurerm with version 2.27.0. The features block is an optional block that is used to enable specific features for the provider.

Once the provider is defined, you can use it to manage resources in Microsoft Azure, such as creating a virtual network, creating a virtual machine, or creating a storage account. Here is an example of creating a virtual network in Microsoft Azure using the Terraform Provider:

resource "azurerm_virtual_network" "b59_network" {
  name                = "b59-network"
  address_space       = ["10.0.0.0/16"]
  location            = "northcentralus"
  resource_group_name = azurerm_resource_group.b59_group.name
}

In the example above, we create a virtual network resource in Microsoft Azure using the Terraform Provider. The azurerm_virtual_network block is used to define the virtual network and the example_network block is used to give the virtual network a unique name. The address_space block is used to specify the CIDR block for the virtual network, and the location block is used to specify the location of the virtual network in Microsoft Azure. The resource_group_name block is used to specify the resource group that the virtual network belongs to.

Related: There are multiple Terraform Providers available for working with Microsoft Azure resources, in addition to the azurerm provider. To learn about Terraform Providers for Azure, you’ll want to read the “Terraform: Overview of Azure Providers and Tools” article written by Chris Pietschmann.

Terraform Provisioner Syntax

Terraform Provisioners are scripts that are executed after a resource is created and before it is destroyed. They allow you to configure additional actions to be taken on your infrastructure after Terraform has created it. In this section, we’ll explain how to create and manage Terraform Provisioners in your Terraform configurations using syntax and code examples targeting Microsoft Azure.

Here’s an example of how to use a Terraform Provisioner to install a web server on an Azure virtual machine after it has been created:

provider "azurerm" {
  version = "2.40.0"
}

resource "azurerm_resource_group" "b59_rg" {
  name     = "b59group"
  location = "eastus"
}

resource "azurerm_virtual_machine" "b59_vm" {
  name                  = "b59-vm"
  resource_group_name   = azurerm_resource_group.b59_rg.name
  location              = azurerm_resource_group.b59_rg.location
  size                  = "Standard_B1s"
  storage_os_disk {
    name              = "example-os-disk"
    caching           = "ReadWrite"
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
  }
  os_profile {
    computer_name  = "b59-vm"
    admin_username = "adminuser"
    admin_password = "Password1234!"
  }
  os_profile_linux_config {
    disable_password_authentication = false
  }
  network_interface_ids = [azurerm_network_interface.b59_nic.id]
  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get -y install apache2",
    ]
  }
}

resource "azurerm_network_interface" "b59_nic" {
  name                = "b59-nic"
  location            = azurerm_resource_group.b59_rg.location
  resource_group_name = azurerm_resource_group.b59_rg.name
  network_security_group_id = azurerm_network_security_group.b59_nsg.id
}

resource "azurerm_network_security_group" "b59_nsg" {
  name                = "b59-nsg"
  location            = azurerm_resource_group.b59_rg.location
  resource_group_name = azurerm_resource_group.b59_rg.name
}

In this example, the Terraform Provisioner block is defined within the azurerm_virtual_machine resource block. It specifies the remote-exec provisioner, which will execute a list of shell commands on the virtual machine after it has been created. In this case, the commands update the package list and install the Apache2 web server.

It’s important to note that provisioners are optional, but they provide a powerful way to configure additional actions on your infrastructure after Terraform has created it.

Supported Provisioners

These are the different types of Terraform Provisioners that are available, along with code examples targeting Microsoft Azure. The specific configuration and use may vary based on the individual requirements of the infrastructure being managed.

local-exec Provisioner

The local-exec provisioner is a type of Terraform provisioner that runs a local executable after Terraform has completed its work. This provisioner can be useful in situations where some additional post-processing of the Terraform-created resources is required. For example, you might use a local-exec provisioner to install software on a virtual machine after Terraform has finished creating the virtual machine and its associated resources.

The syntax for a local-exec provisioner in Terraform is as follows:

provisioner "local-exec" {
  command = "command to run"

  environment = {
    VAR1 = "value1"
    VAR2 = "value2"
  }
}

In this example, the command field specifies the command to run, and the environment field allows you to specify environment variables that should be set for the command. These environment variables can be used to pass information about the Terraform-created resources to the command being run by the provisioner.

Here’s an example of using a local-exec provisioner to run a command that installs the Apache web server on a virtual machine in Microsoft Azure:

resource "azurerm_virtual_machine" "b59_vm" {
  # ... (other configuration omitted)

  provisioner "local-exec" {
    command = "apt-get update && apt-get install -y apache2"
  }
}

In this example, the local-exec provisioner runs the apt-get update && apt-get install -y apache2 command after Terraform has created the virtual machine. This installs the Apache web server on the virtual machine.

The destroy local-exec provisioner in Terraform is used to execute a local command when a Terraform-managed resource is destroyed. The provisioner is executed on the same machine as the Terraform command line.

The destroy local-exec provisioner is useful for performing cleanup actions or for sending notifications when a resource is destroyed. For example, it could be used to clean up additional resources that were created outside of Terraform, or to log the destruction of a resource to a monitoring service.

Here is an example code of a destroy local-exec provisioner for Microsoft Azure:

resource "azurerm_virtual_machine" "b59_vm" {
  # resource configuration here

  provisioner "local-exec" {
    when = "destroy"
    command = "echo 'Virtual machine has been destroyed!'"
  }
}

In this example, when the Terraform-managed Azure virtual machine is destroyed, the local-exec provisioner will execute the echo command and log the message to the console.

remote-exec Provisioner

The remote-exec provisioner in Terraform allows executing a command on a remote resource after it has been created. It is commonly used to perform post-provisioning steps on instances created by Terraform.

Here is an example code that shows how the remote-exec provisioner is used in Terraform to run a script on a Microsoft Azure virtual machine after it has been created:

provider "azurerm" {
  version = "2.0"
}

resource "azurerm_virtual_machine" "example" {
  name                  = "b59-vm"
  location              = "northcentralus"
  resource_group_name   = "b59-rg"
  network_interface_ids = [azurerm_network_interface.b50_nic.id]
  vm_size               = "Standard_DS1_v2"

  storage_os_disk {
    name              = "myosdisk1"
    caching           = "ReadWrite"
    create_option     = "FromImage"
  }

  os_profile {
    computer_name  = "hostname"
    admin_username = "testadmin"
    admin_password = "Password1234!"
  }

  os_profile_linux_config {
    disable_password_authentication = false
  }

  tags = {
    environment = "B59 Terraform Test"
  }
}

resource "azurerm_network_interface" "b50_nic" {
  name                = "b59-nic"
  location            = "eastus"
  resource_group_name = "b59-rg"

  ip_configuration {
    name                          = "testconfiguration1"
    subnet_id                     = azurerm_subnet.b59_subnet.id
    private_ip_address_allocation = "Dynamic"
  }
}

resource "azurerm_subnet" "b59_subnet" {
  name                 = "b59-subnet"
  resource_group_name  = "b59-rg"
  virtual_network_name = "b59-vnet"
  address_prefix       = "10.0.2.0/24"
}

provisioner "remote-exec" {
  connection {
    type        = "ssh"
    user        = "testadmin"
    password    = "Password1234!"
    host        = self.network_interface.0.private_ip_address
  }

  inline = [
    "sudo apt-get update",
    "sudo apt-get install apache2 -y",
    "sudo systemctl start apache2",
    "sudo systemctl enable apache2"
  ]
}

In the example above, the remote-exec provisioner runs a series of commands on the virtual machine created by Terraform using SSH protocol. The commands install and configure Apache web server on the virtual machine.

Terraform Module Syntax

Terraform modules are self-contained packages of Terraform configurations that are managed as a group. They are designed to allow Terraform configurations to be reused and shared across multiple projects and teams. A Terraform module is a collection of Terraform files and directories that are packaged together as a single unit. They can include Terraform resources, variables, outputs, and data sources, and they can be called from other Terraform configurations using the module block. Terraform modules help to encapsulate and isolate Terraform code, making it easier to manage, test, and maintain.

Create a Terraform Module

Terraform modules are a way to encapsulate Terraform configuration into reusable and composable units. Modules allow you to abstract away implementation details and simplify Terraform configurations. With Terraform modules, you can extract common patterns and resources into a separate module that can be easily shared and reused across multiple Terraform configurations.

Here’s an example of creating a module for an Azure resource group:

# Example Terraform Module for deploying an Azure Resource Group
provider "azurerm" {
  version = "2.25.0"
}

resource "azurerm_resource_group" "b59_rg" {
  name     = var.resource_group_name
  location = var.location

  tags = {
    environment = "dev"
  }
}

variable "resource_group_name" {
  type        = string
  description = "Name of the Resource Group"
}

variable "location" {
  type        = string
  description = "Location of the Resource Group"
}

The module in this example is a configuration for an Azure resource group. It declares the Azure provider and a single resource, azurerm_resource_group, which creates an Azure resource group. The name and location of the resource group are passed in as variables, var.resource_group_name and var.location, respectively. This allows the user to create multiple resource groups with the same module by simply passing in different values for the variables.

In a separate Terraform configuration, you can then call this module and pass in the required variables:

module "example_resource_group" {
  source = "./example-module"

  resource_group_name = "b59-rg"
  location            = "northcentralus"
}

In this example, the Terraform configuration calls the module located in the “example-module” directory and sets the values of the resource_group_name and location variables. The module is then used to create the Azure resource group, and the name and location of the group are set based on the values passed in as variables.

Related: For a deeper explanation and tutorial on creating and using Terraform Modules to create reusable Terraform code, you’ll want to read the “Terraform Modules: Create Reusable Infrastructure as Code” article written by Chris Pietschmann.

Using Terraform Modules

Terraform Modules are self-contained packages of Terraform configurations that are managed as a group. They allow for sharing of Terraform code and can make it easier to manage and reuse Terraform configurations.

There are several ways to use Terraform modules:

Source Control

A Terraform module can be used from a Git source control by specifying the Git repository as the source in the module block in your Terraform configuration file.

Here’s an example of using a Terraform module from a Git repository in a Terraform configuration:

module "b59_module" {
  source = "git::https://github.com/user/b59_module.git"
}

In this example, the module block defines a module named example_module, and the source argument specifies the Git repository URL where the module is located.

Once the Terraform configuration file is run with the terraform apply command, Terraform will download the module from the specified Git repository and use it to create and manage resources.

It’s important to note that the Git repository should contain the necessary Terraform configuration files and resources to run the module, as well as any dependencies required by the module. Additionally, when using a Terraform module from a Git repository, you can specify a specific version of the module using Git tags or commit hashes.

Module Registry

The Terraform Registry is a public repository of Terraform modules that can be used as a source for Terraform configurations.

To use a Terraform Module from the Terraform Registry, you will first need to specify the source for the module in your Terraform configuration using the following syntax:

module "b59_module" {
  source = "registry.terraform.io/b59_module"
}

In this example, the source for the module is specified as registry.terraform.io/module_name, where module_name is the name of the module you want to use from the Terraform Registry.

After specifying the source, you can use the module by referencing it in your Terraform configurations, such as by using the outputs of the module in your configurations or passing inputs to the module.

Using a Terraform Module from the Terraform Registry can help you to reduce the amount of duplicated code in your Terraform configurations and improve the maintainability and scalability of your infrastructure.

Local Directories

Terraform modules can be used from local directories by specifying the path to the module directory in the module block of your Terraform configuration file. For example, if you have a module stored in a local directory named module_example within the same directory as your Terraform configuration file, you could use it with the following configuration:

module "b59_module" {
  source = "./b59_module"
  # other inputs and configurations specific to this module usage
}

You can then use the resources defined within the module by referencing them with the syntax module.example.RESOURCE_NAME.

This way of using Terraform modules is useful if you have a module that you have developed in-house and you want to use it in multiple projects or if you have a module stored locally that you want to use in your Terraform configuration without making it public in a registry or Git repository.

Terraform Module Input Variables

When using Terraform modules, you can also pass input variables to customize the module’s behavior. You can use the variables block within a module block to set values for input variables.

module "azure_example_module" {
  source = "./b59_module"

  example_variable = "value"
}

With Terraform modules, you can encapsulate and reuse Terraform code to streamline the development and management of your Terraform configurations. By using modules, you can simplify your Terraform configurations, reduce the amount of code you need to write and maintain, and make it easier to reuse and share Terraform code.

Chris Pietschmann is a Microsoft MVP, HashiCorp Ambassador, and Microsoft Certified Trainer (MCT) with 20+ years of experience designing and building Cloud & Enterprise systems. He has worked with companies of all sizes from startups to large enterprises. He has a passion for technology and sharing what he learns with others to help enable them to learn faster and be more productive.
Microsoft MVP HashiCorp Ambassador

Discover more from Build5Nines

Subscribe now to keep reading and get access to the full archive.

Continue reading