Search code examples
google-cloud-platformterraformterraform-provider-gcpterraform0.12+terraform-provider-kubernetes

Inheriting Values from output.tf with conditional resources using TF 0.12+


I have a module for service-accounts in GCP being used to populate kubernetes secrets

Here is my module

resource "google_service_account" "service_account" {
  count        = var.enabled ? 1 : 0
  account_id   = var.account_id
  display_name = var.display_name
}

resource "google_project_iam_member" "service_account_roles" {
  count  = var.enabled ? length(var.roles) : 0
  role   = "roles/${element(var.roles, count.index)}"
  member = "serviceAccount:${google_service_account.service_account[0].email}"
}

resource "google_service_account_key" "service_account_key" {
  count              = var.enabled ? 1 : 0
  service_account_id = google_service_account.service_account[0].name
}

'output.tf' contains the following

output "private_decoded_key" {
  value = base64decode(
    element(
      concat(
        google_service_account_key.service_account_key.*.private_key,
        [""],
      ),
      0,
    ),
  )
  description = "The base 64 decoded version of the credentials"
}

Since there is a conditional that none of these resources are created without the enabled flag, I had to handle it in TF 0.11.14 this way, and the tf0.12 autoupgrade tool didnt do much changes here.

How can I simplify this in Terraform 0.12.24, I tried modifying the output to simply

value = base64decode(google_service_account_key.service_account_key[0].private_key)

But the problem there is that if the corresponding kubernetes cluster gets deleted during a deletion, and there are errors midway because terraform, I will not be able to cleanup the terraform state of the rest of the resources using `terraform destroy'

Attempts to convert the count to for_each as shown below gave me the following errors

resource "google_service_account" "service_account" {
  # count        = var.enabled ? 1 : 0
  for_each     = var.enabled ? 1 : 0
  account_id   = var.account_id
  display_name = var.display_name
}

resource "google_project_iam_member" "service_account_roles" {
  # count  = var.enabled ? length(var.roles) : 0
  for_each = var.enabled ? toset(var.roles) : 0
  # role   = "roles/${element(var.roles, count.index)}"
  role     = "roles/${each.value}" 
  member   = "serviceAccount:${google_service_account.service_account[0].email}"
}
for_each = var.enabled ? toset(var.roles) : 0

The true and false result expressions must have consistent types. The given
expressions are set of dynamic and number, respectively.

What am I doing wrong above ?


Solution

  • In the terraform version you mentioned (0.12.24) you should be able to use try() in your outputs.tf:

    value = try(base64decode(google_service_account_key.service_account_key[0].private_key), "")
    

    This would default to "" if google_service_account_key.service_account_key[0].private_key is not resolvable for any reason; you can also default to null of course.

    Edit/Update: To answer second (edited) part of the question:

    To get rid of the error that both sides need to have the same type, you need to use [] as an empty set instead of 0 when converting to for_each:

    for_each = var.enabled ? toset(var.roles) : []
    

    Please pay attention with existing infrastructure as you need to manipulate the state file when converting from count to for_each or terraform will try to destroy and create resources.

    (I will cover this in more detail in part 3 of a series of stories I am currently working on about how to write terraform modules. You can find part 1 on medium and part 2 will be released next week.)