SHIFT-WIKI

--- Sjoerd Hooft's InFormation Technology ---

User Tools

Site Tools


terraform

Terraform - How to get started

Summary: In this post I'll show you how to prepare your Windows desktop to work with terraform for both Azure and AWS. I will also explain the most common terraform commands and terminology and show you how to apply these.
Date: Around 2019
Refactor: 11 January 2025: Checked links, formatting and split up some content to different files.

Terraform on Windows Setup

First prepare your windows desktop. At a minimum you'll need terraform to be installed and if you're using VSCode you will need the terraform plugin.

Note that if you plan to use terraform only on the Azure Cloud Shell (see below) you can skip the installation of terraform on your desktop.

Install Terraform

You can download terraform from the terraform website. After downloading the zip file, extract it to a folder and add the folder to your path. You can do this by adding the folder to the system environment variables, so for example if you've extracted the zip file to c:\terraform you can go to the 'Advanced system settings' → 'Advanced' → 'Environment Variables' → 'Path' → 'Edit' → 'New' → 'C:\terraform'.

Alternatively, you can add the file location to your path using the following powershell command:

# Add terraform to your path
$targetDir="C:\terraform"
$oldPath = [System.Environment]::GetEnvironmentVariable("Path","User")
$oldPathArray=($oldPath) -split ";"
if(-Not($oldPathArray -Contains "$targetDir")) {
    write-host "Permanently adding $targetDir to User Path"
    $newPath = "$oldPath;$targetDir" -replace ";+", ";"
    [System.Environment]::SetEnvironmentVariable("Path",$newPath,"User")
    $env:Path = [System.Environment]::GetEnvironmentVariable("Path","User"),[System.Environment]::GetEnvironmentVariable("Path","Machine") -join ";"
}
Note that you need to restart your command prompt to make the changes effective.

Install Terraform using Chocolatey

You could also install terraform with chocolatey. After installing chocolatey you can run choco install terraform.

VSCode Terraform Plugin

Add the terraform plugin from hashicorp. Once installed you can make the following changes to your settings.json file to make sure the terraform plugin is used for all terraform files:

{
    "files.associations": {
        "*.tf": "terraform",
        "*.tfvars": "terraform"
    },
    "[terraform]": {
        "editor.defaultFormatter": "hashicorp.terraform"
    }
}

Ready for Use

After the installation you should be able to run the terraform version command both in a shell/terminal and in the VSCode integrated terminal.

Terraform Basics

The main commands are:

  • terraform init: Checks the config file and downloads the provider
  • terraform validate: Checks the config file for syntax errors
  • terraform plan: Creates the plan, what to do based on the config file and state file
  • terraform apply: Applies the plan
    • Use the option -auto-approve to suppress the summary and confirmation
  • terraform destroy: Destroys the plan
    • Use the option -auto-approve to suppress the summary and confirmation

Terraform State

After you have applied a terraform config you get a tfstate file, which is the heart of your terraform operation. You can check the file for all resources created and information about these resources such as the aws arn for example. Don't delete this file!

Terraform Variables and How to Use them

variable "vpcname" {
    type = string
    default = "myvpc"
}

variable "sshport" {
    type = number
    default = 22
}

variable "enabled" {
    default = true
}

variable "mylist" {
    type = list(string)
    default = ["myvalue0","myvalue1"]
}

variable "mymap" {
    type = map
    default = {
        Key1 = "Value1"
        Key2 = "Value2"
    }
}

# A tuple is like a list but with support for multiple datatypes
variable "mytuple" {
    type = tuple("string", "number", "string")
    default ["cat", 2, "dog"]
}

# An object is like a JSON object, and like a map but with support for multiple datatypes
variable "myobject" {
    type = object({ name = string, port = list(number)})
    default = {
        name = "portlist"
        port = (22, 25, 80)
    }
}

## Use strings/numbers/boolean
resource "aws_vpc" "myvpc" {
  cidr_block = "10.0.0.0/16"

  tag = {
      Name = var.vpcname
  }
}

## Use lists
resource "aws_vpc" "myvpc" {
  cidr_block = "10.0.0.0/16"

  tag = {
      Name = var.mylist[0]
  }
}

## Use maps
resource "aws_vpc" "myvpc" {
  cidr_block = "10.0.0.0/16"

  tag = {
      Name = var.mymap["Key1"]
  }
}

## InputVariables - Terraform will ask for the input during terraform plan
variable "inputname" {
    type = string
    description = "Provide the name of the VPC: "
}

# Use input variable
resource "aws_vpc" "myvpc" {
  cidr_block = "10.0.0.0/16"

  tag = {
      Name = var.inputname
  }
}

# Output
output "vpcid" {
    value = aws_vpc.myvpc.id
}

Terraform Advanced Options

Terraform Dependencies

You can make one resource dependent another resource to make sure resources are created and started in the right order:

provider "aws" {
    profile = "terraform"
}
resource "aws_instance" "db" {
    ami = "ami-0d1bf5b68307103c2"
    instance_type = "t2.micro"
}
resource "aws_instance" "web" {
    ami = "ami-0d1bf5b68307103c2"
    instance_type = "t2.micro"
    depends_on = [aws_instance.db]
}

Terraform Count

Use count to create multiple resources with the same configuration:

provider "aws" {
    profile = "terraform"
}
 
resource "aws_instance" "db" {
    ami = "ami-0d1bf5b68307103c2"
    instance_type = "t2.micro"
 
    count = 3
 
    tags = {
        Name = "Server ${count.index}"
    }
}

Note that you could use length to input the names from a variable

provider "aws" {
    profile = "terraform"
}
 
variable "server_names" {
    type = list(string)
    default = ["mariadb","mysql","mssql"]
}
 
resource "aws_instance" "db" {
    ami = "ami-0d1bf5b68307103c2"
    instance_type = "t2.micro"
 
    count = length(var.server_names)
 
    tags = {
        Name = var.server_names[count.index]
    }
}

Terraform Variable Files

You can use variable files, for example, when using terraform to deploy production and test and you don't want to create separate terraform files per environment:

number_of_servers = 4
number_of_servers = 2

main.tf

provider "aws" {
    profile = "terraform"
}
 
variable "number_of_servers" {
    type = number
}
resource "aws_instance" "db" {
    ami = "ami-0d1bf5b68307103c2"
    instance_type = "t2.micro"
 
    count = var.number_of_servers
}

Now, when you run terraform plan or apply, use the following option to define the vars file:

terraform plan -var-file=test.tfvars
terraform apply

Terraform Variables and Env tfvars Files

When working with terraform variables throughout applications, modules and environments, it is important to understand how to use variables and env files. The following is a guideline on how to use variables and env files in terraform based on my own experience and the best practices I have learned:

  • Module variables.tf file:
    • Declares all variables that may need to be configured now or in the future
    • Variable name is the same as the attribute name
      • Except for the 'version' attribute as that is considered a module attribute. Name the version attribute as 'module*name_version'
    • No default value (as it is ignored by the application)
  • Application variables.tf file
    • Declares all variables that may need to be configured now or in the future for this application
      • Variables that do not need to be able to change for this application need a hardcoded value if the module is called
    • Set a default value for all variables (this allows for deployments per environment)
      • Except for variables that are environment specific, for example the names
    • For deployments that are not done in all environments use the prd values for the default value
      • Including the variables that are environment specific
  • env.tfvars
    • Does not declare variables
    • Sets the value for variables that have a different value per environment
    • Required to set all env.tfvars values that do not have a default value upon merge to main even if the deployment for that env is disabled

Terraform Import

You can bring existing resources in AWS under control of terraform.

Let's say you have an existing VPC with the id of vpc-xxx123ccc123cccc which has a cidr block of 192.168.0.0/24. You can import this using the following main.tf:

resource "aws_vpc" "myvpc" {
  cidr_block = "192.168.0.0/24"
}

And run the following command:

terraform import aws_vpc.myvpc vpc-xxx123ccc123cccc
Note that this is quite limited because all the settings need to match, so if you've configured lots of options for your resources this might prove too much of a hassle.

Terraform Data

You can use the data option to query the statefile, or if the information is not in there, to query (in this case) aws about information of the resources you have. Adding this snippet for example to your main.tf file queries for all the EC2 instances that have a specific tag:

data "aws_instance" "dbsearch" {
    filter {
        name = "tag:Name"
        values = ["DB Server"]
    }
}
 
output "dbservers" {
    value = data.aws_instance.dbsearch.id
}
 
output "dbserveraz" {
    value = data.aws_instance.dbsearch.availability_zone
}
 
output "dbserversandaz" {
    value = ["data.aws_instance.dbsearch.id","data.aws_instance.dbsearch.availability_zone"]
}
Note that values is a list, so you can search for multiple values at once.


Note that you can re-use the search results for multiple properties of the aws instances. Here is a list of all the attributes available on aws_instance.

Terraform on AWS

Credentials for AWS

To be able to use terraform with AWS you need to have an AWS account and you need to create an access key and secret key. You can do this in the AWS console by going to your account, my security credentials, access keys, create new access key. You can then download the keys, but be sure to store them in a safe place as you can only download them once. The credentials can then be used in terraform to authenticate with AWS. There are several ways to do this:

You can add the credentials into the main.tf file, this is not recommended as the credentials are stored in plain text and can be read by anyone who has access to the file, and it also means you can't check the files into a shared or public repository. But if you want to do this you can add the credentials like this:

provider "aws" {
  region = "eu-west-1"
  access_key = "AKIAXXXXXXXXXX"
  secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

You can also add the credentials to the environment variables by running these commands in powershell. This has the advantage that the credentials are not stored anywhere, and the credentials are only available for the duration of the session:

$Env:AWS_ACCESS_KEY_ID="AKIAXXXXXXXXXXX"
$Env:AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$Env:AWS_DEFAULT_REGION="eu-west-1"

If you set an environment variable at the PowerShell prompt as shown in the previous examples, it saves the value for only the duration of the current session. To make the environment variable setting persistent across all PowerShell and Command Prompt sessions, store it by using the System application in Control Panel. Alternatively, you can set the variable for all future PowerShell sessions by adding it to your PowerShell profile, or use setx.

C:\> setx AWS_ACCESS_KEY_ID AKIAXXXXXXXXXX
C:\> setx AWS_SECRET_ACCESS_KEY xxxxxxxxxxxxxxxxxxxxx
C:\> setx AWS_DEFAULT_REGION eu-west-1

Using 'set' to set an environment variable changes the value used until the end of the current command prompt session, or until you set the variable to a different value. Using setx to set an environment variable changes the value used in both the current command prompt session and all command prompt sessions that you create after running the command. It does not affect other command shells that are already running at the time you run the command.

Only store the credentials in a safe place and make sure you do not load them into a terminal session where other people can access them. I usually store the commands in a secure note in a password tool and copy and paste them every time I need to use them.

To remove a secret from your powershell history use (Get-PSReadlineOption).HistorySavePath and remove the line with the password in it.

Using Profiles

If you are working with multiple accounts you could also install the AWS CLI and run the aws configure command to create profiles. Download the installer from here, and run the aws configure command which allows you to setup your default profile:

PS C:\Users\sjoer> aws configure
AWS Access Key ID [None]: AKIAXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Default region name [None]: eu-west-1
Default output format [None]: json

This will create a credentials file in a .aws directory in your home directory. You can edit the file and rename the profile so you can use it in your main.tf file: Credential file:

# Amazon Web Services Credentials File used by AWS CLI, SDKs, and tools
# This file was created by the AWS Toolkit for Visual Studio Code extension.
#
# Your AWS credentials are represented by access keys associated with IAM users.
# For information about how to create and manage AWS access keys for a user, see:
# https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html
#
# This credential file can store multiple access keys by placing each one in a
# named "profile". For information about how to change the access keys in a
# profile or to add a new profile with a different access key, see:
# https://docs.aws.amazon.com/cli/latest/userguide/cli-config-files.html
#
[terraform]
region=eu-west-1
aws_access_key_id = AKIAXXXXXXXXXX
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxx
[prod]
region=eu-west-1
aws_access_key_id = AKIAXXXXXXXXXX
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxx

main.tf file:

provider "aws" {
    profile = "terraform"
}

Terraform on Azure

To be able to use terraform with Azure you need to have an Azure subscription. The most easy way to start working with terraform in Azure is to use the Azure Cloud Shell. This is a shell that is hosted in the Azure portal and has all the tools you need to work with terraform, which eliminates the need to install any software on your local machine.

Azure Cloud Shell

To work with terraform on Azure it's very convenient to to use the cloud shell. Terraform is already installed, so you can simply upload your config file and run terraform commands:

provider "azurerm"{
    subscription_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    tenant_id       = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
    features {}
}

Now in the Azure Cloud Shell, you can run terraform init and start adding additional files, as explained here.

Terraform in Azure DevOps from a local environment

You can also work with terraform on Azure from your local environment as explained here. You need to install Powershell Core and the Azure CLI. Once you've done so and also installed terraform as explained above you can continue with setting up the credentials:

  • Login to Azure using the Azure CLI: az login
  • Get your subscription and create a service principal with permissions on the subscription:
# Get the subscription id
$subscriptionId = az account show --query id -o tsv
# Create a service principal with contributor permissions
az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/$subscriptionId"

The last command will output a json object with the service principal details. Note that this includes a secret, which you should store safely. You can use these details in your main.tf file:

provider "azurerm" {
    subscription_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    client_id       = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
    client_secret   = "************************************"
    tenant_id       = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
    features {}
}
Note that you should not commit files with a client secret to a repository.

Or you can set them as variables in your powershell session, which makes the credentials available for the duration of the session and the main.tf file simpler:

$Env:ARM_CLIENT_ID = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
$Env:ARM_CLIENT_SECRET = "************************************"
$Env:ARM_SUBSCRIPTION_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$Env:ARM_TENANT_ID = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
provider "azurerm" {
    features {}
}

Only store the credentials in a safe place and make sure you do not load them into a terminal session where other people can access them. I usually store the commands in a secure note in a password tool and copy and paste them every time I need to use them.

To remove a secret from your powershell history use (Get-PSReadlineOption).HistorySavePath and remove the line with the password in it.

Useful Links

terraform.txt · Last modified: 2025/02/11 17:03 by 127.0.0.1