I have a case where I need to create multiple namespaces in the vault and I have to write down separate providers for each namespace. Is there any workaround available for this?
Provider configurations in Terraform must be statically-defined because they are one object in Terraform which is needed for essentially all operations, including planning and destroying resources. In particular, a provider configuration must always outlive all of the resource instances that belong to it, and so must survive for at least one additional terraform apply
than the associated resource configurations do.
However, you can use a shared module called multiple times as a way to factor out everything except the provider configuration so it can be maintained in a central location.
For example, you could create a module in ./modules/vault-namespace
(just for example) and call it once for each namespace, each one coupled with its own provider configuration, like this:
provider "vault" {
alias = "a"
# ...
}
module "a" {
source = "./modules/vault-namespace"
# (any required settings)
providers = {
vault = vault.a
}
}
provider "vault" {
alias = "b"
# ...
}
module "b" {
source = "./modules/vault-namespace"
# (any required settings)
providers = {
vault = vault.b
}
}
The providers
argument inside a module
block tells Terraform to give the called module a different “view” of the available provider configurations than the caller had. In this case, the first module
call says that the default vault
provider configuration in the child module maps to the “a”-aliased configuration from the root module, whereas the second one uses the “b”-aliased configuration.
In order to meet the requirement I mentioned above about resource instances always outliving their provider configurations, if you ever want to remove one of your namespaces that would require a two step process. For example, to remove the “b” namespace:
Remove the module "b"
block, and apply that change to destroy all of the objects it declared.
Now remove the provider "vault"
block that has alias = "b"
, which no longer has any resource instances associated with it. You can run terraform plan
to confirm that there are no remaining references to that provider configuration.
The addition of a new namespace can typically happen in only a single step though, because when you first add the new module
block and associated provider "vault"
block the provider configuration will be available to support the initial creation of the objects declared in that module.
2 Likes
irab
June 19, 2022, 4:34am
3
Not sure if I should create my own thread for this but my question is very similar to OPs.
I currently have a lot of terraform code that creates a bunch of GKE clusters for various dev teams. I’d like to bootstrap these clusters by using terraform to deploy Kubernetes manifests. But since I use modules and for_each
I can’t use a kubernetes provider within the module. But I also can’t pass in the Kubernetes provider details into the module, such the the host IP, as the cluster doesn’t exist when I call the module.
My final attempt at an approach was to have the cluster details as an output from the module that creates the cluster and pass it into a new cluster_bootstrap
module that deploys the manifest. But this can’t work, as the provider config that’s passed in has to be static.
I’d love to be able to do something like this:
module "fluxcd" {
for_each = module.cluster
providers = {
kubernetes = {
host = each.endpoint
client_certificate = each.client_certificate
client_key = each.client_key
cluster_ca_certificate = each.cluster_ca_certificate
}
}
}
Is there anything on the roadmap to support something like this?
2 Likes
irab
June 19, 2022, 5:04am
4
Ok, I see this is being tracked here:
opened 03:27AM - 15 Jun 20 UTC
enhancement
config
<!--
Hi there,
Thank you for opening an issue. Please note that we try to ke… ep the Terraform issue tracker reserved for bug reports and feature requests. For general usage questions, please see: https://www.terraform.io/community.html.
If your issue relates to a specific Terraform provider, please open it in the provider's own repository. The index of providers is at https://github.com/terraform-providers .
-->
### Current Terraform Version
<!---
Run `terraform -v` to show the version, and paste the result between the ``` marks below. This will record which version was current at the time of your feature request, to help manage the request backlog.
If you're not using the latest version, please check to see if something related to your request has already been implemented in a later version.
-->
```
Terraform v0.13.0-beta1
```
### Use-cases
<!---
In order to properly evaluate a feature request, it is necessary to understand the use-cases for it.
Please describe below the _end goal_ you are trying to achieve that has led you to request this feature.
Please keep this section focused on the problem and not on the suggested solution. We'll get to that in a moment, below!
-->
It would be nice to be able to create dynamic providers. The main reason for my usage would be for aws assume_role. I have Terraform to create a number of AWS Subaccounts, and then I want to configure those subaccounts in one apply, instead of breaking them up across multiple apply steps.
Currently this is done via modules, but with 0.12, I had to manually define copies of the modules for each subaccount.
As said by the 0.13 modules doc: https://github.com/hashicorp/terraform/blob/master/website/docs/configuration/modules.html.md#limitations-when-using-module-expansion
> Modules using count or for_each cannot include configured provider blocks within the module. Only proxy configuration blocks are allowed.
### Attempted Solutions
<!---
If you've already tried to solve the problem within Terraform's existing features and found a limitation that prevented you from succeeding, please describe it below in as much detail as possible.
Ideally, this would include real configuration snippets that you tried, real Terraform command lines you ran, and what results you got in each case.
Please remove any sensitive information such as passwords before sharing configuration snippets and command lines.
--->
```terraform
# Organisational Group for Crimson App.
module "org_crimson_app" {
source = "./modules/organisation-group"
organisation_id = local.root_organisation_id
group_name = "crimson-app"
billing_alert_emails = ["<billing account>"]
accounts = {
production = {}
staging = {}
test = {}
}
zones = {
public = {
create = true
zone_id = ""
domain = "app.crimsoneducation.org"
}
internal = {
create = true
zone_id = ""
domain = "app.crimsoneducation.io"
}
}
master_role_name = local.organisation_root_role
account_owner_username = var.account_owner_username
account_owner_domain = var.account_owner_domain
// Workaround for https://github.com/hashicorp/terraform/issues/17519
account_configuration = {
production = module.org_crimson_app_production_config.config
staging = module.org_crimson_app_staging_config.config
test = module.org_crimson_app_test_config.config
}
}
// Workaround for https://github.com/hashicorp/terraform/issues/17519
module "org_crimson_app_production_config" {
source = "./modules/organisation-account-config"
account_info = module.org_crimson_app.account_information.production
}
// Workaround for https://github.com/hashicorp/terraform/issues/17519
module "org_crimson_app_staging_config" {
source = "./modules/organisation-account-config"
account_info = module.org_crimson_app.account_information.staging
}
// Workaround for https://github.com/hashicorp/terraform/issues/17519
module "org_crimson_app_test_config" {
source = "./modules/organisation-account-config"
account_info = module.org_crimson_app.account_information.test
}
```
Source for `./modules/organisation-group`:
```terraform
# The Organisational Unit for this group.
resource "aws_organizations_organizational_unit" "unit" {
name = var.group_name
parent_id = var.organisation_id
lifecycle {
prevent_destroy = true
}
}
# The environment accounts.
resource "aws_organizations_account" "environments" {
for_each = var.accounts
parent_id = aws_organizations_organizational_unit.unit.id
# Use account_name if provided, otherwise [group]-[account].
name = local.account_full_name[each.key]
email = "${var.account_owner_username}+aws-org-${local.account_full_name[each.key]}@${var.account_owner_domain}"
role_name = var.master_role_name
lifecycle {
# There is no AWS Organizations API for reading role_name
ignore_changes = [role_name]
prevent_destroy = true
}
}
# Collect all Account Names
locals {
account_full_name = {
for account_name, account in var.accounts :
account_name =>
lookup(account, "account_name", "${var.group_name}-${account_name}")
}
}
...
```
Source for `./modules/organisation-account-config`:
```terraform
// Workaround for https://github.com/hashicorp/terraform/issues/17519
variable "account_info" {
type = object({
group_name = string
account_name = string
account_full_name = string
alias = string
master_role_arn = string
zones = map(object({
zone_name = string
fqdn = string
}))
add_terraform_bucket = bool
billing_alert_amount = number
billing_alert_currency = string
billing_alert_emails = list(string)
})
}
# Create Provisioner Assuming Child Account Role.
provider "aws" {
region = "ap-southeast-2"
assume_role {
role_arn = var.account_info.master_role_arn
}
}
resource "aws_iam_account_alias" "alias" {
account_alias = var.account_info.alias
}
# Bucket for Terraform State.
resource "aws_s3_bucket" "terraform_state" {
count = var.account_info.add_terraform_bucket ? 1 : 0
bucket = "${var.account_info.account_full_name}-terraform"
versioning {
enabled = true
}
}
# Fix for Issue: https://medium.com/faun/aws-eks-the-role-is-not-authorized-to-perform-ec2-describeaccountattributes-error-1c6474781b84
resource "aws_iam_service_linked_role" "elasticloadbalancing" {
aws_service_name = "elasticloadbalancing.amazonaws.com"
}
...
```
### Proposal
<!---
If you have an idea for a way to address the problem via a change to Terraform features, please describe it below.
In this section, it's helpful to include specific examples of how what you are suggesting might look in configuration files, or on the command line, since that allows us to understand the full picture of what you are proposing.
If you're not sure of some details, don't worry! When we evaluate the feature request we may suggest modifications as necessary to work within the design constraints of Terraform Core.
-->
Module `for_each`:
```terraform
# Organisational Group for Crimson App.
module "org_crimson_app" {
source = "./modules/organisation-group"
organisation_id = local.root_organisation_id
group_name = "crimson-app"
billing_alert_emails = ["<billing account>"]
accounts = {
production = {}
staging = {}
test = {}
}
zones = {
public = {
create = true
zone_id = ""
domain = "app.crimsoneducation.org"
}
internal = {
create = true
zone_id = ""
domain = "app.crimsoneducation.io"
}
}
master_role_name = local.organisation_root_role
account_owner_username = var.account_owner_username
account_owner_domain = var.account_owner_domain
account_configuration = module.org_crimson_app_config
}
module "org_crimson_app_config" {
for_each = module.org_crimson_app.account_information
source = "./modules/organisation-account-config"
account_info = each.value
}
```
Provider `for_each`:
This doesn't look as clean, but appeases the docs saying:
> In all cases it is recommended to keep explicit provider configurations only in the root module and pass them (whether implicitly or explicitly) down to descendent modules. This avoids the provider configurations from being "lost" when descendent modules are removed from the configuration. It also allows the user of a configuration to determine which providers require credentials by inspecting only the root module.
```terraform
# Organisational Group for Crimson App.
module "org_crimson_app" {
source = "./modules/organisation-group"
organisation_id = local.root_organisation_id
group_name = "crimson-app"
billing_alert_emails = ["<billing account>"]
accounts = {
production = {}
staging = {}
test = {}
}
zones = {
public = {
create = true
zone_id = ""
domain = "app.crimsoneducation.org"
}
internal = {
create = true
zone_id = ""
domain = "app.crimsoneducation.io"
}
}
master_role_name = local.organisation_root_role
account_owner_username = var.account_owner_username
account_owner_domain = var.account_owner_domain
account_configuration = module.org_crimson_app_config
}
provider "aws" {
for_each = module.org_crimson_app.account_information
alias = each.key
assume_role {
role_arn = each.value.master_role_arn
}
}
module "org_crimson_app_config" {
for_each = module.org_crimson_app.account_information
source = "./modules/organisation-account-config"
providers = {
aws = aws[each.key]
}
account_info = each.value
}
```
### References
<!--
Are there any other GitHub issues, whether open or closed, that are related to the problem you've described above or to the suggested solution? If so, please create a list below that mentions each of them. For example:
- #6017
-->
- Another user references a use case: https://discuss.hashicorp.com/t/terraform-v0-13-0-beta-program/9066/9
- https://github.com/hashicorp/terraform/issues/25320
And here:
opened 10:14PM - 26 Mar 20 UTC
enhancement
providers
### Use-cases
<!---
In order to properly evaluate a feature request, it is nec… essary to understand the use-cases for it.
Please describe below the _end goal_ you are trying to achieve that has led you to request this feature.
Please keep this section focused on the problem and not on the suggested solution. We'll get to that in a moment, below!
-->
I'd like to be able to provision the same set of resources in multiple regions a `for_each` on a module. However, looping over providers (which are tied to regions) is currently not supported.
We deploy most of our infra in 2 regions in an active-passive configuration. So being able to instantiate both regions using the same module block would be a huuuge win. It's also our primary use case for for_each on modules being implemented in https://github.com/hashicorp/terraform/issues/17519.
### Proposal
<!---
If you have an idea for a way to address the problem via a change to Terraform features, please describe it below.
In this section, it's helpful to include specific examples of how what you are suggesting might look in configuration files, or on the command line, since that allows us to understand the full picture of what you are proposing.
If you're not sure of some details, don't worry! When we evaluate the feature request we may suggest modifications as necessary to work within the design constraints of Terraform Core.
-->
Proposed syntax from @jakebiesinger-onduo
```tf
provider "google" {
alias = "goog-us-east1"
region = "us-east1"
}
provider "google" {
alias = "goog-us-west1"
region = "us-west1"
}
locals {
regions = toset(['us-east1', 'us-west1'])
providers = {
us-east1 = google.goog-us-east1
us-west1 = google.goog-us-west1
}
}
module "vpc" {
for_each = local.regions
providers = {
google = local.providers[each.key]
}
...
}
```
Another option would be to de-couple the region from providers, and allow the region to be passed in to individual resources that are region aware. As far as I know, both AWS and GCP credentials at least are global.
### References
<!--
Are there any other GitHub issues, whether open or closed, that are related to the problem you've described above or to the suggested solution? If so, please create a list below that mentions each of them. For example:
- #6017
-->
- count and for_each for modules https://github.com/hashicorp/terraform/issues/17519
- looping over providers https://github.com/terraform-providers/terraform-provider-bigip/issues/247
- Multiple regions with same Provider https://github.com/hashicorp/terraform/issues/451
- Reddit: https://www.reddit.com/r/Terraform/comments/cwp0d4/terraform_multi_region_question_can_i_just_use/
1 Like
I’m looking at a use case where your ‘module’ snippet perfectly describes what I’d love to do, but it is not supported by Terraform