Terraform plan propose changes when it should not

Hello,

I need some help to figure out why does terraform show changes (ie parameters are replaced by something “known after apply”).

The main source of data is map of maps having a list of users as attribute, affected to each projects:

projects = {
  "one": {
    "users": [ "john", "albert"]
  },
 "two":{
    "users": [ "john", "frank"]
  },
}

module "grafana_user" {
  for_each   = local.data.groups.grafana_users # this locals adds all users found as subkeys in projects
  source     = "git@git.local:terraform-modules/terraform-grafana-user.git"
  ldap_login = each.key # it takes a username and retrieve information from ldap
}

# Each grafana_* property is a list like "users"
module "tenant_grafana_org" {
  for_each         = local.data.projects
  source           = "git@git.local:terraform-modules/terraform-grafana-org.git"
  grafana_org_name = each.key
  ldap_login_admins = contains(keys(each.value), "grafana_admins") ? keys(each.value.grafana_admins) : (
    keys(each.value.members)
  )
  ldap_login_editors = contains(keys(each.value), "grafana_editors") ? keys(each.value.grafana_editors) : []
  ldap_login_viewers = contains(keys(each.value), "grafana_viewers") ? keys(each.value.grafana_viewers) : []

  depends_on = [
    module.grafana_user
  ]
}

If I change the project one’s user list, I don’t expect such a change on other projects, but the plan shows changes within the second module:

 # module.tenant_grafana_org["two"].data.ldap_object.ldap_admins_attributes[0] will be read during apply
  # (config refers to values not yet known)
 <= data "ldap_object" "ldap_admins_attributes"  {
!       attributes        = [
---         {
---             "mail" = "john@example.com"
            },
        ] -> (known after apply)
!       attributes_json   = {
---         "mail" = jsonencode(
                [
---                 "john@example.com",
                ]
            )
        } -> (known after apply)
---     depth             = "subtree" -> null
!       dn                = "uid=john,ou=people,dc=example,dc=com" -> (known after apply)
!       id                = "-" -> (known after apply)
        # (3 unchanged attributes hidden)
    }
 # module.tenant_grafana_org["two"].data.ldap_object.ldap_admins_attributes[1] will be read during apply
  # (config refers to values not yet known)
 <= data "ldap_object" "ldap_admins_attributes"  {
!       attributes        = [
---         {
---             "mail" = "frank@example.com"
            },
        ] -> (known after apply)
!       attributes_json   = {
---         "mail" = jsonencode(
                [
---                 "frank@example.com",
                ]
            )
        } -> (known after apply)
---     depth             = "subtree" -> null
!       dn                = "uid=frank,ou=people,dc=example,dc=com" -> (known after apply)
!       id                = "-" -> (known after apply)
        # (3 unchanged attributes hidden)
    }
  1. The two modules don’t iterate over the same list of objects (because users could be in many projects and i deduplicate the list)
  2. Adding the dependency to the first module triggers a re-read of all projects when we remove a user from a single project in module #2
  3. The module #2 use a ldap data source with the logins but is not linked to the user’s list passed to #1

What could I do to avoid such plans (that does nothing in the end) who scares the project users (one change in a project triggers a change in all project) ?

Thanks

Hi @jbe-dw,

By declaring that one entire module call depends on another you have told Terraform that any data resource in the second module must always wait until the apply step if the are any planned changes for anything in the first module.

I would suggest that you remove the depends_on argument from all module blocks and instead specify the required dependencies more precisely, by referring to the outputs of the first module in the arguments to the second one. Terraform can infer dependencies itself more precisely than what you can specify with depends_on, particularly with module calls.

More precise dependencies may not fully resolve this, but it will at least make it easier to follow where the changes are coming from and potentially make further changes to avoid them altogether, if possible.

Hello,

Thank you for your help. This is what I managed to do with all other modules in this project. I’m also not fully satisfied of this code. I may take some time to figure out the best way to run these two modules altogether.

Even if you didn’t clearly tell the why, I also feel that removing this dependency (like it was before) could be better.

The main issue is that I probably need to write a loop like this:

ldap_login_admins = contains(keys(each.value), "grafana_admins") ? (
[for u in keys(each.value.grafana_admins): module.grafana_user.users[u].id]) : (
[for u in keys(each.value.members): module.grafana_user.users[u].id]
  )

with a change in the output (and a review of the content of local.data.groups.grafana_users)