Search code examples
terraformterraform-provider-gcp

Terraform 1.2.0: Referencing resources and object mapping


I have deployed a cloud run application for currently two domains with a load balancer, which is already running. Now this setup needs to be rolled out to other domains. Because the resource setup is always the same, I face some issues:

  1. I want to prevent repeating code (which is managed through a for_each)
  2. Still there are some domain-specific values to cover, which i tried through a mapping table
  3. Referencing resources, which are created with for_each in another resource

The first issue I solved like this, which seems to work:

Old:

resource "google_cloud_run_service" "cr_domain1" {
  name     = "cr-domain1"
  location = "europe-west6"
  project  = "my_project"

  template {
   ...
  }
}

resource "google_cloud_run_service" "cr_domain2" {
  name     = "cr-domain2"
  location = "europe-southwest1"
  project  = "my_project"

  template {
   ...
  }
}

New:

resource "google_cloud_run_service" "cr" {
  for_each = toset( ["domain1", "domain2"] )
  name     = "cr-${each_key}"
  location = "tdb" # This is my second issue
  project  = "my_project"

  template {
   ...
  }
}

Regarding second issue I still need domain-specific location setup, which I tried to solve like this, but I am getting errors:

variable "cr_location" {
    type    = list(object({
       domain1 = string
       domain2 = string
    }))
    default = [{
        domain1 = "europe-west6"
        domain2 = "europe-southwest1"
    }]
}

resource "google_cloud_run_service" "cr" {
  for_each = toset( ["domain1", "domain2"] )
  name     = "cr-${each_key}"
  location = "${var.cr_location[0]}.${each.key}"
  project  = "my_project"

  template {
   ...
  }
}

Error is "Cannot include the given value in a string template: string required". But I have already declared it as a string in my variable "cr_location". Any idea what's the issue here? The expected output should be:

  • location = "europe-west6" # For domain1
  • location = "europe-southwest1" # For domain2

Also regarding issue 3 I do not understand how to referencing resources, which are created with for_each in another resource. So before my for_each in the cloud run resource block (see issue 1) I had this 2 resources:

  • resource "google_cloud_run_service" "cr_domain1"
  • resource "google_cloud_run_service" "cr_domain2"

Now I only have resource "google_cloud_run_service" "cr". But in my loadbalancer.tf I still have to references to the old namings (last coderow within "service"):

resource "google_compute_region_network_endpoint_group" "backendneg" {
  for_each              = toset( ["domain1", "domain2"] )
  name                  = "backendneg-${each.key}"
  project               = "my_project"
  network_endpoint_type = "SERVERLESS"
  region                = "${var.cr_location[0]}.${each.key}" # Here same issues as issue 2
  cloud_run {
    service = google_cloud_run_service.cr_domain1.name # Old reference
  }
}

So if there is no "cr_domain1" anymore how do I reference to this resource? My issue is that I have to create over 20 resources like that and I couldn't figure it out how to do it. I appreciate any guideline here.


Solution

  • What I would suggest here is to try and refactor the variable because it is making a lot of things harder than they should be. So I would go for this kind of a variable definition:

    variable "cr_location" {
      type = map(string)
      default = {
        domain1 = "europe-west6"
        domain2 = "europe-southwest1"
      }
    }
    

    Then, the rest should be easy to create:

    resource "google_cloud_run_service" "cr" {
      for_each = var.cr_location
      name     = "cr-${each.key}"
      location = each.value
      project  = "my_project"
    
      template {
       ...
      }
    }
    

    And for the network endpoint resource:

    resource "google_compute_region_network_endpoint_group" "backendneg" {
      for_each              = var.cr_location
      name                  = "backendneg-${each.key}"
      project               = "my_project"
      network_endpoint_type = "SERVERLESS"
      region                = each.value
      cloud_run {
        service = google_cloud_run_service.cr[each.key].name
      }
    }
    

    You could even try resource chaining with for_each [1] to make sure you are doing this for all the Cloud Run resources created:

    resource "google_compute_region_network_endpoint_group" "backendneg" {
      for_each              = google_cloud_run_service.cr
      name                  = "backendneg-${each.key}"
      project               = "my_project"
      network_endpoint_type = "SERVERLESS"
      region                = each.value.location
      cloud_run {
        service = each.value.name
      }
    }
    

    [1] https://www.terraform.io/language/meta-arguments/for_each#chaining-for_each-between-resources