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
- Terraform version
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.exerequired_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:
- Parameters in the provider configuration
- Environment variables
- Shared credentials files
- Shared configuration files
- Container credentials
- 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"
- Run with the format
- Variable definitions (.tfvars) files
- By default, looks for and loads
terraform.tfvars. For a custom filename, runterraform apply -var-file="custom_name.tfvars"
- By default, looks for and loads
- Environment variables
- Set as an environment variable:
export TF_VAR_image_id='ami-abc123'
- Set as an environment variable:
- 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/configfile. - Do not upload the
.terraformfolder,terraform.tfvarsfile,terraform.tfstatefile, orcrash.logfile to git. - The
terraform.tfstatefile contains credentials in plain text, so manage access via S3. - Do not write credentials in
.tffiles. Reference password files from.tfusing the file function, likefile("../password.txt"). - Setting the
sensitiveparameter 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
taintedin 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 taintis used to restore resources to their state when manual changes have been made.
- Tainting a specific resource causes it to be recreated on
-
terraform refresh
- Reflects the current state of provisioned resources in the
terraform.tfstatefile.
- Reflects the current state of provisioned resources in the
-
terraform graph
- Can visualize (graph) the resource creation order.
-
terraform init
- Plugins are downloaded with
terraform init.
- Plugins are downloaded with
-
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 theterraform applycommand to output the result.
-
terraform apply -target=ec2
-targetallows 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 applysends a large number of API requests to retrieve their status. - Using
terraform apply -refresh=falsereduces the number of API requests.
- When many resources have already been created,
-
terraform apply -refresh-only
- According to this,
terraform refreshis apparently deprecated andterraform apply -refresh-onlyshould be used instead.
- According to this,
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.