Getting an error with count meta argument

resource “google_sql_database_instance” “default” {
count = 2
project = var.project_id
name = var.name[count.index]
}

resource “google_sql_user” “default” {
name = “postgres”
project = var.project_id
instance = google_sql_database_instance.default[*].name
}

variable “name” {
type = list(string)
}

tfavars

name = [ “testdb1”, “testdb2” ]

Getting an error google_sql_database_instance.default is tuple with 2 elements.

Inappropriate value for attributeattribute instance : string required.

Is it possible to solve this with count & splat expression. Please help

Hi @Sanjoy1995,

As you have not said what you’re trying to achieve, but just looking at your code what I think you are attempting to do is:

  1. deploy 2x google_sql_database_instance resources, named testdb1 & testdb2 based upon a list of names provided as an input variable. But this list could change/increase/decrease.
  2. Have a user named “postgres” configured as a default user in each database instance provisioned.

Based upon this assumption:

  1. No changes to your input variable - we will continue to work with a list of strings but we will make a small change to accommodate this when using a for_each

  2. Consider changing your google_sql_database_instance to use a for_each rather than a count. The reason for this is explained briefly here: When to Use for_each Instead of count but count is fragile and can cause unexpected destroy/recreate of resources when you add or remove an item from your list (except for the last element) or reorder the list:

resource “google_sql_database_instance” “default” {
  for_each = toset(var.name)
  project = var.project_id
  name = each.value
}
  • notes: toset() is used to convert the list to a set as for_each requires a map or a set.
  1. We will now refactor your google_sql_user default user code as follows:
resource “google_sql_user” “default” {
  for_each = google_sql_database_instance.default
  name = “postgres”
  project = var.project_id
  instance = each.value.name
}
  • notes: This will iterate through all database instances created and add the user. The reference to google_sql_database_instance.default in the for_each sets an implicit dependency between the resources.

I have not been able to test the above in gcp (due to access or familiarity) but the example is idiomatic of the usage of for_each instead of count to create multiple instances of the same resource based upon a list (or set or map) passed as a variable, and then creating further specific resources/sub-resources associated with each of them.

Hope that helps

Happy Terraforming

Hello @ExtelligenceIT ,

Thanks for your reply.

There is another resource where I’m using for_each to create multiple database for each instance.

resource “google_sql_database” “additional_databases” {
for_each = local.databases
project = var.project_id
name = each.value.name
instance = google_sql_database_instance.default.name
}

How do I reference the two instances in this setup?

I was trying to solve this problem using count & splat expression to avoid this kind of scenario.

So, as my recommendation in the above example was to use a for_each you will now have two google_sql_database_instance.default instances that have the keys testdb1 & testdb2

My assumption based upon what you’ve provided is that you want to associate each of databases created with the google_sql_database with a specific instance (as opposed to creating all of the “additional databases” against all instances (but if this is incorrect then please clarify)

To reference the name attribute for the instances now they are part of a for_each it takes the form: google_sql_database_instance.default["testdb1"].name

Assuming your local.databases is a map it may look like this:

locals {
  databases = {
    db100 =  { # <-- a database to be deployed to instance testdb1
      name = "nameofdb100"
      instance = "testdb1"
    }
    db200 =  { # <-- a database to be deployed to instance testdb2
      name = "nameofdb200"
      instance = "testdb2"
    }
  }
   db45 =  { # <-- another database to be deployed to instance testdb2
      name = "nameofdb45"
      instance = "testdb2"
    }
}

And therefore your code would be updated along these lines:

resource “google_sql_database” “additional_databases” {
    for_each = local.databases
    project = var.project_id
    name = each.value.name
    instance = google_sql_database_instance.default[each.value.instance].name
}

This is iterating through your local.databases map. Using the value of the name value of each map item for the name of the google_sql_database instance, and the instance value of the map item for the instance

These new database resources will use the key from local.database and thus would be able to be referenced as:

google_sql_database.additional_databases["db100 "]
google_sql_database.additional_databases["db200 "]
google_sql_database.additional_databases["db45"]

Again, I have not been able to test the above against gcp, but the examples are straightforward and typical.

Hello @ExtelligenceIT ,

Thank you for your help. I will test it as per your suggestion and let you know.

However, is it possible to solve it using count & splat.

For the instance name variable, I’m using type=list(string). I’m confused from where it’s getting tuple.

Error message when giving ref

instance = google_sql_database_instance.default[*].name

google_sql_database_instance.default is tuple with 2 elements.

Inappropriate value for attributeattribute instance : string required.

Hi @Sanjoy1995

google_sql_database_instance.default[*].name is a tuple

Given the below (to simulate:

locals {
    name = [ "testdb1", "testdb2" ]
}

Then local.name[*] is equivalent to local.name:

PS C:\Development\discuss\67582> terraform console
> local.name[*]
[
  "testdb1",
  "testdb2",
]
> type(local.name[*])
tuple([
    string,
    string,
])
> type(local.name)
tuple([
    string,
    string,
])
> local.name
[
  "testdb1",
  "testdb2",
]

Remember that you need to create each database as a resource. Just because you have a database of the same name across different database_instances does not mean that they are the same database (They are different databases with the same name.)

Therefore the for_each is required to create the multiple database instances, and a database can only belong to one database_instance.

Using a splat in the instance reference is the same as saying ‘associate this database to all of the database instances contained in the tuple’ which you can’t do due to a database only being able to be associated to one instance.

Extending this example

If the requirement is to create the same named databases in the list for each database instance then you will need to create a new map containing every combination of database x instance:

instance A instance B
db 1 A1 B1
db 2 A2 B2

Therefore you would create 4x database resources (A1,B1,A2,B2)

The below code has two lists and creates a local which contains maps of the above. It then creates a terraform_data resource (to simulate the database resource) for each combination via a for_each against local.combinations

locals {
  instances = ["instance_a", "instance_b"]
  databases = ["db_1", "db_2"]

  # create a cartesian product of instances and databases as maps with a composite key
  combinations = merge([
    for instance in local.instances : {
      for database in local.databases : "${instance}-${database}" => {
        instance  = instance
        database  = database
      }
    }
  ]...)
}

# create a data resource for each combination (Simulates the database resource)
resource "terraform_data" "databases" {
  for_each = local.combinations
  input = {
    instance  = each.value.instance
    databases_name = each.value.database
  }

}

output "combinations" {
  value = local.combinations
}

Place this code into a main.tf in an empty directory and run terraform plan/apply to see the outcome and to experiment.

HTH

Happy Terraforming