Nested for loop help on list of list of data

Can anybody help me ; im struggling to apply the split manifests as its a list of kubectl_file_documents[*].manifests … I somehow need to use a nested loop but am strugging to figure it out.

Thanks in advance

## complete example...

data "http" "this" {
  # for each url load it into response_body
  for_each = toset(["https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/24.0.5/kubernetes/keycloaks.k8s.keycloak.org-v1.yml",
                  "https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/24.0.5/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml"])
  url      = each.key
}

data "kubectl_file_documents" "this" {
  # handle multi resource yamls ( split into multiple objects )
  for_each = data.http.this
  content = each.value.response_body
}

resource "kubectl_manifest" "this" {
  # kubectl_manifest will only deploy one resource normally so if you have a file with multiple we need
  # to instead treat each resouce as a manifest and walk them one by 
  for_each = data.kubectl_file_documents.this[ "*" /* i know cannot use wild care here its just to show its a list clearly */ ].manifests 
  yaml_body          = each.value
}

This an example of the data data.kubectl_file_documents.this
Sorry its so long

x = [
  {
    "https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/24.0.5/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml" = {
      "content" = <<-EOT
      # Generated by Fabric8 CRDGenerator, manual edits might get overwritten!
      apiVersion: apiextensions.k8s.io/v1
      kind: CustomResourceDefinition
      metadata:
        name: keycloakrealmimports.k8s.keycloak.org
      EOT
      "documents" = tolist([
        <<-EOT
        # Generated by Fabric8 CRDGenerator, manual edits might get overwritten!
        apiVersion: apiextensions.k8s.io/v1
        kind: CustomResourceDefinition
        metadata:
          name: keycloakrealmimports.k8s.keycloak.org
        EOT,
      ])
      "id" = "c81d707e127bae22b058fd6193af1337a21014ab40a0d1583b9438c97d865ff7"
      "manifests" = tomap({
        "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/keycloakrealmimports.k8s.keycloak.org" = <<-EOT
        apiVersion: apiextensions.k8s.io/v1
        kind: CustomResourceDefinition
        metadata:
          name: keycloakrealmimports.k8s.keycloak.org
        
        EOT
      })
    }
    "https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/24.0.5/kubernetes/kubernetes.yml" = {
      "content" = <<-EOT
      ---
      apiVersion: v1
      kind: ServiceAccount
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRole
      ---
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRole    
      ---    
      apiVersion: apps/v1
      kind: Deployment
      EOT
      "documents" = tolist([
        <<-EOT
        ---
        apiVersion: v1
        kind: ServiceAccount
      
        EOT,
        <<-EOT
        apiVersion: rbac.authorization.k8s.io/v1
        kind: ClusterRole
      
  
        EOT
      })
    }
  },
]

Hi @stecullum,

Firstly - Thank you so much for providing a complete example!

Based upon the data structure of kubectl_file_documents.this (Output from terraform console)

> type(data.kubectl_file_documents.this)
object({
    https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/24.0.5/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml: object({
        content: string,
        documents: list(string),
        id: string,
        manifests: map(string),
    }),
    https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/24.0.5/kubernetes/keycloaks.k8s.keycloak.org-v1.yml: object({
        content: string,
        documents: list(string),
        id: string,
        manifests: map(string),
    }),
})

This should create a separate kubectl_manifest resource for each manifest in each file.

## proposed (complete) solution

data "http" "this" {
  # for each url load it into response_body
  for_each = toset(["https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/24.0.5/kubernetes/keycloaks.k8s.keycloak.org-v1.yml",
                  "https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/24.0.5/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml"])
  url      = each.key
}

data "kubectl_file_documents" "this" {
  # handle multi resource yamls ( split into multiple objects )
  for_each = data.http.this
  content = each.value.response_body
}

# Create a kubectl_manifest for each manifest in each file
# Used a local to help with debugging, but the merge(...) could just
# be used in the for_each

locals {
  manifests = merge([
    for file, data in data.kubectl_file_documents.this : {
      for manifest in data.manifests : "${file}-${manifest}" => { #create a map with composite key from file and manifest
        content = manifest
        id = "${file}-${manifest}"
      }
    }
  ]...)
}

resource "kubectl_manifest" "this" {
# Create a resource for each manifest in each file
  for_each = local.manifests
  yaml_body          = each.value.content
}

I’ve not been able to test it all the way through to an apply, but the plan output looks sensible (if a bit verbose with all of the YAML content!)

Hope that Helps

Happy Terraforming

Thanks …that makes sense.
Appreciate the effort you took