Providers within Modules, Provider Aliases, and Proxy Configs

I have two instances of the AzureRM provider in my root module. One is the default, with no alias. The second has an alias and is only used in a couple of cases.

I have a child module that needs to use both of these providers. When calling the child module I must use the ‘providers’ meta-argument. But, here is where I need clarification. In the ‘providers’ meta-argument, do I only need to define the aliased provider? Or do I need to define both the non-aliased provider plus the aliased provider?

In other words, can the child module inherit my default provider from the root plus use the aliased provider passed to it?

The end goal would be to have a mix of resources in the child module, some resources that do not have a ‘provider’ attribute set and they would inherit the default provider from the root module, and then I’d have resources with ‘provider’ set and it would use what’s passed to it via the ‘providers’ meta-argument.

Hi @nnellans-acts,

Specifying the providers meta-argument in a module block overrides all of the automatic inheritance behaviors, because shared modules that need multiple configurations often don’t agree with their caller about which one is the “default” (if any), what aliases the additional configurations have, etc.

However, if your two modules do happen to agree about the configurations then you can pass them through with an identity mapping like this:

  providers = {
    azurerm = azurerm
    azurerm.other = azurerm.other
  }

The child module will, in current versions of Terraform, need to also declare that it expects to receive an additional configuration with alias “other”, using a proxy configuration block like this:

provider "azurerm" {
  alias = "other"
}

Fantastic, thank you!

@apparentlymart I am slightly confused about the phrase " providers meta-argument in a module block overrides all of the automatic inheritance behaviors". The documentation says the same but could you clarify the following: does the block override all automatic inheritance for that specific provider (e.g. here azure), or also for any provider that the module uses. Say that the child module also uses the (unaliased) aws provider. Does it need to be passed explicitly in the providers block as well?

Additionally, since Terraform 15, the configuration_aliases attribute has been introduced and the documentation removed any trace of the old proxy configuration block that you also mention. Is the configuration_aliases attribute backported to previous Terraform versions? If not, the documentation is kind of confusing now since there is a gap between the very legacy config and Terraform 15.

Hi @tguvdamm,

Yes, if you specify a providers meta-argument then you’ll need to include a configuration for all providers that module depends on, because Terraform won’t do any automatic inheritance at all in that case. The automatic method is intended for simpler situations where you are only using default (unaliased) configurations.

Also, indeed unfortunately the old “proxy provider block” methodology is no longer in the documentation. The new configuration_aliases approach came as a result of many years of trying to explain proxy provider blocks well in the documentation but never succeeding, with almost everyone ending up asking questions about it like in this thread, and so I think we concluded that there wasn’t much value in preserving the old configuration that wasn’t understandable anyway, and instead we’re focusing on the new method and recommending that those who have more complicated needs with multiple provider configurations aim to upgrade to Terraform v0.15 and use the new syntax that is more explicit and thus hopefully more clear to future maintainers of your module.

Thanks for the explanation.
I tried this in a small working example to see which errors Terraform would give.
I made a child module which requires unaliased Google and Cloudflare providers:

terraform {
  required_version = ">=0.13.0"
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "2.21.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "3.70.0"
    }
  }
}

Both providers require credentials. The module contains for Google and for Cloudflare a data source that just probes my online resources. Each data source then outputs the name of a project/domain in my account for Google/Cloudflare. This way, I can check that the module uses credentials for the provider.

The google provider configuration in the root has alias “test”, the Cloudflare provider is unaliased.

Now when I call the module in the root with

module "my_test" {
  source = "./mymodule"
  providers = {
    google = google.test
  }
}

the module still succeeds in grabbing the zone name from Cloudflare, which requires credentials of the unaliased provider (which I did not pass in the providers block).

So does this mean that there is some kind of inheritance?

@apparentlymart
I tried the above once more for Terraform 1.0.0 since maybe it was some backwards compatibility quirk, but this still works fine.
Am I misunderstanding something?
Could you point me to some configuration that will fail because Terraform does not use implicit inheritance any more because the providers{} block is used?

Hi @tguvdamm,

I believe the stated behavior was only for older releases. “Inheritance” is much simpler now; basically if a provider is not specified in the config, the resource is connected to the root module provider of the same type. The new validation present in the latest releases should eliminate ambiguous configurations where this was more of a concern.

Hi @jbardin
I see, thanks for the extra explanation. We will be able to keep our config cleaner when we only have to pass the aliased providers.

So, I’m having some problems with aws_s3_bucket and configuration_aliases in a module. I suspect this is a provider problem, but I’m not sure and I want to confirm that my expectations are correct.

Given this –

# terraform 1.1.2
# AWS Provider 3.70

# /main.tf
provider "aws" {
  # AWS_DEFAULT_REGION is usually, but not always, us-east-1
}
provider "aws" {
  alias = "backup"
  region = "us-east-2"
}

module "xyz" {
  source = "./xyz"
  providers = {
    aws.backup = aws.backup
  }
}

# ./xyz/main.tf
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      configuration_aliases = [ aws.backup ]
    }
  }
}
resource "aws_s3_bucket" "b1" {
  bucket = "bucket-1-01234567890"
  acl =  "private"
}
resource "aws_s3_bucket" "b2" {
  bucket = "bucket-2-01234567890"
  acl =  "private"
  provider = aws.backup
}

The b1 always get created properly in AWS_DEFAULT_REGION. But b2 always just spins at “Still creating…” until it times out after ~45min.

Ultimately my goal is (simplified) to create two buckets from the same .tf - one in whatever my AWS_DEFAULT_REGION happens to be and one in the backup region. It may so happen that both regions are the same sometimes, and different at other times.

I’ll also note that this appears to be a problem with using this pattern specifically with aws_s3_bucket, as, for example, “aws_key_pair” works perfectly as expected.

Are my expectations of this correct, or am I missing something?

Thanks!