Can I have nested count.index in a resources?

1.9k Views Asked by At

I have an aws_api_gateway_domain_name resource in terraform with this configuration:

resource "aws_api_gateway_domain_name" "apigw_domain_name" {
  count                    = var.api_server_custom_domain_name != "" ? 1 : 0
  domain_name              = var.api_server_custom_domain_name
  regional_certificate_arn = var.api_server_cert_arn
  security_policy          = "TLS_1_2"

  endpoint_configuration {
    types = [var.api_endpoint_configuration_type]
  }
}

In case the count above is set to 1 (or >1, lets's say I want to have multiple domain names). For each of those resources, I want to have multiple aws_api_gateway_base_path_mapping resources as follows: (I assume I have 4 aws_api_gateway_rest_api and aws_api_gateway_stage resources)

resource "aws_api_gateway_base_path_mapping" "api_gateway_base_path_mapping" {
  count       = 4
  domain_name = aws_api_gateway_domain_name.apigw_domain_name[<Indexing the domain names>].domain_name
  api_id      = aws_api_gateway_rest_api.apigw[count.index].id
  stage_name  = aws_api_gateway_stage.stage[count.index].stage_name
}

So I imagine it would be like a 2D matrix, where for every domain name there is some fixed number of mappings. Is there a way to do it?

1

There are 1 best solutions below

3
On

I would suggest to use for_each instead of count and create a variable/local to add some more data to it. With for_each you can iterate to all of those values instead of having a count variable. Those will be automatically used with their exact value and not with an index value:

https://www.terraform.io/language/meta-arguments/for_each

Using count in your case can have one big disadvantage: You are starting with count=4. What will you do, if you want to delete one of those? With count, you can reduce the value to 3, but you are not able to select the one, which should be deleted.

With for_each, you can do something like this (taken from reference of terraform and adjusted):

resource "aws_api_gateway_base_path_mapping" "api_gateway_base_path_mapping" {
  for_each = toset( ["url1", "url2", "url3", "url4"] )
  name     = each.key
}

If you now want to remove url3, just delete this and all other mappings will be kept.

With a more complex example (using map), you can also provide further information, not only to the base_path_mapping, so you can also create some more resources based on this mapping. Removing one, will keep all other created resources.

Here is a full working example:

variable "api_domains" {
  type = map(any)
  default = {
    "domain1" = { certarn = "arn1", stages = ["stage1", "stage2", "stage3"] }
    "domain2" = { certarn = "arn2", stages = ["stage1", "stage2", "stage3"] }
    "domain3" = { certarn = "arn3", stages = ["stage1", "stage2", "stage3"] }
  }

}

locals {
  mappings = merge([
    for domain, certarn in var.api_domains : {
      for stagename in certarn.stages :
      "${domain}_${stagename}" => {
        domain    = domain
        stagename = stagename
      }
    }
  ]...) # https://www.terraform.io/language/expressions/function-calls#expanding-function-arguments
}

resource "aws_api_gateway_domain_name" "apigw_domain_name" {
  for_each                 = var.api_domains # loop through your variable
  domain_name              = each.key
  regional_certificate_arn = each.value.certarn
  security_policy          = "TLS_1_2"

  endpoint_configuration {
    types = [var.api_endpoint_configuration_type]
  }
}

resource "aws_api_gateway_base_path_mapping" "api_gateway_base_path_mapping" {
  for_each    = local.mappings # loop through your local
  domain_name = each.value.domain
  api_id      = aws_api_gateway_rest_api.apigw.id
  stage_name  = each.value.stagename
}