315 lines
11 KiB
Plaintext
315 lines
11 KiB
Plaintext
---
|
|
page_title: Upgrading to Terraform 0.11
|
|
description: Upgrading to Terraform v0.11
|
|
---
|
|
|
|
# Upgrading to Terraform v0.11
|
|
|
|
Terraform v0.11 is a major release and thus includes some changes that
|
|
you'll 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](https://github.com/hashicorp/terraform/blob/main/CHANGELOG.md).
|
|
After reviewing this guide, we recommend reviewing the Changelog to check on
|
|
specific notes about the resources and providers you use.
|
|
|
|
This guide focuses on changes from v0.10 to v0.11. Each previous major release
|
|
has its own upgrade guide, so please consult the other guides (available
|
|
in the navigation) if you are upgrading directly from an earlier version.
|
|
|
|
## Interactive Approval in `terraform apply`
|
|
|
|
Terraform 0.10 introduced a new mode for `terraform apply` (when run without
|
|
an explicit plan file) where it would show a plan and prompt for approval
|
|
before proceeding, similar to `terraform destroy`.
|
|
|
|
Terraform 0.11 adopts this as the default behavior for this command, which
|
|
means that for interactive use in a terminal it is not necessary to separately
|
|
run `terraform plan -out=...` to safely review and apply a plan.
|
|
|
|
The new behavior also has the additional advantage that, when using a backend
|
|
that supports locking, the state lock will be held throughout the refresh,
|
|
plan, confirmation and apply steps, ensuring that a concurrent execution
|
|
of `terraform apply` will not invalidate the execution plan.
|
|
|
|
A consequence of this change is that `terraform apply` is now interactive by
|
|
default unless a plan file is provided on the command line. When
|
|
[running Terraform in automation](https://learn.hashicorp.com/tutorials/terraform/automate-terraform?in=terraform/automation&utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS)
|
|
it is always recommended to separate plan from apply, but if existing automation
|
|
was running `terraform apply` with no arguments it may now be necessary to
|
|
update it to either generate an explicit plan using `terraform plan -out=...`
|
|
or to run `terraform apply -auto-approve` to bypass the interactive confirmation
|
|
step. The latter should be done only in unimportant environments.
|
|
|
|
**Action:** For interactive use in a terminal, prefer to use `terraform apply`
|
|
with out an explicit plan argument rather than `terraform plan -out=tfplan`
|
|
followed by `terraform apply tfplan`.
|
|
|
|
**Action:** Update any automation scripts that run Terraform non-interactively
|
|
so that they either use separated plan and apply or override the confirmation
|
|
behavior using the `-auto-approve` option.
|
|
|
|
## Relative Paths in Module `source`
|
|
|
|
Terraform 0.11 introduces full support for module installation from
|
|
[Terraform Registry](https://registry.terraform.io/) as well as other
|
|
private, in-house registries using concise module source strings like
|
|
`hashicorp/consul/aws`.
|
|
|
|
As a consequence, module source strings like `"child"` are no longer
|
|
interpreted as relative paths. Instead, relative paths must be expressed
|
|
explicitly by beginning the string with either `./` (for a module in a child
|
|
directory) or `../` (for a module in the parent directory).
|
|
|
|
**Action:** Update existing module `source` values containing relative paths
|
|
to start with either `./` or `../` to prevent misinterpretation of the source
|
|
as a Terraform Registry module.
|
|
|
|
## Interactions Between Providers and Modules
|
|
|
|
Prior to Terraform 0.11 there were several limitations in deficiencies in
|
|
how providers interact with child modules, such as:
|
|
|
|
* Ancestor module provider configurations always overrode the associated
|
|
settings in descendent modules.
|
|
|
|
* There was no well-defined mechanism for passing "aliased" providers from
|
|
an ancestor module to a descendent, where the descendent needs access to
|
|
multiple provider instances.
|
|
|
|
Terraform 0.11 changes some of the details of how each resource block is
|
|
associated with a provider configuration, which may change how Terraform
|
|
interprets existing configurations. This is notably true in the following
|
|
situations:
|
|
|
|
* If the same provider is configured in both an ancestor and a descendent
|
|
module, the ancestor configuration no longer overrides attributes from
|
|
the descendent and the descendent no longer inherits attributes from
|
|
its ancestor. Instead, each configuration is entirely distinct.
|
|
|
|
* If a `provider` block is present in a child module, it must either contain a
|
|
complete configuration for its associated provider or a configuration must be
|
|
passed from the parent module using
|
|
[the new `providers` attribute](/language/configuration-0-11/modules#providers-within-modules).
|
|
In the latter case, an empty provider block is a placeholder that declares
|
|
that the child module requires a configuration to be passed from its parent.
|
|
|
|
* When a module containing its own `provider` blocks is removed from its
|
|
parent module, Terraform will no longer attempt to associate it with
|
|
another provider of the same name in a parent module, since that would
|
|
often cause undesirable effects such as attempting to refresh resources
|
|
in the wrong region. Instead, the resources in the module resources must be
|
|
explicitly destroyed _before_ removing the module, so that the provider
|
|
configuration is still available: `terraform destroy -target=module.example`.
|
|
|
|
The recommended design pattern moving forward is to place all explicit
|
|
`provider` blocks in the root module of the configuration, and to pass
|
|
providers explicitly to child modules so that the associations are obvious
|
|
from configuration:
|
|
|
|
```hcl
|
|
provider "aws" {
|
|
region = "us-east-1"
|
|
alias = "use1"
|
|
}
|
|
|
|
provider "aws" {
|
|
region = "us-west-1"
|
|
alias = "usw1"
|
|
}
|
|
|
|
module "example-use1" {
|
|
source = "./example"
|
|
|
|
providers = {
|
|
"aws" = "aws.use1"
|
|
}
|
|
}
|
|
|
|
module "example-usw1" {
|
|
source = "./example"
|
|
|
|
providers = {
|
|
"aws" = "aws.usw1"
|
|
}
|
|
}
|
|
```
|
|
|
|
With the above configuration, any `aws` provider resources in the module
|
|
`./example` will use the us-east-1 provider configuration for
|
|
`module.example-use1` and the us-west-1 provider configuration for
|
|
`module.example-usw1`.
|
|
|
|
When a default (non-aliased) provider is used, and not explicitly
|
|
declared in a child module, automatic inheritance of that provider is still
|
|
supported.
|
|
|
|
**Action**: In existing configurations where both a descendent module and
|
|
one of its ancestor modules both configure the same provider, copy any
|
|
settings from the ancestor into the descendent because provider configurations
|
|
now inherit only as a whole, rather than on a per-argument basis.
|
|
|
|
**Action**: In existing configurations where a descendent module inherits
|
|
_aliased_ providers from an ancestor module, use
|
|
[the new `providers` attribute](/language/configuration-0-11/modules#providers-within-modules)
|
|
to explicitly pass those aliased providers.
|
|
|
|
**Action**: Consider refactoring existing configurations so that all provider
|
|
configurations are set in the root module and passed explicitly to child
|
|
modules, as described in the following section.
|
|
|
|
### Moving Provider Configurations to the Root Module
|
|
|
|
With the new provider inheritance model, it is strongly recommended to refactor
|
|
any configuration where child modules define their own `provider` blocks so
|
|
that all explicit configuration is defined in the _root_ module. This approach
|
|
will ensure that removing a module from the configuration will not cause
|
|
any provider configurations to be removed along with it, and thus ensure that
|
|
all of the module's resources can be successfully refreshed and destroyed.
|
|
|
|
A common configuration is where two child modules have different configurations
|
|
for the same provider, like this:
|
|
|
|
```hcl
|
|
# root.tf
|
|
|
|
module "network-use1" {
|
|
source = "./network"
|
|
region = "us-east-1"
|
|
}
|
|
|
|
module "network-usw2" {
|
|
source = "./network"
|
|
region = "us-west-2"
|
|
}
|
|
```
|
|
|
|
```hcl
|
|
# network/network.tf
|
|
|
|
variable "region" {
|
|
}
|
|
|
|
provider "aws" {
|
|
region = "${var.region}"
|
|
}
|
|
|
|
resource "aws_vpc" "example" {
|
|
# ...
|
|
}
|
|
```
|
|
|
|
The above example is problematic because removing either `module.network-use1`
|
|
or `module.network-usw2` from the root module will make the corresponding
|
|
provider configuration no longer available, as described in
|
|
[issue #15762](https://github.com/hashicorp/terraform/issues/15762), which
|
|
prevents Terraform from refreshing or destroying that module's `aws_vpc.example`
|
|
resource.
|
|
|
|
This can be addressed by moving the `provider` blocks into the root module
|
|
as _additional configurations_, and then passing them down to the child
|
|
modules as _default configurations_ via the explicit `providers` map:
|
|
|
|
```hcl
|
|
# root.tf
|
|
|
|
provider "aws" {
|
|
region = "us-east-1"
|
|
alias = "use1"
|
|
}
|
|
|
|
provider "aws" {
|
|
region = "us-west-2"
|
|
alias = "usw2"
|
|
}
|
|
|
|
module "network-use1" {
|
|
source = "./network"
|
|
|
|
providers = {
|
|
"aws" = "aws.use1"
|
|
}
|
|
}
|
|
|
|
module "network-usw2" {
|
|
source = "./network"
|
|
|
|
providers = {
|
|
"aws" = "aws.usw2"
|
|
}
|
|
}
|
|
```
|
|
|
|
```hcl
|
|
# network/network.tf
|
|
|
|
# Empty provider block signals that we expect a default (unaliased) "aws"
|
|
# provider to be passed in from the caller.
|
|
provider "aws" {
|
|
}
|
|
|
|
resource "aws_vpc" "example" {
|
|
# ...
|
|
}
|
|
```
|
|
|
|
After the above refactoring, run `terraform apply` to re-synchoronize
|
|
Terraform's record (in [the Terraform state](/language/state)) of the
|
|
location of each resource's provider configuration. This should make no changes
|
|
to actual infrastructure, since no resource configurations were changed.
|
|
|
|
For more details on the explicit `providers` map, and discussion of more
|
|
complex possibilities such as child modules with additional (aliased) provider
|
|
configurations, see [_Providers Within Modules_](/language/configuration-0-11/modules#providers-within-modules).
|
|
|
|
## Error Checking for Output Values
|
|
|
|
Prior to Terraform 0.11, if an error occurred when evaluating the `value`
|
|
expression within an `output` block then it would be silently ignored and
|
|
the empty string used as the result. This was inconvenient because it made it
|
|
very hard to debug errors within output expressions.
|
|
|
|
To give better feedback, Terraform now halts and displays an error message
|
|
when such errors occur, similar to the behavior for expressions elsewhere
|
|
in the configuration.
|
|
|
|
Unfortunately, this means that existing configurations may have erroneous
|
|
outputs lurking that will become fatal errors after upgrading to Terraform 0.11.
|
|
The prior behavior is no longer available; to apply such a configuration with
|
|
Terraform 0.11 will require adjusting the configuration to avoid the error.
|
|
|
|
**Action:** If any existing output value expressions contain errors, change these
|
|
expressions to fix the error.
|
|
|
|
### Referencing Attributes from Resources with `count = 0`
|
|
|
|
A common pattern for conditional resources is to conditionally set count
|
|
to either `0` or `1` depending on the result of a boolean expression:
|
|
|
|
```hcl
|
|
resource "aws_instance" "example" {
|
|
count = "${var.create_instance ? 1 : 0}"
|
|
|
|
# ...
|
|
}
|
|
```
|
|
|
|
When using this pattern, it's required to use a special idiom to access
|
|
attributes of this resource to account for the case where no resource is
|
|
created at all:
|
|
|
|
```hcl
|
|
output "instance_id" {
|
|
value = "${element(concat(aws_instance.example.*.id, list("")), 0)}"
|
|
}
|
|
```
|
|
|
|
Accessing `aws_instance.example.id` directly is an error when `count = 0`.
|
|
This is true for all situations where interpolation expressions are allowed,
|
|
but previously _appeared_ to work for outputs due to the suppression of the
|
|
error. Existing outputs that access non-existent resources must be updated to
|
|
use the idiom above after upgrading to 0.11.0.
|