Terraform Scripting Concepts: Part 2 of 2
Published: 2021-12-31 01:25:02

## Overview

This is the second part of the tutorial. If not done already, please read the first part: [Terraform Scripting Concepts: Part 1 of 2](https://dev.to/serdigital64/terraform-scripting-concepts-part-1-of-2-484f)

## Using functions

Another particular characteristic of the **Terraform Language** is that it doesn't support user-defined functions. Instead, it provides groups of built-in functions that can be used when assigning value to variables and attributes:

- Numeric
- String
- Collection
- Encoding
- Filesystem
- Date and Time
- Hash and Crypto
- Networking
- Type conversion

Use the following template for calling functions: `FUNCTION_NAME(PARAMETER1, PARAMETER2, PARAMETERN)`

For example:

```terraform
locals {
  # Find the maximum value from a list of numbers
  numeric_max = max(2, 3, 10, 1, 20)
  # Find the minimum value from a list of numbers
  numeric_min = min(2, 3, 10, 1, 20)
  # Conver string to lowercase
  string_lower = lower("ServerX.DomainY")
  # Extrat dir from path
  filesystem_dir = dirname("/full/path/to/file")
  # Extrat file name from path
  filesystem_file = basename("/full/path/to/file")
  # Record timestamp
  date_time_timestamp = timestamp()
}
```

## Managing infrastructure

Up until now, the script takes no action. In order for **Terraform** to actually manage the target infrastructure and make changes to reach the required end-state, the script needs providers.

**Terraform** defines the following block types for working with the target infrastructure:

- `terraform`: used to set workspace-wide run-time options.
- `required_providers`: used to declare what providers will be used
- `provider`: used to define provider-specific options.
- `resource`: used to create a logical representation of the managed resource. Each provider will further specialize the resource with attributes and subtypes.
- `data`: optional block that is used to query infrastructure resources managed by the provider. The query result is then made available for other blocks to use as variables.

### Declare Providers

Providers are declared by adding a `required_providers` nested block to the `terraform` block. Each provider will be assigned to an attribute of type map:

- Map name: used to reference the provider from other blocks.
- Attribute `version`: used to define version requirements.
- Attribute `source`: defines from where the provider will be downloaded.

Additional provider-specific attributes can be defined by using the `provider` block. The block will use the same label as the map name in `required_providers`.

The following example declares two providers: `aws` and `azure`:

```terraform
terraform {
  required_providers {
    aws = {
      version = ">= 3.0.0"
      source = "hashicorp/aws"
    }
    azurerm = {
      version = ">= 2.0.0"
      source = "hashicorp/azurerm"
    }
  }
}
provider "aws" {
  region = "us-east-1"
}
provider "azurerm" {
  features {}
}
```

### Define Managed Resources

Once the provider has been declared, it will make available additional `resource` and `data` types. For example:

- Provider: AWS
  - Resource Type: `aws_instance`
  - Data Type: `aws_ami`
- Provider: Azure
  - Resource Type: `azurerm_linux_virtual_machine`
  - Data Type: `azurerm_image`

Use the following template to define blocks:

```terraform
data "RESOURCE_TYPE" "RESOURCE_LABEL" {
  ATTRIBUTE1 = VALUE
  ATTRIBUTEN = VALUE
}

resource "RESOURCE_TYPE" "RESOURCE_LABEL" {
  ATTRIBUTE1 = VALUE
  ATTRIBUTEN = VALUE
}
```

The data and resource blocks can be used together to improve automation. In the following example, the `data` block is used to query AWS for a particular AMI and then the `resource` block will use the resulting AMI ID to create the VM instance:

```terraform
data "aws_ami" "coreos" {
  most_recent = true    # Return the latest one if more than one result is available

  filter {              # Look for fedora coreos v35
    name   = "name"
    values = ["fedora-coreos-35**"]
  }

  filter {              # Look for HVM virtualization
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["125523088429"]     # CentOS / Fedora account that owns AMIs
}

resource "aws_instance" "aws_test_instance" {
  ami = data.aws_ami.coreos.id    # Use the AMI that was found in the data query
  instance_type = "t2.micro"      # Select the instance size
}
```

## Next Steps

Explore Terraform resources:

- Source code: [GitHUB Repository](https://github.com/hashicorp/terraform)
- Tool documentation: [Manual](https://www.terraform.io/docs)

Learn advanced features:

- [Functions](https://www.terraform.io/language/functions)
- [Expresions](https://www.terraform.io/language/expressions)
- [Custom validation rules](https://www.terraform.io/language/values/variables#custom-validation-rules)
- [Built-in Functions](https://www.terraform.io/language/functions)
- [Modules](https://www.terraform.io/language/modules)

Understand provider-specific implementation details:

- [Providers](https://registry.terraform.io/)

## 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.