opentofu/website/upgrade-guides/0-13.html.markdown
2021-02-24 13:36:47 -05:00

23 KiB

layout page_title sidebar_current description
language Upgrading to Terraform v0.13 upgrade-guides-0-13 Upgrading to Terraform v0.13

Upgrading to Terraform v0.13

Terraform v0.13 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. After reviewing this guide, we recommend reviewing the Changelog to check for specific notes about less-commonly-used features.

This guide focuses on changes from v0.12 to v0.13. 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.12 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.

In particular, Terraform v0.13 no longer includes the terraform 0.12upgrade command for automatically migrating module source code from v0.11 to v0.12 syntax. If your modules are written for v0.11 and earlier you may need to upgrade their syntax using the latest minor release of Terraform v0.12 before using Terraform v0.13.

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

Before You Upgrade

When upgrading between major releases, we always recommend ensuring that you can run terraform plan and see no proposed changes on the previous version first, because otherwise pending changes can add additional unknowns into the upgrade process.

For this upgrade in particular, completing the upgrade will require running terraform apply with Terraform 0.13 after upgrading in order to apply some upgrades to the Terraform state, and we recommend doing that with no other changes pending.

Explicit Provider Source Locations

Prior versions of Terraform have supported automatic provider installation only for providers packaged and distributed by HashiCorp. Providers built by the community have previously required manual installation by extracting their distribution packages into specific local filesystem locations.

Terraform v0.13 introduces a new hierarchical namespace for providers that allows specifying both HashiCorp-maintained and community-maintained providers as dependencies of a module, with community providers distributed from other namespaces on Terraform Registry from a third-party provider registry.

In order to establish the hierarchical namespace, Terraform now requires explicit source information for any providers that are not HashiCorp-maintained, using a new syntax in the required_providers nested block inside the terraform configuration block:

terraform {
  required_providers {
    azurerm = {
      # The "hashicorp" namespace is the new home for the HashiCorp-maintained
      # provider plugins.
      #
      # source is not required for the hashicorp/* namespace as a measure of
      # backward compatibility for commonly-used providers, but recommended for
      # explicitness.
      source  = "hashicorp/azurerm"
      version = "~> 2.12"
    }
    newrelic = {
      # source is required for providers in other namespaces, to avoid ambiguity.
      source  = "newrelic/newrelic"
      version = "~> 2.1.1"
    }
  }
}

If you are using providers that now require an explicit source location to be specified, terraform init will produce an error like the following:

Error: Failed to install providers

Could not find required providers, but found possible alternatives:

  hashicorp/datadog -> terraform-providers/datadog
  hashicorp/fastly -> terraform-providers/fastly

If these suggestions look correct, upgrade your configuration with the
following command:
    terraform 0.13upgrade

As mentioned in the error message, Terraform v0.13 includes an automatic upgrade command terraform 0.13upgrade that is able to automatically generate source addresses for unlabelled providers by consulting the same lookup table that was previously used for Terraform v0.12 provider installation. This command will automatically modify the configuration of your current module, so you can use the features of your version control system to inspect the proposed changes before committing them.

We recommend running terraform 0.13upgrade even if you don't see the message, because it will generate the recommended explicit source addresses for providers in the "hashicorp" namespace.

For more information on declaring provider dependencies, see Provider Requirements. That page also includes some guidance on how to write provider dependencies for a module that must remain compatible with both Terraform v0.12 and Terraform v0.13; the terraform 0.13upgrade result includes a conservative version constraint for Terraform v0.13 or later, which you can weaken to >= 0.12.26 if you follow the guidelines in v0.12-Compatible Provider Requirements.

Each module must declare its own set of provider requirements, so if you have a configuration which calls other modules then you'll need to run this upgrade command for each module separately. The terraform 0.13upgrade documentation includes an example of running the upgrade process across all directories under a particular prefix that contain .tf files using some common Unix command line tools, which may be useful if you want to upgrade all modules in a single repository at once.

After you've added explicit provider source addresses to your configuration, run terraform init again to re-run the provider installer.

-> Action: Either run terraform 0.13upgrade for each of your modules, or manually update the provider declarations to use explicit source addresses.

The upgrade tool described above only updates references in your configuration. The Terraform state also includes references to provider configurations which need to be updated to refer to the correct providers.

Terraform will automatically update provider configuration references in the state the first time you run terraform apply after upgrading, but it relies on information in the configuration to understand which provider any existing resource belongs to, and so you must run terraform apply at least once (and accept any changes it proposes) before removing any resource blocks from your configuration after upgrading.

If you are using Terraform Cloud or Terraform Enterprise with the VCS-driven workflow (as opposed to CLI-driven runs), refer to The UI- and VCS-driven Run Workflow to learn how to manually start a run after you select a Terraform v0.13 release for your workspace.

If you remove a resource block (or a module block for a module that contains resource blocks) before the first terraform apply, you may see a message like this reflecting that Terraform cannot determine which provider configuration the existing object ought to be managed by:

Error: Provider configuration not present

To work with null_resource.foo its original provider configuration at
provider["registry.terraform.io/-/aws"] is required, but it has been removed.
This occurs when a provider configuration is removed while objects created by
that provider still exist in the state. Re-add the provider configuration to
destroy aws_instance.example, after which you can remove the provider
configuration again.

In this specific upgrade situation the problem is actually the missing resource block rather than the missing provider block: Terraform would normally refer to the configuration to see if this resource has an explicit provider argument that would override the default strategy for selecting a provider. If you see the above after upgrading, re-add the resource mentioned in the error message until you've completed the upgrade.

-> Action: After updating all modules in your configuration to use the new provider requirements syntax, run terraform apply to create a new state snapshot containing the new-style provider source addresses that are now specified in your configuration.

New Filesystem Layout for Local Copies of Providers

As part of introducing the hierarchical provider namespace discussed in the previous section, Terraform v0.13 also introduces a new hierarchical directory structure for manually-installed providers in the local filesystem.

If you use local copies of official providers or if you use custom in-house providers that you have installed manually, you will need to adjust your local directories to use the new directory structure.

The previous layout was a single directory per target platform containing various executable files named with the prefix terraform-provider, like linux_amd64/terraform-provider-google_v2.0.0. The new expected location for the Google Cloud Platform provider for that target platform within one of the local search directories would be the following:

registry.terraform.io/hashicorp/google/2.0.0/linux_amd64/terraform-provider-google_v2.0.0

The registry.terraform.io above is the hostname of the registry considered to be the origin for this provider. The provider source address hashicorp/google is a shorthand for registry.terraform.io/hashicorp/google, and the full, explicit form is required for a local directory.

Note that the version number given as a directory name must be written without the "v" prefix that tends to be included when a version number is used as part of a git branch name. If you include that prefix, Terraform will not recognize the directory as containing provider packages.

As before, the recommended default location for locally-installed providers is one of the following, depending on which operating system you are running Terraform under:

  • Windows: %APPDATA%\terraform.d\plugins
  • All other systems: ~/.terraform.d/plugins

Terraform v0.13 introduces some additional options for customizing where Terraform looks for providers in the local filesystem. For more information on those new options, see Provider Installation.

If you use only providers that are automatically installable from Terraform provider registries but still want to avoid Terraform re-downloading them from registries each time, Terraform v0.13 includes the terraform providers mirror command which you can use to automatically populate a local directory based on the requirements of the current configuration file:

terraform providers mirror ~/.terraform.d/plugins

-> Action: If you use local copies of official providers rather than installing them automatically from Terraform Registry, adopt the new expected directory structure for your local directory either by running terraform providers mirror or by manually reorganizing the existing files.

In-house Providers

If you use an in-house provider that is not available from an upstream registry at all, after upgrading you will see an error similar to the following:

- Finding latest version of hashicorp/happycloud...

Error: Failed to install provider

Error while installing hashicorp/happycloud: provider registry
registry.terraform.io does not have a provider named
registry.terraform.io/hashicorp/happycloud

Terraform assumes that a provider without an explicit source address belongs to the "hashicorp" namespace on registry.terraform.io, which is not true for your in-house provider. Instead, you can use any domain name under your control to establish a virtual source registry to serve as a separate namespace for your local use. For example:

terraform.example.com/awesomecorp/happycloud/1.0.0/linux_amd64/terraform-provider-happycloud_v1.0.0

You can then specify explicitly the requirement for that in-house provider in your modules, using the requirement syntax discussed in the previous section:

terraform {
  required_providers {
    happycloud = {
      source  = "terraform.example.com/awesomecorp/happycloud"
      version = "1.0.0"
    }
  }
}

If you wish, you can later run your own Terraform provider registry at the specified hostname as an alternative to local installation, without any further modifications to the above configuration. However, we recommend tackling that only after your initial upgrade using the new local filesystem layout.

-> Action: If you use in-house providers that are not installable from a provider registry, assign them a new source address under a domain name you control and update your modules to specify that new source address.

If your configuration using one or more in-house providers has existing state snapshots that include resources belonging to those providers, you'll also need to perform a one-time migration of the provider references in the state, so Terraform can understand them as belonging to your in-house providers rather than to providers in the public Terraform Registry. If you are in this situation, terraform init will produce the following error message after you complete the configuration changes described above:

Error: Failed to install legacy providers required by state

Found unresolvable legacy provider references in state. It looks like these
refer to in-house providers. You can update the resources in state with the
following command:

    terraform state replace-provider registry.terraform.io/-/happycloud terraform.example.com/awesomecorp/happycloud

Provider source addresses starting with registry.terraform.io/-/ are a special way Terraform marks legacy addresses where the true namespace is unknown. For providers that were automatically-installable in Terraform 0.12, Terraform 0.13 can automatically determine the new addresses for these using a lookup table in the public Terraform Registry, but for in-house providers you will need to provide the appropriate mapping manually.

The terraform state replace-provider subcommand allows re-assigning provider source addresses recorded in the Terraform state, and so we can use this command to tell Terraform how to reinterpret the "legacy" provider addresses as properly-namespaced providers that match with the provider source addresses in the configuration.

~> Warning: The terraform state replace-provider subcommand, like all of the terraform state subcommands, will create a new state snapshot and write it to the configured backend. After the command succeeds the latest state snapshot will use syntax that Terraform v0.12 cannot understand, so you should perform this step only when you are ready to permanently upgrade to Terraform v0.13.

terraform state replace-provider 'registry.terraform.io/-/happycloud' 'terraform.example.com/awesomecorp/happycloud'

The command above asks Terraform to update any resource instance in the state that belongs to a legacy (non-namespaced) provider called "happycloud" to instead belong to the fully-qualified source address terraform.example.com/awesomecorp/happycloud.

Whereas the configuration changes for provider requirements are made on a per-module basis, the Terraform state captures data from throughout the configuration (all of the existing module instances) and so you only need to run terraform state replace-provider once per configuration.

Running terraform init again after completing this step should cause Terraform to attempt to install terraform.example.com/awesomecorp/happycloud and to find it in the local filesystem directory you populated in an earlier step.

-> Action: If you use in-house providers that are not installable from a provider registry and your existing state contains resource instances that were created with any of those providers, use the terraform state replace-provider command to update the state to use the new source addressing scheme only once you are ready to commit to your v0.13 upgrade. (Terraform v0.12 cannot parse a state snapshot that was created by this command.)

Destroy-time provisioners may not refer to other resources

Destroy-time provisioners allow introducing arbitrary additional actions into the destroy phase of the resource lifecycle, but in practice the design of this feature was flawed because it created the possibility for a destroy action of one resource to depend on a create or update action of another resource, which often leads either to dependency cycles or to incorrect behavior due to unsuitable operation ordering.

In order to retain as many destroy-time provisioner capabilities as possible while addressing those design flaws, Terraform v0.12.18 began reporting deprecation warnings for any provisioner block setting when = destroy whose configuration refers to any objects other than self, count, and each.

Addressing the flaws in the destroy-time provisioner design was a pre-requisite for new features in v0.13 such as module depends_on, so Terraform v0.13 concludes the deprecation cycle by making such references now be fatal errors:

Error: Invalid reference from destroy provisioner

Destroy-time provisioners and their connection configurations may only
reference attributes of the related resource, via 'self', 'count.index',
or 'each.key'.

References to other resources during the destroy phase can cause dependency
cycles and interact poorly with create_before_destroy.

Some existing modules using resource or other references inside destroy-time provisioners can be updated by placing the destroy-time provisioner inside a null_resource resource and copying any data needed at destroy time into the triggers map to be accessed via self:

resource "null_resource" "example" {
  triggers = {
    instance_ip_addr = aws_instance.example.private_ip
  }

  provisioner "remote-exec" {
    when = destroy

    connection {
      host = self.triggers.instance_ip_addr
      # ...
    }

    # ...
  }
}

In the above example, the null_resource.example.triggers map is effectively acting as a temporary "cache" for the instance's private IP address to guarantee that a value will be available when the provisioner runs, even if the aws_instance.example object itself isn't currently available. The provisioner's connection configuration can refer to that value via self, whereas referring directly to aws_instance.example.private_ip in that context is forbidden.

Provisioners are a last resort, so we recommend avoiding both create-time and destroy-time provisioners wherever possible. Other options for destroy-time actions include using systemd to run commands within your virtual machines during shutdown or using virtual machine lifecycle hooks provided by your chosen cloud computing platform, both of which can help ensure that the shutdown actions are taken even if the virtual machine is terminated in an unusual way.

-> Action: If you encounter the "Invalid reference from destroy provisioner" error message after upgrading, reorganize your destroy-time provisioners to depend only on self-references, and consider other approaches if possible to avoid using destroy-time provisioners at all.

Data resource reads can no longer be disabled by -refresh=false

In Terraform v0.12 and earlier, Terraform would read the data for data resources during the "refresh" phase of terraform plan, which is the same phase where Terraform synchronizes its state with any changes made to remote objects.

An important prerequisite for properly supporting depends_on for both data resources and modules containing data resources was to change the data resource lifecycle to now read data during the plan phase, so that dependencies on managed resources could be properly respected.

If you were previously using terraform plan -refresh=false or terraform apply -refresh=false to disable the refresh phase, you will find that under Terraform 0.13 this will continue to disable synchronization of managed resources (declared with resource blocks) but will no longer disable the reading of data resources (declared with data blocks).

~> Updating the data associated with data resources is crucial to producing an accurate plan, and so there is no replacement mechanism in Terraform v0.13 to restore the previous behavior.

Frequently Asked Questions

Why do I see -/provider during init?

Provider source addresses starting with registry.terraform.io/-/ are a special way Terraform marks legacy addresses where the true namespace is unknown. For providers that were automatically-installable in Terraform 0.12, Terraform 0.13 can automatically determine the new addresses for these using a lookup table in the public Terraform Registry. That lookup table is accessed by using the special namespace -.

When you run init, terraform generates a list of required providers based on both the configuration and state. Legacy-style providers - such as providers in a statefile written with Terraform v0.12 - don't have a namespace, so terraform uses the placeholder namespace - to query the registry. That is why you may see output like this during your first init:

- Finding latest version of -/null...
- Finding latest version of -/random...
- Finding latest version of hashicorp/null...
- Finding latest version of hashicorp/random...

Terraform found providers null and random in the statefile without a namespace. Terraform also found hashicorp/null and hashicorp/random in the configuration files. Providers in configuration are automatically assumed to be default (HashiCorp) providers, while providers found in state are first looked up in the registry.

While this does not cause any problems for Terraform, it has been confusing. You may circumvent this by using the terraform state replace-provider subcommand to tell Terraform exactly what provider addresses are required in state. Continuing from the example above, the following commands tell Terraform the source address for the null and random providers:

terraform state replace-provider -- -/random registry.terraform.io/hashicorp/random
terraform state replace-provider -- -/null registry.terraform.io/hashicorp/null

If you are seeing these messages with errors, and are using in-house or locally-installed providers, please see the section on in-house providers.