Terraform: Provider Aliases Not Working w/ Modules?

Created on 21 Jan 2016  ·  24Comments  ·  Source: hashicorp/terraform

I'm having an issue regarding a particular multi-region cluster configuration. Note that I'm currently only using modules to create separation within my configuration rather than focusing on creating reusable modules, so I'll have a root module with variables & providers and then, say, an AWS module that configures things like security groups or S3 buckets.

So in my top level (root) module I have a providers.tf file like:

provider "aws" {
  access_key = "${var.aws_access_key}"
  secret_key = "${var.aws_secret_key}"
  region = "${var.aws_region}"
}

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

And then in a module I use to define bare AWS infrastructure resources I have something like this:

resource "aws_instance" "some_us_cluster_node" {
  # config ...
}

resource "aws_instance" "some_eu_cluster_node" {
  provider = "aws.eu"
  # config ...
}

...

However when I do this I get an error:
aws_instance.some_eu_cluster_node: resource depends on non-configured provider 'aws.eu'

So I tried instead defining the provider aliases in the module block rather than the root level module (while keeping the default aws provider at the root level):

module "aws" {
  source = "./aws"

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

But then, interestingly enough, I get no errors but I get a prompt asking me what region to use for the provider.aws.eu resource as if I had left it out or as if I'm using a variable without a default value:

provider.aws.eu.region
  [description...]

  Default: us-east-1
  Enter a value:

So am I using the provider incorrectly? Do I need to propagate the provider alias down to the aws module like I do with variables? Or can I simply not use top level provider aliases inside of submodules? (which would be surprising since I can use a default aws provider defined at the top level inside of my submodules with no problem)

Finally I am on terraform version 0.6.9

bug core

Most helpful comment

Thanks Jay - the pattern described there is perfect for this use case.

For others looking for this info, the pattern to use is this:

provider "google" {
  region = "us-central1"
  alias = "usc1"
}

module "network-usc1" {
  source = "tasdikrahman/network/google"

  providers = {
    "google" = "google.usc1"
  }
}

This will mean the network-usc1 module the aliased client, and so connects to the us-central1 region, without needing any changes within the module.

All 24 comments

After playing around a while I finally found that if I placed the aliases (leaving the default at the top level) within the same module as the resources that used them, as opposed to the root or the root's module definition, it will work. But I'm under the impression that the correct behavior would be to make root level provider aliases available to submodules, though correct me if I'm wrong.

I got similar problems these days and I agree on what @tylerFowler said:

But I'm under the impression that the correct behavior would be to make root level provider aliases available to submodules,

I am having the same issues as @tylerFowler. Using the provider in module work around.

:( ok just hit this as well, I can't get multi-region working at all, seems like interpolation is broken or something.

I tried to do the same but I found the same issue.. Even if it worked, I do not like the "provider alias" feature with regards to modules.

A common scenario is to modularize some part of the configuration and then use the same across all AWS regions. The best pattern I have found so far is to define the provider inside the module based on variables like region, key_id, key_secret.. and then instantiate the module multiples times.

Here is an example:

module/foo/variables.tf

variable "region" {
    description = "The region of AWS, for AMI lookups."
}

provider "aws" {
    region = "${var.region}"
}

Then in the root:

main_eu.tf

module "foo_eu" {
    source = "./modules/foo"
    region = "eu-central-1"
}

main_us.tf

module "foo_us" {
    source = "./modules/foo"
    region = "us-west-1"
}

The downside of @jfromaniello's workaround is that the workflow for removing resources is really awful. Suppose you have things set up as in his comment, then you decide you no longer need foo_eu. If you remove main_eu.tf and run terraform apply it no longer understands what provider to use to destroy any resources in foo_eu:

$ terraform apply
provider.aws.region
  The region where AWS operations will take place. Examples
  are us-east-1, us-west-2, etc.

  Default: us-east-1
  Enter a value:

With the new graphs in 0.8.0 (now in beta), this all works. See tests in attached commit.

IMHO this does not work at all:

$ cat mymodule/main.tf
resource "openstack_compute_instance_v2" "myinstance" {
  provider = "openstack.myalias"
...
cat providers.tf
provider "openstack" {
  alias = "myalias"
  user_name = "myusername"
  tenant_name = "mytenantname"
  auth_url = "http://1.2.3.4:5000/"
}
$ terraform get
Error loading Terraform: module mymodule.root: 1 error(s) occurred:

* openstack_compute_instance_v2.myinstance: resource depends on non-configured provider 'openstack.myalias'
$ terraform --version
Terraform v0.8.0

Unfortunately, this renders Terraform unusable for multi-provider deployments using modules.

@michalmedvecky reproduced, I'm looking into it!

New PR queued up to fix this. :) You can workaround it in the meantime by just declaring your usage of an alias in the module, it'll still inherit properly.

Thanks, I can confirm that this PR fixed the issue.

Well works for statically defined providers, like this:

cat providers.tf
provider "openstack" {
  alias = "myalias"
  user_name = "myusername"
  tenant_name = "mytenant"
  auth_url = "http://1.2.3.4:5000/"
}
$ cat lb_vip/main.tf
variable "instance_name" {}
variable "domain" {}

resource "openstack_networking_port_v2" "otp_vip" {
  provider = "openstack.myalias"
  name = "${var.instance_name}.${var.domain}"
  network_id = "58da51e2-4e4a-4626-9fc4-61deb0d45d0b"
  admin_state_up = "true"
}

But changing "provider" to a variable fails:

$ cat lb_vip/main.tf
variable "instance_name" {}
variable "domain" {}
variable "prov" {}

resource "openstack_networking_port_v2" "otp_vip" {
  provider = "${var.prov}"
# ^^^^^^^^^^^^^^^^^^^^^^^
  name = "${var.instance_name}.${var.domain}"
  network_id = "58da51e2-4e4a-4626-9fc4-61deb0d45d0b"
  admin_state_up = "true"
}

and

$ cat somevip.tf

module "lb_vip" {
  source = "./lb_vip"
  domain = "${var.domain}"
  instance_name = "something"
  prov = "openstack.myalias"
}

Result:

$ terraform plan
1 error(s) occurred:

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

I still can't imagine how to use Terraform to create multi-provider deployments with modules :(

Ah, @michalmedvecky that's never been allowed we don't support dynamic provider aliases at this time. This could use a better error message though definitely.

So what are modules for, then? If I need to start 200 instances across 20 different providers (= different OpenStack tenants), just with different metadata, I was hoping to use one parametrized module instead of copy/pasting definition of 200 instances :(

You can pass in the variables to configure the OpenStack provider through and use those:

provider "openstack" {
  arg = "${var.input}"
}

There isn't a way to override only specific elements of a provider at the moment. We have plans to support that in the future but haven't gotten to it yet.

Modules are useful for reusable components, especially those that don't reconfigure providers. For example, companies often expose modules [internally] for common services or to expose metadata.

@michalmedvecky It seems to me that to do that, the easiest way is to have separate directory per provider within which the provider is the default (non-aliased provider). This requires some amount of code duplication, but works around this limitation.

@jfromaniello how does one then reference the provider in the module in a resource definition at root?

+1 to above – when trying to pass through a provider alias that I had defined in what I thought was the parent, getting an error that states provider alias must be defined by the module or a parent is definitely confusing unless I'm misunderstanding what parent means relative to the module.

Since I don't want the module to require passing in the parameters required to redefine the provider alias (for an assume_role block), I'll likely have to resort to some code duplication. I'd be interested to hear if anyone has come up with a cleaner workaround.

You guys should take a look at v0.11. It goes a long ways towards addressing the issues here:
https://www.hashicorp.com/blog/hashicorp-terraform-0-11

@jaygorrell I hadn't yet gotten around to reviewing significant changes in 0.11.x but it looks like this use case has been addressed. Thanks!

Thanks Jay - the pattern described there is perfect for this use case.

For others looking for this info, the pattern to use is this:

provider "google" {
  region = "us-central1"
  alias = "usc1"
}

module "network-usc1" {
  source = "tasdikrahman/network/google"

  providers = {
    "google" = "google.usc1"
  }
}

This will mean the network-usc1 module the aliased client, and so connects to the us-central1 region, without needing any changes within the module.

Thanks for the working example. From my experiments I wanted to also document the module's empty variable declaration to enable passing (mine was AWS but I've substituted google below):

provider "google" {
}

and the usage in the module:

resource ...
   provider "google"
   ...

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