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:
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"
}
}
}
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:
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.
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.
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.
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.
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:
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.
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.
Terraform Enterprise now supports more flexible deployment options for self-hosted environments, including cloud-managed Kubernetes services.
Assigning agents at the organization level provides a faster, more consistent, and scalable approach to agent pool configuration.
Learn how creating a golden image pipeline can help unify and streamline your imaging and provisioning workflows throughout your infrastructure estate.