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:
- Add the
provider "pagerduty"
block to myshared-infrastructure/terraform/gateway/main.tf
file. I don’t love this since it shouldn’t need any knowledge of PagerDuty – the AWSTerraform
user will only work within the AWS ecosystem. - 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 mainterraform-modules/user/main.tf
module as necessary. This would let me be explicit in myshared-infrastructure/terraform/gateway/main.tf
module that I only want to include theterraform-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). - Define our
Terraform
user in thecompany-user-system
repository. Since that repository is already getting passed the PagerDuty API key, if we omit thepagerduty.role
string, it’ll just create the AWS user. This would work, but I’d rather keep theTerraform
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 thecompany-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 toshared-infrastructure
.
Is there a more elegant way around this? Note: I’m using Terraform 0.12.24.