terraform-google-service-accounts icon indicating copy to clipboard operation
terraform-google-service-accounts copied to clipboard

Error: Invalid for_each argument

Open floflock opened this issue 3 years ago • 13 comments

If you want to create a single service account with the module, a error appears about a for_each:

module "gke_service_account" {
  source      = "terraform-google-modules/service-accounts/google"
  version     = "~> 4.0"
  project_id  = var.project_id
  description = "Service Account for Cluster xyz (managed by Terraform)"

  names  = ["test"]

  project_roles = [
    "${var.project_id}=>roles/monitoring.viewer",
    "${var.project_id}=>roles/monitoring.metricWriter",
    "${var.project_id}=>roles/logging.logWriter",
    "${var.project_id}=>roles/stackdriver.resourceMetadata.writer",
    "${var.project_id}=>roles/artifactregistry.reader",
  ]
}

The error output is:

╷
│ Error: Invalid for_each argument
│ 
│   on .terraform/modules/gke_service_account/main.tf line 38, in resource "google_service_account" "service_accounts":
│   38:   for_each     = local.names
│     ├────────────────
│     │ local.names is set of string with 1 element
│ 
│ The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work
│ around this, use the -target argument to first apply only the resources that the for_each depends on.
╵

I think it worked with v0.15.x but I am not sure.

My Terraform Version is now:

➜  ~ tf -version
Terraform v1.0.0
on darwin_amd64

floflock avatar Jun 21 '21 21:06 floflock

what was the fix here?

jmymy avatar Jul 06 '21 09:07 jmymy

Unfortunately, I have to reopen the issue. This error appears again on following version:

➜  cloud git:(feature/BB-864-production-infrastructure) ✗ terraform version                         
Terraform v1.0.4
on darwin_amd64
+ provider registry.terraform.io/hashicorp/external v2.1.0
+ provider registry.terraform.io/hashicorp/google v3.78.0
+ provider registry.terraform.io/hashicorp/google-beta v3.79.0
+ provider registry.terraform.io/hashicorp/kubernetes v2.4.1
+ provider registry.terraform.io/hashicorp/null v3.1.0
+ provider registry.terraform.io/hashicorp/random v3.1.0
+ provider registry.terraform.io/hashicorp/time v0.7.2

Just to summarize, this is the issue thrown in the console:

╷
│ Error: Invalid for_each argument
│ 
│   on .terraform/modules/gke_service_account/main.tf line 38, in resource "google_service_account" "service_accounts":
│   38:   for_each     = local.names
│     ├────────────────
│     │ local.names is set of string with 2 elements
│ 
│ The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends
│ on.
╵

The module source code is:

module "gke_service_account" {
  source      = "terraform-google-modules/service-accounts/google"
  version     = "~> 4.0"
  project_id  = var.project_prefix
  description = "Service Account for Cluster ${var.cluster_name}-${var.environment} (managed by Terraform)"

  prefix = "gke"

  names = [
    "${var.cluster_name}-${var.environment}-${random_id.gke_suffix.hex}"]

  project_roles = [
    "${var.project_prefix}=>roles/monitoring.viewer",
    "${var.project_prefix}=>roles/monitoring.metricWriter",
    "${var.project_prefix}=>roles/logging.logWriter",
    "${var.project_prefix}=>roles/stackdriver.resourceMetadata.writer",
    "${var.project_prefix}=>roles/artifactregistry.reader",
  ]
}

The module cannot handle creating a single service account since "name" list has only one element...? @jmymy were you able to fix that somehow?

floflock avatar Aug 10 '21 06:08 floflock

@floflock The problem is not with the length of your list. It's that you are using random_id.gke_suffix.hex in the names of the Service Accounts. Terraform does not allow you to use dynamically-computed values in the key of for_each resources.

You could instead inject the random ID as a prefix using the existing prefix variable. Alternatively, I'd be happy to review a PR adding a suffix variable as well.

As a workaround, you can also apply your random ID resource terraform apply -target=random_id.gke_suffix separately before applying your full Terraform config.

morgante avatar Aug 10 '21 06:08 morgante

@morgante, thanks for the quick response! Will give a try and will also try to fork in order to prepare the PR.

floflock avatar Aug 10 '21 06:08 floflock

So I had a similar error and am looking for a way to workaround this limitation.

I am using the terraform-google-modules/project-factory/google module to create a VPC and then create an additional service account at the same time.

module "project" {
  source                      = "terraform-google-modules/project-factory/google"
  version                     = "11.1.1"
  name                        = lower(var.project_name)
  random_project_id           = true
  org_id                      = var.org_id
  folder_id                   = var.folder_id
  billing_account             = var.billing_account
  activate_apis               = concat(var.activate_apis, var.additional_apis)
  default_service_account     = "disable"
  disable_services_on_destroy = false
}

module "terraform_cloud_sa" {
  source        = "terraform-google-modules/service-accounts/google"
  version       = "4.0.2"
  project_id    = module.project.project_id
  prefix        = "tfc"
  names         = ["generated-sa"]
  project_roles = ["${module.project.project_id}=>roles/owner", "${module.project.project_id}=>roles/serviceusage.serviceUsageAdmin"]
  generate_keys = true
}

I can confirm it is the random_project_id var in the project module as it appends a 2-byte hex to the end of the project name to get the project_id.

I really like this naming/ID feature and would like to keep it. I am also trying to limit the number of things I have to rename every time I want a copy of these 2 blocks. For example, these 2 blocks are part of a standard "module" we use when spinning up new VPCs. Everything used to work fine, and now on version 1.0+ it seems we hit this issue.

I can manually make up a 4 digit ending for the project_id and provide that but seems pointless if I could just use a random_id automatically.

Outside of basically using a template and generating a new block and committing that, is there any other way I could do this? the apply -target= is not feasible as we are using terraform cloud and only allow remote runs via git workflows ultimately I would just like to instantiate both of those modules like so:

module "new_vpc" {
  source                      = "../modules/vpc"
  name                        = "new-vpc-name
  
}

jmymy avatar Sep 20 '21 14:09 jmymy

I think there's a decent case to be made for us to refactor this module so you don't need to provide the project ID in project_roles and therefore don't run into that issue.

morgante avatar Sep 20 '21 14:09 morgante

I think there's a decent case to be made for us to refactor this module so you don't need to provide the project ID in project_roles

I think that would be super helpful

My example is:

provider "google" {
  project = var.project_id
  region  = var.region
}

module "service_accounts" {
  for_each    = { for sa in var.service_accounts : sa.name => sa }

  source        = "terraform-google-modules/service-accounts/google"
  version       = "~> 4.0.3"
  project_id    = var.project_id
  names         = [each.key]
  description   = each.value.description
  project_roles = each.value.roles
}

Where var.service_accounts looks like:

service_accounts = [
    { "name":	"ci-deploy-user",
       "description": "Service account with enough roles to perform deployment operation",
       "roles": ["roles/pubsub.editor", "roles/run.invoker", "roles/storage.objectAdmin"]
    },
    { "name": "monitoring-example",
      "description": "Service account to do some fancy stuff",
      "roles": ["roles/monitoring.viewer"]
    }
]

My problem here is that I now need to somehow inject project_id mapping into each element of the roles list dynamically and my tiny brain cant compute that :)

erzz avatar Sep 24 '21 09:09 erzz

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days

github-actions[bot] avatar Nov 23 '21 23:11 github-actions[bot]

I happen to have the same issue, for the exact reason that I am using a random suffix for the project ID, as @morgante explained. To make this refactor happen would be awesome. We currently have to use -target for each run, which is not recommended by Terraform and is slow.

I think there's a decent case to be made for us to refactor this module so you don't need to provide the project ID in project_roles and therefore don't run into that issue.

devgioele avatar Jan 19 '22 08:01 devgioele

We would be happy to accept a PR with that refactor.

morgante avatar Jan 19 '22 19:01 morgante

just hit this issue 2 Apr 2022

laithrafid avatar Apr 03 '22 00:04 laithrafid

We tried to setup a project with terraform and using random project_id so that we can recreate this at any given time. We also ran into this issue. From my point of view, this is a design error in this module. I really hope this could be refactored to not use "names" or "project_roles" in any for_each. "names" should also allow dynamic names like suffixes.

See also this stack overflow question / answer: https://stackoverflow.com/questions/70144554/how-to-solve-for-each-terraform-cannot-predict-how-many-instances-will-be-cre

DennisBecker avatar May 16 '22 13:05 DennisBecker

Hit this issue today, as well.

mattyoungberg avatar Sep 06 '22 19:09 mattyoungberg

Uggg.... 🤢 Hitting this issue today and banging my head as there is no good way my boss will approve my PR if I'm hardcoding the project id into the module.

davidlbyrne avatar Jun 07 '24 23:06 davidlbyrne