使用 terraform yamldecode 访问多级元素

mat*_*ttb 2 yaml terraform

我有一个 yaml 文件(也在 azure devops 管道中使用,因此需要采用这种格式),其中包含一些我想直接从我的 terraform 模块访问的设置。

该文件类似于:

variables:
  - name: tenantsList
    value: tenanta,tenantb
  - name: unitName
    value: canary
Run Code Online (Sandbox Code Playgroud)

我想要一个这样的模块来访问设置,但我看不到如何到达底层:

locals {
  settings = yamldecode(file("../settings.yml"))
}

module "infra" {
  source = "../../../infra/terraform/"
  unitname = local.settings.variables.unitName
}
Run Code Online (Sandbox Code Playgroud)

但是这个terraform plan错误:

Error: Unsupported attribute

  on canary.tf line 16, in module "infra":
  16:   unitname  = local.settings.variables.unitName
    |----------------
    | local.settings.variables is tuple with 2 elements

This value does not have any attributes.
Run Code Online (Sandbox Code Playgroud)

Mar*_*ins 6

看起来这很困难的主要原因是因为这个 YAML 文件在逻辑上代表了一个单一的地图,但在物理上表现为一个 YAML 地图列表。

当从这样的单独文件中读取数据时,我喜欢编写一个显式表达式来规范化它,并有选择地转换它以便在 Terraform 模块的其余部分中更方便地使用。在这种情况下,似乎variables将地图作为 Terraform 值最有用的表示形式,因此我们可以编写这样的转换表达式:

locals {
  raw_settings = yamldecode(file("${path.module}/../settings.yml"))
  settings = {
    variables = tomap({
      for v in local.raw_settings.variables : v.name => v.value
    })
  }
}
Run Code Online (Sandbox Code Playgroud)

上面使用for表达式将映射列表投影到单个映射中,使用name值作为键。

将地图列表转换为单个地图后,您可以按照最初尝试的方式访问它:

module "infra" {
  source = "../../../infra/terraform/"
  unitname = local.settings.variables.unitName
}
Run Code Online (Sandbox Code Playgroud)

如果您将转换后的值作为 YAML输出local.settings,它将看起来像这样,这就是为什么现在可以直接访问地图元素:

variables:
  tenantsList: tenanta,tenantb
  unitName: canary
Run Code Online (Sandbox Code Playgroud)

这仅name在输入中的所有字符串都是唯一的情况下才有效,否则每个元素都不会有唯一的映射键。


(编写这样的规范化表达式也可以作为对该 YAML 文件形状的一些隐式验证:如果variables不是列表或者值不是所有相同类型,则 Terraform 会引发类型错误评估该表达式。即使不需要转换,无论如何我喜欢写出这种表达式,因为它可以作为 YAML 文件预期具有的形状的一些文档,而不必在整个配置的其余部分研究对它的所有引用。 )