Infrastructure automation to provision and manage resources in any cloud or data center.
https://sites.google.com/site/pawneecity/terraform/backend-terraform
https://sites.google.com/site/pawneecity/terraform/terraformer-terraform
https://sites.google.com/site/pawneecity/terraform/trivy-terraform
$ git clone https://github.com/hashicorp/terraform-provider-aws.git && \
cd ./terraform-provider-aws/examples/
https://acloudguru.com/blog/engineering/the-ultimate-terraform-cheatsheet
https://www.terraform-best-practices.com/naming
https://www.terraform.io/language/values/variables
https://developer.hashicorp.com/terraform/language/functions
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:
touch ~/.terraformrc
echo "plugin_cache_dir = \"$HOME/.terraform.d/plugin-cache/\"" >> ~/.terraformrc
Note: If the directory does not exist, the 1st execution of terraform init will create it.
If manual creation is needed> mkdir -p $HOME/.terraform.d/plugin-cache/
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
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.
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
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
Terraform CLI> https://www.terraform.io/docs/cli/index.html
$ 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"
}
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
Format:
terraform fmt -recursive
Note: 'graphviz' must be installed: sudo apt install graphviz
terraform graph | dot -Tsvg > graph.svg
If no access to the AWS account available, you can disable the backend so that validate subcommand can be used.
terraform init -backend=false
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"
[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.
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 list
terraform state pull
Reference:
https://www.terraform.io/language/values/variables
string
number
bool
list(<TYPE>)
set(<TYPE>)
map(<TYPE>)
object({<ATTR NAME> = <TYPE>, ... })
tuple([<TYPE>, ...])
variable "fqdm" {
description = "Fully qualified domain name"
type = string
nullable = false
}
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'}"
}
}
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 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" }
]
See Reference > Functions
mostly used for parsing preexisting lists and maps rather than generating ones. For example, we are able to convert all elements in a list of strings to upper case
[for el in var.list : upper(el)]
we can include an if statement as a filter in for expressions. Unfortunately, we are not able to use if in logical operations like the ternary operators we used before. The following state will try to return a list of all non-empty elements in their uppercase state
[for el in var.list : upper(el) if el != ""]
Reference > https://developer.hashicorp.com/terraform/language/meta-arguments/for_each
For an example, see> https://sites.google.com/site/pawneecity/terraform/ecr-terraform
If a resource or module block includes a for_each argument whose value is a map or a set of strings, Terraform creates one instance for each member of that map or set.
In blocks where for_each is set, an additional each object is available in expressions, so you can modify the configuration of each instance. This object has two attributes:
each.key — The map key (or set member) corresponding to this instance.
each.value — The map value corresponding to this instance. (If a set was provided, this is the same as each.key.)
Map example:
resource "azurerm_resource_group" "rg" {
for_each = tomap({
a_group = "eastus"
another_group = "westus2"
})
name = each.key
location = each.value
}
Set of string example:
resource "aws_iam_user" "the-accounts" {
for_each = toset(["Todd", "James", "Alice", "Dottie"])
name = each.key
}
Child module example:
# my_buckets.tf
module "bucket" {
for_each = toset(["assets", "media"])
source = "./publish_bucket"
name = "${each.key}_bucket"
}
See Reference > Functions
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" })
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
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"
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")
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"])
Extracts a substring from a given string by offset and (maximum) length.
substr("hello world", 0, 32)
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