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

huangapple go评论45阅读模式
英文:

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

问题

我正在尝试使用for_each创建一些lambda函数的地图,这些lambda函数具有不同的配置,其中一个参数是“layers”,这意味着某些lambda函数将具有3个层,其他lambda函数将具有2个层,其他lambda函数将具有1个层,最后一些lambda函数将不具有任何层。我尝试仅测试具有1个层的lambda函数,但我收到以下错误消息:

 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

### 全局变量 ###
variable "stack_id" {
  type = string
}
variable "vpc_id" {
  type = string
}

### Lambda 变量 ###
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(lambda函数)

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(层)

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" } }
}
英文:

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" } }
 

答案1

得分: 0

错误消息显示,将列表用作映射的查找键是无效的,因为在 Terraform 中,映射键始终是单个字符串。

如果您的目标是让 aws_lambda_functionlayers 函数包含 layers1 列表中指定的每个层的 ARN,那么可以通过以下方式编写:

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

for 表达式each.value.layer1 的每个元素进行迭代,并使用它来查找具有匹配键的 aws_lambda_layer_version.lm 实例,然后获取该对象的 arn 属性。

这只会成功,如果在 layer1 中给定的所有值都与 var.layers 中的键对应,但根据您分享的示例 terraform.tfvars 文件,似乎这不成立,但似乎是您首次尝试时假定的。


请注意,您的 variable "layers" 声明是不正确的,因为您的模块显然 不能 接受 "任何元素类型":我们可以在 local.configuration_layer 的定义中看到,您的模块需要一个 filename 属性,并且在您的 resource "aws_lambda_layer_version" "lm" 中,模块需要 compatible_runtimescompatible_architectures 属性。

因此,该变量的正确声明应该是:

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

正确指定类型约束将有助于 Terraform 在您犯错误时提供更好的建议。

(鉴于名称,我也希望 compatible_runtimescompatible_architectures 要么是 set(string),要么是 list(string),但我看到您的模块期望只提供一个,然后在模块内部将其包装到集合中,因此我编写了与您的模块当前编写方式兼容的类型约束。)

英文:

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.)

huangapple
  • 本文由 发表于 2023年5月25日 04:00:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/76327023.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定