Announcing Terraform 0.12

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:

“`hcl
data "consulkeyprefix" "environment" {
path = "apps/example/env"
}

resource "awselasticbeanstalkenvironment" "example" {
name = "test
environment"
application = "testing"

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

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

output "environment" {
value = {
id = awselasticbeanstalkenvironment.example.id
vpc
settings = {
for s in awselasticbeanstalkenvironment.example.allsettings :
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:

“`hcl
variable "basenetworkcidr" {
default = "10.0.0.0/8"
}

resource "googlecomputenetwork" "example" {
name = "test-network"
autocreatesubnetworks = false
}

resource "googlecomputesubnetwork" "example" {
count = 4

name = "test-subnetwork"
ipcidrrange = "${cidrsubnet(var.basenetworkcidr, 4, count.index)}"
region = "us-central1"
network = "${googlecomputenetwork.custom-test.self_link}"
}
“`

In Terraform 0.12, the expressions can be given directly:

“`hcl
variable "basenetworkcidr" {
default = "10.0.0.0/8"
}

resource "googlecomputenetwork" "example" {
name = "test-network"
autocreatesubnetworks = false
}

resource "googlecomputesubnetwork" "example" {
count = 4

name = "test-subnetwork"
ipcidrrange = cidrsubnet(var.basenetworkcidr, 4, count.index)
region = "us-central1"
network = googlecomputenetwork.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:

“`hcl
module "network" {
source = "./modules/network"

basenetworkcidr = "10.0.0.0/8"
}

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

vpcid = module.network.vpcid
vpccidrblock = module.network.vpccidrblock
subnetids = module.network.subnetids
}
“`

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:

“`hcl
module "network" {
source = "./modules/network"

basenetworkcidr = "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:

“`hcl
module "network" {
source = "./modules/network"

basenetworkcidr = "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:

hcl
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:

hcl
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:

“`hcl
variable "sourceimageregion" {
type = string
}

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

resource "azurermsharedimageversion" "ubuntu" {
name = "1.0.1"
gallery
name = azurermsharedimagegallery.imagegallery.name
imagename = azurermsharedimage.imagedefinition.name
resourcegroupname = azurermresourcegroup.imagegallery.name
location = var.source
imagelocation
managed
imageid = data.azurermimage.ubuntu.id[count.index]

dynamic "targetregion" {
for
each = var.targetimageregions
content {
name = targetregion.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:

# kubernetespod.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 "awssecuritygroup" "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.

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.

from Hashicorp Blog

Sharing is caring!

Comments are closed.