terraform/website/upgrade-guides/0-12.html.markdown

544 lines
23 KiB
Markdown

---
layout: "guides"
page_title: "Upgrading to Terraform 0.12"
sidebar_current: "upgrade-guides-0-12"
description: |-
Upgrading to Terraform v0.12
---
# Upgrading to Terraform v0.12
~> Terraform 0.12 has not yet been released. This guide includes some initial
information to help when trying out the beta releases of Terraform v0.12.0, and
will be updated with more detail until the final release. Please do not use
v0.12.0 prereleases against production infrastructure.
~> If you are trying v0.12.0-beta1, please see [the release announcement](https://www.hashicorp.com/blog/announcing-terraform-0-1-2-beta1) for some important extra information.
[Terraform v0.12 will be a major release](https://hashicorp.com/blog/terraform-0-1-2-preview)
focused on configuration language improvements and thus will include some
changes that you'll need to consider when upgrading. The goal of this guide is
to cover the most common upgrade concerns and issues.
For most users, upgrading configuration should be completely automatic. Some
simple configurations will require no changes at all, and most other
configurations can be prepared by running
[the automatic upgrade tool](/docs/commands/0.12upgrade.html). Please read on
for more information and recommendations on the upgrade process.
## Upgrade to Terraform 0.11 first
If you are currently using Terraform v0.10 or earlier, we strongly recommend
first completing an upgrade to the latest Terraform v0.11 release first. This
will give you an opportunity to address any changes required for the previous
major version upgrades separately, rather than making multiple changes at
once.
In particular, if you are upgrading from a Terraform version prior to v0.9,
you _must_ first [upgrade to Terraform v0.9](/upgrade-guides/0-9.html) and
switch to initializing with `terraform init`, because v0.12 no longer includes
the functionality for automatically migrating from the legacy remote state
mechanism.
This guide focuses on changes from v0.11 to v0.12. Each previous major release
has its own upgrade guide, so please consult the other guides (available in the
navigation) to upgrade step-by-step to v0.11 first.
## Upgrading Terraform providers
The new language features in Terraform v0.12 required some changes to the
protocol Terraform uses to interact with provider plugins. These changes give
Terraform CLI access to the resource type schemas of each provider, allowing
for more helpful validation-related error messages and more predictable behavior.
However, this means that provider releases built before v0.12 cannot be used.
We have updated the provider SDK to support both the old and new protocols at
once, to allow upgrading to newer provider versions while remaining on
Terraform v0.11.
We recommend upgrading to the latest versions of all providers you use
and ensuring that `terraform plan` is working with them before upgrading to
Terraform v0.12, since this allows you to reduce risk by changing only one
component at a time, particularly if you will be adopting a new major version
of a provider which may have breaking changes of its own.
### Third-party Providers
The Terraform team at HashiCorp is working with the maintainers of the
HashiCorp-distributed providers to produce v0.12-compatible releases, which
will appear gradually before the v0.12.0 final release.
Third-party providers that are not distributed by HashiCorp will also require
updates. We will share more information on the upgrade procedure as we get
closer to final release. In the mean time, the first step is to upgrade the
vendored `github.com/hashicorp/terraform` packages to a v0.12 release tag and
verify that the acceptance tests are still working. Because acceptance tests
contain configuration snippets, you may need to perform some of the
configuration upgrade steps described in the following sections to make the
acceptance tests compatible with the v0.12 configuration language.
## Upgrading Terraform configuration
Some users with simple configurations may find that no changes are required at
all, and most configurations that _do_ require updates can be upgraded
automatically using [the automatic upgrade tool](/docs/commands/0.12upgrade.html).
Some users have written configurations that include workarounds for limitations
in previous versions of the Terraform language, such as:
- Treating block types like attributes in an attempt to work around Terraform
not supporting generating nested blocks dynamically.
([#7034](https://github.com/hashicorp/terraform/issues/7034))
- Wrapping redundant list brackets (`[` and `]`) around splat expressions in
order to force them to be interpreted as lists even when there are unknown
items in the list.
These workarounds were clever solutions offered by community members, and have
been partial inspiration for new language features. These workarounds should no
longer be necessary in Terraform v0.12, but the same results may now need to be
achieved using new language constructs.
The upgrade tool can replace many of these workarounds with the new solutions
automatically. In rarer cases, the intent of the original configuration may be
ambiguous, in which case the tool will add to your configuration a comment
containing the marker `TF-UPGRADE-TODO` to indicate a situation where your
human intuition is required to decide how to proceed.
We recommend running the upgrade tool in a clean version control work tree so
that you can use the VCS diffing tools to easily see and review all of the
proposed updates. Search the upgraded module for `TF-UPGRADE-TODO` to find
the situations where human attention is required.
Even if your existing configuration works without upgrading, we still recommend
to run the upgrade tool to update to the more readable syntax conventions
supported in this release, and to draw attention to any potential issues.
The following sections describe in more detail some of the situations that will
be detected and upgraded by the upgrade tool, both to help understand the
purpose of certain proposed changes and to help users who may not wish to
use the automatic upgrade tool. However, the following sections are not
completely comprehensive so we still recommend using the upgrade tool to review
its output, even if you then discard the proposed changes and make your updates
manually.
### Remote state references
The `terraform_remote_state` data source has changed slightly for the v0.12
release to make all of the remote state outputs available as a single map
value, rather than as top-level attributes as in previous releases.
In previous releases, a reference to a `vpc_id` output exported by the remote
state data source might have looked like this:
```hcl
data.terraform_remote_state.vpc.vpc_id
```
This value must now be accessed via the new `outputs` attribute:
```hcl
data.terraform_remote_state.vpc.outputs.vpc_id
```
Where appropriate, you can also access the outputs attribute directly to
work with the whole map as a single value:
```hcl
data.terraform_remote_state.vpc.outputs
```
### Attributes vs. blocks
Terraform resource configurations consist of both arguments that set
individual properties of the main object being described, and nested blocks
which declare zero or more other objects that are modeled as being part of
their parent. For example:
```hcl
resource "aws_instance" "example" {
instance_type = "t2.micro"
ami = "ami-abcd1234"
tags = {
Name = "example instance"
}
ebs_block_device {
device_name = "sda2"
volume_type = "gp2"
volume_size = 24
}
}
```
In the above resource, `instance_type`, `ami`, and `tags` are both direct
arguments of the `aws_instance` resource, while `ebs_block_device` describes
a separate EBS block device object that is, in some sense, a part of the
parent instance.
Due to the design of the configuration language decoder in Terraform v0.11 and
earlier, it was in many cases possible to interchange the argument syntax
(with `=`) and the block syntax (with just braces) when dealing with map
arguments vs. nested blocks. However, this led to some subtle bugs and
limitations, so Terraform v0.12 now requires consistent usage of argument
syntax for arguments and nested block syntax for nested blocks.
In return for this new strictness, Terraform v0.12 now allows map keys to be
set dynamically from expressions, which is a long-requested feature. The
main difference between a map attribute and a nested block is that a map
attribute will usually have user-defined keys, like we see in the `tags`
example above, while a nested block always has a fixed set of supported
arguments defined by the resource type schema, which Terraform will validate.
The configuration upgrade tool uses the provider's schema to recognize the
nature of each construct and will select the right syntax automatically. For
most simple usage, this will just involve adding or removing the equals sign
as appropriate.
A more complicated scenario is where users found that they could exploit this
flexibility to -- with some caveats -- dynamically generate nested blocks even
though this wasn't intentionally allowed:
```hcl
# Example of no-longer-supported workaround from 0.11 and earlier
ebs_block_device = "${concat(map("device_name", "sda4"), var.extra_block_devices)}"
```
Terraform v0.12 now includes a first-class feature for dynamically generating
nested blocks using expressions, using the special `dynamic` block type. The
above can now be written like this, separating the static block device from
the dynamic ones:
```hcl
ebs_block_device {
device_name = "sda4"
}
dynamic "ebs_block_device" {
for_each = var.extra_block_devices
content {
device_name = ebs_block_device.value.device_name
volume_type = ebs_block_device.value.volume_type
volume_size = ebs_block_device.value.volume_size
}
}
```
The configuration upgrade tool will detect use of the above workaround and
rewrite it as a `dynamic` block, but it may make non-ideal decisions for how to
flatten your expression down into static vs. dynamic blocks, so we recommend
reviewing the generated `dynamic` blocks to see if any simplifications are
possible.
Terraform v0.12 now also requires that each argument be set only once within
a particular block, whereas before Terraform would either take the last
definition or, in some cases, attempt to merge together multiple definitions
into a list. The upgrade tool does not remove or attempt to consolidate
any existing duplicate arguments, but other commands like `terraform validate`
will detect and report these after upgrading.
## Integer vs. Float Number Types
From Terraform v0.12, the Terraform language no longer distinguishes between
integer and float types, instead just having a single "number" type that can
represent high-precision floating point numbers. This new type can represent
any value that could be represented before, plus many new values due to the
expanded precision.
In most cases this change should not cause any significant behavior change, but
please note that in particular the behavior of the division operator is now
different: it _always_ performs floating point division, whereas before it
would sometimes perform integer division by attempting to infer intent from
the argument types.
If you are relying on integer division behavior in your configuration, please
use the `floor` function to obtain the previous result. A common place this
would arise is in index operations, where the index is computed by division:
```hcl
example = var.items[floor(count.index / var.any_number)]
```
Using a fractional number to index a list will produce an error telling you
that this is not allowed, serving as a prompt to add `floor`:
```
Error: Invalid index
The given key does not identify an element in this collection value: indexing a
sequence requires a whole number, but the given index (0.5) has a fractional
part.
```
Unfortunately the automatic upgrade tool cannot apply a fix for this case
because it does not have enough information to know if floating point or integer
division was intended by the configuration author, so this change must be made
manually where needed.
## Referring to List Variables
In early versions of Terraform, before list support became first-class, we
required using seemingly-redundant list brackets around a single expression
in order to hint to the language interpreter that a list interpretation was
desired:
```hcl
# Example for older versions of Terraform; not valid for v0.12
example = ["${var.any_list}"]
```
This strange requirement was subsequently lifted after the introduction of
first-class list support, but we retained compatibility with this older usage
for a transitional period by including some fixup logic that would detect when
list brackets contain list expressions and automatically flatten to a single
list.
As part of implementing the first-class expressions support for v0.12, we needed
to finally remove that backward-compatibility mechanism to avoid ambiguity
in the language, so an expression like the above will now produce a list of
lists and thus produce a type checking error for any argument that was expecting
a list of some other type.
The upgrade tool is able to recognize most simple usage of this pattern and
rewrite automatically to just refer to the list directly:
```hcl
example = var.any_list
```
However, an unintended side-effect of this compatiblity mechanism was to
also flatten mixed lists of single-value and list expressions into a single
list automatically. We didn't intend for this to be a part of the language, but
in retrospect it was an obvious consequence of how the compatibility mechanism
was implemented. If you have expressions in your modules that produce a list
of strings by using list brackets with a mixture of string and list-of-string
sub-expressions, you will need to rewrite this to explicitly use
[the `flatten` function](/docs/configuration/functions/flatten.html)
to make the special treatment more obvious to the reader:
```hcl
example = flatten([
"single string",
var.any_list,
])
```
The configuration upgrade tool unfortunately cannot make this change
automatically, because it doesn't have enough information to know for certain
which interpretation was intended for a given list.
## Terraform Configuration upgrades requiring human intervention
There are some known situations that will be detected, but not upgrade, by the
upgrade tool. Some examples of these situatations include:
* `count` can no longer be used a variable name.
* `resource` names cannot start with a number, though they can still contain numbers.
## Working with `count` on resources
The `count` feature allows declaration of multiple instances of a particular
resource constructed from the same configuration. In Terraform v0.11, any
use of `count` would generally lead to referring to the resource in question
using the "splat expression" syntax elsewhere in the configuration:
```
aws_instance.example.*.id[0]
```
Because `aws_instance.example` itself was not directly referencable in
Terraform v0.11, the expression system allowed some flexibility in how such
expressions were resolved. For example, Terraform would treat
`aws_instance.example.id` as an alias for `aws_instance.example.*.id[0]`.
Terraform v0.12 allows referring to an entire resource as an object value,
but that required making a decision on what type of value is returned by
`aws_instance.example`. The new rules are as follows:
* For resources where `count` is _not_ set, a reference like
`aws_instance.example` returns a single object, whose attributes can be
accessed in the usual way, like `aws_instance.example.id`.
* For resources where `count` _is_ set -- even if the expression evaluates to
`1` -- `aws_instance.example` returns a list of objects whose length is
decided by the count. In this case `aws_instance.example.id` is an error,
and must instead be written as `aws_instance.example[0].id` to access
one of the objects before retrieving its `id` attribute value.
The splat syntax is still available and will still be useful in situations
where a list result is needed, but we recommend updating expressions like
`aws_instance.example.*.id[count.index]` to instead be
`aws_instance.example[count.index].id`, which should be easier to read and
understand for those who are familiar with other languages.
Another consequence of the new handling of `count` is that you can use the
`length` function directly with references to resources that have `count` set:
```
length(aws_instance.example)
```
This replaces the v0.11 special case of `aws_instance.example.count`, which
can no longer be supported due to `aws_instance.example` being a list.
The upgrade tool will automatically detect references that are inconsistent
with the `count` setting on the target resource and rewrite them to use the
new syntax. The upgrade tool will _not_ rewrite usage of splat syntax to
direct index syntax, because the old splat syntax form is still compatible.
Another `count`-related change is that Terraform now requires `count` to be
assigned a numeric value, and will not automatically convert a boolean value
to a number in the interests of clarity. If you wish to use a boolean value
to activate or deactivate a particular resource, use the conditional operator
to show clearly how the boolean value maps to a number value:
```hcl
count = var.enabled ? 1 : 0
```
## First-class expressions
Terraform v0.11 and earlier allowed expressions only within interpolation
sequences, like `"${var.example}"`. Because expressions are such an important
part of Terraform -- they are the means by which we connect the attributes of
one resource to the configuration of another -- Terraform v0.12 now allows
you to use expressions directly when defining most attributes.
```
ami = var.ami
```
The generalization of expression handling also has some other benefits. For
example, it's now possible to directly construct lists and maps within
expressions using the normal syntax, whereas in Terraform v0.11 we required
using the `list` and `map` functions:
```
# Old 0.11 example
tags = "${merge(map("Name", "example"), var.common_tags)}"
# Updated 0.12 example
tags = merge({ Name = "example" }, var.common_tags)
```
The automatic upgrade tool will perform rewrites like these automatically,
making expressions easier to read and understand.
## Default settings in `connection` blocks
Terraform v0.11 and earlier allowed providers to pre-populate certain arguments
in a `connection` block for use with remote provisioners. Several resource
type implementations use this to pre-populate `type` as `"ssh"` and `host`
as one of the IP addresses of the compute instance being created.
While that feature was convenient in some cases, we found that in practice it
was hard for users to predict how it would behave, since each provider had its
own rules for whether to prefer public vs. private IP addresses, which network
interface to use, whether to use IPv4 or IPv6, etc.
It also violated our design principle of "explicit is better than implicit": we
think it's important that someone who is unfamiliar with a particular Terraform
configuration (or with Terraform itself) to be able to read the configuration
and make a good guess as to what it will achieve, and the default connection
settings feature left an important detail unstated: how do the provisioners
access the host?
With this in mind, Terraform v0.12 no longer performs any automatic population
of `connection` blocks. Instead, if you are using any remote provisioners you
should explicitly set the connection type and the hostname to connect to:
```hcl
connection {
type = "ssh"
host = self.public_ip
# ...
}
```
The automatic upgrade tool will detect existing `connection` blocks that are
lacking these settings within resource types that are known to have previously
set defaults, and it will write out an expression that approximates whatever
selection logic the provider was previously doing in its own implementation.
Unfortunately in some cases the provider did not export the result of the
possibly-rather-complex host selection expression as a single attribute, and so
for some resource types the generated `host` expression will be quite
complicated. We recommend reviewing these and replacing them with a simpler
expression where possible, since you will often know better than Terraform does
which of the instance IP addresses are likely to be accessible from the host
where Terraform is running.
## Upgrades for reusable modules
If you are making upgrades to a reusable module that is consumed by many
different configurations, you may need to take care with the timing of your
upgrade and of how you publish it.
We strongly recommend using module versioning, either via a Terraform registry
or via version control arguments in your module source addresses, to pin
existing references to the old version of the module and then publish the
upgraded version under a new version number. If you are using semantic
versioning, such as in a Terraform registry, the updates made by the upgrade
tool should be considered a breaking change and published as a new major
version.
The migration tool will automatically add a `>= 0.12.0` Terraform version
constraint to indicate that the module has been upgraded to use v0.12-only
features. By using version constraints, users can gradually update their callers
to use the newly-upgraded version as they begin to use Terraform v0.12 with
those modules.
For simpler modules it may be possible to carefully adapt them to be both
0.11 and 0.12 compatible at the same time, by following the upgrade notes in
earlier sections and avoiding any 0.12-only features. However, for any module
using a undocumented workarounds for 0.11 limitations it is unlikely to be
possible to both update it for Terraform v0.12 and retain v0.11 compatibility
at the same time, because those undocumented workarounds have been replaced
with new features in Terraform v0.12.
## Map variables no longer merge when overridden
In prior versions of Terraform, a variable of type `"map"` had a special
behavior where any value provided via mechanisms such as the `-var` command
line option would be keywise-merged with any default value associated with
the variable. This was useful in early versions of Terraform that lacked
mechanisms for doing such merging explicitly, but since Terraform v0.10
introduced the concept of local values we consider it preferable to perform
such merges manually so that they are explicit in configuration:
```
variable "example_map" {
type = map(string)
default = {}
}
locals {
default_map_keys = {
"a" = "b"
}
merged_map_keys = merge(local.default_map_keys, var.example_map)
}
```
In order to improve the consistency of variable handling across types, the
map variable merging behavior is removed in Terraform v0.12. Because this
mechanism was driven by command line options rather than configuration, the
automatic upgrade tool cannot automatically handle it. If you are relying on
the merging feature, you must reorganize your configuration to use explicit
merging like in the above example, or else your default map value will be
entirely overridden by any explicitly-set value.
## Upgrading Sentinel policies
Terraform Enterprise users of Sentinel will need to complete the below steps to
upgrade Sentinel to work with Terraform 0.12.
1. Update Terraform configurations to 0.12
1. Update Sentinel policies
Because Sentinel is applied across all workspaces in Terraform Enterprise, all
workspaces must be upgraded to Terraform 0.12 otherwise Sentinel policies will
fail on versions below 0.12.
More details on this upgrade process will be added prior to the final release.