Root module with for_each and lookup() can't find values

Hi,

I have a root module that calls a child module:

Root module

# Root module - main.tf
locals {
  container_projects = {
    base-image-debian-11 : {
      description                     = "Basic Debian non-root container images."
      name                            = "Debian 11 Base Image"
      project_access_token            = {
        bob = {
          scopes = ["api"]
        }
      }
    }
    base-image-debian-11-slim : {
      description                     = "Basic Debian (slim) non-root container images."
      name                            = "Debian 11 Slim Base Image"
    }
  }
}

module "gitlab_projects" {
  source = "../../terraform/terraform-gitlab-project"
  for_each = local.container_projects

  path         = each.key
  name         = title(lookup(each.value, "name", ""))
  description  = lookup(each.value, "description", null)

  project_access_token = {
    for_each = lookup(each.value, "project_access_token", null) == null ? {} : lookup(each.value, "project_access_token", null)
    (each.key) = {
      scopes = each.value.scopes
    }
  }

  #project_access_token = [
  #  for_each = lookup(each.value, "project_access_token", null) != null ? { for token in lookup(each.value, "project_access_token", null) : token.name => token } : []
  #  {
  #    name     = each.key
  #    scopes   =  lookup(each.value, "scopes", null)
  #  }
  #]
}

Child module:

main.tf

# Module - main.tf
resource "gitlab_project_access_token" "main" {
  for_each = var.project_access_token != null ? var.project_access_token : {}
  #for_each = var.project_access_token != [] ? { for token in var.project_access_token : token.name => token } : []

  project = gitlab_project.main.id

  access_level = lookup(each.value, "access_level", null)
  expires_at   = lookup(each.value, "expires_at", null)
  name         = each.key
  scopes       = each.value["scopes"]
}

variables.tf

# Module - variables.tf
variable "project_access_token" {
  type = map(object({
    access_level = optional(string)
    expires_at   = optional(string)
    scopes       = set(string)
  }))
  description = "Manage the lifecycle of project access token."
  default     = null
}

As configured above, I get this error message:

 Error: Invalid value for input variable

   on projects.tf line 71, in module "gitlab_projects":
   71:   project_access_token = {
   72:     for_each = lookup(each.value, "project_access_token", null) == null ? {} : lookup(each.value, "project_access_token", null)
   73:     (each.key) = {
   74:       scopes = ["api"]
   75:     }
   76:   }

 The given value is not suitable for module.gitlab_projects["base-image-debian-11-slim"].var.project_access_token declared at
 ../../terraform/terraform-gitlab-project/variables.tf:517,1-32: element "for_each": attribute "scopes" is required.
        
 Error: Invalid value for input variable

   on projects.tf line 71, in module "gitlab_projects":
   71:   project_access_token = {
   72:     for_each = lookup(each.value, "project_access_token", null) == null ? {} : lookup(each.value, "project_access_token", null)
   73:     (each.key) = {
   74:       scopes = ["api"]
   75:     }
   76:   }

 The given value is not suitable for module.gitlab_projects["base-image-debian-11"].var.project_access_token declared at
 ../../terraform/terraform-gitlab-project/variables.tf:517,1-32: element "for_each": object required.

I have been staring at this problem for what seems like days and cant’ figure out what is going wrong.

  1. The lookup isn’t finding scopes
  2. The error for base-image-debian-11-slim should not even exist since there is no project_access_token for it.

I have tried map(object) and list(object) for project_access_token in variables.tf and in the local values.

I am also skeptical that (each.key) will work although it seems to be since terraform is trying to find scopes.

I am thoroughly confused at this point, I have tried to configure this in a few different ways and I am at a total loss as to how to make this work. What am I missing.

Thanks so much for any pointers you can provide.

Hi @tracphil,

I think the root problem here is that you are using for_each in a context where it isn’t valid: for_each is an argument for use in resource, data and module blocks to declare multiple instances of a resource or module, but your goal here seems to be just to construct a map value.

In Terraform we construct values using expressions, and in this case I think what you were trying for is a for expression which allows constructing one collection by projecting the contents of another one.

I’m not sure I follow fully what result you were intending to produce here but it seems like your each.value.project_access_token already has the same structure – an object type with a scopes attribute that is a sequence of strings – so it might be sufficient to just assign it directly like this:

  project_access_token = each.value.project_access_token

…but if you do need to transform it somehow then you could write a for expression based on that each.value.project_access_token value:

  project_access_token = {
    for k, t in each.value.project_access_token : k => {
      scopes = ["api"]
    }
  }

Thank you so much!

This is what I ended up doing:

  project_access_token = {
    for k, v in lookup(each.value, "project_access_token", {}) : k => {
      name   = k
      scopes = v.scopes
    }
  }

Thank you for all that you do! I lurk here a lot and your answers are always so helpful.