Terraform
Upgrading to Terraform v0.15
Terraform v0.15 is a major release and so it includes some small changes in behavior that you may need to consider when upgrading. This guide is intended to help with that process.
The goal of this guide is to cover the most common upgrade concerns and issues that would benefit from more explanation and background. The exhaustive list of changes will always be the Terraform Changelog. After reviewing this guide, we recommend reviewing the Changelog to check for specific notes about less-commonly-used features.
This guide is also not intended as an overview of the new features in Terraform v0.15. This release includes other enhancements that don't need any special attention during upgrade, but those are described in the changelog and elsewhere in the Terraform documentation.
This guide focuses on changes from v0.14 to v0.15. Terraform supports upgrade tools and features only for one major release upgrade at a time, so if you are currently using a version of Terraform prior to v0.14 please upgrade through the latest minor releases of all of the intermediate versions first, reviewing the previous upgrade guides for any considerations that may be relevant to you.
Unlike the previous few Terraform major releases, v0.15's upgrade concerns are largely conclusions of deprecation cycles left over from previous releases, many of which already had deprecation warnings in v0.14. If you previously responded to those while using Terraform v0.14 then you hopefully won't need to make any special changes to upgrade, although we still recommend reviewing the content below to confirm, particularly if you see new errors or unexpected behavior after upgrading from Terraform v0.14.
If you run into any problems during upgrading that are not addressed by the information in this guide, please feel free to start a topic in The Terraform community forum, describing the problem you've encountered in enough detail that other readers may be able to reproduce it and offer advice.
Upgrade guide sections:
- Sensitive Output Values
- Legacy Configuration Language Features
- Alternative (Aliased) Provider Configurations Within Modules
- Commands Accepting a Configuration Directory Argument
- Microsoft Windows Terminal Support
- Other Minor Command Line Behavior Changes
- Azure Backend
arm_
-prefixed Arguments
Sensitive Output Values
Terraform v0.14 previously introduced the ability for Terraform to track and propagate the "sensitivity" of values through expressions that include references to sensitive input variables and output values. For example:
variable "example" {
type = string
sensitive = true
}
resource "example" "example" {
# The following value is also treated as sensitive, because it's derived
# from var.example.
name = "foo-${var.example}"
}
As part of that feature, Terraform also began requiring you to mark an output value as sensitive if its definition includes any sensitive values itself:
output "example" {
value = "foo-${var.example}"
# Must mark this output value as sensitive, because it's derived from
# var.example that is declared as sensitive.
sensitive = true
}
Terraform v0.15 extends this mechanism to also work for values derived from resource attributes that the provider has declared as being sensitive. Provider developers will typically mark an attribute as sensitive if the remote system has documented the corresponding field as being sensitive, such as if the attribute always contains a password or a private key.
As a result of that, after upgrading to Terraform v0.15 you may find that Terraform now reports some of your output values as invalid, if they were derived from sensitive attributes without also being marked as sensitive:
╷
│ Error: Output refers to sensitive values
│
│ on sensitive-resource-attr.tf line 5:
│ 5: output "private_key" {
│
│ Expressions used in outputs can only refer to sensitive values if the
│ sensitive attribute is true.
╵
If you were intentionally exporting a sensitive value, you can address the
error by adding an explicit declaration sensitive = true
to the output
value declaration:
output "private_key" {
value = tls_private_key.example.private_key_pem
sensitive = true
}
With that addition, if this output value was a root module output value then
Terraform will hide its value in the terraform plan
and terraform apply
output:
Changes to Outputs:
+ private_key = (sensitive value)
Note: The declaration of an output value as sensitive must be made within the module that declares the output, so if you depend on a third-party module that has a sensitive output value that is lacking this declaration then you will need to wait for a new version of that module before you can upgrade to Terraform v0.15.
The value is only hidden in the main UI, and so the sensitive value
will still be recorded in the state. If you declared this output value in order
to use it as part of integration with other software, you can still retrieve
the cleartext value using commands intended for machine rather than human
consumption, such as terraform output -json
or terraform output -raw
:
$ terraform output -raw private_key
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAoahsvJ1rIxTIOOmJZ7yErs5eOq/Kv9+5l3h0LbxW78K8//Kb
OMU3v8F3h8jp+AB/1zGr5UBYfnYp5ncJm/OTCXLFAHxGibEbRnf1m2A3o0hEaWsw
# (etc...)
If you consider Terraform's treatment of a sensitive value to be too
conservative and you'd like to force Terraform to treat a sensitive value as
non-sensitive, you can use
the nonsensitive
function to
override Terraform's automatic detection:
output "private_key" {
# WARNING: Terraform will display this result as cleartext
value = nonsensitive(tls_private_key.example.private_key_pem)
}
For more information on the various situations where sensitive values can originate in Terraform, refer to the following sections:
Note: The new behavior described in this section was previously
available in Terraform v0.14 as the
language experiment
provider_sensitive_attrs
. That experiment has now concluded, so if you were
participating in that experiment then you'll now need to remove the experiment
opt-in from your module as part of upgrading to Terraform v0.15.
Legacy Configuration Language Features
Terraform v0.12 introduced new syntax for a variety of existing Terraform language features that were intended to make the language easier to read and write and, in some cases, to better allow for future changes to the language.
Many of the old forms remained available but deprecated from v0.12 through to
v0.14, with these deprecations finally concluding in the v0.15 release. Those
who used the terraform 0.12upgrade
command when upgrading from Terraform v0.11
to v0.12 will have had these updated automatically, but we've summarized the
changes below to help with any remaining legacy forms you might encounter while
upgrading to Terraform v0.15:
The built-in functions
list
andmap
were replaced with first-class syntax[ ... ]
and{ ... }
in Terraform v0.12, and we've now removed the deprecated functions in order to resolve the ambiguity with the syntax used to declare list and map type constraints insidevariable
blocks.If you need to update a module which was using the
list
function, you can get the same result by replacinglist(...)
withtolist([...])
. For example:- list("a", "b", "c") + tolist(["a", "b", "c"])
If you need to update a module which was using the
map
function, you can get the same result by replacingmap(...)
withtomap({...})
. For example:- map("a", 1, "b", 2) + tomap({ a = 1, b = 2 })
The above examples include the type conversion functions
tolist
andtomap
to ensure that the result will always be of the same type as before. However, in most situations those explicit type conversions won't be necessary because Terraform can infer the necessary type conversions automatically from context. In those cases, you can just use the[ ... ]
or{ ... }
syntax directly, without a conversion function.In
variable
declaration blocks, thetype
argument previously accepted v0.11-style type constraints given as quoted strings. This legacy syntax is removed in Terraform v0.15.To update an old-style type constraint to the modern syntax, start by removing the quotes so that the argument is a bare keyword rather than a string:
variable "example" { type = string }
Additionally, if the previous type constraint was either
"list"
or"map
", add a type argument to specify the element type of the collection. Terraform v0.11 typically supported only collections of strings, so in most cases you can set the element type tostring
:variable "example" { type = list(string) } variable "example" { type = map(string) }
In
lifecycle
blocks nested insideresource
blocks, Terraform previously supported a legacy value["*"]
for theignore_changes
argument, which is removed in Terraform v0.15.Instead, use the
all
keyword to indicate that you wish to ignore changes to all of the resource arguments:lifecycle { ignore_changes = all }
Finally, Terraform v0.11 and earlier required all non-constant expressions to be written using string interpolation syntax, even if the result was not a string. Terraform v0.12 introduced a less confusing syntax where arguments can accept any sort of expression without any special wrapping, and so the interpolation-style syntax has been redundant and deprecated in recent Terraform versions.
For this particular change we have not made the older syntax invalid, but we do still recommend updating interpolation-only expressions to bare expressions to improve readability:
- example = "${var.foo}" + example = var.foo
This only applies to arguments where the value is a single expression without any string concatenation. You must continue to use the
${ ... }
syntax for situations where you are combining string values together into a larger string.The
terraform fmt
command can detect and repair simple examples of the legacy interpolation-only syntax, and so we'd recommend runningterraform fmt
on your modules once you've addressed any of the other situations above that could block configuration parsing in order to update your configurations to the typical Terraform language style conventions.
Alternative Provider Configurations Within Modules
Terraform's provider configuration scheme includes the idea of a "default" (unaliased) provider configuration along with zero or more alternative (aliased) provider configurations.
The required_providers
block now has a new field for providers to indicate
aliased configuration names, replacing the need for an empty "proxy
configuration block" as a placeholder. In order to declare
configuration aliases,
add the desired names to the configuration_aliases
argument for the provider
requirements.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 2.7.0"
configuration_aliases = [ aws.alternate ]
}
}
}
Warnings will be emitted now where empty configuration blocks are present but no longer required, though they continue to work unchanged in the 0.15 release. There are a few cases where existing configurations may return new errors:
The
providers
map in a module call cannot override a provider configured within the module. This is not a supported configuration, but was previously missed in validation and now returns an error.A provider alias within a module that has no configuration requires a provider configuration be supplied in the module
providers
map.All entries in the
providers
map in a module call must correspond to a provider name within the module. Passing in a configuration to an undeclared provider is now an error.
Commands Accepting a Configuration Directory Argument
A subset of Terraform's CLI commands have historically accepted a final positional argument to specify which directory contains the root module of the configuration, overriding the default behavior of expecting to find it in the current working directory.
However, the design of that argument was flawed in a number of ways due to it being handled at the wrong level of abstraction: it only changed where Terraform looks for configuration and not any of the other files that Terraform might search for, and that could therefore violate assumptions that Terraform configurations might make about the locations of different files, leading to confusing error messages. It was also not possible to support this usage pattern across all commands due to those commands using positional arguments in other ways.
To address these design flaws, Terraform v0.14 introduced a new global option
-chdir
which you can use before the subcommand name, causing Terraform to
run the subcommand as if the given directory had been the current working
directory:
$ terraform -chdir=example init
This command causes the Terraform process to actually change its current working directory to the given directory before launching the subcommand, and so now any relative paths accessed by the subcommand will be treated as relative to that directory, including (but not limited to) the following key directory conventions:
As with the positional arguments that
-chdir
replaces, Terraform will look for the root module's.tf
and.tf.json
files in the given directory.The
.tfvars
and.tfvars.json
files that Terraform automatically searches for, and any relative paths given in-var-file
options, will be searched in the given directory.The
.terraform
directory which Terraform creates to retain the working directory internal state will appear in the given directory, rather than the current working directory.
After treating the v0.14 releases as a migration period for this new behavior,
Terraform CLI v0.15 no longer accepts configuration directories on any
command except terraform fmt
. (terraform fmt
is special compared to the
others because it primarily deals with configuration files in isolation,
rather than modules or configurations as a whole.)
If you built automation which previously relied on overriding the configuration
directory alone, you will need to transition to using the -chdir
command line
option before upgrading to Terraform v0.15.
Since the -chdir
argument behavior is more comprehensive than the positional
arguments it has replaced, you may need to make some further changes in the
event that your automation was relying on the limitations of the old mechanism:
If your system depends on the
.terraform
directory being created in the real current working directory while using a root module defined elsewhere, you can use theTF_DATA_DIR
environment variable to specify the absolute path where Terraform should store its working directory internal state:TF_DATA_DIR="$PWD/.terraform"
If your system uses
.tfvars
or.tfvars.json
files either implicitly found or explicitly selected in the current working directory, you must either move those variables files into the root module directory or specify your files from elsewhere explicitly using the-var-file
command line option:terraform plan -var-file="$PWD/example.tfvars"
As a special case for backward compatibility, Terraform ensures that the
language expression path.cwd
will return the original working directory,
before overriding with -chdir
, so that existing configurations referring to
files in that directory can still work. If you want to refer to files in the
directory given in -chdir
then you can use path.root
, which returns the
directory containing the configuration's root module.
Microsoft Windows Terminal Support
Until the first Windows 10 update, Microsoft Windows had a console window implementation with an API incompatible with the virtual terminal approach taken on all other platforms that Terraform supports.
Previous versions of Terraform accommodated this by using an API translation layer which could convert a subset of typical virtual terminal sequences into corresponding Windows Console API function calls, but as a result this has prevented Terraform from using more complex terminal features such as progress indicators that update in place, menu prompts, etc.
Over the course of several updates to Windows 10, Microsoft has introduced virtual terminal support similar to other platforms and now recommends the virtual terminal approach for console application developers. In response to that recommendation, Terraform v0.15 no longer includes the terminal API translation layer and consequently it will, by default, produce incorrectly-formatted output on Windows 8 and earlier, and on non-updated original retail Windows 10 systems.
If you need to keep using Terraform on an older version of Windows, there are two possible workarounds available in the v0.15.0 release:
Run Terraform commands using the
-no-color
command line option to disable the terminal formatting sequences.This will cause the output to be unformatted plain text, but as a result will avoid the output being interspersed with uninterpreted terminal control sequences.
Alternatively, you can use Terraform v0.15.0 in various third-party virtual terminal implementations for older Windows versions, including ConEmu, Cmder, and mintty.
Although we have no immediate plans to actively block running Terraform on older versions of Windows, we will not be able to test future versions of Terraform on those older versions and so later releases may contain unintended regressions. We recommend planning an upgrade to a modern Windows release on any system where you expect to continue using Terraform CLI.
Other Minor Command Line Behavior Changes
Finally, Terraform v0.15 includes a small number of minor changes to the details of some commands and command line arguments, as part of a general cleanup of obsolete features and improved consistency:
Interrupting Terraform commands with your operating system's interrupt signal (
SIGINT
on Unix systems) will now cause Terraform to exit with a non-successful exit code. Previously it would, in some cases, exit with a success code.This signal is typically sent to Terraform when you press Ctrl+C or similar interrupt keyboard shortcuts in an interactive terminal, but might also be used by automation in order to gracefully cancel a long-running Terraform operation.
The
-lock
and-lock-timeout
options are no longer available for theterraform init
command. Locking applies to operations that can potentially change remote objects, to help ensure that two concurrent Terraform processes don't try to run conflicting operations, butterraform init
does not interact with any providers in order to possibly effect such changes.These options didn't do anything in the
terraform init
command before, and so you can remove them from any automated calls with no change in behavior.The
-verify-plugins
and-get-plugins
options toterraform init
are no longer available. These have been non-functional since Terraform v0.13, with the introduction of the new Terraform Registry-based provider installer, because in practice there are very few operations Terraform can perform which both require aterraform init
but can also run without valid provider plugins installed.If you were using these options in automated calls to
terraform init
, remove them from the command line for compatibility with Terraform v0.15. There is no longer an option to initialize without installing the required provider plugins.The
terraform destroy
command no longer accepts the option-force
. This was a previous name for the option in earlier Terraform versions, but we've since adopted-auto-approve
for consistency with theterraform apply
command.If you are using
-force
in an automated call toterraform destroy
, change to using-auto-approve
instead.
Azure Backend Removed Arguments
In an earlier release the azure
backend changed to remove the arm_
prefix
from a number of the configuration arguments:
Old Name | New Name |
---|---|
arm_client_id | client_id |
arm_client_secret | client_secret |
arm_subscription_id | subscription_id |
arm_tenant_id | tenant_id |
The old names were previously deprecated, but we've removed them altogether in Terraform v0.15 in order to conclude that deprecation cycle.
If you have a backend configuration using the old names then you may see errors like the following when upgrading to Terraform v0.15:
╷
│ Error: Invalid backend configuration argument
│
│ The backend configuration argument "arm_client_id" given on
│ the command line is not expected for the selected backend type.
╵
If you see errors like this, rename the arguments in your backend configuration as shown in the table above and then run the following to re-initialize your backend configuration:
terraform init -reconfigure
The -reconfigure
argument instructs Terraform to just replace the old
configuration with the new configuration directly, rather than offering to
migrate the latest state snapshots from the old to the new configuration.
Migration would not be appropriate in this case because the old and new
configurations are equivalent and refer to the same remote objects.