terraform-google-modules / terraform-google-service-accounts

Creates one or more service accounts and grants them basic roles
https://registry.terraform.io/modules/terraform-google-modules/service-accounts/google
Apache License 2.0
115 stars 98 forks source link

Error: Invalid for_each argument #46

Closed floflock closed 3 months ago

floflock commented 3 years ago

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
jmymy commented 3 years ago

what was the fix here?

floflock commented 3 years ago

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?

morgante commented 3 years ago

@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.

floflock commented 3 years ago

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

jmymy commented 3 years ago

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

}
morgante commented 3 years ago

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.

erzz commented 3 years ago

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

github-actions[bot] commented 2 years ago

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

devgioele commented 2 years ago

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.

morgante commented 2 years ago

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

laithrafid commented 2 years ago

just hit this issue 2 Apr 2022

DennisBecker commented 2 years ago

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

mattyoungberg commented 2 years ago

Hit this issue today, as well.

davidlbyrne commented 4 months ago

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.