terraformterraform-provider-awsterraform0.12+terraform-template-file

Terraform : I get "Invalid index" when I use a map and for_each to try to create some lambdas


I’m triying to use a map with for_each for created some lambdas, this lambdas has diferents configurations, and one is the argument "layers", I mean, that some lambdas will has 3 layers, others 2 layers, others 1 layer and finally others nothing layer, I try to test only with the lambdas that has 1 layer, but I get this error:

 Error: Invalid index
│ 
│   on modules/lm/main.tf line 31, in resource "aws_lambda_function" "lml3":
│   31:   layers        = each.value.layer1 == "null" ? null : aws_lambda_layer_version.lm[each.value.layer1].arn
│     ├────────────────
│     │ aws_lambda_layer_version.lm is object with 1 attribute "capa-openpyxl-s3fs-xlrd-fsspec"
│     │ each.value.layer1 is list of string with 1 element
│ 
│ The given key does not identify an element in this collection value: string required.

terraform_version": "1.4.1" provider registry.terraform.io/hashicorp/aws v4.67.0

variables.tf

### Global variables ###
variable "stack_id" {
  type = string
}
variable "vpc_id" {
  type = string
}

### Lambda variables ###
variable "lambda_function_l3" {
  type = map(object({
    memory_size = number,
    runtime     = string,
    handler     = string,
    s3_key      = string,
    role        = string
    layer1      = optional(list(string)),
    env_key = map(any)

  }))
}

variable "layers" {
  type = map(any)
}

main.tf (lambdas)

resource "aws_lambda_function" "lml3" {
  for_each      = var.lambda_function_l3
  s3_bucket     = aws_s3_bucket.lm.id
  s3_key        = "code/${each.value.s3_key}"
  function_name = "lm-${var.stack_id}-${each.key}"
  role          = aws_iam_role.lm[each.value.role].arn
  handler       = "index.${each.value.handler}"
  runtime       = each.value.runtime
  memory_size   = each.value.memory_size
layers = each.value.layer1 == "null" ? null :aws_lambda_layer_version.lm[each.value.layer1].arn 
  depends_on = [ aws_lambda_layer_version.lm ]
  environment {
    variables = each.value.env_key
  }
  vpc_config {
    subnet_ids         = [local.subnet_private[0], local.subnet_private[5]]
    security_group_ids = ["sg-0591b309a73f15dd4"]
  }
}

main.tf(layers)

locals {
  configuration_layer = merge([
    for key, values in var.layers : {
      for layer in values.filename :
      "${layer}" =>
      merge(values, {
        layer = layer
      })
    }
  ]...)
}

resource "aws_lambda_layer_version" "lm" {
  for_each                 = local.configuration_layer
  filename                 = "${path.module}/layers/${each.key}.zip"
  layer_name               = "lm-layer-${var.stack_id}-${each.value.layer}"
  compatible_runtimes      = [each.value.compatible_runtimes]
  compatible_architectures = [each.value.compatible_architectures]
}

terraform.tfvars

layers = {
  "layers-lm-test-01" = { filename = ["capa-openpyxl-s3fs-xlrd-fsspec"], compatible_runtimes = "python3.9", compatible_architectures = "x86_64" }
}
lambda_function_l3 = {
  "test-01" = { memory_size = 512, runtime = "python3.9", handler = "lambda_handler", s3_key = "test_lm.zip", role = "s3_rw", layer1 = ["capa-openpyxl-s3fs-xlrd-fsspec"], env_key = { house = "car", test2 = "apple" } }
 


Solution

  • The error you saw is correct that it isn't valid to use a list as a lookup key for a map, because in Terraform map keys are always individual strings.

    If your goal is for the layers function of aws_lambda_function to include the ARN of each layer indicated in the layers1 list then one way to write that would be:

      layers = (
        each.value.layer1 != null ?
        [ for k in each.value.layer1 : aws_lambda_layer_version.lm[k].arn ] :
        null
      )
    

    This for expression takes each element of each.value.layer1 and uses it to look up one instance of aws_lambda_layer_version.lm with a matching key, and then takes the arn attribute of that object.

    This will succeed only if all of the values given in layer1 correspond with keys in var.layers, which doesn't seem to be true in the example terraform.tfvars file you shared, but does seem to be assumed by what you tried on your first attempt.


    Note that your declaration of variable "layers" is incorrect, because your module clearly cannot accept "any element type": we can see in the definition of local.configuration_layer that your module requires a filename attribute, and in your resource "aws_lambda_layer_version" "lm" the module requires compatible_runtimes, and compatible_architectures attributes.

    Therefore the correct declaration for that variable would be:

    variable "layers" {
      type = map(object({
        filename                 = string
        compatible_runtimes      = string
        compatible_architectures = string
      }))
    }
    

    Specifying your type constraints correctly will help Terraform give you better suggestions when you make a mistake.

    (Given the names I'd also expect compatible_runtimes and compatible_architectures to either be set(string) or list(string), but I see that your module expects to be given only a single one and then wraps it in a collection itself, so I wrote a type constraint compatible with how your module is currently written.)