opentofu/website/upgrade-guides/0-11.html.markdown

318 lines
12 KiB
Markdown
Raw Normal View History

---
layout: "language"
page_title: "Upgrading to Terraform 0.11"
sidebar_current: "upgrade-guides-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.
2017-11-17 16:54:33 -06:00
* 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](/docs/configuration-0-11/modules.html#providers-within-modules).
2017-11-17 16:54:33 -06:00
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`.
2017-11-17 16:54:33 -06:00
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](/docs/configuration-0-11/modules.html#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](/docs/language/state/index.html)) 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_](/docs/configuration-0-11/modules.html#providers-within-modules).
## Error Checking for Output Values
2019-03-21 14:20:29 -05:00
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.