Ability to specify optional provider/ignore unused providers?

I have a module called user in a Git repository that 2 other repositories depend on. One of those repositories is our company’s user system (i.e., my accounts across services like AWS, our CDN, PagerDuty, etc.). The other is called shared-infrastructure and houses modules with resources for things shared between our various applications (e.g., DNS zones, AWS VPCs, etc.).

I want to create a new AWS user called Terraform that will perform the work of managing our modules’ remote state files (in an S3 bucket), and also assuming various cross-account IAM roles to do necessary work across our environments (e.g., staging, production, etc.). My user module looks something like this:

# terraform-modules/user/main.tf

variable "email" {
  type = string
}

variable "name" {
  type        = string
  description = "The user's full name"
}

variable "aws" {
  type = object({
    username = string
    groups   = list(string)
  })

  default = {
    username = ""
    groups   = []
  }
}

variable "pagerduty" {
  type = object({
    role = string
  })

  default = {
    role = ""
  }
}

resource "aws_iam_user" "this" {
  count = length(trimspace(var.aws.username)) > 0 ? 1 : 0

  name = var.aws.username
}

resource "pagerduty_user" "this" {
  count = length(trimspace(var.pagerduty.role)) > 0 ? 1 : 0

  name  = var.name
  email = var.email
  role  = var.pagerduty.role
}

And in our company-user-system repo, the module for our users looks like this:

# company-user-system/main.tf

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

provider "pagerduty" {
  token = var.pagerduty_api_key
}

module "adam" {
  source = "git@github.com:my-organization/terraform-modules.git//user"

  email = "adam@company.com"
  name  = "Adam"

  aws = {
    username = "Adam"
    groups   = ["Admin"]
  }

  pagerduty = {
    role = "admin"
  }
}

That works fine when defining users with both AWS and PagerDuty credentials, but now I want to define a user with only AWS credentials. Since the pagerduty_user resource is conditional on the presence of a non-empty pagerduty.role string, I figured just omitting it and using the variable’s default value would be fine. Something like this for the AWS user that will be responsible for Terraform changes:

# shared-infrastructure/terraform/gateway/main.tf

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

module "user" {
  source = "git@github.com:my-organization/terraform-modules.git//user"

  name  = "DevOps"
  email = "devops@company.com"

  aws = {
    username = "DevOps"
    groups   = ["Admin"]
  }
}

Note the omission of both the pagerduty provider, as well as the pagerduty object in the user module block. This, unfortunately, doesn’t seem to work when I try to apply my changes, as Terraform comes back with an error:

Error: Missing required argument

The argument "token" is required, but was not set.

The token argument is PagerDuty’s provider asking for an API token. But since I didn’t actually create a pagerduty_user resource, it would be great if I can just omit the provider "pagerduty" block entirely.

I can see a few ways around this, but I don’t really like any of them:

  1. Add the provider "pagerduty" block to my shared-infrastructure/terraform/gateway/main.tf file. I don’t love this since it shouldn’t need any knowledge of PagerDuty – the AWS Terraform user will only work within the AWS ecosystem.
  2. Split up my terraform-modules/user/main.tf module into multiple modules, one per service (e.g., terraform-modules/user/aws/main.tf, terraform-modules/user/pagerduty/main.tf, etc.). Then, reference those child modules from the main terraform-modules/user/main.tf module as necessary. This would let me be explicit in my shared-infrastructure/terraform/gateway/main.tf module that I only want to include the terraform-modules/user/aws module. It seems like it might work (I haven’t actually tried it yet), but I don’t like the second level of child modules (i.e., a child module including more child modules).
  3. Define our Terraform user in the company-user-system repository. Since that repository is already getting passed the PagerDuty API key, if we omit the pagerduty.role string, it’ll just create the AWS user. This would work, but I’d rather keep the Terraform user alongside its other Terraform-related resources (S3 remote state bucket and other resources that aren’t necessarily part of our user system). You could argue that logically it is a user, so it should be a part of the company-user-system – and I might end up going down this route if there aren’t any other options – but in my head it’s more related to shared-infrastructure.

Is there a more elegant way around this? Note: I’m using Terraform 0.12.24.

5 Likes