| --- |
| 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. |