Welcome to Byte Sized Terraform

The intent of this series is to start from the very basics. In the first few posts you will actually write no Terraform. Based on our experience, there is a bit of knowledge that needs to exist prior to using Terraform beyond a simple “Hello World” tutorial.

This series is written to be consumed in small portions. A single page should take no more than 10 minutes to consume. You may need to revisit, reread or rewatch the YouTube supplementary, but all the content will hopefully stay within 10 minutes or less.

We have no affiliation with HashiCorp. Our views do not reflect those of our employers past or present. What we’re presenting here is purely our experiences. We felt there was a gap. This is our attempt to fill that gap and hopefully help others be less frustrated when using Terraform.

A Word of Caution

We will do our very best to use examples that don’t require services that cost money. Modern cloud providers often require a means of payment up front to do anything. Even a “free tier” is not free. Corey Quinn says it better than anyone else, here. If you choose to spin up resources on one of these providers, please understand the potential pitfalls. We don’t want anything bad to happen to you.

And with that, let’s get started!

Subsections of

Chapter 1

Prerequisites

Prerequisites

Tools

In this section, we will cover the tools we commonly use. This ecosystem is ever-growing and it is not an exhaustive list. These are simply the tools we use, that add value to our interactions with Terraform and we felt were worth passing along. Get started here

Types

One of the biggest obstacles we have encountered when mentoring folks new to Terraform is they will pass the incorrect type to Terraform. It has been our experience that there is value in understanding what type Terraform is expecting you to pass to it. The docs often fall short here. Having an awareness of the types Terraform supports, and how to find out what type the Resource or Data Source is expecting is what we cover in this section. We feel with this knowledge and awareness will create a better experience and hopefully a less frustrating experience. Get started here.

Subsections of Prerequisites

Tools

  • tfenv

    • We don’t recommend installing Terraform globally rather, we are big fans of tfenv.
    • We also recommend you use a .terraform-version file in your project’s root.
    • This is useful when you’re testing TF upgrades. Though this is less of a problem today with HashiCorp’s v1.x Compatiability Promises if you’re lucky enough to be using Terraform 1.x.x
  • pre-commit-terraform

    • Your uses case may be different than ours, see here for available hooks.

    • For what it’s worth, most of our projects have terraform_fmt, terraform_lint and terraform_validate.

    • Some projects also require terraform_tfsec, terrascan and terraform_docs

      • I have a .terraform-docs.yml configuration like so:
      settings:
        html: false
        anchor: false
      formatter: "markdown table"
      
  • If you use Visual Studio Code, the Terraform Plugin is highly recommended.

Types

It has been our experience, the best way to understand Terrarform is to be aware of Types. Types are common in other lanuages, so if you’ve used Ruby, Python, NodeJS, etc. you may already be familiar with types. If you’re not, here are the types defined by Terraform.

Source Code?!

We’re strong believers that no one should ever have to look at the source code to figure something out. But, we have found ourselves having to eventually look at the source code to figure out what a Resource or a Data Source is expecting. The source code presented here is not meant to teach you Golang or the internals of Terraform. The intent is to create an awareness of what these types may look like so if or when you see them again when you’re trying to figure something out, you’re not staring at it really really confused.

Additional Resources

Subsections of Types

Null

null

A value that represents absence or omission. Setting an arugment’s value to null, Terraform will act as though you had completely omitted it. It will use the argument’s default value if it has one, or raise an error if the argument is mandatory.

Example

References

Map

Map

A map is represented as a key/value pair.

Example Resource

The Terraform Resource digitalocean_loadbalancer (truncated for brevity)

...
forwarding_rule {
  entry_port     = 80
  entry_protocol = "http"

  target_port     = 80
  target_protocol = "http"
}
...

The keys entry_port, entry_protocol, target_port and target_protocol above and their respective values, 80, and http.

Source Code

The corresponding source code

"forwarding_rule": {
  Type:     schema.TypeSet,
  Required: true,
  MinItems: 1,
  Elem: &schema.Resource{
   Schema: map[string]*schema.Schema{
    "entry_protocol": {
  Type:     schema.TypeString,
  Required: true,
  ValidateFunc: validation.StringInSlice([]string{
   "http",
   "https",
   "http2",
   "http3",
   "tcp",
   "udp",
  }, false),
    },
    "entry_port": {
  Type:         schema.TypeInt,
  Required:     true,
  ValidateFunc: validation.IntBetween(1, 65535),
    },
    "target_protocol": {
  Type:     schema.TypeString,
  Required: true,
  ValidateFunc: validation.StringInSlice([]string{
   "http",
   "https",
   "http2",
   "tcp",
   "udp",
  }, false),
    },
    "target_port": {
  Type:         schema.TypeInt,
  Required:     true,
  ValidateFunc: validation.IntBetween(1, 65535),
    },
    "certificate_id": {
  Type:         schema.TypeString,
  Optional:     true,
  ValidateFunc: validation.NoZeroValues,
    },
    "tls_passthrough": {
  Type:     schema.TypeBool,
  Optional: true,
  Default:  false,
    },
   },
  },
  Set: hashForwardingRules,
  },

References

Bool

bool

Can either be true or false. A bool value can be used in conditional logic.

Example

digitalocean_droplet

...
backups = true
...

The source

   "backups": {
        Type:     schema.TypeBool,
        Optional: true,
        Default:  false,
   },

References

List

List

A list or tuple is a squence of values (e.g ipAddr = ["192.168.1.1", "192.168.1.2"]). Lists are zero-indexed, so elements can be selected with this syntax ipAddr[0].

Example Resource

The Terraform Resource digitalocean_spaces_bucket

...
  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["PUT", "POST", "DELETE"]
    allowed_origins = ["https://www.example.com"]
    max_age_seconds = 3000
  }
...

Source Code

The corresponding source code.

   "cors_rule": {
        Type:        schema.TypeList,
        Optional:    true,
        Description: "A container holding a list of elements describing allowed methods for a specific origin.",
        Elem: &schema.Resource{
         Schema: map[string]*schema.Schema{
          "allowed_methods": {
           Type:        schema.TypeList,
           Required:    true,
           Description: "A list of HTTP methods (e.g. GET) which are allowed from the specified origin.",
           Elem:        &schema.Schema{Type: schema.TypeString},
          },
          "allowed_origins": {
           Type:        schema.TypeList,
           Required:    true,
           Description: "A list of hosts from which requests using the specified methods are allowed. A host may contain one wildcard (e.g. http://*.example.com).",
           Elem:        &schema.Schema{Type: schema.TypeString},
          },
          "allowed_headers": {
           Type:        schema.TypeList,
           Optional:    true,
           Description: "A list of headers that will be included in the CORS preflight request's Access-Control-Request-Headers. A header may contain one wildcard (e.g. x-amz-*).",
           Elem:        &schema.Schema{Type: schema.TypeString},
          },
          "max_age_seconds": {
           Type:     schema.TypeInt,
           Optional: true,
          },
         },
        },
   },

References

Terraform Docs

Number

Number

Example

The Terraform Resource aws_ecs_service.

...
  load_balancer {
    target_group_arn = aws_lb_target_group.foo.arn
    container_name   = "mongo"
    container_port   = 8080
  }

Source Code

The corresponding source code.

      "container_port": {
           Type:         schema.TypeInt,
           Required:     true,
           ValidateFunc: validation.IntBetween(0, 65536),
      },

References

Strings

Strings

Example Resource

The Terraform Resource aws_ecs_service

resource "aws_ecs_service" "mongo" {
  name            = "mongodb"
  cluster         = aws_ecs_cluster.foo.id
...

Source Code

The corresponding source code.

   "name": {
    Type:     schema.TypeString,
    Required: true,
    ForceNew: true,
   },

Example

References

Chapter 2

Terraform 101

What follows is a general introduction. We may or may not write actual Terraform here.

Subsections of Terraform 101

Data Source

Data Source

Resource

Resource

Chapter 4

Terraform State

If you’re touching Terraform state, hold on to your butts!

Subsections of Terraform State

List

Show

Remove