- terraform version => find the terraform version
- terraform -chdir=<path_to/tf> => switch the working directory
- terraform plan => create an execution plan
- terraform plan -out <plan_name> => output a deployment plan
- terraform plan -destroy => output a destroy plan
- terraform init => initialize the directory
- terraform apply => apply changes
- terraform apply <plan_name> => apply a specific plan
- terraform apply -target= => only apply changes to a targeted resource
- terraform apply -var my_variable= => pass a variable via the command line
- terraform destroy => destroy the managed infrastructure
- terraform providers => get provider info used in configuration
- terraform init or terraform init -upgrade
- terraform plan or terraform plan -out
- terraform apply -auto-approve
- terraform output -json | jq
resource “aws_vpc” “main” {
cidr_block = var.base_cidr_block
}
BLOCK TYPE "BLOCK LABEL” “BLOCK LABEL” {
IDENTIFIER = EXPRESSION
}
- Language consists of blocks, arguments, and expressions. Blocks are containers for objects like resources.
- File extension - .tf and .tf.json
- Text encoding - plain text files (UTF-8), Unix-style line endings(LF), Windows-style line endings(CRLF)
- Modules are a collection of .tf and/or .tf.json files in a directory
Resources are the most important part of the Terraform language. Resource blocks describe infrastructure objects like virtual networks, compute instances, or components like DNS records.
- depends_on -> specify hidden dependencies
- count -> create multiple resource instances according to a count
- for_each -> create multiple instances according to a map or a set of strings
- provider -> select a non-default provider configuration
- lifecycle -> set lifecycle customizations
- provisioner and connection -> take extra actions after resource creation
resource "aws_db_instance" "example" {
timeouts {
create = "60m"
delete = "2h"
}
}
- There are some resource types that provide special timeouts, nested block arguments that allow for customization of how long certain operations are allowed to take before they are deemed failed
- create -> create resources that exist in the configuration but are not associated with a real infrastructure object in the state
- destroy -> destroy resources that exist in the state but no longer exist in the configuration
- update in-place -> update in-place resources whose arguments have changed
- destroy and re-create -> destroy and re-create resources whose arguments have changed, but which cannot be updated in-place due to remote API limitations
- Local-only resources -> specialized resource types that operate only within Terraform itself, calculating some results and saving those results in the state for future use.
Examples: ssh keys, self-signed certs, random ids.
- the name of a variable can be any valid identifier except for:
source, version, providers, count, for_each, lifecycle, depends_on, locals
- Example:
variable "arbitrary_name" {
type = string
}
- optional arguments for variable declaration:
default type description validation sensitive
- type constraints -> allows you to restrict the type of value that will be accepted as the value for a variable. These are optional, but are recommended. They can serve as a helpful reminder for users of a module and return helpful error messages if the wrong type is used. such as:
string number bool
- type constructors -> allows you to specify complex types such as collections.
list(type) set(type) map(type) object({attribute = type, ...}) tuple([type, ...])
- Argument Examples -> we can add a description or validation rules to input variables
input variable description
variable "image id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
}
Custom Validation Rules
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-"."
}
}
- Using the sensitive argument -> prevents terraform from showing it's value in the plan or apply output
variable "user_information" {
type = object({
name = string
address = string
})
sensitive = true
}
- Using input variable values
vars can be accessed with an expression such as var.{var_name}
ex. ami = var.image_id
- How to assign variables to Root Modules Variables
How to assign values:
in a terraform cloud workspace individually, with the -var command like option
terraform apply -var='image_id_list=["ami-abc123", "ami-def456"]' -var="instance_type=t2.micro" in variable definitions like .tfvrs or .tfvars.json as environment variables
Calling the variable definition file from the command line:
terraform apply -var-file="testing.tfvars"
Terraform automatically loads definition files if present:
Files named exactly terraform.tfvars or terraform.tfvars.json Any files ending in .auto.tfvars or .auto.tfvars.json
- Environment Variables
As a fallback for the other ways of defining variables, terraform will search the environment variables named TF_VAR_
ex. export TF_VAR_image_id=ami-abc123 terraform plan
- Precedence -> the order in which variables are loaded
Environment variables terraform.tfvars terraform.tfvars.json *.auto.tfvars or *.auto.tfvars.json Any command-line options like -var and -var-file
- They have many use cases:
- A child module can use them to expose a subset of resource attributes to the parent module.
- A root module can use them to print values in the CLI.
- Root module outputs can be accessed by other configurations via the terraform_remote_state data.
- Each output value that is exported by a module must be declared using an output block. The label after the output keyword must be a valid identifier. Within a root module this name is displayed to the user. In a child module, it can be used to access the output's value. The value argument takes an expression whose output is to be returned to the user.
- Optional Arguments for variable declaration
description
output "instance_ip_addr" {
value = aws_instance.server.private_ip
description = "The private IP address of the main server instance."
}
sensitive
output "out" {
value = "xyz"
sensitive = true
}
depends_on
variable "instance_ip_addr" {
value = aws_instance.server.private_ip
description = "The private IP address of the main server instance"
depends_on = [
// Security group rule must be created before this IP address could
// actually be used, otherwise the services will be unreachable.
aws_security_group_rule.local_access,
]
}
- Local Values are like a function's temporary local variables
- allow you to assign a name to an expression
- allow you to use the variable multiple times within a module without repeating it
locals {
service_name = "forum"
owner = "community team"
}
resource "aws_instance" "example" {
type = string
tags = local.common_tags
}
they can consist of .tf files as well as .tf.json files in a directory
- 3 Module Types:
- Root Module -> you need at least one root module.consists of the resources defined in the .tf files in the main working directory
- Child Modules -> modules that are called by the root module. they can be called multiple times within the same configuration. Multiple configurations can use the same child module
- Public Modules -> modules loaded from a public or private registry.
Calling a Child Module
module "servers" {
source = "./app-cluster"
servers = 5
}
- Module Arguments -> 4 argument types
- Source argument -> required for all modules
- Version argument -> recommended for modules from a registry
- Input variable arguments
- Meta-arguments -> ex. for_each, depends_on, count, providers
- Accessing Module Output Values
- Child modules can declare output values to selectively export values which are accessible by the calling module.
- if the module exported an output value named instance_ids, then the calling module can reference that result using the expression module.servers.instance_ids.. see example below:
resource "aws_elb" "example" {
instances = "module.servers.instance_ids"
}
- Transferring Resource State We can use the terraform state mv command to inform terraform that the child module block has been moved to a different module. Because? when you split code into other child modules, or when moving resource blocks between modules you can use Terraform to see the new location of the module block as an entirely different resource.
when passing resource addresses:
- Resources within the child modules must be prefixed with module."module_name"
- If a module is called with count or for_each, it must be prefixed with module."module_name"["index"] instead.
- Taint Resources Within A Module command terraform taint module."module_name"."resource_name"
- It is not possible to taint an entire module. Instead each resource in a module must be tainted separately
Modules Sources tell terraform where to look for source code. During terraform init module installation step, terraform uses the module source. There are 8 Module Source Types:
- local paths -> must begin with either ./ or ../
module "consul" {
source = "./consul" }
- terraform registry
module "consul" {
source = "hashicorp/consul/aws" version = "0.1.0" }
- GitHub
https example
module "consul" {
source = "github.com/hashicorp/example" }
ssh example
module "consul" {
source = "git@github.com:hashicorp/example.git" }
- BitBucket
module "consul" {
source = "bitbucket.org/hashicorp/terraform-consul-aws" }
- Generic Git, Mercurial repositories
Generic Git
module "vpc" {
source = "git::https://example.com/vpc.git" }
module "storage" {
source = "git::ssh://username@example.com/storage.git" }
we can select a specific revision
module "vpc" {
source = "git::https://example.com/vpc.git?ref=v1.2.0" }
Generic Mercurial
module "vpc" {
source = "hg::https://example.com/vpc.hg" }
- HTTP URLs
if the GET request is successful, Terraform looks in the following locations:
the value of a response header field named X-Terraform-Get
if the response is an HTML page, a meta element w/ the name terraform-get
here's the example of the meta element
< meta name="terraform-get"
content="github.com/hashicorp/example" />
Fetching Archives over HTTP
module "vpc" {
source = "https://example.com/vpc-module.zip"
}
The extensions Terraform recognizes are zip, tar.bz2(tbz2), tar.gz(tgz), tar.xz(txz)
module "vpc" {
source = "https://example.com/vpc-module?archive=zip"
}
- S3 Buckets the module installer will look for AWS creds first by checking set environment variables: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. Second, the default profile in the .aws/credentials. Third, if it's running on an EC2 instance, it will check the temporary creds associated w/ the instance's IAM instance profile.
module "consul" {
source = "s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip" }
- GCS buckets
To set credentials: enter the path of your service account key file in the GOOGLE_APPLICATION_CREDENTIALS environment variable. If you're running Terraform from a GCE instance, the default credentials are automatically available. On your computer, you can make your Google identity available by running: gcloud auth application-default login.
module "consul" {
source = "gcs::https://googleapis.com/storage/v1/modules/foomodule.zip" }
- The simplest expressions are literal values, like ACG or 1, but the terraform language also allows more complex expressions, such as references to data exported by resources and a number of built-in functions.
- Terraform uses types values such as stirng, number, bool, list/tuple, map/object, null
- 7 types of named values in Terraform: resources, input variables, local values, child module outputs, data sources, filesystem and workspace info, block-local values
- Conditional expressions. syntax:
condition ? true_val : false_val
Here's an example of conditional expressions. This defines defaults to replace invalid values:
var.a != "" ? var.a : "default-a" -> if var.a is empty it will to default to the string "default-a" otherwise it will equal the actual values of var.a
These result type examples perform the same functionality:
var.example ? 12 : "hello" var.example ? tostring(12) :
Terraform includes a number of built-in functions you can call from within expressions to transform and combine values. Built-in function syntax:
< FUNCTION NAME > (< ARGUMENT 1 >, < ARGUMENT 2 >)
function example:
min([55, 2453, 2]...)
- using sensitive data as a function argument:
local.baz
{
"a" = (sensitive)
"b" = "dog"
}
so if I call a function with named keys along with the local vars as an argument I get a keys(local.baz) produces a (sensitive) output
- Function calls
function call general syntax:
max(5, 12, 9)
We can use the terraform console and try using the max built-in function:
terraform console
max(5, 12, 9)
Each terraform configuration can specify a backend.
- local backend for beginners
- remote backend for working with team and managing large infrastructure
Terraform includes a built-in selection of backends. Backend configuration is only used by terraform CLI. Terraform cloud and Enterprise always use their own state storage. It is common to use terraform Cloud along with terraform CLI, so they recommend to include a backend block in their configuration and configure the remote backend to use the relevant terraform cloud workspaces.
- Backend Block
terraform {
backend "remote" {
organization = "corp_example"
workspaces {
name = "ex-app-prod"
}
}
}
Limitations for backend blocks: A configuration can only provide one backend block. A backend block cannot refer to named values.
When the backend changes in a configuration, you must run terraform init When the backend changes, Terraform gives you the option to migrate your state. You can backup your state by copying the terraform.tfstate file
terraform {
backend "local" {
path = "/path/to/terraform.tfstate"
}
}
we can use two configuration variables: path(which is used in the example above) -> the path to the tfstate file. workspace_dir -> the path to non-default workspaces.
The data source configuration for a local backend would be:
data "terraform_remote_state" "zland" {
backend = "local"
config = {
path = "${path.module}/../../terraform.tfstate"
}
}
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "company"
workspaces {
name = "my-app-prod"
}
}
}
But if we wanted to specify the backend from the CLI instead of having it in the main.tf we have to give the empty brackets in the main.tf
terraform {
required_version = "~> 0.12.0"
backend "remote" {}
}
on the CLI we use this command: terraform init -backend-config=backend.hcl
backend.hcl
workspaces {
name = "workspace"
}
hostname = "app.terraform.io"
organization = "company"
data "terraform_remote_state" "foo" {
backend = "remote"
config = {
organization = "company"
workspaces = {
name = "workspaces"
}
}
}
Configuration Variables:
- hostname (optional)
- organization (Requireed)
- token (optional)
- workspaces (Required) 4a. name (optional) 4b. prefix (optional)
The below block configuration will look for an s3 bucket with that key and region but it will also need the IAM permissions to do so. The permissions are: s3:ListBucket, s3:GetObject, s3:PutObject. If using the state locking feature, you will need the IAM permissions on the DynamoDB table. The permissions are: dynamodb:GetItem, dynamodb:PutItem, dynamodb:DeleteItem.
The terraform remote state data source will return all of the root module outputs defined in the reference remote state.
example block configuration:
terraform {
backend "s3" {
bucket = "mybucket"
key = "path/to/my/key"
region = "us-east-1"
}
}
data source configuration:
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "terraform-state-prod"
key = "network/terraform.tfstate"
region = "us-east-1"
}
}
using azure CLI or a Service Principal:
terraform {
backend "azurerm" {
resource_group_name = "StorageAccount-ResourceGroup"
storage_account_name = "abcd1234"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}
data source using a service principal:
data "terraform_remote_state" "foo" {
backend = "azurerm"
config = {
storage_account_name = "terraform123abc"
container_name = "terraform-state"
key = "prod.terraform.tfstate"
}
}
using azure AD:
terraform {
backend "azurem" {
storage_account_name = "abcd1234"
container_name = "tfstate"
key = "prod.terraform.tfstate"
use_azuread_auth = true
subscription_id = "0000-000-0000"
tenant_id = "0000-000-0000"
}
}
data source config using azure AD:
data "terraform_remote_state" "foo" {
backend = "azurerm"
config = {
storage_account_name = "terraform123abc"
container_name = "terraform-state"
key = "prod.terraform.tfstate"
use_azuread_auth = true
subscription_id = "0000-000-0000"
tenant_id = "0000-000-0000"
}
}
configuration variables -> storage_account_name (required), container_name (required), key (required), environment (optional), endpoint (optional), snapshot (optional)
service principal config variables -> resource_group_name (required), cliend_id (optional), client_certification_password (optional), client_certification_path (optional), subscription_id (optional), tenant_id (optional), client_secret (optional)
use ACG-Terraform-course folder, the main.tf was downloaded.
- terraform fmt => to make sure our configuration is formatted correctly
- terraform validate => to validate configuration