Terraform

Introduction

Infrastructure automation to provision and manage resources in any cloud or data center.


Backend (subpage)

https://sites.google.com/site/pawneecity/terraform/backend-terraform

Terraformer (subpage)

https://sites.google.com/site/pawneecity/terraform/terraformer-terraform

Trivy (subpage)

https://sites.google.com/site/pawneecity/terraform/trivy-terraform


Reference

HashiCorp examples of terraform aws provider

$ git clone https://github.com/hashicorp/terraform-provider-aws.git && \ 

cd ./terraform-provider-aws/examples/

The Ultimate Terraform Commands Cheat Sheet

https://acloudguru.com/blog/engineering/the-ultimate-terraform-cheatsheet

Naming conventions

https://www.terraform-best-practices.com/naming

Input Variables

https://www.terraform.io/language/values/variables

Functions

https://developer.hashicorp.com/terraform/language/functions

Using AWS & AWSCC Provider Together

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/using-aws-with-awscc-provider


Install / Upgrade

Indications:

APT Packages for Debian and Ubuntu > https://developer.hashicorp.com/terraform/cli/install/apt

Install Terraform > https://developer.hashicorp.com/terraform/install?ajs_aid=f6da5882-e92e-45a0-aaf8-38d9ee0a0337&product_intent=terraform#Linux


Configuration:

sudo apt update && sudo apt install gpg


wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg


echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

Install:

sudo apt update && sudo apt install terraform

List available terraform versions

sudo apt update && apt policy terraform

Upgrade to terraform specific version, eg:

sudo apt update && sudo apt install terraform=1.9.2-1

Editors and tools

HashiCorp Terraform - Syntax highlighting and autocompletion for Terraform

CLI tool that generates tf/json and tfstate files based on existing infraestructure (reverse Terraform). It supports AWS, among many other providers.


Sample basic usage

Note: It assumes aws CLI is installed and configured with: aws configure


mkdir terraform-aws-dev-box

cd terraform-aws-dev-box


touch main.tf

with content:

terraform {

  required_version = ">= 1.3, < 2.0"


  required_providers {

    aws = {

      source  = "hashicorp/aws"

      version = "~> 4.25.0"

    }

    random = {

      source  = "hashicorp/random"

      version = "~> 3.2.0"

    }

    template = {

      source  = "hashicorp/template"

      version = "~> 2.2.0"

    }

  }

}


provider "aws" {

  #alias  = "ireland"

  default_tags {#requires hashicorp/aws >=3.38.0

    tags = {

      COUEnv      = local.env

      ci          = local.ci

      Department  = local.department

      Program     = local.program

    }

  }

  region = "eu-west-1"

}


resource "aws_instance" "examplej" {

  ami           = "ami-0c55b159cbfafe1f0"

  instance_type = "t2.micro"

}


Apply / Destroy

terraform init 


terraform validate

terraform plan

terraform apply

// Apply only module 'ssm'

terraform apply -target=module.ssm


terraform destroy

// Destroy only module 'ssm'

terraform destroy -target=module.ssm

Moved block

The moved block, introduced as part of Terraform 1.1, provided a programmatic method for refactoring resources within a Terraform configuration file.

Reference> https://developer.hashicorp.com/terraform/language/modules/develop/refactoring

Commands

Terraform CLI> https://www.terraform.io/docs/cli/index.html


terraform console

$ terraform console

> module.apigateway

{

  "deployment_invoke_domain_name" = "jjkibz9fvg.execute-api.eu-west-1.amazonaws.com"

  "deployment_invoke_url" = "https://jjkibz9fvg.execute-api.eu-west-1.amazonaws.com/dev"

}

terraform destroy [options]

Only asking for confirmation for the first target, auto-aprove for others:

terraform destroy -target=module.fargate \

&& terraform destroy -auto-approve \

    -target=module.iam\

    -target=module.cloudwatch

terraform fmt

Format:

terraform fmt -recursive

terraform graph [options]

Note: 'graphviz' must be installed:  sudo apt install graphviz

terraform graph | dot -Tsvg > graph.svg

terraform init [options]

If no access to the AWS account available, you can disable the backend so that validate subcommand can be used.

terraform init -backend=false

terraform output [options] [NAME]

Print outputs after 'terraform apply'. Note that sensitive values are only printed when queried explicitly:

$ terraform output

access_key_id = "AKIAWKIV7VD7AIDXXV5Q"

secret_access_key = <sensitive>

but:

$ terraform output secret_access_key

"A279SIlInmTFttyLj+Ojpzw4sPBqGZDqeGpCmn5n"


terraform plan [options]

[Planning modes]

-destroy Destroy mode

-refresh-only Refresh-only mode


[Planning options]

-refresh=false Disables the default behavior of synchronizing the Terraform state with remote objects before checking for configuration changes.

-replace=ADDRESS Instructs Terraform to plan to replace the resource instance with the given address.

-target=ADDRESS Instructs Terraform to focus its planning efforts only on resource instances which match the given address and on any objects that those instances depend on.

-var 'NAME=VALUE' Sets a value for a single input variable declared in the root module of the configuration.

-var-file=FILENAME Sets values for potentially many input variables declared in the root module of the configuration, using definitions from a "tfvars" file.


[Other options]

-no-color Disables terminal formatting sequences in the output.


terraform show [options] [file]

Provide output from a state or plan file. If needed, use terraform refresh (although deprecated) to update it.

terraform show

terraform show -json

terraform state <subcommand> [options] [args]

terraform state list

terraform state pull


Variables

Reference:

https://www.terraform.io/language/values/variables


Type keywords and constructors

string

number

bool

list(<TYPE>)

set(<TYPE>)

map(<TYPE>)

object({<ATTR NAME> = <TYPE>, ... })

tuple([<TYPE>, ...])


Type string, not null (sample)

variable "fqdm" {

  description = "Fully qualified domain name"

  type        = string

  nullable    = false

}


Type string, not null, validate predifined values (sample)

variable "on_off_centralized" {

  description = "Allow to control the switching on and off of the environment in a centralized way? {'no', 'yes'}"

  type        = string

  nullable    = false

  validation {

    condition     = can(regex("^(no|yes)$", var.on_off_centralized))

    error_message = "The on_off_centralized value must be one of {'no', 'yes'}"

  }

}


Type map<string, string> usable for tags (sample)

Note: Tags type is map(string) rather than map(list(string)) since each key should have one value

variable "tags_common" {

  description = "Common tags map: env, ci, department, program"

  type        = map(string)

  nullable    = false

}

Instantiation:

tags_common =  {

    env         = local.env

    ci          = local.ci

    department  = "My department"

    program     = "My program"

}


Static value lookup:

program = var.tags_common["program"]

program = lookup(var.tags_common, "program")


Optional Object Type Attributes with Defaults (sample)

Optional defaults were introduced in Terraform 1.3

https://www.hashicorp.com/blog/terraform-1-3-improves-extensibility-and-maintainability-of-terraform-modules

Below is an example of using the optional object type attribute with a default value when defining a variable as "ingress_rules": 

variable "ingress_rules" {

  type = list(object({

    port = number,

    description = optional(string),

    protocol = optional(string, "tcp"),

  }))

}

You can apply the following values to this variable: 

rules = [

  { port = 80, description = "web" },

  { port = 53, protocol = "udp" }

]


Functions

See Reference > Functions


merge

It takes an arbitrary number of maps or objects, and returns a single map or object that contains a merged set of elements from all arguments.

tags = merge(var.tags_common, { Name = "${var.bucket_myapp_data1}-${var.env}-s3" })


length(list, map, or string)

If given a list or map, the result is the number of elements in that collection.

If given a string, the result is the number of characters in the string.

> length([])

0

> length(["a", "b"])

2

> length({"a" = "b"})

1

> length("hello")

5


one(list, set, or tuple value with either zero or one elements)

Specialized function intended for the common situation where a conditional item is represented as either a zero- or one-element list, where a module author wishes to return a single value that might be null instead. 

> one([])

null

> one(["hello"])

"hello"


replace(string, substring, replacement)

If substring is wrapped in forward slashes, it is treated as a regular expression, using the same pattern syntax as regex. If using a regular expression for the substring argument, the replacement string can incorporate captured strings from the input by using an $n sequence, where n is the index or name of a capture group.

name_elb  = "ecs-lb-private-${replace(local.ci, "_", "-")}${local.env}"

Eg: Domain name of URL

replace(aws_api_gateway_deployment.deployment_defensatf.invoke_url, "/^https?://([^/]*).*/", "$1")


setunion(sets...)

The given arguments are converted to sets, so the result is also a set and the ordering of the given elements is not preserved.

setunion(["a", "b"], ["b", "c"], ["d"])


substr(string, offset, length)

Extracts a substring from a given string by offset and (maximum) length.

substr("hello world", 0, 32)


try(expressions...)

It evaluates all of its argument expressions in turn and returns the result of the first one that does not produce any errors.

> try(local.foo.doesnotexist, "fallback")

fallback