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
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
Visual Studio Code has marketplace extension
HashiCorp Terraform - Syntax highlighting and autocompletion for Terraform
Terraformer (see subpage)
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
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