For each iteration => [each.key] is not working

Hello,
I would like to kindly ask you to provide me help about iteration for each, please.

I would like to enable autoscaling for ecs tasks. I used [each.key] but I am getting error.

Here is th tfvars:

#-------------SERVICES--------#
services = [
  {
    us_name  = "fe",
    alb_paths = [ 
      "/ras-fe/*"
    ],
    healthcheck_path = "/ras-fe/health",
    container_port = 80,
    desiredCount = 2
  },
  {
    us_name  = "bff",
    alb_paths = [
      "/bff/*"
    ],
    healthcheck_path = "/bff/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "pdfgen",
    alb_paths = [
      "/pdfgen/*"
    ],
    healthcheck_path = "/pdfgen/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "dhct-fe",
    alb_paths = [
      "/dhct-fe/*"
    ],
    healthcheck_path = "/dhct-fe/health",
    container_port = 80,
    desiredCount = 2
  },
  {
    us_name  = "afint",
    alb_paths = [
      "/afint/*"
    ],
    healthcheck_path = "/afint/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "lyra",
    alb_paths = [
      "/lyra/*"
    ],
    healthcheck_path = "/lyra/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "ps",
    alb_paths = [
      "/ps/*"
    ],
    healthcheck_path = "/ps/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "techl",
    alb_paths = [
      "/techl/*"
    ],
    healthcheck_path = "/techl/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "vehicles",
    alb_paths = [
      "/aftersales-vehicles/*"
    ],
    healthcheck_path = "/aftersales-vehicles/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "dhct",
    alb_paths = [
      "/dhct/*"
    ],
    healthcheck_path = "/dhct/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "custchnl",
    alb_paths = [
      "/customer-channels/*"
    ],
    healthcheck_path = "/customer-channels/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "maintplan",
    alb_paths = [
      "/maintenance-plan/*"
    ],
    healthcheck_path = "/maintenance-plan/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "tire-fe",
    alb_paths = [
      "/tire-fe/*"
    ],
    healthcheck_path = "/tire-fe/health",
    container_port = 80,
    desiredCount = 1
  },
  {
    us_name  = "tire-be",
    alb_paths = [
      "/tire-be/*"
    ],
    healthcheck_path = "/tire-be/health",
    container_port = 8080,
    desiredCount = 1
  },
  {
    us_name  = "custsess",
    alb_paths = [
      "/customer-sessions/*",
      "/customer-sessions/",
      "/appointments/*/customer-sessions/*",
      "/appointments/*/customer-sessions/"
    ],
    healthcheck_path = "/health",
    container_port = 8080,
    desiredCount = 1
  },
  {
    us_name  = "appointments",
    alb_paths = [
      "/appointments/*"
    ],
    healthcheck_path = "/appointments/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "dealers",
    alb_paths = [
      "/dealers/*"
    ],
    healthcheck_path = "/dealers/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "dlkexport",
    alb_paths = [
      "/datalake-export/*"
    ],
    healthcheck_path = "/datalake-export/health",
    container_port = 8080,
    desiredCount = 2
  },
  {
    us_name  = "loadplan",
    alb_paths = [
      "/load-plan/*"
    ],
    healthcheck_path = "/load-plan/health",
    container_port = 8080,
    desiredCount = 2
  }
]

#--------ECS AUTOSCALING---------#
create_ecs_autoscaling = true
ecs_autoscaling_max_capacity = 10
ecs_autoscaling_min_capacity = 3

Here is the code:

resource "aws_iam_role" "ecs-autoscale-role" {
  for_each  = {for service in var.services:  service.us_name => service if var.ecs_autoscale}
  name      = "ecs-scale-app-${var.sia}-${var.irn}-${each.value.us_name}-${var.env}"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "application-autoscaling.amazonaws.com"
      },
      "Effect": "Allow"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "ecs-autoscale" {
  for_each  = {for service in var.services:  service.us_name => service if var.ecs_autoscale}
  role       = aws_iam_role.ecs-autoscale-role[each.key].id
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole"
}

resource "aws_appautoscaling_target" "ecs_target" {
  for_each  = {for service in var.services:  service.us_name => service if var.ecs_autoscale}
  max_capacity       = var.max_capacity
  min_capacity       = var.min_capacity
  resource_id        = "service/${var.ecs_clustername}/${var.ecs_servicename}"
  scalable_dimension = "ecs:service:DesiredCount"
  service_namespace  = "ecs"
  role_arn           = aws_iam_role.ecs-autoscale-role[each.key].arn
}

resource "aws_appautoscaling_policy" "ecs_target_cpu" {
  for_each           = {for service in var.services:  service.us_name => service if var.ecs_autoscale}
  name               = "app-scaling-policy-${var.sia}-${var.irn}-${each.value.us_name}-${var.env}-cpu"
  policy_type        = "TargetTrackingScaling"
  resource_id        = aws_appautoscaling_target.ecs_target[each.key].resource_id
  scalable_dimension = aws_appautoscaling_target.ecs_target[each.key].scalable_dimension
  service_namespace  = aws_appautoscaling_target.ecs_target[each.key].service_namespace

  target_tracking_scaling_policy_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ECSServiceAverageCPUUtilization"
    }
    target_value = var.cpu_target_value
  }
  depends_on = [aws_appautoscaling_target.ecs_target]
}
resource "aws_appautoscaling_policy" "ecs_target_memory" {
  for_each           = {for service in var.services:  service.us_name => service if var.ecs_autoscale}
  name               = "app-scaling-policy-${var.sia}-${var.irn}-${each.value.us_name}-${var.env}-memory"
  policy_type        = "TargetTrackingScaling"
  resource_id        = aws_appautoscaling_target.ecs_target[each.key].resource_id
  scalable_dimension = aws_appautoscaling_target.ecs_target[each.key].scalable_dimension
  service_namespace  = aws_appautoscaling_target.ecs_target[each.key].service_namespace

  target_tracking_scaling_policy_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ECSServiceAverageMemoryUtilization"
    }
    target_value = var.mem_target_value
  }
  depends_on = [aws_appautoscaling_target.ecs_target]
}

Here is the error:

Error: Invalid index
  on .terraform/modules/ecs-autoscaling/main.tf line 23, in resource "aws_iam_role_policy_attachment" "ecs-autoscale":
  23:   role       = aws_iam_role.ecs-autoscale-role[each.key].id
    |----------------
    | aws_iam_role.ecs-autoscale-role is object with 18 attributes
    | each.key is "dhct-fe"
The given key does not identify an element in this collection value.
Error: Invalid index
  on .terraform/modules/ecs-autoscaling/main.tf line 34, in resource "aws_appautoscaling_target" "ecs_target":
  34:   role_arn           = aws_iam_role.ecs-autoscale-role[each.key].arn
    |----------------
    | aws_iam_role.ecs-autoscale-role is object with 18 attributes
    | each.key is "dhct-fe"
The given key does not identify an element in this collection value.

Could someone help me, please?

Thank you very much.

Hi @martin.smola!

Unfortunately I don’t see anything obviously wrong with the configuration you shared, but since it’s relatively long it’s quite possible that I’m not noticing all of the details.

When I encounter an unexplained problem like this, I typically like to start by trying to simplify the configuration so that there are fewer opportunities for error, and therefore hopefully easier to focus on the parts that are necessarily complex.

With that in mind, I think I would suggest starting by factoring out the common for_each you seem to have on all of your resources into a local value, just so that we can be sure that all of them are working with the same result:

locals {
  autoscale_services = {
    for service in var.services : service.us_name => service
    if var.ecs_autoscale
  }
}

Then in each of your resources which use this:

  for_each = local.autoscale_services

I don’t expect that this will actually change the outcome, because as far as I can see this change should be exactly equivalent to what you already wrote. If this does change the behavior then that would be helpful information to narrow down what the possible causes could be. If it doesn’t change the behavior (which is what I’m expecting) then we can at least eliminate the possibility that these resources are somehow getting different for_each results, and try to look for other possibilities instead.

Hello @apparentlymart,
thanks for your support. I made what you asked for me and unfortunately it didn’t work.

I don’t know what I can do more. Maybe terraform version is bad. I am using version 0.13.

I tried to upgrade terraform version to 0.14 and it started working.

Unfortunately, it wanted to add all resources again, so I stopped it.

Oh, it behaving differently after upgrading is interesting! I think that might suggest that you’ve hit a bug in v0.13 that was fixed in v0.14, though I must admit I don’t remember a specific bug off the top of my head.

I think it would be good if we can figure out a way to get you upgraded to a newer version of Terraform, though. Would you mind sharing the output of terraform plan you saw after you upgraded and saw it apparently planning to add everything again? I’d like to see exactly what you mean by that, and sharing the terraform plan output is typically the best way to do that since I’m very familiar with how Terraform describes changes and so I might be able to notice some detail about what it’s describing that but hints at what the problem is even if it’s not super obvious.