terraform

Announcing Terraform 0.12

We are very proud to announce the release of Terraform 0.12.

Terraform 0.12 is a major update that includes dozens of improvements and features spanning the breadth and depth of Terraform's functionality.

Some highlights of this release include:

  • First-class expression syntax: express references and expressions directly rather than using string interpolation syntax.
  • Generalized type system: use lists and maps more freely, and use resources as object values.
  • Iteration constructs: transform and filter one collection into another collection, and generate nested configuration blocks from collections.
  • Structural rendering of plans: plan output now looks more like configuration making it easier to understand.
  • Context-rich error messages: error messages now include a highlighted snippet of configuration and often suggest exactly what needs to be changed to resolve them.

The full release changelog can be found here.

Here is an example of a Terraform configuration showing some new language features:

data "consul_key_prefix" "environment" {
  path = "apps/example/env"
}

resource "aws_elastic_beanstalk_environment" "example" {
  name        = "test_environment"
  application = "testing"

  setting {
    namespace = "aws:autoscaling:asg"
    name      = "MinSize"
    value     = "1"
  }

  dynamic "setting" {
    for_each = data.consul_key_prefix.environment.var
    content {
      namespace = "aws:elasticbeanstalk:application:environment"
      name      = setting.key
      value     = setting.value
    }
  }
}

output "environment" {
  value = {
    id           = aws_elastic_beanstalk_environment.example.id
    vpc_settings = {
      for s in aws_elastic_beanstalk_environment.example.all_settings :
      s.name => s.value
      if s.namespace == "aws:ec2:vpc"
    }
  }
}

»Getting Started

We have many resources available for 0.12 for new and existing users. To learn more about the new functionality of 0.12 you can:

To get started using 0.12:

  • Download the Terraform 0.12 release.
  • If you are upgrading from a previous release, read the upgrade guide to learn about the required upgrade steps.

»First-class Expression Syntax

Terraform uses expressions to propagate results from one resource into the configuration of another resource, and references within expressions create the dependency graph that Terraform uses to determine the order of operations during the apply step.

Prior versions of Terraform required all non-literal expressions to be included as interpolation sequences inside strings, such as "${azurerm_shared_image.image_definition_ubuntu.location}". Terraform 0.12 allows expressions to be used directly in any situation where a value is expected.

The following example shows syntax from prior Terraform versions:

variable "base_network_cidr" {
  default = "10.0.0.0/8"
}

resource "google_compute_network" "example" {
  name                    = "test-network"
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "example" {
  count = 4

  name          = "test-subnetwork"
  ip_cidr_range = "${cidrsubnet(var.base_network_cidr, 4, count.index)}"
  region        = "us-central1"
  network       = "${google_compute_network.custom-test.self_link}"
}

In Terraform 0.12, the expressions can be given directly:

variable "base_network_cidr" {
  default = "10.0.0.0/8"
}

resource "google_compute_network" "example" {
  name                    = "test-network"
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "example" {
  count = 4

  name          = "test-subnetwork"
  ip_cidr_range = cidrsubnet(var.base_network_cidr, 4, count.index)
  region        = "us-central1"
  network       = google_compute_network.custom-test.self_link
}

The difference is subtle in this simple example, but as expressions and configurations get more complex, this cleaner syntax will improve readability by focusing on what is important.

For more information on the Terraform 0.12 expression syntax, see Expressions.

»Generalized Type System

Terraform was originally focused on working just with strings. Although better support for data structures such as lists and maps was introduced in subsequent versions, many of the initial language features did not work well with them, making data structures frustrating to use.

One case where this was particularly pronounced was when using module composition patterns, where objects created by one module would need to be passed to another module. If one module creates an AWS VPC and some subnets, and another module depends on those resources, we would previously need to pass all of the necessary attributes as separate output values and input variables:

module "network" {
  source = "./modules/network"

  base_network_cidr = "10.0.0.0/8"
}

module "consul_cluster" {
  source = "./modules/aws-consul-cluster"

  vpc_id         = module.network.vpc_id
  vpc_cidr_block = module.network.vpc_cidr_block
  subnet_ids     = module.network.subnet_ids
}

Terraform 0.12's generalized type system makes composition more convenient by giving more options for passing objects and other values between modules. For example, the "network" module could instead be written to return the whole VPC object and a list of subnet objects, allowing them to be passed as a whole:

module "network" {
  source = "./modules/network"

  base_network_cidr = "10.0.0.0/8"
}

module "consul_cluster" {
  source = "./modules/aws-consul-cluster"

  vpc     = module.network.vpc
  subnets = module.network.subnets
}

Alternatively, if two modules are more tightly coupled to one another, you might choose to just pass the whole source module itself:

module "network" {
  source = "./modules/network"

  base_network_cidr = "10.0.0.0/8"
}

module "consul_cluster" {
  source = "./modules/aws-consul-cluster"

  network = module.network
}

This capability relies on the ability to specify complex types for input variables in modules. For example, the "network" variable in the aws-consul-cluster module might be declared like this:

variable "network" {
  type = object({
    vpc = object({
      id         = string
      cidr_block = string
    })
    subnets = set(object({
      id         = string
      cidr_block = string
    }))
  })
}

For more information on the different types that can be used when passing values between modules and between resources, see Type Constraints.

»Iteration Constructs

Another way in which data structures were inconvenient in prior versions was the lack of any general iteration constructs that could perform transformations on lists and maps.

Terraform 0.12 introduces a new for operator that allows building one collection from another by mapping and filtering input elements to output elements:

locals {
  public_instances_by_az = {
    for i in aws_instance.example : i.availability_zone => i...
    if i.associate_public_ip_address
  }
}

This feature allows us to adapt collection data returned in one format into another format that is more convenient to use elsewhere, such as turning a list into a map as in the example above. The output elements can be the result of any arbitrary Terraform expression, including another nested for expression!

Terraform 0.12 also introduces a mechanism for dynamically generating nested configuration blocks for resources. The dynamic "setting" block in the first example above illustrates that feature. Here is another example using an input variable to distribute Azure shared images over a specific set of regions:

variable "source_image_region" {
  type = string
}

variable "target_image_regions" {
  type = list(string)
}

resource "azurerm_shared_image_version" "ubuntu" {
  name                = "1.0.1"
  gallery_name        = azurerm_shared_image_gallery.image_gallery.name
  image_name          = azurerm_shared_image.image_definition.name
  resource_group_name = azurerm_resource_group.image_gallery.name
  location            = var.source_image_location
  managed_image_id    = data.azurerm_image.ubuntu.id[count.index]

  dynamic "target_region" {
    for_each = var.target_image_regions
    content {
      name                   = target_region.value
      regional_replica_count = 1
    }
  }
}

For more information on these features, see for expressions and dynamic blocks.

»Structural Rendering of Plans

Prior versions of Terraform reduced plan output to a flat list of key, value pairs, even when using resource types with deeply-nested configuration blocks. This would tend to become difficult to read, particularly when making changes to nested blocks where it was hard to understand exactly what changed.

Terraform 0.12 has an entirely new plan renderer which integrates with Terraform's new type system to show changes in a form that resembles the configuration language, and which indicates nested structures by indentation:

Terraform will perform the following actions:

  # kubernetes_pod.example will be updated in-place
  ~ resource "kubernetes_pod" "example" {
        id = "default/terraform-example"

        metadata {
            generation       = 0
            labels           = {
                "app" = "MyApp"
            }
            name             = "terraform-example"
            namespace        = "default"
            resource_version = "650"
            self_link        = "/api/v1/namespaces/default/pods/terraform-example"
            uid              = "5130ef35-7c09-11e9-be7c-080027f59de6"
        }

      ~ spec {
            active_deadline_seconds          = 0
            dns_policy                       = "ClusterFirst"
            host_ipc                         = false
            host_network                     = false
            host_pid                         = false
            node_name                        = "minikube"
            restart_policy                   = "Always"
            service_account_name             = "default"
            termination_grace_period_seconds = 30

          ~ container {
              ~ image                    = "nginx:1.7.9" -> "nginx:1.7.10"
                image_pull_policy        = "IfNotPresent"
                name                     = "example"
                stdin                    = false
                stdin_once               = false
                termination_message_path = "/dev/termination-log"
                tty                      = false

                resources {
                }
            }
        }
    }

Along with reflecting the natural configuration hierarchy in the plan output, Terraform will also show line-oriented diffs for multiline strings and will parse and show structural diffs for JSON strings, both of which have been big pain points for plan readability in prior versions.

»Context-Rich Error Messages

Terraform 0.12 includes much improved error messages for configuration errors and for many other potential problems.

The error messages in prior versions were of varying quality, sometimes giving basic context about a problem but often lacking much context at all, and being generally inconsistent in terminology.

The new Terraform 0.12 error messages follow a predictable structure:

Error: Unsupported Attribute

  on example.tf line 12, in resource "aws_security_group" "example":
  12:   description = local.example.foo
    |-----------------
    | local.example is "foo"

This value does not have any attributes.

Not every error message will include all of these components, but the general makeup of a new-style error message is:

  • A short description of the problem type, to allow quick recognition of familiar problems.
  • A reference to a specific configuration construct that the problem relates to, along with a snippet of the relevant configuration.
  • The values of any references that appear in the expression being evaluated.
  • A more detailed description of the problem and, where possible, potential solutions to the problem.

»Provider Compatibility

There are still providers that do not yet have 0.12-ready releases. We have made the decision to move forward with Terraform Core release in spite of that, in the interest of making this new release available as soon as possible for as many users as possible. We have published a 0.12 readiness guide for provider developers with details on how to make a provider 0.12-ready.

Providers that are not yet compatible will report that no compatible versions are available during terraform init, with the following error message:

Error: no available version is compatible with this version of Terraform

Our provider teams are still hard at work making these releases and we expect to have compatible releases for all HashiCorp-hosted providers shortly after the final Core 0.12 release.

»Conclusion

The changes described above are just a few of the highlights of Terraform 0.12. For more details, please see the full changelog. This release also includes a number of code contributions from the community, and wouldn't have been possible without all of the great community feedback we've received over the years via GitHub issues and elsewhere. Thank you!

We're very excited to share Terraform 0.12 with the community and we will continue building out features and functionality. In addition, HashiCorp recently released Terraform Cloud Remote State Storage and have plans for adding more functionality to make using Terraform a great experience for teams. You can download Terraform 0.12 here and sign up for a Terraform Cloud account here.


Sign up for the latest HashiCorp news

By submitting this form, you acknowledge and agree that HashiCorp will process your personal information in accordance with the Privacy Policy.