英文:
Terraform for_each over yaml file contents which is an object
问题
I have a yaml file which is similar to the following (FYI: ssm_secrets can be an empty array):
rabbitmq:
repo_name: bitnami
namespace: rabbitmq
target_revision: 11.1.1
path: rabbitmq
values_file: charts/rabbitmq/values.yaml
ssm_secrets: []
app_name_1:
repo_name: repo_name_1
namespace: namespace_1
target_revision: target_revision_1
path: charts/path
values_file: values.yaml
ssm_secrets:
- name: name-dev-1
key: .env
ssm_path: ssm_path/dev
name-backend:
repo_name: repo_name_2
namespace: namespace_2
target_revision: target_revision_2
path: charts/name-backend
values_file: values.yaml
ssm_secrets:
- name: name-backend-app-dev
ssm_path: name-backend/app/dev
key: app.ini
- name: name-backend-abi-dev
ssm_path: name-backend/abi/dev
key: contractTokenABI.json
- name: name-backend-widget-dev
ssm_path: name-backend/widget/dev
key: name.ini
- name: name-abi-dev
ssm_path: name-abi/dev
key: name_1.json
- name: name-website-dev
ssm_path: name/website/dev
key: website.ini
- name: name-name-dev
ssm_path: name/name/dev
key: contract.ini
- name: name-key-dev
ssm_path: name-key/dev
key: name.pub
And using External Secrets and EKS Blueprints, I am trying to generate the yaml file necessary to create the secrets:
resource "kubectl_manifest" "secret" {
for_each = toset(flatten([for service in var.secrets : service.ssm_secrets[*].ssm_path]))
yaml_body = <<YAML
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: ${replace(each.value, "/", "-")}
namespace: ${split("/", each.value)[0]}
spec:
refreshInterval: 30m
secretStoreRef:
name: ${local.cluster_secretstore_name}
kind: ClusterSecretStore
data:
- secretKey: .env
remoteRef:
key: ${each.value}
YAML
depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
}
The above works fine, but I also need to use the key value from the yaml into secretKey: <key_value from yaml>
.
If I try with for_each = toset(flatten([for service in var.secrets : service.ssm_secrets[*]]))
, it just gives me the following error:
The given "for_each" argument value is unsuitable: "for_each" supports
maps and sets of strings, but you have provided a set containing type
object.
I have tried converting the variable into a map, used lookup, but it doesn't work. Any help would be much appreciated.
Update 1:
As per @MattSchuchard suggestion, changing the for_each into for_each = toset(flatten([for service in var.secrets : service.ssm_secrets]))
gave the following error:
Error: Invalid for_each set argument
on ../../modules/02-plugins/external-secrets.tf line 58, in resource "kubectl_manifest" "secret":
58: for_each = toset(flatten([for service in var.secrets : service.ssm_secrets]))
The given "for_each" argument value is unsuitable: "for_each" supports maps and sets of strings, but you have provided a set containing type object.
Update 2:
@mariux gave the perfect solution, but here is what I came up with. It's not that cleaner, but definitely works (PS: I myself am going to use Mariux's solution):
locals {
my_list = tolist(flatten([for service in var.secrets : service.ssm_secrets[*]]))
}
resource "kubectl_manifest" "secret" {
count = length(local.my_list)
yaml_body = <<YAML
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: ${replace(local.my_list[count.index]["ssm_path"], "/", "-")}
namespace: ${split("/", local.my_list[count.index]["ssm_path"])[0]}
spec:
refreshInterval: 30m
secretStoreRef:
name: ${local.cluster_secretstore_name}
kind: ClusterSecretStore
data:
- secretKey: ${local.my_list[count.index]["key"]}
remoteRef:
key: ${local.my_list[count.index]["ssm_path"]}
YAML
depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
}
英文:
I have a yaml file which is similar to the following (FYI: ssm_secrets can be an empty array):
rabbitmq:
repo_name: bitnami
namespace: rabbitmq
target_revision: 11.1.1
path: rabbitmq
values_file: charts/rabbitmq/values.yaml
ssm_secrets: []
app_name_1:
repo_name: repo_name_1
namespace: namespace_1
target_revision: target_revision_1
path: charts/path
values_file: values.yaml
ssm_secrets:
- name: name-dev-1
key: .env
ssm_path: ssm_path/dev
name-backend:
repo_name: repo_name_2
namespace: namespace_2
target_revision: target_revision_2
path: charts/name-backend
values_file: values.yaml
ssm_secrets:
- name: name-backend-app-dev
ssm_path: name-backend/app/dev
key: app.ini
- name: name-backend-abi-dev
ssm_path: name-backend/abi/dev
key: contractTokenABI.json
- name: name-backend-widget-dev
ssm_path: name-backend/widget/dev
key: name.ini
- name: name-abi-dev
ssm_path: name-abi/dev
key: name_1.json
- name: name-website-dev
ssm_path: name/website/dev
key: website.ini
- name: name-name-dev
ssm_path: name/name/dev
key: contract.ini
- name: name-key-dev
ssm_path: name-key/dev
key: name.pub
And using External Secrets and EKS Blueprints, I am trying to generate the yaml file necessary to create the secrets
resource "kubectl_manifest" "secret" {
for_each = toset(flatten([for service in var.secrets : service.ssm_secrets[*].ssm_path]))
yaml_body = <<YAML
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: ${replace(each.value, "/", "-")}
namespace: ${split("/", each.value)[0]}
spec:
refreshInterval: 30m
secretStoreRef:
name: ${local.cluster_secretstore_name}
kind: ClusterSecretStore
data:
- secretKey: .env
remoteRef:
key: ${each.value}
YAML
depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
}
The above works fine, but I also need to use the key value from the yaml into secretKey: <key_value from yaml>.
If I try with for_each = toset(flatten([for service in var.secrets : service.ssm_secrets[*]]))
resource "kubectl_manifest" "secret" {
for_each = toset(flatten([for service in var.secrets : service.ssm_secrets[*]]))
yaml_body = <<YAML
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: ${replace(each.value["ssm_path"], "/", "-")}
namespace: ${split("/", each.value["ssm_path"])[0]}
spec:
refreshInterval: 30m
secretStoreRef:
name: ${local.cluster_secretstore_name}
kind: ClusterSecretStore
data:
- secretKey: .env
remoteRef:
key: ${each.value["ssm_path"]}
YAML
depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
}
It just gives me the following error:
> The given "for_each" argument value is unsuitable: "for_each" supports
> maps and sets of strings, but you have provided a set containing type
> object.
I have tried converting the variable into a map, used lookup, but it doesn't work.
Any help would be much appreciated.
Update 1:
As per @MattSchuchard suggestion, changing the for_each into
for_each = toset(flatten([for service in var.secrets : service.ssm_secrets]))
Gave the following error:
Error: Invalid for_each set argument
│
│ on ../../modules/02-plugins/external-secrets.tf line 58, in resource "kubectl_manifest" "secret":
│ 58: for_each = toset(flatten([for service in var.secrets : service.ssm_secrets]))
│ ├────────────────
│ │ var.secrets is object with 14 attributes
│
│ The given "for_each" argument value is unsuitable: "for_each" supports maps and sets of strings, but you have provided a set containing type object.
Update 2:
@mariux gave the perfect solution, but here is what I came up with. It's not that cleaner, but definitely works (PS: I myself am going to use Mariux's solution):
locals {
my_list = tolist(flatten([for service in var.secrets : service.ssm_secrets[*]]))
}
resource "kubectl_manifest" "secret" {
count = length(local.my_list)
yaml_body = <<YAML
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: ${replace(local.my_list[count.index]["ssm_path"], "/", "-")}
namespace: ${split("/", local.my_list[count.index]["ssm_path"])[0]}
spec:
refreshInterval: 30m
secretStoreRef:
name: ${local.cluster_secretstore_name}
kind: ClusterSecretStore
data:
- secretKey: ${local.my_list[count.index]["key"]}
remoteRef:
key: ${local.my_list[count.index]["ssm_path"]}
YAML
depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
}
答案1
得分: 3
假设
根据您分享的信息,我做出以下假设:
- 您并不真正关心该服务,因为您想使用给定的
key
和ssm_path
属性创建外部密钥ssm_secrets.*.name
。 - 每个
name
对于所有服务来说都是全局唯一的,不会重复使用。
Terraform 技巧
根据这些假设,您可以创建所有 ssm_secrets
的数组,使用以下代码:
locals {
ssm_secrets_all = flatten(values(var.secrets)[*].ssm_secrets)
}
并将其转换为可以在 for_each
中使用的映射,通过按 .name
键入值:
locals {
ssm_secrets_map = { for v in local.ssm_secrets_all : v.name => v }
}
完整(工作)示例
以下示例对我来说有效,并做出了一些变量应该使用的假设。
- 使用
yamldecode
将原始输入解码为local.input
- 使用
yamlencode
使读取清单更容易,并删除了一些字符串插值。这还确保缩进正确,因为我们将 HCL 转换为 yaml。
terraform init && terraform plan
将计划创建以下资源:
kubectl_manifest.secret["name-abi-dev"] 将被创建
kubectl_manifest.secret["name-backend-abi-dev"] 将被创建
kubectl_manifest.secret["name-backend-app-dev"] 将被创建
kubectl_manifest.secret["name-backend-widget-dev"] 将被创建
kubectl_manifest.secret["name-dev-1"] 将被创建
kubectl_manifest.secret["name-key-dev"] 将被创建
kubectl_manifest.secret["name-name-dev"] 将被创建
kubectl_manifest.secret["name-website-dev"] 将被创建
locals {
# input = var.secrets
ssm_secrets_all = flatten(values(local.input)[*].ssm_secrets)
ssm_secrets_map = { for v in local.ssm_secrets_all : v.name => v }
cluster_secretstore_name = "not provided secretstore name"
}
resource "kubectl_manifest" "secret" {
for_each = local.ssm_secrets_map
yaml_body = yamlencode({
apiVersion = "external-secrets.io/v1beta1"
kind = "ExternalSecret"
metadata = {
name = replace(each.value.ssm_path, "/", "-")
namespace = split("/", each.value.ssm_path)[0]
}
spec = {
refreshInterval = "30m"
secretStoreRef = {
name = local.cluster_secretstore_name
kind = "ClusterSecretStore"
}
data = [
{
secretKey = ".env"
remoteRef = {
key = each.value.key
}
}
]
}
})
# not included dependencies
# depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
}
locals {
input = yamldecode(<<-EOF
rabbitmq:
repo_name: bitnami
namespace: rabbitmq
target_revision: 11.1.1
path: rabbitmq
values_file: charts/rabbitmq/values.yaml
ssm_secrets: []
app_name_1:
repo_name: repo_name_1
namespace: namespace_1
target_revision: target_revision_1
path: charts/path
values_file: values.yaml
ssm_secrets:
- name: name-dev-1
key: .env
ssm_path: ssm_path/dev
name-backend:
repo_name: repo_name_2
namespace: namespace_2
target_revision: target_revision_2
path: charts/name-backend
values_file: values.yaml
ssm_secrets:
- name: name-backend-app-dev
ssm_path: name-backend/app/dev
key: app.ini
- name: name-backend-abi-dev
ssm_path: name-backend/abi/dev
key: contractTokenABI.json
- name: name-backend-widget-dev
ssm_path: name-backend/widget/dev
key: name.ini
- name: name-abi-dev
ssm_path: name-abi/dev
key: name_1.json
- name: name-website-dev
ssm_path: name/website/dev
key: website.ini
- name: name-name-dev
ssm_path: name/name/dev
key: contract.ini
- name: name-key-dev
ssm_path: name-key/dev
key: name.pub
EOF
)
}
terraform {
required_version = "~> 1.0"
required_providers {
kubectl = {
source = "gavinbunney/kubectl"
version = "~> 1.7"
}
}
}
提示:您也可以尝试使用 kubernetes_manifest
资源 而不是 kubectl_manifest
。
附注:我们创建了 Terramate 来使复杂的 Terraform 代码创建变得更容易。但这对于纯粹的 Terraform 来说似乎已经足够了。
英文:
Assumptions
Based on what you shared, i make the following assumptions:
- the service is not actually important for you as you want to create external secrets by
ssm_secrets.*.name
using the givenkey
andssm_path
attributes. - each
name
is globally unique for all services and never reused.
terraform hacks
Based on the assumptions you can create an array of ALL ssm_secrets using
locals {
ssm_secrets_all = flatten(values(var.secrets)[*].ssm_secrets)
}
and convert it to a map that can be used in for_each
by keying the values by .name
:
locals {
ssm_secrets_map = { for v in local.ssm_secrets_all : v.name => v }
}
Full (working) example
The example below works for me and makes some assumption where the variables should be used.
- Using
yamldecode
to decode your original input intolocal.input
- Using
yamlencode
to make reading the manifest easier and removing some string interpolcations. This also ensures that the indent is correct as we convert HCL to yaml.
A terraform init && terraform plan
will plan to create the following resources:
kubectl_manifest.secret["name-abi-dev"] will be created
kubectl_manifest.secret["name-backend-abi-dev"] will be created
kubectl_manifest.secret["name-backend-app-dev"] will be created
kubectl_manifest.secret["name-backend-widget-dev"] will be created
kubectl_manifest.secret["name-dev-1"] will be created
kubectl_manifest.secret["name-key-dev"] will be created
kubectl_manifest.secret["name-name-dev"] will be created
kubectl_manifest.secret["name-website-dev"] will be created
locals {
# input = var.secrets
ssm_secrets_all = flatten(values(local.input)[*].ssm_secrets)
ssm_secrets_map = { for v in local.ssm_secrets_all : v.name => v }
cluster_secretstore_name = "not provided secretstore name"
}
resource "kubectl_manifest" "secret" {
for_each = local.ssm_secrets_map
yaml_body = yamlencode({
apiVersion = "external-secrets.io/v1beta1"
kind = "ExternalSecret"
metadata = {
name = replace(each.value.ssm_path, "/", "-")
namespace = split("/", each.value.ssm_path)[0]
}
spec = {
refreshInterval = "30m"
secretStoreRef = {
name = local.cluster_secretstore_name
kind = "ClusterSecretStore"
}
data = [
{
secretKey = ".env"
remoteRef = {
key = each.value.key
}
}
]
}
})
# not included dependencies
# depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
}
locals {
input = yamldecode(<<-EOF
rabbitmq:
repo_name: bitnami
namespace: rabbitmq
target_revision: 11.1.1
path: rabbitmq
values_file: charts/rabbitmq/values.yaml
ssm_secrets: []
app_name_1:
repo_name: repo_name_1
namespace: namespace_1
target_revision: target_revision_1
path: charts/path
values_file: values.yaml
ssm_secrets:
- name: name-dev-1
key: .env
ssm_path: ssm_path/dev
name-backend:
repo_name: repo_name_2
namespace: namespace_2
target_revision: target_revision_2
path: charts/name-backend
values_file: values.yaml
ssm_secrets:
- name: name-backend-app-dev
ssm_path: name-backend/app/dev
key: app.ini
- name: name-backend-abi-dev
ssm_path: name-backend/abi/dev
key: contractTokenABI.json
- name: name-backend-widget-dev
ssm_path: name-backend/widget/dev
key: name.ini
- name: name-abi-dev
ssm_path: name-abi/dev
key: name_1.json
- name: name-website-dev
ssm_path: name/website/dev
key: website.ini
- name: name-name-dev
ssm_path: name/name/dev
key: contract.ini
- name: name-key-dev
ssm_path: name-key/dev
key: name.pub
EOF
)
}
terraform {
required_version = "~> 1.0"
required_providers {
kubectl = {
source = "gavinbunney/kubectl"
version = "~> 1.7"
}
}
}
hint: you could also try to use the kubernetes_manifest
resource instead of kubectl_manifest
p.s.: We created Terramate to make complex creation of Terraform code easier. But this seems perfectly fine for pure Terraform.
答案2
得分: 1
如果您修改 for_each
元参数为:
for_each = toset(flatten([for service in var.secrets : service.ssm_secrets]))
那么在名为 each
的默认名称下,kubernetes_manifest.secret
资源内的 lambda/closure 作用域迭代变量将是 list(object)
类型,表示与 YAML 中的哈希列表类似的所需值(Kubernetes 中的映射列表),可以使用 each.value["ssm_path"]
访问 ssm_path
,使用 each.value["key"]
访问 key
。
英文:
If you modify the for_each
meta-parameter to:
for_each = toset(flatten([for service in var.secrets : service.ssm_secrets]))
then the lambda/closure scope iterator variable within the kubernetes_manifest.secret
resource with default name each
will be a list(object)
type representing the desired values analogous to the list of hashes in the YAML (list of maps within Kubernetes), and one can access ssm_path
with each.value["ssm_path"]
, and key
with each.value["key"]
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论