Terraform Scripting Concepts: Part 1 of 2
Tags: terraform, devops, tutorial
Published: 2021-12-31 01:23:54

## Overview

[Terraform](https://www.terraform.io/) is an automation tool focused on infrastructure provisioning. The product is developed by HashiCorp, and the command line version is provided as an Open Source Project available on GitHub.

**Terraform** has the following core components:

- **Terraform CLI**: stand-alone app that provides the run-time environment for **Terraform Scripts** and maintains build-state information of **Managed Resources**.
- **Terraform Script**: text file with **Terraform Language** code used to describe the required end-state of the **Managed Resources**.
- **Managed Resource**: logical representation of a real infrastructure resource in the target infrastructure.
- **Terraform Provider**: stand-alone apps (plugins) that are used by the **Terraform CLI** to interact with **Managed Resources**.
- **Terraform Workspace**: working directory where **Terraform Scripts** are located and where the **Terraform CLI** stores data files (state, temporary, plugins, etc.)

## Using Terraform

### CLI Installation

The recommended method for production environments is to add [HashiCorp's package repository](https://www.terraform.io/downloads) to the OS package manager and install it using native tools.

For more relaxed environments the binary package provides a simpler alternative that does not require root privilege:

```shell
# Create the installation repository
INSTALL_PATH="${HOME}/terraform-cli"; mkdir "${INSTALL_PATH}"; cd "${INSTALL_PATH}"
# Download the binary package
curl -LO https://releases.hashicorp.com/terraform/1.1.2/terraform_1.1.2_linux_amd64.zip
# Uncompress the binary package
ZIP_PACKAGE='terraform_1.1.2_linux_amd64.zip'; unzip "${ZIP_PACKAGE}" && rm -f "${ZIP_PACKAGE}"
```

### Running scripts

The execution process of **Terraform Scripts** is divided into stages:

1) `init`: prepares the **Terraform Workspace**, sets run-time parameters, downloads, and installs providers.
2) `plan`: creates/updates state information of managed resources and executes a dry-run to create the change plan. For already existing resources, if the provider is unable to modify attributes on the fly it will propose a destructive change (remove and recreate the resource with the new end-state).
3) `apply`: executes the change plan, and updates resource state information. The provider will modify the target infrastructure by creating, updating and removing resources until the end-state is reached.

To execute the script the **Terraform** CLI must be already inside the workspace directory:

```shell
cd WORKSPACE_PATH
# Initialize the workspace
terraform init
# Prepare and review the action plan
terraform plan
# Execute the action plan
terraform apply
```

Notice that the user running the process doesn't need root privilege, just write permissions to the workspace path.

## Script structure

The first thing to understand about **Terraform Scripts** is that the **Terraform Language** is not for general-purpose development. Based on HCL ([Hashicorp Configuration Language](https://github.com/hashicorp/hcl)), it's specifically designed to describe infrastructure resources.

**Terraform Scripts** are developed by defining blocks: a complex data structure that describes a configuration object through its attributes.

Use the following template for declaring blocks:

```terraform
BLOCK_TYPE "RESOURCE_TYPE" "RESOURCE_LABEL1" {

  ATTRIBUTE1 = VALUE
  ATTRIBUTE2 = VALUE
  ATTRIBUTEN = VALUE

  # Nested block
  BLOCK_TYPE "RESOURCE_TYPE" "RESOURCE_LABEL2" {
    ATTRIBUTE = VALUE
  }

}
```

- **BLOCK_TYPE**: defines what type of configuration object is going to be described. Notice that Blocks can have any number of attributes and other blocks (nested).
- **RESOURCE_TYPE**: optional field used to select object subtypes. Not all objects will require one.
- **RESOURCE_LABEL**: optional field to assign a label (ID) to the object.
- **ATTRIBUTE**: name of the object's attribute.
- **VALUE**: value that will be assigned to the object's attribute.

The following list shows common built-in block types:

- `variable`
- `output`
- `local`
- `terraform`
- `provider`
- `resource`
- `data`

Additional types and subtypes are implemented through **Terraform Providers**. For example:

- [`aws`](https://registry.terraform.io/providers/hashicorp/aws/latest)
- [`google`](https://registry.terraform.io/providers/hashicorp/google/latest)
- [`azurerm`](https://registry.terraform.io/providers/hashicorp/azurerm/latest)
- [`kubernetes`](https://registry.terraform.io/providers/hashicorp/kubernetes/latest)

Scripts can have comments anywhere:

- `//`: single-line comment. Anything at the right is ignored.
- `#`: single-line comment. Anything at the right is ignored.
- `/* */`: multi-line comment. Anything between the delimiters is ignored.

As a good practice, after the script is created/updated run the built-in linter and code-formatter:

```shell
cd WORKSPACE_PATH
terraform fmt
terraform validate
```

## Using Variables

### Declaration

As everything in **Teraform**, variables are also a block type. Use the following template to declare variables:

```terraform
locals {
  VARIABLE_NAME = VALUE
}
```

### Data Types

Both variables and attributes have types:

- `string`: a sequence of Unicode characters.
- `number`: integer or float numbers.
- `bool`: boolean values.
- `list`: indexed sequence of values. The index is numerical and starts at cero. VALUEs can be of any type.
- `map`: group of key + value pairs. VALUEs can be of any type.
- `null`: empty value.

Types are inferred from the value (i.e. no explicit data type declaration). For example:

```terraform
locals {
  test_string = "abc123"
  test_number_integer = 15
  test_number_float = 15.1
  test_bool = true
  test_list = [
    "value 1",
    100
  ]
  test_map = {
    key1 = "value 1",
    key2 = 100
  }
}
```

### Parameters

Parameters are variables that are used to provide run-time data to **Terraform Scripts**. They are declared using the `variable` block type:

```terraform
variable "PARAMETER_NAME" {
  # Optional parameter attributes
  ATTRIBUTE = VALUE
}
```

They are different from regular variables as they allow attributes to further describe the parameter:

- `default`: value that is assigned if none is provided.
- `description`: text message that is used to describe the parameter.
- `type`: expected variable type. This is not used to declare the type, but rather to constrain the provided value.
- `validation`: validation block used to perform additional checks.
- `sensitive`: flag to restrict script output. Used to mask sensitive values such as passwords.
- `nullable`: flag to indicate if the value can be null.

For example:

```terraform
variable "aws_region" {
  type = string
  default = "us-west-1a"
  description = "target AWS region"
  nullable = false
  sensitive = false
}

variable "xaas_test_api_secret" {
  type = string
  description = "API secret for the Test API"
  nullable = false
  sensitive = true
}
```

### Output

In addition to the `local` and `variable` block types **Terraform** provides the `output` type to define variables that will be exported and made available to external apps and scripts. The following attributes are available:

- `value`: the value assigned to the variable.
- `description`: describe what the value represents.
- `sensitive`: flag to restrict script output. Used to mask sensitive values such as passwords.

For example:

```terraform
output "sysadmin_key" {
  value = "secret-api-key"
  description = "API secret for the new service"
  sensitive = true
}
```

### Retreive variable's value

In general, any block attribute can be referenced from within other blocks by using the following structure: `BLOCK_TYPE.ATTRIBUTE` or `BLOCK_TYPE.BLOCK_LABEL.ATTRIBUTE`

In addition to the generic format there are special cases to consider:

- Locals: use `local` instead of `locals`: `local.ATTRIBUTE`
- Parameters: use the short form `var` instead of the full type name `variable`: `var.ATTRIBUTE`
- Outputs: requires the module name where the output is declared: `module.MODULE NAME.OUTPUT_NAME`

Variables can also be used in **String Templates** (simple programming code that can be embedded inside string declarations): `"${BLOCK_TYPE.ATTRIBUTE}"`

The following example shows the three variable types seen so far:

```terraform
locals {
  api_secret_seed = "abc123"
}

variable "new_service_name" {
  type = string
  default = "new_service_name"
  description = "What name will the new service have"
  nullable = false
  sensitive = false
}

output "service_secret" {
  value = "${local.api_secret_seed}${var.new_service_name}"
  description = "API secret for the new service"
  sensitive = true
}
```

## Next Steps

Continue reading the second part of the tutorial: [Terraform Scripting Concepts: Part 2 of 2](https://dev.to/serdigital64/terraform-scripting-concepts-part-2-of-2-36nl)

## Copyright information

This article is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/). For copyright information on the product or products mentioned inhere refer to their respective owner.

## Disclaimer

Opinions presented in this article are personal and belong solely to me, and do not represent people or organizations associated with me in a professional or personal way. All the information on this site is provided "as is" with no guarantee of completeness, accuracy or the results obtained from the use of this information.