3

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 ?

Sam-Tahir
  • 191
  • 3
  • 15
  • I am not sure if I understand your problem completely. But As per my understanding are you talking about the condition in output. If you are then you may use `depends_on` with the output values. – mohit Apr 24 '20 at 21:28

1 Answers1

3

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

mariux
  • 2,807
  • 12
  • 21
  • Do you know how to solve the count to for_each problem above ? – Sam-Tahir Apr 25 '20 at 07:43
  • @ShoaibAhmedNasir updated the answer explaining how to fix your problem. – mariux Apr 25 '20 at 14:41
  • Can for_each also be used for conditional resources ? for_each = var.enabled ? 1 : 0, gives me an error "The given "for_each" argument value is unsuitable: the "for_each" argumentmust be a map, or set of strings, and you have provided a value of type number." – Sam-Tahir Apr 25 '20 at 19:25
  • 1
    @ShoaibAhmedNasir you basically find three types of resources in terraform today (as described in my linked article): single resources, bulk resources with `count`, and bulk resources with `for_each`.. it does not make sense to replace all `count` with `for_each` just for the sake of using `for_each`.. so it makes sense to see how the resource will be extended/used in the future.. for single resources I would keep it with `count` to enable/disable them. if you know this will be used as a bulk resource soon, try to make it a `for_each` unless the keys depend on computed values. – mariux Apr 25 '20 at 22:58