How can I limit a resource inside a module to one account using workspaces?

Im trying to setup AWS Config and I created a module that contains all of the resources I need. I use workspaces to differ between dev, stage, and prod accounts and I also have a root account. I want my S3 bucket to live in my root account and all other resources to be created in the other accounts. Right now we are in just one region so that is not an issue.

Currently I call my module like this:

module “config” {
source = “./modules/config”
primary_region = var.primary_region
encryption_enabled = var.encryption_enabled
prefix = var.prefix
workspaces = var.workspaces
snapshot_frequency = var.snapshot_frequency
config_aggregator_account = var.config_aggregator_account
transition_expiration = var.transition_expiration
transition_ia = var.transition_ia
transition_ia_class = var.transition_ia_class
delete_non_current_version = var.delete_non_current_version
abort_incomplete_multipart_upload = var.abort_incomplete_multipart_upload
}

I then create my bucket as so…

resource “aws_s3_bucket” “config_bucket” {
count = terraform.workspace == “root” ? 1 : 0
bucket = “bucket-config-data”
}

resource “aws_s3_bucket_policy” “config_bucket_access” {
count = terraform.workspace == “root” ? 1 : 0
bucket = aws_s3_bucket.config_bucket[0].id
policy = data.aws_iam_policy_document.config_bucket_policy.json
}

etc…

If I run plan for the root workspace it wants to create the bucket as I want. If I try and run a plan in any other workspaces I get this error…

│ Error: Invalid index

│ on modules/config/config.tf line 15, in resource “aws_config_delivery_channel” “config_channel”:
│ 15: s3_bucket_name = aws_s3_bucket.config_bucket[0].id
│ ├────────────────
│ │ aws_s3_bucket.config_bucket is empty tuple

│ The given key does not identify an element in this collection value: the collection has no elements.

If I remove the [0] and run plan again I get the following error…

│ Error: Missing resource instance key

│ on modules/config/s3.tf line 8, in resource “aws_s3_bucket_policy” “config_bucket_access”:
│ 8: bucket = aws_s3_bucket.config_bucket.id

│ Because aws_s3_bucket.config_bucket has “count” set, its attributes must be accessed on specific instances.

│ For example, to correlate with indices of a referring resource, use:
│ aws_s3_bucket.config_bucket[count.index]

How should I be building my module with all resources and limit one piece to only one account?
Or should I pull that resource out of the module and build it separately?

Yes, you should absolutely do this.

And then, use a data source: Terraform Registry to look up the bucket ID from the name, in the dev/stage/prod workspaces where you need to reference it.

1 Like

Hi @seth.floyd!

The direct answer to what you saw is actually exactly what that last error message suggested:

aws_s3_bucket.config_bucket[count.index]

If you use count.index here then Terraform can see that in the case where there are no instances of aws_s3_bucket_policy.config_bucket_access there cannot possibly be any valid value of count.index and therefore this expression could not be evaluated. If you specify exactly [0] then Terraform will check whether that’s a valid index and fail if not, in order to give you early feedback that this expression isn’t valid.

However, I would indeed suggest a different design here which avoids the conditional instantiation: factor out the parts that are common across all regions and the “root” into a separate shared module. Then you can use a separate configuration for the regions (still using workspaces, if you wish) vs. the root. The root configuration can then combine the shared module with these few extra resources that are unique to it, rather than trying to write one whole configuration that tries to dynamically cater to these two different situations.

That design would be an example of module composition, which involves breaking a problem into modular units and then writing different configurations that use those modules in different combinations to represent different scenarios.