Terraform for_each поверх содержимого файла yaml, который является объектом
У меня есть файл yaml, который похож на следующий (к вашему сведению: ssm_secrets может быть пустым массивом):
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
И используя внешние секреты и чертежи EKS, я пытаюсь создать файл yaml, необходимый для создания секретов.
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]
}
Вышеупомянутое работает нормально, но мне также нужно использовать значение ключа из yaml в secretKey: <key_value from yaml>.
Если я попробую с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]
}
Это просто дает мне следующую ошибку:
Данное значение аргумента «for_each» не подходит: «for_each» поддерживает карты и наборы строк, но вы предоставили набор, содержащий объект типа.
Я попытался преобразовать переменную в карту, использовал поиск, но это не сработало. Любая помощь приветствуется.
Обновление 1:
Согласно предложению @MattSchuchard, изменение for_each наfor_each = toset(flatten([for service in var.secrets : service.ssm_secrets]))
Выдал следующую ошибку:
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.
Обновление 2:
@mariux предложил идеальное решение, но вот что я придумал. Это не так уж и чище, но определенно работает (PS: сам собираюсь использовать решение Mariux):
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]
}
2 ответа
Предположения
На основании того, что вы поделились, я делаю следующие предположения:
- на самом деле эта услуга для вас не важна, так как вы хотите создать внешние секреты,
ssm_secrets.*.name
используя данныйkey
иssm_path
атрибуты. - каждый
name
является глобально уникальным для всех сервисов и никогда не используется повторно.
хаки терраформирования
Основываясь на предположениях, вы можете создать массив ВСЕХ 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"] 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"
}
}
}
подсказка: вы также можете попробовать использоватьkubernetes_manifest
ресурс вместоkubectl_manifest
PS: Мы создали Terramate , чтобы упростить сложное создание кода Terraform. Но для чистого Terraform это кажется вполне приемлемым.
Если вы изменитеfor_each
метапараметр для:
for_each = toset(flatten([for service in var.secrets : service.ssm_secrets]))
затем переменная итератора области лямбда/замыкания внутриkubernetes_manifest.secret
ресурс с именем по умолчаниюeach
будетlist(object)
тип, представляющий желаемые значения, аналогичный списку хешей в YAML (список карт в Kubernetes), и можно получить доступssm_path
сeach.value["ssm_path"]
, иkey
сeach.value["key"]
.