Terraform: Terraform ignores provider in module

Created on 30 Aug 2017  ยท  15Comments  ยท  Source: hashicorp/terraform

I'm trying to setup multi-region, which resources in each region in its own module.

Having one provider in each module doesn't work, TF uses the "top" one.

Terraform Version

0.9.8

Terraform Configuration Files

./main.tf

provider "aws" {
  region  = "eu-central-1" # Just for the sake of having three distinct regions
}


module "test-ireland" {
  source = "./ireland"
}

module "test-london" {
  source = "./london"
}


data "aws_availability_zones" "available" {}
data "aws_region" "current" {
  current = true
}


output "region_main" {
  value = "${data.aws_region.current.name}"
}

output "region_ireland" {
  value = "${module.test-ireland.region}"
}

output "region_london" {
  value = "${module.test-london.region}"
}


output "azs_main" {
  value = "${data.aws_availability_zones.available.names}"
}

output "azs_ireland" {
  value = "${module.test-ireland.azs}"
}

output "azs_london" {
  value = "${module.test-london.azs}"
}

./ireland/ireland.tf

provider "aws" {
  region  = "eu-west-1"
}

data "aws_availability_zones" "available" {}
data "aws_region" "current" {
  current = true
}

output "region" {
  value = "${data.aws_region.current.name}"
}

output "azs" {
  value = "${data.aws_availability_zones.available.names}"
}

./london/london.tf

provider "aws" {
  region  = "eu-west-2"
}

data "aws_availability_zones" "available" {}
data "aws_region" "current" {
  current = true
}

output "region" {
  value = "${data.aws_region.current.name}"
}

output "azs" {
  value = "${data.aws_availability_zones.available.names}"
}

Expected Behavior

The output should have outputted three different values, one for the main one, one for Ireland and one for London.

Actual Behavior

TF uses the provider from the main.tf (Frankfurt) file, NOT the one within the modules.

azs_ireland = [
    eu-central-1a,
    eu-central-1b,
    eu-central-1c
]
azs_london = [
    eu-central-1a,
    eu-central-1b,
    eu-central-1c
]
azs_main = [
    eu-central-1a,
    eu-central-1b,
    eu-central-1c
]
region_ireland = eu-central-1
region_london = eu-central-1
region_main = eu-central-1

Steps to Reproduce

  1. terraform get
  2. terraform apply
bug core

All 15 comments

Do you see this behaviour with version 0.10.2?

I have not dared upgrade. And I'm to concerned with my infrastructure to upgrade just yet. I want it to have a few more releases before I do - that rewrite is way to big to entrust with my production environment!

But with my files in the ticket, it should be fairly easy to replicate the problem and test with a newer version for those that have it.

I downloaded 0.10.3 and tried it on my test directory. Same problem...

@FransUrbo -- You just need to alias the provider in your module. For example:

./ireland/ireland.tf

provider "aws" {
  alias = "ireland" # Add alias for this module
  region  = "eu-west-1"
}

data "aws_availability_zones" "available" { provider = "aws.ireland" }
data "aws_region" "current" {
  provider = "aws.ireland" # Indicate which provider to use
  current = true
}

Ah, cool!

But what if I have any other resource in ireland, say a aws_instance or example, do I also have to add a provider = "aws.ireland" to each and every resource in that directory??!

If you are creating the resource within the module, then yes.

I successfully use this framework to help manage infrastructure in 13 separate AWS accounts (organization setup), across the 14 regions. It's 182 module instance references in my main file, and each reference passes an account number (for role-based access) and region.

There's probably an easier way, but I use some bash scripts to help build my main.tf (mainly because the module block doesn't play well with counts, so it's easier to just build this piece outside of Terraform).

Hi all! Sorry for the delayed reply here.

The current behavior for providers in child modules is that they merge with the config in their parent module with the _parent_ taking precedence. Thus the region attribute from the root is overriding what's set in the child module.

This is weird, but known, behavior. We're hoping to address this by changing the way this is processed in a forthcoming major release... we're just being cautious about it because of course it's a breaking change for anyone relying on the current overriding behavior.

For the moment my suggested workaround would be to define providers either _only_ in the root, or _only_ in the child modules, rather than a mixture of both. In practice I've achieved this in the past by making a root module that has nothing in it except other module blocks, moving anything that would normally have been in the root down into a child module. That way the overriding behavior doesn't apply because there is no parent provider configuration to inherit.

This approach also helps to mitigate some of the surprise caused by #15762. If you have a provider configuration in the root then it will be used to try to destroy a module when it's removed from config -- which is usually _not_ what you want if the region is different -- whereas if you have no provider config in the root _at all_, this will cause Terraform to produce an explicit error to remind you to do the "awkward steps" I enumerated over there.

There will be some changes/improvements to the interactions between providers and modules in a future major release that should make these interactions less tricky.

Having nothing in the root probably works if you're not using a backend to store the state in S3, which needs a provider to be able to write...

I had to go with the workaround (I moved all my providers to the root dir - makes it easier to maintain) and then add provider arguments to ALL (there where a few!! :) resources in my submodules.

Possible correct solution

Maybe a way to solve this (for TF) and still allow current behaviour would be to add a provider option to the module resource.

That would then mean that any/all resources in/within that module would use the specified provider by default, without having to add a provider argument to all resources in that module.

That way we can still maintain the "old" behaviour (having a/the provider definition in the root, which overrides the/any provider in the module, if any, like you described) and at the same time introduce a "new" behaviour, without them clashing.

That 'module provider option' would then negate the need for a provider option in all resources - TF would use it "by default", because it's specified in the module definition...

Example

provider "aws" {
  region   = "eu-west-1"
}

provider "aws" {
  region   = "eu-central-1"
  alias    = "frankfurt"
}

provider "aws" {
  region   = "eu-west-2"
  alias    = "london"
}

# ==================

module "ireland" {
  source   = "./ireland"
}

module "london" {
  source   = "./london"
  provider = "aws.ireland"
}

module "frankfurt" {
  source   = "./frankfurt"
  provider = "aws.frankfurt"
}
  • All resources in the root directory/module would be created in Ireland (unless otherwise overridden with a provider argument).
  • All resources in the ireland directory/module would be created in Ireland (unless overridden specified with a provider argument).
  • All resources in the london directory/module would be created in London (unless overridden specified with a provider argument).
  • All resources in the frankfurt directory/module would be created in Frankfurt (unless overridden specified with a provider argument).

This was actually my first thought when I noticed this behaviour, but of course provider is an unknown argument to module at the moment.

Personally, without knowing how anything about the TF code base, seems to be a much easier solution than changing the way the providers are parsed and used - and we don't break anything for existing users.

If the provider option is specified (optional!) in the module resource, then simply (?) overwrite any and all arguments from specified provider on the "default" one.

If I'm not misstaken, my solution would also solve the "surprise" you mention in #15762.

To clarify (after double reading that ticket), I'm not suggesting passing provider in as a variable into the module, but an "internal" (?) argument, much like source, for the module resource used by TF.

Hi @FransUrbo,

Thanks for the feedback! It's very helpful since we are focusing on some module features at moment, and funny that you mention the special "provider" entry in the module -- we have something similar in the works already. It will have to wait until at least 0.11 however, since it would break any module config that used "provider" as a variable name.

As for the backend, you don't need a provider declared for that. While the code is shared, the backend config is completely separate from any provider declarations.

@jbardin Considering that provider is (should be) a TF keyword, anyone using it in a module as a variable should be doing something "wrong" and should therefor not be supported. IMO.

But a quick check to see if the value is actually a valid provider seems reasonable and then use it as such, but if not then just exit with an error seems most reasonable to me.

On the other hand, currently (in 0.9.8), this is illegal anyway:

provider "aws" {
  region  = "eu-west-1"
  alias   = "ireland"
}

module "test-ireland" {
  source   = "./ireland"
  provider = "aws.ireland"
}

Results in: * module root: module test-ireland: provider is not a valid parameter, so there's nothing to support backwards...

Ah, never mind. Sorry, forgot to "import" the variable into the ireland module.

But the rest of my comment still stands (for me).

This behavior is fixed in master with the handling of providers.

The module providers element is the Terraform 0.12 solution.

I'm going to lock this issue because it has been closed for _30 days_ โณ. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

Was this page helpful?
0 / 5 - 0 ratings