Terraform 101

February 27, 2022

What is terraform?

Terraform is a widely used Infrastructure as Code (IaC) tool. This is a tool that is cloud agnostic, therefore can interact with a wide range of supported cloud providers.

Why use terraform?

As an engineer you may find yourself having to provision several resources on a cloud platform. If you were to create it manually through the CLI or a webportal you would find that creating copies of the same resource can be time consuming. Furthermore removing these resources can prove to be tedious as one resource may also have other attached resources that also need to be removed. As an example if you were to provision a virtual machine on any cloud provider you would need to have a boot volume attached to it which is created at the time the virtual machine is created. If you were to delete the virtual machine it is possible that the volume is still lingering around which might also affect your overall cost. Terraform overcomes this problem by using something called a state file terraform.tfstate. This file contains all the information of the resources that were provisioned and therefore if you were to delete your resources terraform would make sure to delete all that was created.

Where do I start with terraform ?

Let’s begin with a very simple yet effective project that will cover many aspects of terraform and should give you the foundation you need.

Project goal

The goal of this project is to create a repository on GitHub using terraform. We will manually create a repository which will contain the initial terraform code, thereafter all future code repositories will be created and managed using terraform.

Prerequisites

Step 1

Clone the created github repository and checkout to a new branch

git clone git@github.com:<username>/<repo>

git checkout -b initial_terraform_code

Step 2

# you can use the following command in bash or make the files manually

touch main.tf providers.tf .gitignore repositories.yml README.md

Why did we create these files?


File name Purpose
main.tf This file will contain some terraform configuration
providers.tf This file will also contain some terraform configuration
repositories.yml This file will contain repository related configuration
README.md A readme file is always recommended. Explain what this repo is.

Does terraform care how many or how few .tf files are in on repo?

How do I populate these files?

  1. Terraform registry

  2. Terraform syntax

step 3

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

provider "github" {

}

Step 4

locals {
  terraform_prefix = "terraform"
  repo             = yamldecode(file("repositories.yml"))
  topics           = ["github"]
}



module "myrepos_module" {
  for_each               = local.repo.repos.repositories
  source                 = "operatehappy/repository/github"
  version                = "3.0.0"
  name                   = each.value.name
  visibility             = each.value.visibility
  description            = each.value.description
  topics                 = compact(flatten([local.topics, try(each.value.topics, "")]))
  license_template       = "apache-2.0"
  allow_merge_commit     = false
  delete_branch_on_merge = true
  default_branch         = "main"
}

Breaking down the code

locals {
  terraform_prefix = "terraform"
  decoded_repos            = yamldecode(file("repositories.yml"))
  topics           = ["github"]
}

locals stanza can (stanza in HCL2 is a code block that capture information with {} brackets) be thought of as a list on internal variables. Here we have defined a variable terraform_prefix as string terraform, topics as an array which has a single string element github. And then we have the repo variable which is a dictionary and makes use of a built-in terraform function.

module "myrepos_module" {
  for_each               = local.decoded_repos.terraform_repos.repositories
  source                 = "operatehappy/repository/github"
  version                = "3.0.0"
  name                   = each.value.name
  visibility             = each.value.visibility
  description            = each.value.description
  topics                 = compact(flatten([local.topics, try(each.value.topics, "")]))
  license_template       = "apache-2.0"
  allow_merge_commit     = false
  delete_branch_on_merge = true
  default_branch         = "main"
}

The GitHub module interface as I like to call it (formally called the module block) which takes in user inputs and creates repositories based on those inputs. There are several key functions that at first glance may not seem apparent. I will elaborate further on these built-in terraform features after populating the repositories.yml file.

Step 5

---
terraform_repos:
  description: "This is a terraform built repository."
  repositories:
    terraform_repo:
      name: "terraform_repository_1"
      description: "This is my first terraform repository."
      topics: ["terraform","myfirstrepo"]
      visibility: "public"


Look back at the module block

for_each               = local.repo.repos.repositories

How many repositories need to be created but also create a key-value pair. The key is the value at line-6 of the YAML file. And all the attributes within that key become values of that key.

source                 = "operatehappy/repository/github"

Where should terraform get this repository from. Remember since this module is available within terraform registry all you need to set as a source is the name of the module. If however you need to use your own module hosted in git you can refer to this link.

version                = "3.0.0"

Version of the module

name                   = each.value.name

As mentioned within the for_each explanation. The keys created by the for_each argument will contain values associated with those keys. These values can the be referred using each.value.some_value_name and in this case some_value_name is name (line-6 YAML). Therefore each.value.name will be terraform_repository_1

visibility             = each.value.visibility
description            = each.value.description

These follow the same explanation as name.

topics                 = compact(flatten([local.topics, try(each.value.topics, "")]))

More built-in tricks! I will explain these built-in features right after breaking down the module. But topics will set the topics or labels for this repository, which can be seen in the About section of the repository. In this example the topics are terraform, github and myfirstrepo.

license_template       = "apache-2.0"
allow_merge_commit     = false
delete_branch_on_merge = true
default_branch         = "main"

The license file is available on GitHub by default. allow_merge_commit is disabled so not all commits are added to the main branch. delete_branch_on_merge this will delete the remote branch once a pull request is complete. default_branch is set to main.

Breakdown of the topics variable

topics                 = compact(flatten([local.topics, try(each.value.topics, "")]))

Step 6


export GITHUB_TOKEN=123234254534

terraform init

Step 7

Let’s plan what infrastructure terraform will be creating. Notice I am using the -out.tfplan flag here. This is not necessary but I recommend you start getting in the habit of doing it to avoid any terraform related confusion. This command will create a plan.tfplan file which should also be within the .gitignore


terraform plan -out=plan.tfplan

Step 8


terraform apply plan.tfplan

Step 9

Final remarks