Sanjay M. P.
Sanjay M. P.

Reputation: 1009

loop across modules in terraform

I need to build about 30 pub sub topics in GCP, creating each module for a pub sub topic is a tedious process, is there any better way for handling it ?

module "a" {
  source       = ""
  project_id   = var.project_id
  topic        = var.a["topic_name"]
  topic_labels = var.a["topic_labels"]
  pull_subscriptions = [
    {
      name                    = var.a["pull_subscription_name"]
      ack_deadline_seconds    = var.a["ack_deadline_seconds"]
      max_delivery_attempts   = var.a["max_delivery_attempts"]
      maximum_backoff         = var.maximum_backoff
      minimum_backoff         = var.minimum_backoff
      expiration_policy       = var.expiration_policy
      enable_message_ordering = true
    }
  ]
}

module "b" {
  source       = ""
  project_id   = var.project_id
  topic        = var.b["topic_name"]
  topic_labels = var.b["topic_labels"]
  pull_subscriptions = [
    {
      name                    = var.b["pull_subscription_name"]
      ack_deadline_seconds    = var.b["ack_deadline_seconds"]
      max_delivery_attempts   = var.b["max_delivery_attempts"]
      maximum_backoff         = var.maximum_backoff
      minimum_backoff         = var.minimum_backoff
      expiration_policy       = var.expiration_policy
      enable_message_ordering = true
    }
  ]
}

In tfvars passing the values to the above modules like below:

a = {
  topic_name             = "abc"
  topic_labels           = { env : "prod", purpose : "a" }
  pull_subscription_name = "abc-sub"
  ack_deadline_seconds   = 600
  max_delivery_attempts  = 3
}

b = {
  topic_name             = "bcd"
  topic_labels           = { env : "prod", purpose : "b" }
  pull_subscription_name = "bcd-sub"
  ack_deadline_seconds   = 600
  max_delivery_attempts  = 3
}

Can we somehow combine the variables in tfvars and pass in to a single module ?

I also wanna know the best practise to maintain the above terraform script to keep them individually or utilise one module to created 50 topics ?

Thank You !

Upvotes: 1

Views: 11748

Answers (2)

Martin Atkins
Martin Atkins

Reputation: 74064

With two separate variables your options are a bit limited, because Terraform can't see those two separate variables as being connected in a systematic way. (Each variable, as with other objects in Terraform, is entirely separate from a dependency-resolving standpoint.)

However, if you can restructure this to be a single variable of a map type then you can use resource for_each to systematically declare an instance for each element of the map:

variable "topics" {
  type = map(object({
    labels                 = map(string)
    pull_subscription_name = string
    ack_deadline_seconds   = number
    max_delivery_attempts  = number
  }))
}

module "topic" {
  source   = "..."
  for_each = var.topics

  project_id   = var.project_id
  topic        = each.key
  topic_labels = each.value.labels
  pull_subscriptions = [
    {
      name                    = each.value.pull_scription_name
      ack_deadline_seconds    = each.value.ack_deadline_seconds
      max_delivery_attempts   = each.value.max_delivery_attempts
      maximum_backoff         = var.maximum_backoff
      minimum_backoff         = var.minimum_backoff
      expiration_policy       = var.expiration_policy
      enable_message_ordering = true
    }
  ]
}

For this example I assumed that your topic_name attribute would be a suitable unique key for instances so I removed it from the declared object type with the intent of putting it in the map key instead. In other words, the value for this topics variable should look like this:

topics = {
  abc = {
    labels                 = { env = "prod", purpose = "a" }
    pull_subscription_name = "abc-sub"
    ack_deadline_seconds   = 600
    max_delivery_attempts  = 3
  }
  bcd = {
    labels                 = { env = "prod", purpose = "b" }
    pull_subscription_name = "bcd-sub"
    ack_deadline_seconds   = 600
    max_delivery_attempts  = 3
  }
}

From this, Terraform will understand that you intend to declare module instances with the following addresses:

  • module.topic["abc"]
  • module.topic["bcd"]

Because the topic name is part of the address, Terraform can recognize the difference between editing an existing topic object (without changing its name) and adding/removing topics from the map, translating that to the corresponding plan with the resource instances declared inside the module.

If it's important that you be able to change the topic names that the remote system knows without Terraform understanding that as separate delete and create operations, you could restore name as an attribute of that object type and then set topic = each.value.name instead, and then the map keys will only be for tracking in Terraform, and not visible in the remote system at all.

Upvotes: 7

LazyEval
LazyEval

Reputation: 859

You can use a for_each meta-argument for modules:

https://www.terraform.io/docs/language/meta-arguments/for_each.html

Your variable could be a set of maps that the for_each can go through.

That being said, this may become overly complex and an important aspect of IaC is to keep it simple and explicit as it is, in a way, self documenting (this is not a valid reason to omit documenting your work).

Upvotes: 0

Related Questions