terraform/website/docs/language/modules/develop/providers.mdx

365 lines
13 KiB
Plaintext

---
page_title: Providers Within Modules - Configuration Language
---
# Providers Within Modules
[inpage-providers]: #providers-within-modules
In a configuration with multiple modules, there are some special considerations
for how resources are associated with provider configurations.
Each resource in the configuration must be associated with one provider
configuration. Provider configurations, unlike most other concepts in
Terraform, are global to an entire Terraform configuration and can be shared
across module boundaries. Provider configurations can be defined only in a
root Terraform module.
Providers can be passed down to descendent modules in two ways: either
_implicitly_ through inheritance, or _explicitly_ via the `providers` argument
within a `module` block. These two options are discussed in more detail in the
following sections.
A module intended to be called by one or more other modules must not contain
any `provider` blocks. A module containing its own provider configurations is
not compatible with the `for_each`, `count`, and `depends_on` arguments that
were introduced in Terraform v0.13. For more information, see
[Legacy Shared Modules with Provider Configurations](#legacy-shared-modules-with-provider-configurations).
Provider configurations are used for all operations on associated resources,
including destroying remote objects and refreshing state. Terraform retains, as
part of its state, a reference to the provider configuration that was most
recently used to apply changes to each resource. When a `resource` block is
removed from the configuration, this record in the state will be used to locate
the appropriate configuration because the resource's `provider` argument
(if any) will no longer be present in the configuration.
As a consequence, you must ensure that all resources that belong to a
particular provider configuration are destroyed before you can remove that
provider configuration's block from your configuration. If Terraform finds
a resource instance tracked in the state whose provider configuration block is
no longer available then it will return an error during planning, prompting you
to reintroduce the provider configuration.
## Provider Version Constraints in Modules
Although provider _configurations_ are shared between modules, each module must
declare its own [provider requirements](/language/providers/requirements), so that
Terraform can ensure that there is a single version of the provider that is
compatible with all modules in the configuration and to specify the
[source address](/language/providers/requirements#source-addresses) that serves as
the global (module-agnostic) identifier for a provider.
To declare that a module requires particular versions of a specific provider,
use a `required_providers` block inside a `terraform` block:
```hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 2.7.0"
}
}
}
```
A provider requirement says, for example, "This module requires version v2.7.0
of the provider `hashicorp/aws` and will refer to it as `aws`." It doesn't,
however, specify any of the configuration settings that determine what remote
endpoints the provider will access, such as an AWS region; configuration
settings come from provider _configurations_, and a particular overall Terraform
configuration can potentially have
[several different configurations for the same provider](/language/providers/configuration#alias-multiple-provider-configurations).
## Provider Aliases Within Modules
To declare multiple configuration names for a provider within a module, add the
`configuration_aliases` argument:
```hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 2.7.0"
configuration_aliases = [ aws.alternate ]
}
}
}
```
The above requirements are identical to the previous, with the addition of the
alias provider configuration name `aws.alternate`, which can be referenced by
resources using the `provider` argument.
If you are writing a shared Terraform module, constrain only the minimum
required provider version using a `>=` constraint. This should specify the
minimum version containing the features your module relies on, and thus allow a
user of your module to potentially select a newer provider version if other
features are needed by other parts of their overall configuration.
## Implicit Provider Inheritance
For convenience in simple configurations, a child module automatically inherits
default (un-aliased) provider configurations from its parent. This means that
explicit `provider` blocks appear only in the root module, and downstream
modules can simply declare resources for that provider and have them
automatically associated with the root provider configurations.
For example, the root module might contain only a `provider` block and a
`module` block to instantiate a child module:
```hcl
provider "aws" {
region = "us-west-1"
}
module "child" {
source = "./child"
}
```
The child module can then use any resource from this provider with no further
provider configuration required:
```hcl
resource "aws_s3_bucket" "example" {
bucket = "provider-inherit-example"
}
```
We recommend using this approach when a single configuration for each provider
is sufficient for an entire configuration.
~> **Note:** Only provider configurations are inherited by child modules, not provider source or version requirements. Each module must [declare its own provider requirements](/language/providers/requirements). This is especially important for non-HashiCorp providers.
In more complex situations there may be
[multiple provider configurations](/language/providers/configuration#alias-multiple-provider-configurations),
or a child module may need to use different provider settings than
its parent. For such situations, you must pass providers explicitly.
## Passing Providers Explicitly
When child modules each need a different configuration of a particular
provider, or where the child module requires a different provider configuration
than its parent, you can use the `providers` argument within a `module` block
to explicitly define which provider configurations are available to the
child module. For example:
```hcl
# The default "aws" configuration is used for AWS resources in the root
# module where no explicit provider instance is selected.
provider "aws" {
region = "us-west-1"
}
# An alternate configuration is also defined for a different
# region, using the alias "usw2".
provider "aws" {
alias = "usw2"
region = "us-west-2"
}
# An example child module is instantiated with the alternate configuration,
# so any AWS resources it defines will use the us-west-2 region.
module "example" {
source = "./example"
providers = {
aws = aws.usw2
}
}
```
The `providers` argument within a `module` block is similar to
[the `provider` argument](/language/meta-arguments/resource-provider)
within a resource, but is a map rather than a single string because a module may
contain resources from many different providers.
The keys of the `providers` map are provider configuration names as expected by
the child module, and the values are the names of corresponding configurations
in the _current_ module.
Once the `providers` argument is used in a `module` block, it overrides all of
the default inheritance behavior, so it is necessary to enumerate mappings
for _all_ of the required providers. This is to avoid confusion and surprises
that may result when mixing both implicit and explicit provider passing.
Additional provider configurations (those with the `alias` argument set) are
_never_ inherited automatically by child modules, and so must always be passed
explicitly using the `providers` map. For example, a module
that configures connectivity between networks in two AWS regions is likely
to need both a source and a destination region. In that case, the root module
may look something like this:
```hcl
provider "aws" {
alias = "usw1"
region = "us-west-1"
}
provider "aws" {
alias = "usw2"
region = "us-west-2"
}
module "tunnel" {
source = "./tunnel"
providers = {
aws.src = aws.usw1
aws.dst = aws.usw2
}
}
```
The subdirectory `./tunnel` must then declare the configuration aliases for the
provider so the calling module can pass configurations with these names in its `providers` argument:
```hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 2.7.0"
configuration_aliases = [ aws.src, aws.dst ]
}
}
}
```
Each resource should then have its own `provider` attribute set to either
`aws.src` or `aws.dst` to choose which of the two provider configurations to
use.
## Legacy Shared Modules with Provider Configurations
In Terraform v0.10 and earlier there was no explicit way to use different
configurations of a provider in different modules in the same configuration,
and so module authors commonly worked around this by writing `provider` blocks
directly inside their modules, making the module have its own separate
provider configurations separate from those declared in the root module.
However, that pattern had a significant drawback: because a provider
configuration is required to destroy the remote object associated with a
resource instance as well as to create or update it, a provider configuration
must always stay present in the overall Terraform configuration for longer
than all of the resources it manages. If a particular module includes
both resources and the provider configurations for those resources then
removing the module from its caller would violate that constraint: both the
resources and their associated providers would, in effect, be removed
simultaneously.
Terraform v0.11 introduced the mechanisms described in earlier sections to
allow passing provider configurations between modules in a structured way, and
thus we explicitly recommended against writing a child module with its own
provider configuration blocks. However, that legacy pattern continued to work
for compatibility purposes -- though with the same drawback -- until Terraform
v0.13.
Terraform v0.13 introduced the possibility for a module itself to use the
`for_each`, `count`, and `depends_on` arguments, but the implementation of
those unfortunately conflicted with the support for the legacy pattern.
To retain the backward compatibility as much as possible, Terraform v0.13
continues to support the legacy pattern for module blocks that do not use these
new features, but a module with its own provider configurations is not
compatible with `for_each`, `count`, or `depends_on`. Terraform will produce an
error if you attempt to combine these features. For example:
```
Error: Module does not support count
on main.tf line 15, in module "child":
15: count = 2
Module "child" cannot be used with count because it contains a nested provider
configuration for "aws", at child/main.tf:2,10-15.
This module can be made compatible with count by changing it to receive all of
its provider configurations from the calling module, by using the "providers"
argument in the calling module block.
```
To make a module compatible with the new features, you must remove all of the
`provider` blocks from its definition.
If the new version of the module declares `configuration_aliases`, or if the
calling module needs the child module to use different provider configurations
than its own default provider configurations, the calling module must then
include an explicit `providers` argument to describe which provider
configurations the child module will use:
```hcl
provider "aws" {
region = "us-west-1"
}
provider "aws" {
region = "us-east-1"
alias = "east"
}
module "child" {
count = 2
providers = {
# By default, the child module would use the
# default (unaliased) AWS provider configuration
# using us-west-1, but this will override it
# to use the additional "east" configuration
# for its resources instead.
aws = aws.east
}
}
```
Since the association between resources and provider configurations is
static, module calls using `for_each` or `count` cannot pass different
provider configurations to different instances. If you need different
instances of your module to use different provider configurations then you
must use a separate `module` block for each distinct set of provider
configurations:
```hcl
provider "aws" {
alias = "usw1"
region = "us-west-1"
}
provider "aws" {
alias = "usw2"
region = "us-west-2"
}
provider "google" {
alias = "usw1"
credentials = "${file("account.json")}"
project = "my-project-id"
region = "us-west1"
zone = "us-west1-a"
}
provider "google" {
alias = "usw2"
credentials = "${file("account.json")}"
project = "my-project-id"
region = "us-west2"
zone = "us-west2-a"
}
module "bucket_w1" {
source = "./publish_bucket"
providers = {
aws.src = aws.usw1
google.src = google.usw2
}
}
module "bucket_w2" {
source = "./publish_bucket"
providers = {
aws.src = aws.usw2
google.src = google.usw2
}
}
```