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:
- Review the updated documentation.
- Visit the HashiCorp Learn Center.
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
More blog posts like this one
Fix the developers vs. security conflict by shifting further left
Resolve the friction between dev and security teams with platform-led workflows that make cloud security seamless and scalable.
HashiCorp at AWS re:Invent: Your blueprint to cloud success
If you’re attending AWS re:Invent in Las Vegas, Dec. 2 - Dec. 6th, visit us for breakout sessions, expert talks, and product demos to learn how to take a unified approach to Infrastructure and Security Lifecycle Management.
Speed up app delivery with automated cancellation of plan-only Terraform runs
Automatic cancellation of plan-only runs allows customers to easily cancel any unfinished runs for outdated commits to speed up application delivery.