Conditional Providers in Terraform - workaround
I ran across a use case where I needed to conditionally add an extra provider
block within one of our Terraform projects - and unfortunately found out count
and for_each
are not parameters allowed ...
Eg. this is not allowed:
provider "aws" {
count = var.enable_aws ? 1 : 0
region = var.region
}
However, the aws
provider can initialised without credentials:
$ vim provider.tf
provider "aws" {
region = "eu-west-2"
}
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installing hashicorp/aws v5.29.0...
- Installed hashicorp/aws v5.29.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
This is how Terraform Providers should be developed ...
Eg. we should be able to define a Provider and allow it to install without authenticating to that Provider by default
This means that we can set the other provider
parameters to null
conditionally
So, if I needed to create a conditional provider based on an external AWS account, we can define it, without requiring input, like so:
variable "external_aws_account_profile_name" {
description = "AWS Profile name for external resources"
type = string
default = ""
}
variable "external_aws_account_assume_role" {
description = "AWS Profile name for external resources"
type = object({
role_arn = optional(string)
session_name = optional(string)
external_id = optional(string)
})
default = {}
}
provider "aws" {
region = var.region
}
provider "aws" {
alias = "external"
profile = var.external_aws_account_profile_name != "" ? external_aws_account_profile_name : null
dynamic "assume_role" {
for_each = var.external_aws_account_assume_role != {} ? [1] : []
content {
role_arn = lookup(var.external_aws_account_assume_role, "role_arn", null)
session_name = lookup(var.external_aws_account_assume_role, "session_name", null)
external_id = lookup(var.external_aws_account_assume_role, "external_id", null)
}
}
}
This will create 2 aws
providers in Terraform during initialisation, but will allow us to conditionally use those providers
For example, I can now create a Route53 Hosted Zone, as well as conditionally create NS records into a Hosted Zone in another AWS account:
locals {
route53_hosted_zone_domain_name = var.route53_hosted_zone_domain_name
route53_hosted_zone_root_domain_name = var.route53_hosted_zone_root_domain_name
enable_route53_delegation = (var.external_aws_account_assume_role != {} || local.external_aws_account_profile_name != "") && local.route53_hosted_zone_domain_name != "" && local.route53_hosted_zone_root_domain_name != ""
}
data "aws_route53_zone" "example_root" {
count = local.enable_route53_delegation ? 1 : 0
name = local.route53_hosted_zone_root_domain_name
}
resource "aws_route53_zone" "subdomain_example" {
name = "subdomain.${local.route53_hosted_zone_root_domain_name}"
}
resource "aws_route53_record" "subdomain_delegation_example" {
count = local.enable_route53_delegation ? 1 : 0
provider = aws.external
name = "subdomain.${local.route53_hosted_zone_root_domain_name}"
ttl = 172800
type = "NS"
zone_id = data.aws_route53_zone.example_root[0].zone_id
records = [
aws_route53_zone.subdomain_example[0].name_servers[0],
aws_route53_zone.subdomain_example[0].name_servers[1],
aws_route53_zone.subdomain_example[0].name_servers[2],
aws_route53_zone.subdomain_example[0].name_servers[3],
]
}
This allows us to configure Terraform in a way that ensures the aws.external
provider will only be used when needed.
If a var.external_aws_account_profile_name
or var.external_aws_account_assume_role
input variable isn't provided, it will just successfully create another default Provider - and then not attempt to create the resources