Search code examples
terraformterraform-provider-gcp

Terraform passing list/set from root module to child module issue


I have this root module which calls the child module to create a GCP project and create IAM role bindings.

module "test_project" {
  source         = "terraform.dev.mydomain.com/Dev/sbxprjmodule/google"
  version        = "1.0.3"
  short_name     = "looker-nwtest"
  owner_bindings = ["group:[email protected]", "group:[email protected]"]
}

variable "owner_bindings" {
  type    = list(string)
  default = null
}

This is the child module which does the assignments

resource "google_project_iam_binding" "g-sbox-iam-owner" {
  count = var.owner_bindings == null ? 0 : length(var.owner_bindings)
  project = "${var.project_id}-${var.short_name}"
  role    = "roles/owner"
  members = [var.owner_bindings[count.index]]
}

variable "owner_bindings" {
  type    = list(string)
  default = null
}
/* 

When I do a terraform plan and apply, it creates both the bindings properly, looping through twice. Then when I run a terraform plan again and apply, it shows this change below.

# module.lookernwtest_project.google_project_iam_binding.g-sbox-iam-owner[0] will be updated in-place
  ~ resource "google_project_iam_binding" "g-sbox-iam-owner" {
        id      = "g-prj-npe-sbox-looker-nwtest/roles/owner"
      ~ members = [
          + "group:[email protected]",
          - "group:[email protected]",
        ]
        # (3 unchanged attributes hidden)
    }

Next time I do a terraform plan and apply, it shows the below. It then alternates between the two of the groups on each subsequent plan and apply.

# module.lookernwtest_project.google_project_iam_binding.g-sbox-iam-owner[1] will be updated in-place
  ~ resource "google_project_iam_binding" "g-sbox-iam-owner" {
        id      = "g-prj-npe-sbox-looker-nwtest/roles/owner"
      ~ members = [
          - "group:[email protected]",
          + "group:[email protected]",
        ]
        # (3 unchanged attributes hidden)
    }

Tried to change the data structure from list to set and had the same issue. The groups are not inherited and are applied only at the project level too. So not sure what I'm doing wrong here.


Solution

  • Instead of count you can use a for_each the change is simple...

    the resource in your child module will look something like this:

    resource "google_project_iam_binding" "g-sbox-iam-owner" {
      for_each = var.owner_bindings == null ? toset([]) : toset(var.owner_bindings)  
      project  = "${var.project_id}-${var.short_name}"
      role     = "roles/owner"
      members  = [each.value]
    }
    

    The count changes for_each and in the members we use the each.value


    With a for_each the state changes, you will no longer see the numeric array:

    # module.lookernwtest_project.google_project_iam_binding.g-sbox-iam-owner[0]
    ...
    # module.lookernwtest_project.google_project_iam_binding.g-sbox-iam-owner[1]
    

    instead it will have the names, something like:

    # module.lookernwtest_project.google_project_iam_binding.g-sbox-iam-owner["abc"]
    ...
    # module.lookernwtest_project.google_project_iam_binding.g-sbox-iam-owner["def"]
    

    To loop or not to loop

    After looking at this for a while; I'm questioning why do we need individual iam_binding if they all will have the same role, if all members have the same "roles/owner" we could just do:

    resource "google_project_iam_binding" "g-sbox-iam-owner" {
      project = "${var.project_id}-${var.short_name}"
      role    = "roles/owner"
      members = [var.owner_bindings]
    }