Help working around an invalid for_each error in module

So as I was adding resources and creating this module I didn’t run into this problem; however, when running it cleanly on another workspace from scratch it has reared its head up and I’m trying to figure out a clean way to work around it.

In my module, I’m passing a topic_arn variable as:

variable "topic_arn" {
  description = "SNS Topic ARN to send SES Bounce and Complaint notifications to"
  type        = string
  default     = null
}

Then I’m generating a local as:

locals {
  notification_types = var.topic_arn != null ? ["Bounce", "Complaint"] : []
}

The error is coming into play when I add use local.notification_types in the resource block:

resource "aws_ses_identity_notification_topic" "this" {
  for_each = toset(local.notification_types)

  topic_arn                = var.topic_arn
  notification_type        = each.value
  identity                 = aws_ses_domain_identity.this.domain
  include_original_headers = true
}

I guess I could just create 2 aws_ses_identity_notification_topic resources as bounce and complaints and just hard code the notification_type and use a count = var.topic_arn != null ? 1 : 0 on the resource to determine if it should be created or not but thought this was cleaner.

Any other thoughts/ideas on how to work around this issue. Currently, if I simply run a terraform apply and actually target the resource the aws_sns_topic that is generated and then sent to the module as a variable it works so it appears to be an issue with the topic resource being created at the same time that the module is called initially I think.

Likewise, if I simply don’t include the topic_arn = aws_sns_topic.sns.arn to the module call so that it defaults to null then the run the plan and apply it works fine. Then executing a second time with the topic_arn variable passed to the module it works which confirms my suspicion.

Did you try using

for_each = var.topic_arn != null ? ["Bounce", "Complaint"] : []

I haven’t tried that option yet but I have another instance of this module to execute so I will try that and see if it works or not. I can see why it might work over using the local variable but i can also see why it might not so I’ll be curious to find out.

You didn’t mention here which error you saw but from the context I’m going to assume it’s the one about the fact that the keys of for_each (or, in the case of a set, the set elements) must be known at planning time, but your topic ARN seems like something that would be decided at apply time by the provider and so not accepted here.

The general trick with this sort of thing is to try to reformulate this decision to be in terms of values that Terraform does know at plan time. You didn’t show the expression assigned into var.topic_arn but I assume that the situation where it is null is some other setting in the root module that allows enabling or disabling the SNS topic, and then you’ve arranged for topic_arn to be null in the case where it’s disabled.

If the decision about whether to enable or disable it is something made statically inside the configuration (rather than dynamically in response to the results from other resources) then one way to make this work would be to use that same static decision to decide the notification types:

module "example" {
  # ...

  sns_disabled = var.sns_enabled
}

If you then use var.sns_disabled instead of var.topic_arn in your notification_types expression Terraform should be able to resolve that decision during planning and thus make the result acceptable to use in for_each.

To add context and be more concrete on the example… This error is being generated from within my UGNS/route53-ses/aws module that I call from my private IaC code as:

module "ses" {
  source  = "UGNS/route53-ses/aws"
  version = "0.1.0"

  zone_id   = aws_route53_zone.this.id
  topic_arn = aws_sns_topic.sns.arn
}

the aws_sns_topic resource is created in the same IaC code that the module is called in and there isn’t anything really special about it as it just sets the name_prefix, display_name and some tags. I suspect this is where the problem is introduced as during the planning time this is ARN is still unknown but is being passed to the module. If I simply don’t add the topic_arn parameter to the module call all is fine as the topic_arn will default to null. I suspect if I separated the SNS topic creation out and imported the existing topic ARN and passed it along it would work fine as is because the ARN would be known at planning time. I’ve reproduced this sitation by running the planning and apply while targetting the aws_sns_topic.arn to create it and then executing without the target to plan and apply the rest of the code.