You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Terraform读取SOPS文件时替换foreach键的.为/适配Vault路径

解决Terraform读取SOPS加密YAML创建Vault KV v2资源的层级适配问题

我读取一个YAML格式的SOPS加密文件,Terraform会自动将YAML的层级结构转换为以.连接的扁平键值对,现在需要基于这些数据创建Vault KV v2资源,但遇到两个问题:

  1. name字段使用.分隔,不符合Vault用/划分子目录的规范;
  2. data_json中的键名也是.连接的格式,而非原始层级的键名。

现有代码

data "sops_file" "account_secrets" {
  source_file = "${path.module}/secrets/secrets.yaml"
  input_type  = "yaml"
}

resource "vault_kv_secret_v2" "secrets" {
  for_each = nonsensitive(data.sops_file.account_secrets.data)
  mount    = "test"
  name     = each.key
  cas      = 1
  data_json = jsonencode(
    {
      (each.key) = (each.value),
    }
  )

  custom_metadata {
    max_versions = 5 #var.max_versions != "" ? 5 : var.max_versions
  }
}

SOPS加密YAML示例

AAAAA:
    AAAAAAAAA: SECRET
    BBBBBBBBB: SECRET
    CCCCCCCCC: SECRET
    DDDDDDDDD: SECRET
BBBBB:
    EEEEEEEEE:
        FFFFFFFFF: SECRET
CCCC:
    DDDDD:
        EEEEEE:
            FFFFFF:
                DDDDDD: SECRET
                IIIIIII: SECRET
    IIIIIIII:
        RRRRRRRR: SECRET
    UUUUUUU:
        OOOOOOOO: SECRET
        RRRRRRRR: SECRET

当前问题

执行计划生成的资源存在两处不符合预期:

  • name字段为.连接的字符串,如ccccc.ddddddd.eeeeeee.ffffffff.dddddddd.iiiiii,不符合Vault的目录路径规范;
  • data_json中的键名与name一致,为.连接格式,而非原始层级中的最终节点键名。

我曾尝试用replace函数修改键名,但写法错误触发类型错误:

data_json = jsonencode(
    {
      replace("/", each.key) = (each.value),
    }
  )

解决方案

根据不同的存储需求,提供两种方案:

方案1:每个叶子节点单独创建Vault Secret

将每个扁平键值对转换为独立的Vault Secret,路径用/分隔,data中仅存储叶子节点的键值对:

resource "vault_kv_secret_v2" "secrets" {
  for_each = nonsensitive(data.sops_file.account_secrets.data)
  mount    = "test"
  # 将键中的`.`替换为`/`,符合Vault路径规范
  name     = replace(each.key, ".", "/")
  cas      = 1
  data_json = jsonencode({
    # 提取键的最后一部分作为data中的键名
    (element(reverse(split(".", each.key)), 0)) = each.value
  })

  custom_metadata {
    max_versions = 5 #var.max_versions != "" ? 5 : var.max_versions
  }
}

执行后,示例中的AAAAA.AAAAAAAAA会生成:

  • name为AAAAA/AAAAAAAAA
  • data_json为{"AAAAAAAAA": "SECRET"}

方案2:还原原始嵌套结构,按顶层节点创建Secret

将扁平的键值对还原为原始YAML的嵌套结构,每个顶层节点对应一个Vault Secret,存储整个子层级的数据:

locals {
  flat_secrets = nonsensitive(data.sops_file.account_secrets.data)

  # 递归函数:将扁平键值对还原为嵌套map
  build_nested_map = func(flat_map) {
    result = {}
    # 处理单层键
    for key, value in flat_map :
    key => value
    if length(split(".", key)) == 1

    # 处理多层键,递归合并到嵌套结构中
    for key, value in flat_map :
    let parts = split(".", key)
    let first_part = parts[0]
    let remaining_key = join(".", slice(parts, 1, length(parts))) :
    first_part => merge(
      try(result[first_part], {}),
      local.build_nested_map({ remaining_key = value })
    )
    if length(split(".", key)) > 1
    return result
  }

  # 生成嵌套结构的secret数据
  nested_secrets = local.build_nested_map(local.flat_secrets)
}

resource "vault_kv_secret_v2" "secrets" {
  for_each = local.nested_secrets
  mount    = "test"
  name     = each.key
  cas      = 1
  data_json = jsonencode(each.value)

  custom_metadata {
    max_versions = 5 #var.max_versions != "" ? 5 : var.max_versions
  }
}

执行后,示例中的AAAAA顶层节点会生成:

  • name为AAAAA
  • data_json为{"AAAAAAAAA": "SECRET", "BBBBBBBBB": "SECRET", "CCCCCCCCC": "SECRET", "DDDDDDDDD": "SECRET"}

关键说明

  • 方案1适合需要将每个敏感值独立存储、精细化权限控制的场景;
  • 方案2更贴合原始YAML的层级结构,适合按业务模块批量存储的场景;
  • replace函数的正确用法是replace(原字符串, 要替换的子串, 替换后的子串),之前的错误写法颠倒了参数顺序。

内容的提问来源于stack exchange,提问作者PsySkeletor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 10:15:03