Terraform basics
Use the short tf
Add to ~/.zshrc
:
# alias
alias tf="terraform"
# or function
tf() {
terraform "$@"
}
Cli
# format the configuration, by default only the current directly is processed.
tf fmt -recursive -check
# validate the configuration
tf validate
# inspect the current state
tf show
# list all the resources in your project's state
tf state list
# reads and output variables from the state file
tf output
# output the `bucket-name`
tf output -raw bucket-name
Providers
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
source
: hostname + namespace + provider type- Terraform installs providers from Terraform Registry by default
- The full name is:
registry.terraform.io/hashicorp/aws
- The full name is:
Versions
# exact version 3.1.0
version = "3.1.0"
# the latest version that is at greater than 4.5.9
version = ">= 4.5.0"
# the latest version of V1.x, but not v2 and beyond
version = "~> 1.2"
- If you have the lock file
.terraform.lock.hcl
,tf init
will install the version specified in the lock file. - If TF did not find a lock file, it would download the latest versions of the providers that fulfils the version constraints you defined in the
required_providers
block. - If the versions defined in the lock files'
provider
block do not match the versions defined in your configuration'srequired_providers
block, Terraform will prompt you to re-initialize your configuration using the-upgrade
flag.
tf init --upgrade
- The
init
command will download and install the providers defined in the configuraion. - The
--upgrade
flag will upgrade all providers to the latest version consistent with the version constraints specified in your configuration - You should NEVER directly modify the lock file.
Resource
resource "aws_instance" "app_server" {
ami = "ami-0cc51e967b1cbe471"
instance_type = "t2.micro"
tags = {
Name = var.instance_name
}
}
- Resource type (
aws_instance
) and resource name (app_server
) form a unique ID for the resource.
Terraform Cloud
terraform {
cloud {
organization = "daniel-tf-aws"
workspaces {
name = "learn-terraform-aws"
}
}
}
- Need to create an account in Terraform Cloud and create an organization.
tf login
- The token will be saved to
~/.terraform.d/credentials.tfrc.json
- Execution Mode:
- Remote: Your plans and applies occur on HCP Terraform's infrastructure. You and your team have the ability to review and collaborate on runs within the app.
- Need to configure
AWS_ACCESS_KEY_ID
andAWS_SECRET_ACCESS_KEY
as Environment variables in the workspace
- Need to configure
- Local: Your plans and applies occur on machines you control. HCP Terraform is only used to store and synchronize state.
- Remote: Your plans and applies occur on HCP Terraform's infrastructure. You and your team have the ability to review and collaborate on runs within the app.
Plan
tf plan -out tfplan
tf show tfplan
tf show -json tfplan | jq > tfplan.json
tf '.configuration.provider_config' tfplan.json
tf apply tfplan
tf plan -destroy -out tfplan-destory
tf destroy
- Generate a saved plan with the
out
flag. - Use the
show
command to print out the saved plan. - Pass the
-json
flag toshow
command to convert the saved plan into JSON; You can pass it tojq
to format it, and save the output into a new file. - You can create a destroy plan with the
destroy
flag. - The
destroy
command is a shortcut that creates a destroy plan and then waits for you to approve it.
tf plan --replace="aws_instance.example"
tf apply --replace="aws_instance.example"
-replace
flag:- you can use it on
plan
andapply
operations - to safely recreate resources in your environment even if you have not edited the configuration
- it allows you to target specific resources and avoid destroying all the resources in your workspace just to fix one of them
- you can use it on
tf plan --refresh-only
tf apply --refresh-only
- The
-refresh-only
mode makes it safer to check Terraform state against real infrastructure by letting you review proposed changes to the state file. - If you suspect that your infrastructure configuration changed outside of the Terraform workflow, you can use
-refresh-only
flag to inspect what the changes to your state file would be. - the
-refresh-only
flag for theapply
operation does not attempt to modify your infrastructure to match your Terraform configuration -- it only gives you the option to review and track the drift in your state file.
NOTES:
- Although you mark an input variable as
sensitive
, Terraform still stores the value in plaintext in the plan file. Never commit a plan file to version control, whether as a binary or in JSON format. - When you apply a saved plan file, Terraform will not prompt you for approval and instead immediately execute the changes. This workflow is primarily used in automation.
- Never commit
.tfvars
files to version control.
Plan output
+
: the resource will be created~
: the resource will be updated in place-/+
: the resource will be destroyed and recreated
Apply
Apply process
When you approve the plan and apply the configuration, Terraform will:
- Lock your workspace's state, so that no other instances of Terraform will attempt to modify your state or apply changes to your resources. If Terraform detects an existing lock file (
.terraform.tfstate.lock.info
), it will report an error and exit. - Create a plan, and wait for you to approve it. Alternatively, you can provide a saved plan created with the
terraform plan
command, in which case Terraform will not prompt for approval. - Execute the steps defined in the plan using the providers you installed when you initialized your configuration. Terraform executes steps in parallel when possible, and sequentially when one resource depends on another.
- Update your workspace's state with a snapshot of the new state of your resources.
- Unlock your workspace's state.
- Report the changes it made, as well as any output values defined in your configuration.
Error
When Terraform encounters an error during an apply step, it will:
- Log the error and report it to the console.
- Update the state file with any changes to your resources.
- Unlock the state file.
- Exit.
Your infrastructure may be in an invalid state after a Terraform apply step errors out. Terraform does not support automatically rolling back a partially-completed apply. After you resolve the error, you must apply your configuration again to update your infrastructure to the desired state.
Common reasons for apply errors include:
- A change to a resource outside of Terraform's control.
- Networking or other transient errors.
- An expected error from the upstream API, such as a duplicate resource name or reaching a resource limit.
- An unexpected error from the upstream API, such as an internal server error.
- A bug in the Terraform provider code, or Terraform itself.
Example: Your existing configuration has created a S3 bucket, now you update the configuration to add an object to the bucket. You created the plan, but then deleted the s3 bucket from the AWS console, then you try to apply the plan you created before deleting the bucket. You need to create a new plan and apply it.
Variables
Variable types
-
string
-
number
-
bool
-
list: a sequence of values of the same type, example:
list(string)
- slice(): get a subnet of a list
-
map: a lookup table, matching keys to values, all of the same type, example:
map(string)
-
set: an unordered collection of unique values, all of the same type, example:
set(string)
-
tuple: a fixed-length sequence of values of specified types
-
object: a lookup table, matching a fixed set of keys to values of specified types
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "resource_tags" {
description = "Tags to set for all resources"
type = map(string)
default = {
project = "project-alpha",
environment = "dev"
}
}
var.resource_tags["project"]
var.resource_tags["environment"]
- Terraform automatically loads all files in the current directory with the exact name
terraform.tfvars
, or matching*.auto.tfvars
. You can use the-var-file
flag to specify other files by name.
Interpolate variables in strings
module "lb_security_group" {
source = "terraform-aws-modules/security-group/aws//modules/web"
version = "4.17.0"
name = "lb-${var.resource_tags["project"]}-${var.resource_tags["environment"]}"
}
Validation
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
condition = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-"
error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
}
}
Sensitive variables
variable "db_password" {
description = "Database administrator password"
type = string
sensitive = true
}
-
How to set value for sensitive variables:
- Option 1: provide the sensitive variable values when you run
tf apply
- Option 2: set the values in
secret.tfvars
- Option 2: use environment variables matching the pattern
TF_VAR_<VARIABLE_NAME>
, for example:TF_VAR_db_username
- Option 1: provide the sensitive variable values when you run
-
Store:
- Redacted in command output and log files
- Plain text in state files
Output
output "lb_url" {
description = "The URL of the load balancer"
value = "http://${module.elb_http.elb_dns_name}/"
}
output "db_password" {
description = "The password for the database"
value = var.db_password
sensitive = true
}
- Terraform stores output values in the configuration's state file.
- You can use string interpolation in the output
- You can mark an output variable as
sensitive
- Terraform will redact sensitive outputs when planing, applying and destroying the configuration, and query all outputs
- The sensitive outputs will be in plain text when you query a specific output by name, query all outputs in JSON format, or when you use outputs from a child module in your root module.
❯ tf output
db_password = <sensitive>
db_username = <sensitive>
lb_url = "http://lb-bNV-project-alpha-dev-447970585.us-east-1.elb.amazonaws.com/"
web_server_count = 4
❯ tf output db_password
"notasecurepassword"
❯ tf output -raw db_password
notasecurepassword
❯ terraform output -json
- Terraform wraps string outputs in quotes by default, use
-raw
flag to get the output without quotes. - Use the
-json
format to get machine-readable format for automation. Remember sensitive outputs will be in plain text because it assumes that an automation tool will use it.
Expressions
resource "aws_instance" "ubuntu" {
count = var.high_availability == true ? 3 : 1
associate_public_ip_address = count.index == 0 ? true : false
tags = local.common_tags
}
resource "aws_elb" "learn" {
instances = aws_instance.ubuntu[*].id
}
output "first_tags" {
value = aws_instance.ubuntu[0].tags
}
output "private_addresses" {
value = aws_instance.ubuntu[*].private_dns
description = "The private IP addresses of the instances"
}
? :
: condition expression*
: splat expression, returns an array
Workspace
❯ tf workspace list
* default
❯ tf workspace new dev
Created and switched to workspace "dev"!
You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.
❯ tf workspace select dev
Switched to workspace "dev".
HCP Terraform workspaces behave differently from Terraform CLI workspaces. Terraform CLI workspaces allow multiple state files to exist within a single directory, letting you use one configuration for multiple environments. HCP Terraform workspaces contain everything needed to manage a given set of infrastructure, and function like separate working directories.
Logging
export TF_LOG_CORE=TRACE
export TF_LOG_PROVIDER=TRACE
export TF_LOG_PATH=logs.txt
- Set the above environment variables to enable logging for Terraform core and providers.
HCP Terraform
Migrate State
- Add
Cloud
configuration tf init
- Remove local state files
tf apply
terraform {
cloud {
organization = "daniel-tf-aws"
workspaces {
name = "learn-state-migration"
}
}
}
tf init
rm -f terraform.tfstate*
tf apply