Utilising Terraform Resource Attributes

Over the past few months I have been doing the typical thing of looking back at my old code and wincing (Terraform in this case). Reason being is that I’ve specified some static variables where I really shouldn’t have which then creates loads of overhead and lots of manual changes. Basically it doesn’t scale, and is really bad practise.

After seeing the error in my approach I started looking around at how we can utilise the remote state, data sources and resource attributes, to then retrospectively replace the static variables.

Resources#

The most important thing you’ll configure with Terraform are resources. Resources are a component of your infrastructure. It might be some low level component such as a physical server, virtual machine, or container. Or it can be a higher level component such as an email provider, DNS record, or database provider.

Typically you would create a set of resources that will utlise attributes from other resources which allows for much cleaner modules. All resources export a number of attributes that would be unique to it.

Creation#

Typically I would create a module that’s made up multiple resources, but here’s an example of a resource:

resource "aws_launch_configuration" "lc_conf" {
  name_prefix   = "${name_prefix}"
  image_id      = "${var.ami}"
  instance_type = "${var.instance_type}"
}

This is generating a Launch Configuration to be used for EC2 instances in AWS.

Usage#

In the above example we are creating a launch configuration, however we want to use that same launch configuration for our autoscaling group:

resource "aws_launch_configuration" "lc_conf" {
  name_prefix   = "${name_prefix}"
  image_id      = "${var.ami}"
  instance_type = "${var.instance_type}"
}

resource "aws_autoscaling_group" "asg_conf" {
  name                 = "${var.asg_name}"
  launch_configuration = "${aws_launch_configuration.lc_conf.name}"
  min_size             = "${var.min_size}"
  max_size             = "${var.max_size}"
}

In the above example (aws_launch_configuration.lc_conf.name) we are using the name attribute from the aws_launch_configuration resource.

Outputs#

Output variables are ways for defining queryable data to be either used as input variables or viewed via terraform output

When you are writing modules, you are able create outputs to then be used by other modules and resources.

Creation#

When you creating your module you define the output for it, this can exist within the same Terrafom file or the same directory e.g.

main.tf

resource "aws_autoscaling_group" "asg" {
  availability_zones   = ["${var.availability_zones}"]
  name                 = "${var.name}"
  max_size             = "${var.asg_max}"
  min_size             = "${var.asg_min}"
  desired_capacity     = "${var.asg_desired}"
  force_delete         = true
  launch_configuration = "${aws_launch_configuration.lc.name}"
}

outputs.tf

output "auto_scaling_group_id" {
  value = "${aws_autoscaling_group.asg.id}"
}

You can output whatever attributes are avaiable for the resource e.g. autoscaling groups allow you to output a whole manner of things like id, name, min_size, max_size, name.

All attributes can be found inside the provider’s documentation

Variables would then live in another file within the same directory for example variables.tf

The tree structure would look like so:

└── example_asg
    ├── main.tf
    ├── outputs.tf
    └── variables.tf

Usage#

To then use the output of the module:

module "test_asg" {
  source = "/path/to/module/asg"

  availability_zones   = "${var.availability_zones}"
  name                 = "${var.test_asg_name}"
  asg_max              = "${var.test_asg_asg_max}"
  asg_min              = "${var.test_asg_asg_min}"
  asg_desired          = "${var.test_asg_asg_desired}"
}

module "pseudo_module" {
  source = "/path/to/pseudo/module/asg"

  test_asg  = "${module.test_asg.auto_scaling_group_id}"
}

You can see that the pseudo_module is then taking the test_asg output as a variable. test_asg is the name of the module, while auto_scaling_group_id is the output I wish to use.

Data Sources#

Data sources allow data to be fetched or computed for use elsewhere in Terraform configuration. Use of data sources allows a Terraform configuration to build on information defined outside of Terraform, or defined by another separate Terraform configuration.

Data Sources are useful for finding resources that either do not live within the same Terraform state or have been generated outside of Terraform.

Data sources can be used as input variables which really come in handy when you want to get around static variables.

Creation#

This is personal preference but I tend to put data sources in a variables.tf that’s used for the environment e.g. integration, staging, production. The data source would look similar to below:

data "aws_ami" "ami" {
  most_recent   = true
  name_regex    = "name-of-ami"
  owners     = ["self"]
}

The data source above is for finding the most recent ami that matches the name_regex given.

Usage#

The data source can be then utilised within a resource or module:

module "pseudo_module" {
  source = "/path/to/pseudo/module/asg"

  ami = "${data.aws_ami.ami.id}"
}

In the example above I am using the ID that’s returned for the matching AMI. Alternatively data sources allow you to use a multitude of exported attributes, take the aws_ami data source for example.