Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terraform ignores provider in module #15961

Closed
FransUrbo opened this issue Aug 30, 2017 · 15 comments
Closed

Terraform ignores provider in module #15961

FransUrbo opened this issue Aug 30, 2017 · 15 comments

Comments

@FransUrbo
Copy link

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
@nokernel
Copy link

Do you see this behaviour with version 0.10.2?

@FransUrbo
Copy link
Author

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.

@FransUrbo
Copy link
Author

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

@jem-Solytica
Copy link

@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
}

@FransUrbo
Copy link
Author

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??!

@jem-Solytica
Copy link

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

@apparentlymart
Copy link
Contributor

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.

@FransUrbo
Copy link
Author

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.

@FransUrbo
Copy link
Author

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.

@jbardin
Copy link
Member

jbardin commented Sep 1, 2017

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.

@FransUrbo
Copy link
Author

FransUrbo commented Sep 3, 2017

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

@FransUrbo
Copy link
Author

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

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

@jbardin
Copy link
Member

jbardin commented Nov 3, 2017

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

@mmell
Copy link
Contributor

mmell commented Sep 6, 2019

The module providers element is the Terraform 0.12 solution.

@ghost
Copy link

ghost commented Sep 7, 2019

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.

@ghost ghost locked and limited conversation to collaborators Sep 7, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

7 participants