Terraform 的 for_each 遍历 YAML 文件内容,该文件是一个对象。

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

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

  1. rabbitmq:
  2. repo_name: bitnami
  3. namespace: rabbitmq
  4. target_revision: 11.1.1
  5. path: rabbitmq
  6. values_file: charts/rabbitmq/values.yaml
  7. ssm_secrets: []
  8. app_name_1:
  9. repo_name: repo_name_1
  10. namespace: namespace_1
  11. target_revision: target_revision_1
  12. path: charts/path
  13. values_file: values.yaml
  14. ssm_secrets:
  15. - name: name-dev-1
  16. key: .env
  17. ssm_path: ssm_path/dev
  18. name-backend:
  19. repo_name: repo_name_2
  20. namespace: namespace_2
  21. target_revision: target_revision_2
  22. path: charts/name-backend
  23. values_file: values.yaml
  24. ssm_secrets:
  25. - name: name-backend-app-dev
  26. ssm_path: name-backend/app/dev
  27. key: app.ini
  28. - name: name-backend-abi-dev
  29. ssm_path: name-backend/abi/dev
  30. key: contractTokenABI.json
  31. - name: name-backend-widget-dev
  32. ssm_path: name-backend/widget/dev
  33. key: name.ini
  34. - name: name-abi-dev
  35. ssm_path: name-abi/dev
  36. key: name_1.json
  37. - name: name-website-dev
  38. ssm_path: name/website/dev
  39. key: website.ini
  40. - name: name-name-dev
  41. ssm_path: name/name/dev
  42. key: contract.ini
  43. - name: name-key-dev
  44. ssm_path: name-key/dev
  45. key: name.pub

And using External Secrets and EKS Blueprints, I am trying to generate the yaml file necessary to create the secrets:

  1. resource "kubectl_manifest" "secret" {
  2. for_each = toset(flatten([for service in var.secrets : service.ssm_secrets[*].ssm_path]))
  3. yaml_body = <<YAML
  4. apiVersion: external-secrets.io/v1beta1
  5. kind: ExternalSecret
  6. metadata:
  7. name: ${replace(each.value, "/", "-")}
  8. namespace: ${split("/", each.value)[0]}
  9. spec:
  10. refreshInterval: 30m
  11. secretStoreRef:
  12. name: ${local.cluster_secretstore_name}
  13. kind: ClusterSecretStore
  14. data:
  15. - secretKey: .env
  16. remoteRef:
  17. key: ${each.value}
  18. YAML
  19. depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
  20. }

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:

  1. The given "for_each" argument value is unsuitable: "for_each" supports
  2. maps and sets of strings, but you have provided a set containing type
  3. 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:

  1. Error: Invalid for_each set argument
  2. on ../../modules/02-plugins/external-secrets.tf line 58, in resource "kubectl_manifest" "secret":
  3. 58: for_each = toset(flatten([for service in var.secrets : service.ssm_secrets]))
  4. 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):

  1. locals {
  2. my_list = tolist(flatten([for service in var.secrets : service.ssm_secrets[*]]))
  3. }
  4. resource "kubectl_manifest" "secret" {
  5. count = length(local.my_list)
  6. yaml_body = <<YAML
  7. apiVersion: external-secrets.io/v1beta1
  8. kind: ExternalSecret
  9. metadata:
  10. name: ${replace(local.my_list[count.index]["ssm_path"], "/", "-")}
  11. namespace: ${split("/", local.my_list[count.index]["ssm_path"])[0]}
  12. spec:
  13. refreshInterval: 30m
  14. secretStoreRef:
  15. name: ${local.cluster_secretstore_name}
  16. kind: ClusterSecretStore
  17. data:
  18. - secretKey: ${local.my_list[count.index]["key"]}
  19. remoteRef:
  20. key: ${local.my_list[count.index]["ssm_path"]}
  21. YAML
  22. depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
  23. }
英文:

I have a yaml file which is similar to the following (FYI: ssm_secrets can be an empty array):

  1. rabbitmq:
  2. repo_name: bitnami
  3. namespace: rabbitmq
  4. target_revision: 11.1.1
  5. path: rabbitmq
  6. values_file: charts/rabbitmq/values.yaml
  7. ssm_secrets: []
  8. app_name_1:
  9. repo_name: repo_name_1
  10. namespace: namespace_1
  11. target_revision: target_revision_1
  12. path: charts/path
  13. values_file: values.yaml
  14. ssm_secrets:
  15. - name: name-dev-1
  16. key: .env
  17. ssm_path: ssm_path/dev
  18. name-backend:
  19. repo_name: repo_name_2
  20. namespace: namespace_2
  21. target_revision: target_revision_2
  22. path: charts/name-backend
  23. values_file: values.yaml
  24. ssm_secrets:
  25. - name: name-backend-app-dev
  26. ssm_path: name-backend/app/dev
  27. key: app.ini
  28. - name: name-backend-abi-dev
  29. ssm_path: name-backend/abi/dev
  30. key: contractTokenABI.json
  31. - name: name-backend-widget-dev
  32. ssm_path: name-backend/widget/dev
  33. key: name.ini
  34. - name: name-abi-dev
  35. ssm_path: name-abi/dev
  36. key: name_1.json
  37. - name: name-website-dev
  38. ssm_path: name/website/dev
  39. key: website.ini
  40. - name: name-name-dev
  41. ssm_path: name/name/dev
  42. key: contract.ini
  43. - name: name-key-dev
  44. ssm_path: name-key/dev
  45. key: name.pub

And using External Secrets and EKS Blueprints, I am trying to generate the yaml file necessary to create the secrets

  1. resource &quot;kubectl_manifest&quot; &quot;secret&quot; {
  2. for_each = toset(flatten([for service in var.secrets : service.ssm_secrets[*].ssm_path]))
  3. yaml_body = &lt;&lt;YAML
  4. apiVersion: external-secrets.io/v1beta1
  5. kind: ExternalSecret
  6. metadata:
  7. name: ${replace(each.value, &quot;/&quot;, &quot;-&quot;)}
  8. namespace: ${split(&quot;/&quot;, each.value)[0]}
  9. spec:
  10. refreshInterval: 30m
  11. secretStoreRef:
  12. name: ${local.cluster_secretstore_name}
  13. kind: ClusterSecretStore
  14. data:
  15. - secretKey: .env
  16. remoteRef:
  17. key: ${each.value}
  18. YAML
  19. depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
  20. }

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[*]]))

  1. resource &quot;kubectl_manifest&quot; &quot;secret&quot; {
  2. for_each = toset(flatten([for service in var.secrets : service.ssm_secrets[*]]))
  3. yaml_body = &lt;&lt;YAML
  4. apiVersion: external-secrets.io/v1beta1
  5. kind: ExternalSecret
  6. metadata:
  7. name: ${replace(each.value[&quot;ssm_path&quot;], &quot;/&quot;, &quot;-&quot;)}
  8. namespace: ${split(&quot;/&quot;, each.value[&quot;ssm_path&quot;])[0]}
  9. spec:
  10. refreshInterval: 30m
  11. secretStoreRef:
  12. name: ${local.cluster_secretstore_name}
  13. kind: ClusterSecretStore
  14. data:
  15. - secretKey: .env
  16. remoteRef:
  17. key: ${each.value[&quot;ssm_path&quot;]}
  18. YAML
  19. depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
  20. }

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:

  1. Error: Invalid for_each set argument
  2. on ../../modules/02-plugins/external-secrets.tf line 58, in resource &quot;kubectl_manifest&quot; &quot;secret&quot;:
  3. 58: for_each = toset(flatten([for service in var.secrets : service.ssm_secrets]))
  4. ├────────────────
  5. var.secrets is object with 14 attributes
  6. The given &quot;for_each&quot; argument value is unsuitable: &quot;for_each&quot; 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):

  1. locals {
  2. my_list = tolist(flatten([for service in var.secrets : service.ssm_secrets[*]]))
  3. }
  4. resource &quot;kubectl_manifest&quot; &quot;secret&quot; {
  5. count = length(local.my_list)
  6. yaml_body = &lt;&lt;YAML
  7. apiVersion: external-secrets.io/v1beta1
  8. kind: ExternalSecret
  9. metadata:
  10. name: ${replace(local.my_list[count.index][&quot;ssm_path&quot;], &quot;/&quot;, &quot;-&quot;)}
  11. namespace: ${split(&quot;/&quot;, local.my_list[count.index][&quot;ssm_path&quot;])[0]}
  12. spec:
  13. refreshInterval: 30m
  14. secretStoreRef:
  15. name: ${local.cluster_secretstore_name}
  16. kind: ClusterSecretStore
  17. data:
  18. - secretKey: ${local.my_list[count.index][&quot;key&quot;]}
  19. remoteRef:
  20. key: ${local.my_list[count.index][&quot;ssm_path&quot;]}
  21. YAML
  22. depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
  23. }

答案1

得分: 3

假设

根据您分享的信息,我做出以下假设:

  • 您并不真正关心该服务,因为您想使用给定的 keyssm_path 属性创建外部密钥 ssm_secrets.*.name
  • 每个 name 对于所有服务来说都是全局唯一的,不会重复使用。

Terraform 技巧

根据这些假设,您可以创建所有 ssm_secrets 的数组,使用以下代码:

  1. locals {
  2. ssm_secrets_all = flatten(values(var.secrets)[*].ssm_secrets)
  3. }

并将其转换为可以在 for_each 中使用的映射,通过按 .name 键入值:

  1. locals {
  2. ssm_secrets_map = { for v in local.ssm_secrets_all : v.name => v }
  3. }

完整(工作)示例

以下示例对我来说有效,并做出了一些变量应该使用的假设。

  • 使用 yamldecode 将原始输入解码为 local.input
  • 使用 yamlencode 使读取清单更容易,并删除了一些字符串插值。这还确保缩进正确,因为我们将 HCL 转换为 yaml。

terraform init && terraform plan 将计划创建以下资源:

  1. kubectl_manifest.secret["name-abi-dev"] 将被创建
  2. kubectl_manifest.secret["name-backend-abi-dev"] 将被创建
  3. kubectl_manifest.secret["name-backend-app-dev"] 将被创建
  4. kubectl_manifest.secret["name-backend-widget-dev"] 将被创建
  5. kubectl_manifest.secret["name-dev-1"] 将被创建
  6. kubectl_manifest.secret["name-key-dev"] 将被创建
  7. kubectl_manifest.secret["name-name-dev"] 将被创建
  8. kubectl_manifest.secret["name-website-dev"] 将被创建
  1. locals {
  2. # input = var.secrets
  3. ssm_secrets_all = flatten(values(local.input)[*].ssm_secrets)
  4. ssm_secrets_map = { for v in local.ssm_secrets_all : v.name => v }
  5. cluster_secretstore_name = "not provided secretstore name"
  6. }
  7. resource "kubectl_manifest" "secret" {
  8. for_each = local.ssm_secrets_map
  9. yaml_body = yamlencode({
  10. apiVersion = "external-secrets.io/v1beta1"
  11. kind = "ExternalSecret"
  12. metadata = {
  13. name = replace(each.value.ssm_path, "/", "-")
  14. namespace = split("/", each.value.ssm_path)[0]
  15. }
  16. spec = {
  17. refreshInterval = "30m"
  18. secretStoreRef = {
  19. name = local.cluster_secretstore_name
  20. kind = "ClusterSecretStore"
  21. }
  22. data = [
  23. {
  24. secretKey = ".env"
  25. remoteRef = {
  26. key = each.value.key
  27. }
  28. }
  29. ]
  30. }
  31. })
  32. # not included dependencies
  33. # depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
  34. }
  35. locals {
  36. input = yamldecode(<<-EOF
  37. rabbitmq:
  38. repo_name: bitnami
  39. namespace: rabbitmq
  40. target_revision: 11.1.1
  41. path: rabbitmq
  42. values_file: charts/rabbitmq/values.yaml
  43. ssm_secrets: []
  44. app_name_1:
  45. repo_name: repo_name_1
  46. namespace: namespace_1
  47. target_revision: target_revision_1
  48. path: charts/path
  49. values_file: values.yaml
  50. ssm_secrets:
  51. - name: name-dev-1
  52. key: .env
  53. ssm_path: ssm_path/dev
  54. name-backend:
  55. repo_name: repo_name_2
  56. namespace: namespace_2
  57. target_revision: target_revision_2
  58. path: charts/name-backend
  59. values_file: values.yaml
  60. ssm_secrets:
  61. - name: name-backend-app-dev
  62. ssm_path: name-backend/app/dev
  63. key: app.ini
  64. - name: name-backend-abi-dev
  65. ssm_path: name-backend/abi/dev
  66. key: contractTokenABI.json
  67. - name: name-backend-widget-dev
  68. ssm_path: name-backend/widget/dev
  69. key: name.ini
  70. - name: name-abi-dev
  71. ssm_path: name-abi/dev
  72. key: name_1.json
  73. - name: name-website-dev
  74. ssm_path: name/website/dev
  75. key: website.ini
  76. - name: name-name-dev
  77. ssm_path: name/name/dev
  78. key: contract.ini
  79. - name: name-key-dev
  80. ssm_path: name-key/dev
  81. key: name.pub
  82. EOF
  83. )
  84. }
  85. terraform {
  86. required_version = "~> 1.0"
  87. required_providers {
  88. kubectl = {
  89. source = "gavinbunney/kubectl"
  90. version = "~> 1.7"
  91. }
  92. }
  93. }

提示:您也可以尝试使用 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 given key and ssm_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

  1. locals {
  2. ssm_secrets_all = flatten(values(var.secrets)[*].ssm_secrets)
  3. }

and convert it to a map that can be used in for_each by keying the values by .name:

  1. locals {
  2. ssm_secrets_map = { for v in local.ssm_secrets_all : v.name =&gt; v }
  3. }

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 into local.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 &amp;&amp; terraform plan will plan to create the following resources:

  1. kubectl_manifest.secret[&quot;name-abi-dev&quot;] will be created
  2. kubectl_manifest.secret[&quot;name-backend-abi-dev&quot;] will be created
  3. kubectl_manifest.secret[&quot;name-backend-app-dev&quot;] will be created
  4. kubectl_manifest.secret[&quot;name-backend-widget-dev&quot;] will be created
  5. kubectl_manifest.secret[&quot;name-dev-1&quot;] will be created
  6. kubectl_manifest.secret[&quot;name-key-dev&quot;] will be created
  7. kubectl_manifest.secret[&quot;name-name-dev&quot;] will be created
  8. kubectl_manifest.secret[&quot;name-website-dev&quot;] will be created
  1. locals {
  2. # input = var.secrets
  3. ssm_secrets_all = flatten(values(local.input)[*].ssm_secrets)
  4. ssm_secrets_map = { for v in local.ssm_secrets_all : v.name =&gt; v }
  5. cluster_secretstore_name = &quot;not provided secretstore name&quot;
  6. }
  7. resource &quot;kubectl_manifest&quot; &quot;secret&quot; {
  8. for_each = local.ssm_secrets_map
  9. yaml_body = yamlencode({
  10. apiVersion = &quot;external-secrets.io/v1beta1&quot;
  11. kind = &quot;ExternalSecret&quot;
  12. metadata = {
  13. name = replace(each.value.ssm_path, &quot;/&quot;, &quot;-&quot;)
  14. namespace = split(&quot;/&quot;, each.value.ssm_path)[0]
  15. }
  16. spec = {
  17. refreshInterval = &quot;30m&quot;
  18. secretStoreRef = {
  19. name = local.cluster_secretstore_name
  20. kind = &quot;ClusterSecretStore&quot;
  21. }
  22. data = [
  23. {
  24. secretKey = &quot;.env&quot;
  25. remoteRef = {
  26. key = each.value.key
  27. }
  28. }
  29. ]
  30. }
  31. })
  32. # not included dependencies
  33. # depends_on = [kubectl_manifest.cluster_secretstore, kubernetes_namespace_v1.namespaces]
  34. }
  35. locals {
  36. input = yamldecode(&lt;&lt;-EOF
  37. rabbitmq:
  38. repo_name: bitnami
  39. namespace: rabbitmq
  40. target_revision: 11.1.1
  41. path: rabbitmq
  42. values_file: charts/rabbitmq/values.yaml
  43. ssm_secrets: []
  44. app_name_1:
  45. repo_name: repo_name_1
  46. namespace: namespace_1
  47. target_revision: target_revision_1
  48. path: charts/path
  49. values_file: values.yaml
  50. ssm_secrets:
  51. - name: name-dev-1
  52. key: .env
  53. ssm_path: ssm_path/dev
  54. name-backend:
  55. repo_name: repo_name_2
  56. namespace: namespace_2
  57. target_revision: target_revision_2
  58. path: charts/name-backend
  59. values_file: values.yaml
  60. ssm_secrets:
  61. - name: name-backend-app-dev
  62. ssm_path: name-backend/app/dev
  63. key: app.ini
  64. - name: name-backend-abi-dev
  65. ssm_path: name-backend/abi/dev
  66. key: contractTokenABI.json
  67. - name: name-backend-widget-dev
  68. ssm_path: name-backend/widget/dev
  69. key: name.ini
  70. - name: name-abi-dev
  71. ssm_path: name-abi/dev
  72. key: name_1.json
  73. - name: name-website-dev
  74. ssm_path: name/website/dev
  75. key: website.ini
  76. - name: name-name-dev
  77. ssm_path: name/name/dev
  78. key: contract.ini
  79. - name: name-key-dev
  80. ssm_path: name-key/dev
  81. key: name.pub
  82. EOF
  83. )
  84. }
  85. terraform {
  86. required_version = &quot;~&gt; 1.0&quot;
  87. required_providers {
  88. kubectl = {
  89. source = &quot;gavinbunney/kubectl&quot;
  90. version = &quot;~&gt; 1.7&quot;
  91. }
  92. }
  93. }

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 元参数为:

  1. 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:

  1. 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[&quot;ssm_path&quot;], and key with each.value[&quot;key&quot;].

huangapple
  • 本文由 发表于 2023年6月2日 02:19:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/76384672.html
匿名

发表评论

匿名网友

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

确定