AAtsushi's Blog
DevOps

Notes: Udemy HashiCorp Certified: Terraform Associate 2022

→ 日本語版を読む
  • Overview
    • Terraform version
      • Specifying the Terraform version
    • Provider version constraints and specification
      • .terraform.lock.hcl file
      • Differences between Terraform versions
      • AWS provider authentication configuration
    • Variable
      • Data types
      • Assigning values to variables
      • Variable loading precedence
    • About Local
    • About Count
      • Simple resource replication
      • Using List and Count together
      • Using ternary operator and Count together
    • About Dynamic blocks
    • About for_each
    • About Provisioner
      • remote-exec
      • self object
      • local-exec
      • on_failure parameter
      • destroy-time provisioner
      • null_resource
    • About Data
    • About Module
      • Terraform Registry
      • Publishing modules to Terraform Registry
      • Standard module structure
    • About Workspace
    • Team collaboration
      • About Git
      • Backends
      • State file locking
      • Technique for not storing passwords on GitHub
      • Protecting .tfstate files
    • Multi-region
    • About Log
    • Resource dependencies
    • Folder structure
    • When resources are at large scale
    • Security considerations
    • Terraform commands
      • terraform state command
      • terraform import command
    • About Terraform Cloud
      • Sentinel
    • About Terraform Vault
    • Terminology
    • About comments
    • Functions
      • toset function
    • Languages supported by Terraform

Overview

Notes from the Udemy course "HashiCorp Certified: Terraform Associate 2022."

It's very easy to understand — I'd recommend Terraform beginners to watch it on Udemy.

Terraform Version

When running Terraform on a local PC for the first time, on Windows you download the binary executable (terraform.exe) from the Terraform website. The website offers the latest stable version and two to three previous generations for download. Older versions can be downloaded from GitHub.

Specifying the Terraform Version

The version of terraform.exe can be specified within the terraform block.

terraform {
  required_version = "> 1.1.0"
}

The symbols available for version constraints are listed here.

If the version of terraform.exe being run does not satisfy the specified condition, it returns an Unsupported Terraform Core version error.

Provider Version Constraints and Specification

A plugin is downloaded for each public cloud (called a provider) such as AWS, Azure, or GCP. When provisioning on AWS, specify the AWS provider.

Note that a Provider block is not required. Even when using a Provider, a Provider block does not need to be defined — Terraform treats it as an empty default configuration and retrieves the plugin. As a best practice, however, explicitly set a Provider block when using a Provider.

Terraform assumes an empty default configuration for any provider that is not explicitly configured.

Provider Configuration - Configuration Language | Terraform by HashiCorp

terraform {
  required_version = "> 1.1.0"
}

provider "aws" {
  region = "us-east-1"
}

Provider version constraints can be specified as required_providers within the terraform block. The version of the provider plugin to download can also be specified as version within the provider block.

terraform block:

  • required_version: version constraint for terraform.exe
  • required_providers: version constraint for providers

provider block:

  • version: version specification for the provider
terraform {
  required_version = "> 1.1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 2.0"
    }
  }
}

provider "aws" {
  region  = "us-east-1"
  version = ">=2.10,<=2.30"
}

If the version of the provider plugin being run does not satisfy the specified condition, it returns a Failed to query available provider packages error.

Note

Since Terraform 0.13, the version argument within the provider block is deprecated.

The version argument in provider configurations is deprecated. In Terraform 0.13 and later, always declare provider version constraints in the required_providers block. The version argument will be removed in a future version of Terraform.

.terraform.lock.hcl File

Provider plugins are downloaded with the terraform init command. Once a plugin is downloaded, its version is locked in the .terraform.lock.hcl file. To change the plugin version, you need to delete the .terraform.lock.hcl file.

# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.

provider "registry.terraform.io/hashicorp/aws" {
  version     = "2.30.0"
  constraints = "~> 2.0, >= 2.10.0, <= 2.30.0"
  hashes = [
    "h1:fSYy...",
    ...,
    ...,
  ]
}

Differences Between Terraform Versions

There are significant differences before and after Terraform v0.13.

  • Terraform v0.13 and later

For providers managed by HashiCorp, the required_providers block is not required, but writing it is recommended.

  terraform {
    required_providers {
      github = {
        source = "integrations/github"
        version = "4.3.2"
      }
    }
  }

  provider "github" {
    token = "8c6f0e838e998844a8bb32b0050a7dee6a31a4df"
  }

  • Terraform v0.12 and earlier

No required_providers block is written.

  provider "github" {
    token = "8c6f0e838e998844a8bb32b0050a7dee6a31a4df"
  }

AWS Provider Authentication Configuration

For the AWS Provider, there are various authentication methods:

  1. Parameters in the provider configuration
  2. Environment variables
  3. Shared credentials files
  4. Shared configuration files
  5. Container credentials
  6. Instance profile credentials and region

When writing credentials directly in the provider block:

provider "aws" {
  region     = "us-west-2"
  access_key = "my-access-key"
  secret_key = "my-secret-key"
}

Variable

Variables can be defined in .tf code. Type constraints are not required but should be written — they prevent mistakes like assigning a value of the wrong data type. description should also be written.

variable "instance_name" {
  description = "Value of the Name tag for the EC2 instance"
  type        = string
  default     = "AppServerInstance"
}

Data Types

The following data types are available:

  • string
  • number
  • bool
  • list
  • map
  • set
  • object
  • tuple

In Terraform, number and bool types are automatically converted to string type. Automatic conversion from string to number and bool types also occurs.

  • number/bool ⇔ string

Assigning Values to Variables

As described here, there are five ways to assign variable values. For production environments, it is best to set variable values in a terraform.tfvars file.

  • Terraform Cloud workspace (described later)
  • -var command line option
    • Run with the format terraform apply -var="image_id=ami-abc123"
  • Variable definitions (.tfvars) files
    • By default, looks for and loads terraform.tfvars. For a custom filename, run terraform apply -var-file="custom_name.tfvars"
  • Environment variables
    • Set as an environment variable: export TF_VAR_image_id='ami-abc123'
  • Variable Defaults: json variable "image_id" { default = "ami-abc123" }

Variable Loading Precedence

Variables are loaded in the following order of precedence. If a value is assigned to the same variable multiple times, the last loaded value overwrites the previous ones.

  • Environment variables
  • The terraform.tfvars file, if present.
  • The terraform.tfvars.json file, if present.
  • Any *.auto.tfvars or *.auto.tfvars.json files, processed in lexical order of their filenames.
  • Any -var and -var-file options on the command line, in the order they are provided. (This includes variables set by a Terraform Cloud workspace.)

About Local

Overusing locals reduces readability and maintainability — be careful not to overuse them.

locals {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
}

resource "aws_instance" "server" {
  ami           = local.ami
  instance_type = local.instance_type
}

About Count

Simple Resource Replication

Setting count = 3 inside a resource block simply creates three identical resources.

resource "aws_instance" "server" {
  count = 3

  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
}

Using List and Count Together

Combining count.index with a list-type variable allows replicating resources with different names.

variable "name" {
  default = ["dev", "stg", "prd"]

}

resource "aws_iam_user" "lb"{
  count = length(var.name)
  name = var.name[count.index]
}

Using Ternary Operator and Count Together

Combining count with the ternary operator allows you to control whether a resource is created or not. With the following, if the variable is true the resource is created, and if false it is not.

variable creation {
  type    = bool
  default = true
}

resource "aws_instance" "server" {
  count = var.creation == true ? 1 : 0

  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
}

  • What is the ternary operator?

<condition> ? <value if true> : <value if false>

About Dynamic Blocks

Dynamic blocks allow you to dynamically replicate blocks.

Set a list-type variable in for_each, then extract the list variable's value with <dynamic block name>.value to replicate the block.

variable "sg_ports" {
  type        = list(number)
  description = "list of ingress ports"
  default     = [8200, 8201,8300, 9200, 9500]
}

resource "aws_security_group" "dynamicsg" {
  name        = "dynamic-sg"
  description = "Ingress for Vault"

  dynamic "ingress" {
    for_each = var.sg_ports
    iterator = port
    content {
      from_port   = port.value
      to_port     = port.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

About for_each

When using count to create multiple resources with different Argument Values, changing the order of elements in the source list can cause issues. In such cases, use for_each.

Use count to replicate identical resources. Use for_each when any Argument Value differs.

Combining for_each with toset makes the index and element the same value. Only the keys of a set can be stored in for_each. In that case, the key value can be retrieved with each.key.

resource "aws_iam_user" "iam" {
  for_each = toset( ["user-01","user-02", "user-03"] )
  name     = each.key
}

Storing a map type in for_each allows storing not just keys but values as well. Retrieve the key with each.key and the value with each.value. Storing a map type in for_each also allows creating multiple resources.

resource "aws_instance" "myec2" {
  ami = "ami-0cea098ed2ac54925"
  for_each  = {
      small = "t2.micro"
      normal = "t2.medium"
   }
  instance_type    = each.value
  key_name         = each.key
  tags =  {
   Name = each.key
  }
}

About Provisioner

Various types of provisioners exist, such as remote-exec and local-exec.

Provisioners are a last resort. Generally there are better alternatives.

remote-exec

Calls and executes a script on the remote resource side after resource creation. For example, you can SSH into a created EC2 and execute a script. Can substitute for Ansible.

SSH and WinRM connections are supported.

resource "aws_instance" "web" {
  # ...

  connection {
    type     = "ssh"
    user     = "root"
    password = var.root_password
    host     = self.public_ip
  }

  provisioner "remote-exec" {
    on_failure = continue
    inline = [
      "puppet apply",
      "consul join ${aws_instance.web.private_ip}",
    ]
  }
}

self Object

Expressions within a block cannot reference the provisioner or parent resource by name. Instead, a special self object can be used.

The self object represents the provisioner's parent resource and has all the attributes of that resource. For example, use self.public_ip to reference the aws_instance.public_ip attribute.

local-exec

Calls an executable on the local side after resource creation. For example, you can run an Ansible playbook placed on the local PC.

resource "aws_instance" "web" {
  # ...

  provisioner "local-exec" {
    command = "echo ${self.private_ip} >> private_ips.txt"
  }
}

on_failure Parameter

With on_failure = continue, even if the provisioner fails to execute, it is treated as if it completed successfully. With on_failure = fail or without the on_failure parameter set, if the provisioner fails, the resource becomes tainted and is recreated on the next terraform apply.

destroy-time Provisioner

Destroy-time provisioner: Writing when = destroy inside a Provisioner block makes it a destroy-time provisioner. A destroy-time provisioner runs only when the resource is deleted (= terraform destroy).

Creation-time provisioner: The default Provisioner is a creation-time provisioner. To explicitly specify it, write when = creation. It runs only when the resource is created. If the provisioner fails, it is marked as tainted in the .tfstate file. Because it is tainted, the next terraform apply will delete and then recreate the resource.

null_resource

Using null_resource allows you to implement a flow where a resource is created only after null_resource confirms that a Google server is up.

Inside null_resource, a provisioner runs a curl command to verify the Google server is running.

resource "aws_eip" "lb" {
  vpc      = true
  depends_on = [null_resource.health_check]
}

resource "null_resource" "health_check" {

 provisioner "local-exec" {

    command = "curl https://google.com"
  }
}

triggers can be set as a parameter in the null_resource block.

Triggers can store map-type data. When the stored data changes, the provisioner is re-executed and null_resource is recreated.

In the example below, aws_eip.lb has count set to 0, meaning no EIP is created. Running terraform apply with this code creates only null_resource.ip_check.

Next, changing count from 0 to 1 makes an EIP get created. Since null_resource.ip_check is already created, without triggers it would not be recreated. However, with triggers, null_resource.ip_check is recreated and the Provisioner is re-executed.

resource "aws_eip" "lb" {
  vpc      = true
  count = 0
}

resource "null_resource" "ip_check" {

 triggers = {
    latest_ips = join(",", aws_eip.lb[*].public_ip)
  }

 provisioner "local-exec" {

   command = "echo Latest IPs are ${null_resource.ip_check.triggers.latest_ips} > sample.txt"

  }
}

About Data

data can be used to retrieve AMI values from AWS. Filtering is possible with filter.

data "aws_ami" "web" {
  most_recent = true
  owners = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm*"]
  }
}

About Module

What is the DRY Principle?

DRY stands for "Don't Repeat Yourself" — a software development principle to avoid repeating the same software patterns.

→ Make reusable resources into "modules" so they can be reused.

In the example below, ec2.tf (the module) is placed under ./modules/ec2, and referenced from ./myec2.tf.

To apply a module, the module must first be loaded using the terraform init command.

./modules/ec2/ec2.tf

resource "aws_instance" "myec2" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = "t2.micro"
}

./myec2.tf

module "ec2module" {
  source = "./modules/ec2"
}

When a variable is assigned inside a resource, its value can be overridden in the module block. To use a variable without allowing it to be overridden, use local.

Note that output blocks defined in a module are not displayed in the CLI.

By using an output block on the module side, the module's output value can be used when creating other resources. Reference the output value using the format module.<Module_Name>.<Output_Name>.

./modules/ec2/ec2.tf

resource "aws_instance" "myec2" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = var.instance_type
}

variable "instance_type" {
  default = "t2.nano"
}

output "instance" {
  value = aws_instance.myec2
}

./myec2.tf

module "ec2module" {
  source        = "./modules/ec2"
  instance_type = "t2.micro"
}

output "module_output" {
  value = module.ec2module.instance
}

The module version can be specified in the format version = "0.0.5" inside the module block.

For details on module version constraint specification, see here.

Terraform Registry

Modules can also be obtained directly from the Terraform Registry.

Modules obtained from the Terraform Registry are stored locally in ./.terraform/modules after terraform init.

module "ec2_cluster" {
  source                 = "terraform-aws-modules/ec2-instance/aws"
  version                = "~> 4.0"

  name                   = "my-cluster"

  ami                    = "ami-0d6621c01e8c2de2c"
  instance_type          = "t2.micro"
  subnet_id              = "subnet-4dbfb206"

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

The Terraform Registry contains modules created by members of the Terraform community. It's worth searching the Terraform Registry for reference modules first.

Verified modules in particular have been reviewed by HashiCorp and are maintained up to date by contributors.

Verified modules have a blue badge next to the module name.

The "Notes" for each module in the Terraform Registry should be read carefully — they explain required parameters.

Publishing Modules to Terraform Registry

Modules can also be published to the Terraform Registry.

There are five requirements for publishing a module to the Terraform Registry. All must be met.

  • The module must be uploaded to a public GitHub repository.

  • The repository naming convention terraform-<PROVIDER>-<NAME> must be followed.

  • The GitHub repository must have a description.

  • The standard module structure must be applied.

  • An x.y.z version tag must be included.

Standard Module Structure

Typically, the following four files are placed in the module folder.

(Filenames are arbitrary but the following naming is common and has become the de facto standard.)

  • README.md
  • main.tf
  • variables.tf
  • outputs.tf

About Workspace

Creating a workspace with terraform workspace new <workspace_name> creates a terraform.tfstate.d folder in the current directory, with a subfolder named after the workspace. A .tfstate file is created under the workspace-named folder. This state file is separate from the default workspace's .tfstate file.

terraform.workspace inside a .tf file retrieves the workspace name.

Using the lookup function as lookup(var.<map_variable>, terraform.workspace) retrieves a value associated with the workspace name.

terraform workspace allows creating different resources per workspace.

To check the list of workspaces, run terraform workspace list.

Team Collaboration

To develop Terraform code as a team, prepare the following environment:

git: Place .tf files and develop collaboratively.

S3: Place the terraform.tfstate file to share the state file among team members. Since the state file contains passwords and other data in plain text, set access restrictions on S3.

DynamoDB: Place the .terraform.lock.hcl file. Share the state file lock status among team members.

The above is the ideal architecture.

Note that the state file can be in S3 and the lock state managed with DynamoDB (i.e., managing Backend and state locking on AWS) while creating resources on Azure or GCP.

The .terraform folder, terraform.tfvars file, terraform.tfstate file, and crash.log file should not be git committed, so add them to .gitignore to prevent accidental commits.

Running git status shows uncommitted files and folders, so verify nothing was accidentally committed.

.gitignore file:

.terraform

*.tfvars

*.tfstate
*.tfstate.*

crash.log

About Git

To reference a local module, write a local path starting with ./ or ../ in source. To reference a module from a .tf file uploaded to a Git repository, prefix the repository URL with git::. To specify the code revision (branch), append ?ref= as a suffix.

module "demomodule" {
  source = "git::https://github.com/zealvora/tmp-repo.git?ref=v1.2.0"
}

Backends

The location where the terraform.tfstate file is stored is called the Backend.

By default, the Backend is the local PC.

To specify S3 as the Backend, add a backend block to the terraform block. The S3 bucket must be created in advance.

Other services besides S3 can also be used as backends.

terraform {
  backend "s3" {
    bucket = "kplabs-terraform-backend"
    key    = "network/terraform.tfstate"
    region = "us-east-1"
  }
}

In addition to the above, access keys are needed to access the S3 bucket.

Access keys should be written in the ~/.aws/config file. Download the AWS CLI and run aws configure to create the ~/.aws/config file.

State File Locking

If someone is performing an operation that modifies the terraform.tfstate file, the terraform.tfstate file must be locked. Otherwise, others could make changes to it and cause inconsistency.

When someone runs terraform apply or terraform destroy, a lock is acquired. A file called .terraform.tfstate.lock.info is automatically created. Terraform uses the presence of this file to determine whether it is locked. When the write operation is complete, the .terraform.tfstate.lock.info file is automatically deleted.

If another user tries to perform a write operation while a lock is held, an Error acquiring the state lock error is returned.

DynamoDB is used for the State Lock feature in team settings.

Note! Not all Backends support the locking feature. Check each Backend's documentation to verify support. S3 supports it and uses DynamoDB.

To implement the state locking feature using DynamoDB, store the pre-created DynamoDB table name in the dynamodb_table variable in the backend block.

Note that the DynamoDB partition key name must be LockID and must be of type String. Otherwise, the state locking feature will be disabled.

terraform {
  backend "s3" {
    bucket = "kplabs-terraform-backend"
    key    = "network/demo.tfstate"
    region = "us-east-1"
    dynamodb_table = "terraform-state-locking"
  }
}

Technique for Not Storing Passwords on GitHub

Place the password file outside the GitHub cloned folder.

Reference the password file from .tf using the file function, like file("../password.txt").

The .gitignore file can specify files and folders to exclude from Git commits.

Protecting .tfstate Files

Even with the above approach, plain-text passwords end up in the .tfstate file.

Multi-region

Writing an alias inside a provider block allows defining two or more providers.

Specify which provider to use inside the resource block.

provider "aws" {
  region     =  "us-west-1"
}

provider "aws" {
  alias      =  "aws02"
  region     =  "ap-south-1"
  profile    =  "account02"
}

resource "aws_eip" "myeip" {
  vpc = "true"
}

resource "aws_eip" "myeip01" {
  vpc = "true"
  provider = "aws.aws02"
}

About Log

Logs can be output by setting environment variables.

See here for log levels.

export TF_LOG=TRACE

Logs can also be written to a file by setting the log file path in the environment variable TF_LOG_PATH.

export TF_LOG_PATH=./trace.log

Resource Dependencies

Terraform automatically finds implicit dependencies and determines the order of resource creation.

Alternatively, explicit dependencies can be defined with the depends_on variable.

When using depends_on, it is recommended to add a comment explaining why it is needed.

resource "aws_iam_role" "example" {
  name = "example"

  # assume_role_policy is omitted for brevity in this example. Refer to the
  # documentation for aws_iam_role for a complete example.
  assume_role_policy = "..."
}

resource "aws_iam_instance_profile" "example" {
  # Because this expression refers to the role, Terraform can infer
  # automatically that the role must be created first.
  role = aws_iam_role.example.name
}

resource "aws_iam_role_policy" "example" {
  name   = "example"
  role   = aws_iam_role.example.name
  policy = jsonencode({
    "Statement" = [{
      # This policy allows software running on the EC2 instance to
      # access the S3 API.
      "Action" = "s3:*",
      "Effect" = "Allow",
    }],
  })
}

resource "aws_instance" "example" {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"

  # Terraform can infer from this that the instance profile must
  # be created before the EC2 instance.
  iam_instance_profile = aws_iam_instance_profile.example

  # However, if software running in this EC2 instance needs access
  # to the S3 API in order to boot properly, there is also a "hidden"
  # dependency on the aws_iam_role_policy that Terraform cannot
  # automatically infer, so it must be declared explicitly:
  depends_on = [
    aws_iam_role_policy.example
  ]
}

Folder Structure

In production environments, it is desirable to split files as provider.tf, variables.tf, <resource>.tf, output.tf.

This makes it immediately clear what is in each file. It's also easy to understand for newcomers to the project.

When Resources Are at Large Scale

When trying to create large-scale resources, there is a risk of hitting provider-side API limits (e.g., AWS). Even if not hitting limits, processing can take longer.

When many resources have already been created, terraform apply sends a large number of API requests to retrieve the status of existing resources. In such cases, using terraform apply -refresh=false reduces the number of API requests.

Splitting resources by type into separate folders and running terraform apply per folder prevents sending a large number of API requests all at once.

Security Considerations

  • Do not write credentials in the provider block — write them in the ~/.aws/config file.
  • Do not upload the .terraform folder, terraform.tfvars file, terraform.tfstate file, or crash.log file to git.
  • The terraform.tfstate file contains credentials in plain text, so manage access via S3.
  • Do not write credentials in .tf files. Reference password files from .tf using the file function, like file("../password.txt").
  • Setting the sensitive parameter to true in an output block causes the output value to be displayed as <sensitive> in standard output. Note, however, that it is still recorded in the state file.

Terraform Commands

  • terraform taint

    • Tainting a specific resource causes it to be recreated on terraform apply. terraform taint aws_instance.mydc2
    • A tainted resource has its status set to tainted in the terraform.tfstate file.
    • Recreating a resource via terraform taint may change the IP assigned to EC2. Understand risks related to recreating dependencies beforehand.
    • In many companies, manual resource changes are not permitted, and terraform taint is used to restore resources to their state when manual changes have been made.
  • terraform refresh

    • Reflects the current state of provisioned resources in the terraform.tfstate file.
  • terraform graph

    • Can visualize (graph) the resource creation order.
  • terraform init

    • Plugins are downloaded with terraform init.
  • terraform init -upgrade

    • Can upgrade the provider version.
  • terraform fmt

    • Fixes indentation inconsistencies.
  • terraform plan -out=<output path>

    • Outputs the terraform plan analysis results as a binary.
    • Run terraform apply <output path> to create resources based on the analysis results.
  • terraform output <output variable>

    • Outputs the value of an output variable recorded in the state file.
    • The value can also be checked by directly viewing .tfstate, or by running the terraform apply command to output the result.
  • terraform apply -target=ec2

    • -target allows running against a specific resource only.
    • This reduces the number of API requests.
  • terraform apply -refresh=false

    • When many resources have already been created, terraform apply sends a large number of API requests to retrieve their status.
    • Using terraform apply -refresh=false reduces the number of API requests.
  • terraform apply -refresh-only

    • According to this, terraform refresh is apparently deprecated and terraform apply -refresh-only should be used instead.

In previous versions of Terraform, the only way to refresh your state file was by using the terraform refresh subcommand. However, this was less safe than the -refresh-only plan and apply mode since it would automatically overwrite your state file without giving you the option to review the modifications first. In this case, that would mean automatically dropping all of your resources from your state file.

  • terraform login

    • The terraform login command can be used to automatically obtain and save an API token for Terraform Cloud, Terraform Enterprise, or any other host that offers Terraform services.
  • terraform force-unlock

    • Releases the state lock.
  • terraform plan -destroy

    • Can preview the terraform destroy command.
  • terraform show

    • The terraform show command is used to provide human-readable output from a state or plan file. This can be used to inspect a plan to ensure that the planned operations are expected, or to inspect the current state as Terraform sees it.

Machine-readable output is generated by adding the -json command-line flag.

terraform state Command

The terraform state command allows you to view and modify the state file.

  • terraform state pull

    • Downloads the state file from the backend.
  • terraform state show

    • Equivalent to terraform state pull for a specific resource.
  • terraform state mv <existing resource name> <new resource name>

    • Changing a Terraform resource name normally causes the resource to be deleted first. Using terraform state mv allows renaming the resource without deleting it.
  • terraform state list

    • Lists resources contained in the state file.
  • terraform state rm <resource type>.<local name> [IMPORTANT]

    • Removes from the state file. Used when you want to stop managing with Terraform.

terraform import Command

Manually created resources can be imported into the state file. However, not all resources can be imported — verify in advance.

When importing, first write a resource block in the .tf file.

Then run the terraform import command.

terraform import aws_instance.foo i-abcd1234

About Terraform Cloud

Terraform Cloud provides the following features:

  • Cost estimation

  • Policy compliance checks against pre-configured policies (Sentinel)

  • Code review records

  • Module registry

  • Integration with Terraform code stored in Git

Apparently, not many companies use Terraform Enterprise or paid Terraform Cloud. In practice, almost none do. The free Terraform Cloud is said to be sufficient.

  • Terraform Cloud Free

    • State management
    • Remote operations
    • Private module registry
  • Terraform Cloud Paid (Team & Governance)

    • Team management
    • Sentinel policy as code
    • Run tasks
    • Additional concurrency
  • Terraform Cloud Paid (Enterprise)

    • Drift detection
    • SSO
    • Audit logs
    • Self-hosted agents
    • Custom concurrency

A detailed comparison of OSS and Terraform Cloud is summarized clearly in the following image (excerpt). See here for the full comparison table.

Sentinel

Sentinel is one of the Policy as Code frameworks. It is used to write policies.

Sentinel enables policy checks at the code level. Code that does not comply with policies is rejected. Since Sentinel only checks Terraform code policies, tools like AWS Config are needed to policy-check manually created resources as well.

About Terraform Vault

Vault issues credential information on demand. Issued credentials are automatically deleted by Vault after a certain period of time, reducing the risk of credential leakage.

Permissions can also be specified when requesting credential issuance from Vault.

Terminology

resource "aws_instance" "server" {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
}

Resource block — the {...} range starting with resource

Resource type — "aws_instance" in the above example

Resource name (= local name) — "server" in the above example

Argument Name — ami, instance_type in the above example

Argument Value — "ami-a1b2c3d4", "t2.micro" in the above example

Child module — what is commonly called a module. The main part of the .tf that calls a Child module is called the "Root module."

Address (Resource Address) — a string that uniquely identifies a resource. Consists of a module path and a resource spec. [module path][resource spec]

Module path — A module path addresses a module within the tree of modules. It takes the form module.module_name[module index]

Resource spec — A resource spec addresses a specific resource instance in the selected module. It has the following syntax resource_type.resource_name[instance index]

aws_instance.myec2 or module.foo[0].module.bar["a"] https://www.terraform.io/cli/state/resource-addressing

About Comments

Three symbols can be used for comments:

#
//
/* */

Functions

UDFs (user-defined functions) cannot be created in Terraform. Only Terraform built-in functions.

toset Function

Converts a list to a set.

A "set" is a collection that, unlike a list, cannot have duplicate values.

Languages Supported by Terraform

Not just HCL — JSON can also be used.

www.terraform.io