Terraform Multiple Provider issue - terraform

I am following the documentation here running Terraform v0.14.10 -> https://www.terraform.io/docs/language/modules/develop/providers.html
My config is as follows:
variables.tf
terraform {
backend "remote" {
organization = "the-xxxx"
workspaces {
prefix = "non-prod-"
}
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "3.32.0"
}
}
}
provider "aws" {
}
provider "aws" {
alias = "core_db_middleware"
profile = "core_db_middleware"
}
provider "aws" {
alias = "us-east-1"
region = "us-east-1"
}
in main.tf where I call my module:
module "vpc" {
source = "../modules/infrastructure/shared/aws/vpc"
providers = {
aws.core_db_middleware = aws.core_db_middleware
}
class_b_block = var.class_b_block
platform_name = var.platform_name
core_platform_aws_account_id = var.core_platform_aws_account_id
core_platform_vpc_id = var.core_platform_vpc_id
core_platform_region = var.core_platform_region
core_platform_cidr_range = var.core_platform_cidr_range
}
Then in the "vpc" module variables.tf I have:
provider "aws" {}
provider "aws" {
alias = "core_db_middleware"
}
data "aws_caller_identity" "requester" {
provider = aws.core_db_middleware
}
data "aws_region" "requester" {
provider = aws.core_db_middleware
}
When I run plan on terraform it keeps giving me the aws account ID of the default provider even though I have:
data "aws_caller_identity" "requester" {
provider = aws.core_db_middleware
}
and use it like so:
resource "aws_vpc_peering_connection" "core_db_middleware_requester" {
peer_owner_id = data.aws_caller_identity.requester.account_id
peer_vpc_id = "vpc-xxxxxxxxxxxxxx"
vpc_id = aws_vpc.main.id
peer_region = data.aws_region.requester.name
auto_accept = false
tags = {
Name = "VPC Peering between ${var.platform_name} and core_db_middleware"
}
}
I tried adding the configuration_aliases = [ aws.core_db_middleware ] to my main.tf in my root directory as described in the official documentation and in the "vpc" module directory but I get the below error when in my root main:
Error: Invalid required_providers object
on variables.tf line 9, in terraform:
9: aws = {
10: source = "hashicorp/aws"
11: version = "3.32.0"
12: configuration_aliases = [ aws.core_db_middleware ]
13: }
required_providers objects can only contain "version" and "source" attributes.
To configure a provider, use a "provider" block.
Error: Variables not allowed
on variables.tf line 12, in terraform:
12: configuration_aliases = [ aws.core_db_middleware ]
Variables may not be used here.
and the below error when I place it in the "vpc" module:
Error: Variables not allowed
On ../modules/infrastructure/shared/aws/vpc/variables.tf line 33: Variables
may not be used here.
Error: Variables not allowed
On ../modules/infrastructure/shared/aws/vpc/variables.tf line 33: Variables
may not be used here.
Error: Variables not allowed
On ../modules/infrastructure/shared/aws/vpc/variables.tf line 33: Variables
may not be used here.
Error: Variables not allowed
On ../modules/infrastructure/shared/aws/vpc/variables.tf line 33: Variables
may not be used here.
I cannot figure out where I am going wrong :( I do also have my default provider aws environment variables set in the pipeline environment e.g
AWS_ACCESS_KEY_ID: $xxxxxxxxxxx_AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $xxxxxxxxxx_AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: 'ap-southeast-2'

Related

Terraform: Set an AWS Resource's provider value via a module variable

I have created a module I want to use across multiple providers (just two AWS providers for 2 regions). How can I set a resource's provider value via variable from a calling module? I am calling a module codebuild.tf (which I want to be region agnostic) from a MGMT module named cicd.tf - Folder structure:
main.tf
/MGMT/
-> cicd.tf
/modules/codebuild/
-> codebuild.tf
main.tf:
terraform {
required_version = ">= 1.0.10"
backend "s3" {
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}
# default AWS provider for MGMT resources in us-east-1 and global
provider "aws" {
region = "us-east-1"
}
# DEV Account resources in us-east-1 and global
provider "aws" {
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::accountid:role/dev-rolename"
}
alias = "dev_us-east-1"
}
# DEV Account resources in us-west-2 and global
provider "aws" {
region = "us-west-2"
assume_role {
role_arn = "arn:aws:iam::accountid:role/dev-rolename"
}
alias = "dev_us-west-2"
}
module "MGMT" {
source = "./MGMT"
count = var.aws_env == "MGMT" ? 1 : 0
aws_env = var.aws_env
}
When I build my TF, its under the MGMT AWS account which uses the the default aws provider that doesn't have an alias - I am then trying to set a provider with an AWS IAM Role (that's cross account) when I am calling the module (I made the resource a module because I want to run it in multiple regions):
/MGMT/cicd.tf:
# DEV in cicd.tf
# create the codebuild resource in the assumed role's us-east-1 region
module "dev_cicd_iac_us_east_1" {
source = "../modules/codebuild/"
input_aws_provider = "aws.dev_us-east-1"
input_aws_env = var.dev_aws_env
}
# create the codebuild resource in the assumed role's us-west-2 region
module "dev_cicd_iac_us_west_2" {
source = "../modules/codebuild/"
input_aws_provider = "aws.dev_us-west_2"
input_aws_env = var.dev_aws_env
}
/modules/codebuild/codebuild.tf:
# Code Build resource here
variable "input_aws_provider" {}
variable "input_aws_env" {}
resource "aws_codebuild_project" "codebuild-iac" {
provider = tostring(var.input_aws_provider) # trying to make it a string, with just the var there it looks for a var provider
name = "${var.input_aws_env}-CodeBuild-IaC"
# etc...
}
I get the following error when I plan the above:
│ Error: Invalid provider reference
│ On modules/codebuild/codebuild.tf line 25: Provider argument requires
│ a provider name followed by an optional alias, like "aws.foo".
How can I make the provider value a proper reference to the aws provider defined in main.tf while still using a MGMT folder/module file named cicd.tf?

Unsupported argument, An argument named "" is not expected here

I am getting the below error when run terraform plan, the idea of this IAM to allow Lambda to run another services in AWS (step-function) once the it will finish executing.
Why does terraform fail with "An argument named "" is not expected here"?
Terraform version
Terraform v0.12.31
The error
Error: Unsupported argument
on iam.tf line 246, in resource "aws_iam_role" "lambda_role":
246: managed_policy_arns = var.managed_policy_arns
An argument named "managed_policy_arns" is not expected here.
Error: Unsupported block type
on iam.tf line 260, in resource "aws_iam_role" "lambda_role":
260: inline_policy {
Blocks of type "inline_policy" are not expected here.
the code for iam.tf:-
resource "aws_iam_role" "lambda_role" {
name = "${var.name}-role"
managed_policy_arns = var.managed_policy_arns
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
},
]
})
inline_policy {
name = "step_function_policy"
policy = jsonencode({
Version = "2012-10-17"
Statement: [
{
Effect: "Allow"
Action: ["states:StartExecution"]
Resource: "*"
}
]
})
}
}
For the future, I fixed this issue by using a higher version of aws provider
The provider.tf was like the following :-
provider "aws" {
region = var.region
version = "< 3.0"
}
Change it to be like this :-
provider "aws" {
region = var.region
version = "<= 3.37.0"
}

loop over aws provider to create ressources in every aws account

I have a list of objects in Terraform called users and the structure is the following:
variable "accounts" {
type = list(object({
id = string #used in assume_role
email = string
is_enabled = bool
}))
}
What I am trying to achieve now is to create a simple S3 bucket in each of those AWS accounts (if the is_enabled is true). I was able to do it for a single account but I am not sure if there is a way to loop over a provider?
Code for a single account - main.tf
provider "aws" {
alias = "new_account"
region = "eu-west-3"
assume_role {
role_arn = "arn:aws:iam::${aws_organizations_account.this.id}:role/OrganizationAccountAccessRole"
session_name = "new_account_creation"
}
}
resource "aws_s3_bucket" "bucket" {
provider = aws.new_account
bucket = "new-account-bucket-${aws_organizations_account.this.id}"
acl = "private"
}
You need to define one provider for each aws account you want to use:
Create a module (i.e. a directory), where your bucket configuration lives:
├── main.tf
└── module
└── bucket.tf
bucket.tf should contain the resource definition: resource "aws_s3_bucket" "bucket" {...}
In main.tf , define multiple aws providers and call the module with each of them:
provider "aws" {
alias = "account1"
region = "eu-west-1"
...
}
provider "aws" {
alias = "account2"
region = "us-west-1"
...
}
module "my_module" {
source = "./module"
providers = {
aws.account1 = aws.account1
aws.account2 = aws.account2
}
}
I guess you could also get fancy by creating a variable containing the providers, and pass it to the module invocation (you could probably also use a filter on the list to take into account the is_enabled flag)
More details about providers: https://www.terraform.io/docs/language/modules/develop/providers.html
Found what I was looking for here: https://github.com/hashicorp/terraform/issues/19932
Thanks Bryan Karaffa
## Just some data... a list(map())
locals {
aws_accounts = [
{ "aws_account_id": "123456789012", "foo_value": "foo", "bar_value": "bar" },
{ "aws_account_id": "987654321098", "foo_value": "foofoo", "bar_value": "barbar" },
]
}
## Here's the proposed magic... `provider.for_each`
provider "aws" {
for_each = local.aws_accounts
alias = each.value.aws_account_id
assume_role {
role_arn = "arn:aws:iam::${each.value.aws_account_id}:role/TerraformAccessRole"
}
}
## Modules reference the provider dynamically using `each.value.aws_account_id`
module "foo" {
source = "./foo"
for_each = local.aws_accounts
providers = {
aws = "aws.${each.value.aws_account_id}"
}
foo = each.value.foo_value
}
module "bar" {
source = "./bar"
for_each = local.aws_accounts
providers = {
aws = "aws.${each.value.aws_account_id}"
}
bar = each.value.bar_value
}

Terraform not passing variable to module

I'm trying to pass a variable from an environment dir to a module, but having some issues. My directory structure looks like this
repository
-> prod
-> test
main.tf
terraform.tf
vars.tfvars
-> modules
infra
main.tf
terraform.tf
In test/main.tf I have
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}
provider "aws" {
region = "eu-west-1"
}
module "launch" {
source = "../../modules/infra"
range = var.range
}
test/terraform.tf looks like this
variable "range" {}
test/vars.tfvars
range="10.0.0.0/16"
modules/infra/main.tf
resource "aws_vpc" "testvpc" {
cidr_block = var.range
}
When I run this I get the prompt
var.range
Enter a value:
I'm expecting it to pick up the value from the variable automatically, but even when I do enter the value I get the error
│ Error: Unsupported argument
on main.tf line 20, in module "launch":
range = var.range
An argument named "range" is not expected here
Is it possible to pass a variable from a file for a given environment to a module?
Make sure the file modules/infra/terraform.tf contains the variable:
variable "range" {}

Terraform Resource does not have attribute for variable

Running Terraform 0.11.7 and getting the following error:
module.frontend_cfg.var.web_acl: Resource 'data.terraform_remote_state.waf' does not have attribute 'waf_nonprod_id' for variable 'data.terraform_remote_state.waf.waf_nonprod_id'
Below is the terraform file:
module "frontend_cfg"
{
source = "../../../../modules/s3_fe/developers"
region = "us-east-1"
dev_shortname = "cfg"
web_acl = "${data.terraform_remote_state.waf.waf_nonprod_id}"
}
data "terraform_remote_state" "waf" {
backend = "local"
config = {
name = "../../../global/waf/terraform.tfstate"
}
}
The file which creates the tfstate file referenced above is below. This file has had no issues building.
resource "aws_waf_web_acl" "waf_fe_nonprod"
{
name = "fe_nonprod_waf"
metric_name = "fenonprodwaf"
default_action
{
type = "ALLOW"
}
}
output waf_nonprod_id
{
value = "${aws_waf_web_acl.waf_fe_nonprod.id}"
}
I will spare the full output of the cloudfront file, however, the following covers the text:
resource "aws_cloudfront_distribution" "fe_distribution"
{
web_acl_id = "${var.web_acl}"
}
If I put the ID of the waf ID into the web_acl variable, it works just fine, so I suspect the issue is something to do with the way I am calling data. This appears to match documentation though.
Use path instead of name in terraform_remote_state,
https://www.terraform.io/docs/backends/types/local.html
data "terraform_remote_state" "waf" {
backend = "local"
config = {
path = "../../../global/waf/terraform.tfstate"
}
}
or
data "terraform_remote_state" "waf" {
backend = "local"
config = {
path = "${path.module}/../../../global/waf/terraform.tfstate"
}
}
I tested it with terraform version 0.11.7 and 0.11.14
If you upgrade terraform to version 0.12.x, syntax using remote_state ouput has changed.
So change
web_acl = "${data.terraform_remote_state.waf.waf_nonprod_id}"
to
web_acl = data.terraform_remote_state.waf.outputs.waf_nonprod_id
or
web_acl = "${data.terraform_remote_state.waf.outputs.waf_nonprod_id}"

Resources