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

Error: module : provider alias must be defined by the module: #18682

Open
Xtigyro opened this issue Aug 15, 2018 · 15 comments
Open

Error: module : provider alias must be defined by the module: #18682

Xtigyro opened this issue Aug 15, 2018 · 15 comments

Comments

@Xtigyro
Copy link

Xtigyro commented Aug 15, 2018

Terraform Version

miroslavhadzhiev@MiroslavHadzhiev-MBP:$ terraform -v
Terraform v0.11.7
+ provider.aws v1.31.0
+ provider.http v1.0.1
+ provider.local v1.1.0
+ provider.null v1.0.0
+ provider.template v1.0.0

Terraform Configuration Files

resource "aws_route53_record" "cert_validation" {
 provider = "${contains( split(",", var.hosted_zones_stg), replace(lookup(local.dvo[count.index], "domain_name"), "*.", "") ) ? "aws.stg" : "aws.prd"}"

  zone_id = "${
      lookup(local.hosted_zone_ids_zipmap,
      replace(lookup(local.dvo[count.index], "domain_name"), "*.", ""))
    }"

  name    = "${replace(lookup(local.dvo[count.index], "resource_record_name"), "\\.$", "")}"
  type    = "${lookup(local.dvo[count.index], "resource_record_type")}"
  records = ["${lookup(local.dvo[count.index], "resource_record_value")}"]
  ttl     = 60

  depends_on = ["aws_acm_certificate.cert"]
  count      = "${length(local.conc_domains_a_domain_alt_names)}"
}

Expected Behavior

The correct provider should have been chosen - either "aws.stg" or "aws.prd".

Actual Behavior

It crashes out with the error:
Error: module : provider alias must be defined by the module: ${contains( replace(split(",", var.hosted_zones_stg), "\\.$", ""), replace(lookup(local.dvo[count.index], "domain_name"), "*.", "") ) ? "aws.stg" : "aws.prd"}

Steps to Reproduce

terraform init

@Xtigyro Xtigyro changed the title Error: module : provider alias must be defined by the module: ${local.stg_prvd} Error: module : provider alias must be defined by the module: Aug 15, 2018
@ghost
Copy link

ghost commented Aug 15, 2018

This issue has been automatically migrated to hashicorp/terraform-provider-aws#5560 because it looks like an issue with that provider. If you believe this is not an issue with the provider, please reply to hashicorp/terraform-provider-aws#5560.

@ghost ghost closed this as completed Aug 15, 2018
@bflad
Copy link
Member

bflad commented Sep 5, 2018

Reopening as this issue does not look specific to the AWS provider or its resources. This other issue about provider aliases within modules may provide some clues here (unless the interpolation within the provider value is the root cause here): #17701

@bflad bflad reopened this Sep 5, 2018
@bflad
Copy link
Member

bflad commented Sep 5, 2018

@Xtigyro out of curiosity, does the provider alias must be defined by the module error still show up if you temporarily hardcode one of the providers for that resource? e.g. provider = "aws.stg"

@apparentlymart
Copy link
Member

Hi @Xtigyro,

This error is just a confusing way of saying that your string is not a valid provider address, because interpolations are not supported in this context. Terraform needs to know which provider will handle each resource very early on because the provider defines the schema and validation rules for the block and handles the planning step.

Without seeing the definition of local.dvo I'm not sure exactly what your goal is here, but in general this sort of dynamic provider selection is not possible and it'll be necessary to approach your problem a different way. If you can share a few additional details about your end goal we may be able to offer an alternative path.

@cfromm1911
Copy link

I've come up against the same limitation. I'm curious why this couldn't work in a similar fix that solved how "count" used to work. Essentially, allow interpolation as long as the
value is non-computed or already known during the plan. A simple interpolation, based on an already known value, should work.

resource "aws_instance" "server" {
    count = "${var.environment != "dev" ? 2 : 1}"
    provider = "${element(list("aws.east", "aws.west"), count.index)}"

Things like creating instances in two regions currently requires double the code because I cant do like above.

@jpsecher
Copy link

jpsecher commented Oct 4, 2018

Another use case is setting up cross-account roles like

resource "aws_iam_role" "devops" {
  provider = "${var.provider}"
  name = "${var.name}"
  force_detach_policies = true
  assume_role_policy = "${data.aws_iam_policy_document.allow-assume-role-from-trusted-account.json}"
  description = "Manipulation of basic infrastructure resources."
}

data "aws_iam_policy_document" "allow-assume-role-from-trusted-account" {
  statement {
    effect = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      type = "AWS"
      identifiers = ["arn:aws:iam::${var.trusted-account}:root"]
    }
  }
}

resource "aws_iam_role_policy_attachment" "ec2" {
  provider = "${var.provider}"
  role = "${aws_iam_role.devops.name}"
  policy_arn = "${data.aws_iam_policy.ec2-full-access.arn}"
}

data "aws_iam_policy" "ec2-full-access" {
  arn = "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
}

@jordimolesblanco
Copy link

jordimolesblanco commented Oct 10, 2018

I've also come across this limitation. @apparentlymart Let me tell you about my use case:

  • We have 1 "main" AWS account for the production environment.
  • We have a separate AWS account for test environments.
  • All environments, prod or test are exactly the same (we use exactly the same automation) and created by Terraform.
  • Right now we have 1 separate Jenkins box in each account to run Terraform. Again, we run the exact same Terraform code but the Jenkins boxes have different roles attached to them so that they can create resources in the account they are in.
  • I've created an IAM role that allows the main account to assume an IAM role from the test account, so that hopefully we can get rid of 1 Jenkins box and run everything from the Jenkins in the main account.

What we are trying to do is use the "assume_role" block in the AWS provider so that we can "smartly" switch between IAM Roles depending on the environment (prod/test).

This is a very silly example of something that fails to work:

provider "aws" {
  region      = "eu-west-1"
  max_retries = 50
  version     = "=1.33.0"
  alias       = "main"
}

provider "aws" {
  region      = "eu-west-1"
  max_retries = 50
  version     = "=1.33.0"
  alias       = "test"

  assume_role {
    role_arn     = "arn:aws:iam::XXXXXXXXXXXX"
    session_name = "test"
  }
}

variable "env" {}

locals {
  provider = "${var.env == "prod" ? "aws.main" : "aws.test"}"
}

data "aws_caller_identity" "current" {
  provider = "${local.provider}"
}

However, if i replace

provider = "${local.provider}"

with

provider = "aws.main"

Terraform run will go through

@bgshacklett
Copy link

I'm running into this right now as well. My problem is that, for some time, we will need to be running Terraform directly using shared credentials/profiles, but the long term goal is to have it run by a Jenkins worker which uses an assumed role. With things as they are, we're going to have to put a pretty nasty workaround in place for a while; perhaps maintaining a separate branch for CD or setting the credentials environment variables manually in the container before the TF runs (assuming that will work).

@raycrawford
Copy link

The use case I'm running into is one where I'm leveraging the MS distributed subscription model. At Ignite this year, they recommended using lots of subscriptions. Subscriptions are tied to providers. I don't mind setting up individual providers and passing that data into modules, but defining separate modules for the same thing (eg Resource Groups which may be created in several different subscriptions during the same TF run) so that the providers can be hard coded seems REALLY bad.

Is this recognized as an issue and, if so, is there a plan against resolution? Thanks!

@pecastro
Copy link

pecastro commented Nov 6, 2018

+1 Same issue here ...

@tfrancisci
Copy link

+1 Same issue here. We are creating Direct Connect VIF's using Terraform. It needs to be able to provision resources in multiple accounts.

I can do, it, but not using modules, which leads to duplicated code.

@apparentlymart
Copy link
Member

Hi all! Sorry for the lack of response here, and thanks for sharing your use-cases.

The intended way to create a region-agnostic module is to write it with no provider configurations or selections at all and then to instantiate the module multiple times with different provider configurations passed in from the root.

Let's use a hypothetical "aws_vpc" module as an example, and set up a similar VPC configuration across two regions:

provider "aws" {
  alias  = "usw1"
  region = "us-west-1"
}

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

module "vpc_west" {
  source = "./aws_vpc"

  cidr_block = "10.1.0.0/16"
  providers = {
    "aws" = "aws.usw1"
  }
}

module "vpc_east" {
  source = "./aws_vpc"

  cidr_block = "10.2.0.0/16"
  providers = {
    "aws" = "aws.use1"
  }
}

This single aws_vpc module can then be written to just accept whatever provider configuration is given by its caller:

variable "cidr_block" {}

resource "aws_vpc" "main" {
  cidr_block = "${var.cidr_block}"
}

# ... and any other related resources, such as subnets.

When this configuration is applied altogether, Terraform will ensure that the default aws provider in the child module is mapped to the appropriate aliased aws provider from the parent.

Although I showed a multi-region example here, the same can be done for multiple accounts or any other provider-level setting.

The constraint motivating this design is that the relationships between resources and their providers must be determined during graph construction, and expressions can only be evaluated after graph construction, during the graph walk. This mechanism for passing providers between modules is a compromise to ensure that all of the providers can be resolved statically (during graph construction) without hard-coding particular provider settings into every resource.

There are more details on this in the configuration section Providers Within Modules. This section of the documentation is planned to get an overhaul for the forthcoming v0.12 release to describe specific patterns for applying modules, rather than just describing technically how they operate as it does today. (This revision will also correct the outdated claim in those docs that a proxy provider "aws" configuration block is required in the child module; as shown in my example above, that is not actually required.)

@cfromm1911
Copy link

cfromm1911 commented Nov 14, 2018

I think the term "module" is confusing the issue. Martin, I believe everyone is talking about the inability to use even simple interpolation in the provider declaration. Simple conditionals based on values that are clearly known at plan should work. provider = "${var.env == "prod" ? "aws.main" : "aws.test"}" should work.

A little over a year ago, you couldn't include interpolations in "count", for the same reason. "Expressions could only be evaluated after graph construction." However, this was changed. Count can now include interpolation as long as its based on values "known at plan".

Why cant the same logic be applied the provider reference? It would allow for much more dynamic execution.


resource "aws_instance" "server" {
    count = "${var.environment != "dev" ? 2 : 1}"
    provider = "${element(list("aws.east", "aws.west"), count.index)}"

@apparentlymart
Copy link
Member

count is not evaluated during graph construction. It is evaluated during the plan walk. The reason for the restriction there (that the expression include only known values) is because the number of instances must be known in order to produce the set of changes in the plan.

The situation with the provider argument is the same as depends_on, rather than count.

@bam0382
Copy link

bam0382 commented Mar 4, 2019

Hi @apparentlymart ,

Can we get this added as an enhancement request? Is there a way to add another construct before graph to handle providers? This seems to be a very reasonable use case.

Terraform Version

$ terraform -v
Terraform v0.11.7
+ provider.archive v1.1.0
+ provider.aws v2.0.0
+ provider.null v2.1.0
+ provider.template v2.1.0

Terraform Configuration Files

provider "aws" {
    count = "${length(var.regions)}"
    alias = "${var.regions[count.index]}"
    region  = "${var.regions[count.index]}"
    profile = "${local.account}"
}
resource "foo" "bar" {
  count            = "${length(var.regions)}"
  provider         = "aws.${var.regions[count.index]}"
  }
variable "regions" { type = "list" default = ["us-east-1","us-west-2"]}

or

provider "aws" {
    region  = "${var.regions[0]}"
    profile = "${local.account}"
}

provider "aws" {
    count = "${var.regions[0] ? 1 : 0 }"
    alias = "${var.regions[0]}"
    region  = "${var.regions[0]}"
    profile = "${local.account}"
}

provider "aws" {
    count = "${var.regions[1] ? 1 : 0 }"
    alias = "${var.regions[1]}"
    region  = "${var.regions[1]}"
    profile = "${local.account}"
}
resource "foo" "bar" {
  count            = "${length(var.regions)}"
  provider         = "aws.${var.regions[count.index]}"
  }
variable "regions" { type = "list" default = ["us-east-1","us-west-2"]}

Expected Behavior

I have a use case to pass n number of regions per aws account (profile) to manage different counts of total regions. One account may only need 2 regions, but other accounts may need 6. Being able to manage all regions in one state resolves tiered dependance on global AWS resources with regional ones.

Actual behavior

provider.aws.${var.regions[count.index]}: count.index: count.index is only valid within resources

or

Error: module : provider alias must be defined by the module: aws.${var.regions[count.index]}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests