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