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

allow variable in provider field #11578

Closed
dmrzzz opened this issue Jan 31, 2017 · 18 comments
Closed

allow variable in provider field #11578

dmrzzz opened this issue Jan 31, 2017 · 18 comments

Comments

@dmrzzz
Copy link

dmrzzz commented Jan 31, 2017

I'm using multiple AWS providers to deploy resources into different regions, but I want my modules to be generic with respect to which regions.

Ideally I would write a module like this:

variable provider { default = "aws" }

resource "aws_vpc" "vpc" {
    # works:
    #provider = "aws.us-east-1"
    # does not work:
    provider = "${var.provider}"
    cidr_block = "192.168.0.0/16"
    tags { Name = "TEST - DELETE ME" }
}

and invoke it like this:

provider "aws" {
  region = "us-east-2"
}

provider "aws" {
  alias = "us-east-1"
  region = "us-east-1"
}

# do other stuff using the default AWS provider

# tell this module to use aws.us-east-1
module "module1" {
  source = "./testmodule"
  provider = "aws.us-east-1"
}

but that fails on terraform get:

dmrz@golbez:~/tmp/tftest2$ terraform get
Get: file:///home/dmrz/tmp/tftest2/testmodule
Error loading Terraform: 1 error(s) occurred:

* module module1: provider alias must be defined by the module or a parent: ${var.provider}

It works fine if I instead hardcode the provider field value within the module, but then my module isn't generic.

Note: the resource I really want to use this for at this moment is aws_cloudwatch_metric_alarm, but aws_vpc behaves the same way and makes a much simpler test case.

Thanks for all the great work on Terraform!

@kevintsai
Copy link

I also ran into this issue. Hope it can be improved. Thanks.

@apparentlymart
Copy link
Contributor

This is somewhat related to #1819.

@kkarimi
Copy link

kkarimi commented Mar 23, 2017

This is quite necessary in order to work effectively with modules with a multi region AWS setup

@ericfode
Copy link

This is causing sadness for us.

@nyetsche
Copy link

nyetsche commented Apr 7, 2017

Also causing sadness for us :(

Here's the example of what I'm hoping to accomplish:

resource "aws_kms_key" "cluster_cmk" {
  count       = "${var.cluster_number}"
  provider = "aws.${element(values(var.regionmap), count.index)}"
}

provider "aws" {
  alias = "us-east-1"
  region = "us-east-1"
}

regionmap = {
  "clusternickname" = "us-east-1"
}

In other words, have multiple KMS keys, one per cluster, and each cluster could be in a different region. So do a map lookup.

Fails with this:

* module root: 1 error(s) occurred:

* module : provider alias must be defined by the module or a parent: aws.${element(values(var.regionmap), count.index)}

I poked around the code but couldn't figure out where a resource overrides the default provider (where the fix should go). If someone has hints, it's important enough I'd like to hack on a PR

@demonwy
Copy link
Contributor

demonwy commented Apr 20, 2017

+1

@gillingw
Copy link

This is an issue for us too having to duplicate modules for each region.

@apparentlymart
Copy link
Contributor

Hi all,

I'm sorry there hasn't been any movement here. This is a tricky issue to resolve because we need to understand provider usage very early on in the Terraform run, before we have enough context to do interpolations. So far a way to implement this as requested has eluded us.

A different take on the problem might be simpler to implement. I've not thought this through all the way yet so I can't promise this is how this will eventually end up, but just wanted to capture this here for future reference and feedback:

module "foo" {
  source = "./foo"

  # ...module arguments, as usual...

  providers {
    aws = "aws.use1"
  }
}

This hypothetical (not yet working!) providers block could allow customizing how providers inherit so that a provider with an alias in the parent module can be unaliased in the child module. By handing this in a first-class way we'd avoid the need to support arbitrary interpolations for it and thus be able to resolve it early in configuration processing.

I understand that this alternative approach is still not as general as the interpolation-based solution requested, but I think it would be adequate to meet the use-case of giving a child module access to an aliased provider without the child module itself needing to know about the alias.

@dmrzzz
Copy link
Author

dmrzzz commented Apr 26, 2017

@apparentlymart, your idea above would solve my immediate problem.

That said, I keep thinking about how nice it would be to have some sort of limited interpolation support available earlier in the run, such that many parameters which currently allow no interpolation at all could instead allow limited interpolation (almost like a preprocessor macro replacement step, but using the same familiar interpolation syntax).

Obviously we wouldn't be able to interpolate anything at this early stage whose value depends on another resource or data source, but for my OP example code the value in question is knowable as soon as we parse the files, because it's already hardcoded elsewhere in those same files. Being able to interpolate such locally-known variable values early on would, from my perspective, cleanly solve not only this issue but also e.g. #13022 (and maybe some others too) without having to introduce new first-class syntax.

@jbarreneche
Copy link

jbarreneche commented Apr 27, 2017

Hi! The overriding provisioner in the module would work for our use case and would be more clean that what we are currently doing.

Just for the record, for the people who need to handle multiple regions or multiple aws accounts this works:

In the module

provider "aws" {
  alias = "module"
  region = "${var.region}" # Or some way to infer the region from the module variables
  # This is in case you want to use multiple accounts
  assume_role {
    role_arn = "arn:aws:iam::${var.account)}:role/terraform"  # Or some way to infer the account number from the module variables, we use a map for naming the accounts.
  }
}
resource "aws_..." "foo" {
  provider = "aws.module"
  ...
}

The assume_role is optional in case you use multiple accounts (and needs all accounts to have a terraform role).
Sadly you can't forget to put the provider (which is a common mistake), and you can't override the "default" or use another that's in use (As far as we tested it). But it allows you to use a module in the same tfstate with multiple accounts/regions.

module "foo" {
  account = "12312312"
  region = "us-east-1"
}

module "bar" {
  account = "32132131"
  region = "sa-east-1"
}

@gillingw
Copy link

gillingw commented May 1, 2017

Thank you @jbarreneche this is working for us.

@paulcollinsiii
Copy link

So @jbarreneche first up thank you for that work around. For creating resources it works perfectly.
The downside to it comes when I'm trying to tear down a module that was created with this. At least with version 0.9.5 when I tried to remove a module that included a

provider "aws" {
  region = "${var.aws_region}"
  alias = "aws.module"
}

Then when starting a plan that would remove that module terraform would ask what region it applied to and then barf during the apply. If I went about it doing a
terraform plan -out=tf.plan -target module.mymodule -destroy
applied that, and THEN removed the module "mymodule { ... } line it was okay.

Anyhow, just a note for future me when I run into this gotcha again.

@joshmyers
Copy link
Contributor

We have a set of "DR regions", which could be one or more. Not being able to have interpolation in the provider makes this not possible to use:

resource "aws_db_event_subscription" "read_replica_in_another_region" {
  count     = "${length(split(" ",var.dr_regions))}"
  provider = "aws.${element(split(" ",var.dr_regions),count.index)}"

This is not using a module but I think the workaround above would not work in my scenario. Any other ideas?

@thomasbiddle
Copy link

This is the second or third time I've had to stop on a project I was working on due to this limitation; definitely should get some extra attention :-)

My use-case: AWS Config

I have a set of rules that I want to apply to every region in AWS. I'm only using a couple, but I want to have config running on all of my regions in-case something gets added where we don't normally work without my knowing. I'd love to define all my rules in a module, and just attach it to every region!

@cfullerton
Copy link

This is a problem for us too

@apparentlymart
Copy link
Contributor

Hi all!

An approach like I sketched in my earlier comment was implemented in Terraform 0.11, allowing generic modules to receive aliased providers from the parent module.

I believe this addresses the use-case that motivated this proposal. Sorry I forgot to update this while we were announcing the 0.11 release.

Since the stated problem is now solved, I'm going to close this. If others in future find use-cases that aren't addressed I'd ask that they open a fresh issue so we can discuss each use-case separately.

Thanks for sharing your use-cases, everyone!

@tamsky
Copy link
Contributor

tamsky commented Jun 6, 2018

For reference: #16379 adds "module provider inheritance"

@ghost
Copy link

ghost commented Apr 3, 2020

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 Apr 3, 2020
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