I have code that I’ve refactored from count to for_each, however since I’m using list(object) as the main variable I’m iterating on, whenever I try to add resources in the middle of the list the order still matters and TF wants to replace the objects.
My code:
variables.tf:
variable "dbs" {
type = list(object({
db_id = string
db_name = string
labels = map(string)
iam_members = list(object({
role = string
member = string
}))
tables = list(object({
id = string
partitioned = string
field = string
type = string
clustering = optional(list(string))
}))
materialized_views = optional(list(object({
id = string
enable_refresh = optional(string)
refresh_interval_ms = optional(string)
})))
views = optional(list(object({
id = string
use_legacy_sql = optional(string)
})))
}))
description = "BigQuery Dataset to be created"
}
main.tf:
resource "google_bigquery_dataset" "database" {
for_each = { for k, v in var.dbs : k => v }
dataset_id = each.value.db_id
friendly_name = each.value.db_name
description = "Dataset created by Terraform"
location = "US"
delete_contents_on_destroy = var.delete_contents_on_destroy
labels = each.value.labels
}
module "tables" {
source = "./tables"
for_each = { for k, v in var.dbs : k => v }
db_id = google_bigquery_dataset.database[each.key].dataset_id
labels = each.value.labels
tables = each.value.tables
}
module "materialized_views" {
source = "./materialized_views"
for_each = { for k, v in var.dbs : k => v if v.materialized_views != null }
db_id = google_bigquery_dataset.database[each.key].dataset_id
materialized_views = each.value.materialized_views
labels = each.value.labels
depends_on = [module.tables]
}
module "views" {
source = "./views"
for_each = { for k, v in var.dbs : k => v if v.views != null }
db_id = google_bigquery_dataset.database[each.key].dataset_id
views = each.value.views
labels = each.value.labels
depends_on = [module.tables]
}
module "iam" {
source = "./iam"
for_each = { for k, v in var.dbs : k => v }
db_id = google_bigquery_dataset.database[each.key].dataset_id
iam_members = each.value.iam_members
}
dev.tfvars:
dbs = [
{
db_id = "some_dataset1"
db_name = "some_dataset1"
labels = {
environment = "dev"
}
iam_members = [
{
role = "roles/bigquery.dataEditor"
member = "serviceAccount:1@project.iam.gserviceaccount.com"
},
{
role = "roles/bigquery.dataOwner"
member = "group:1@thebest.com"
}
]
tables = [
{
id = "results"
partitioned = "true"
field = "date_time_of_run"
type = "DAY"
},
{
id = "results_extended"
partitioned = "true"
field = "date"
type = "DAY"
}
]
views = [
{
id = "results_view"
use_legacy_sql = "false"
}
]
},
{
db_id = "some_dataset3"
db_name = "some_dataset3"
labels = {
environment = "dev"
}
iam_members = [
{
role = "roles/bigquery.dataEditor"
member = "serviceAccount:1@project.iam.gserviceaccount.com",
member = "group:2@thebest.com",
member = "group:3@thebest.com"
},
]
tables = [
{
id = "daily_inventory"
partitioned = "true"
field = "inventory_timestamp"
type = "DAY"
}
]
materialized_views = [
{
id = "inventory_materialized_view"
enable_refresh = "true"
refresh_interval_ms = "1800000"
}
]
},
{
db_id = "some_dataset2"
db_name = "some_dataset2"
labels = {
environment = "dev"
}
iam_members = [
{
role = "roles/bigquery.dataEditor"
member = "serviceAccount:1@project.iam.gserviceaccount.com",
member = "group:1@thebest.com",
member = "group:1@thebest.com"
}
]
tables = []
}
]
As you can see, some_dataset3 was inserted between some_dataset1 and some_dataset2.
Getting:
...
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
-/+ destroy and then create replacement
# google_bigquery_dataset.database["1"] must be replaced
-/+ resource "google_bigquery_dataset" "database" {
~ dataset_id = "some_dataset2" -> "some_dataset3" # forces replacement
....
On my second try, I changed my code to be:
resource "google_bigquery_dataset" "database" {
for_each = { for k in var.dbs : k.db_id => k }
dataset_id = each.value.db_id
friendly_name = each.value.db_name
description = "Dataset created by Terraform"
location = "US"
delete_contents_on_destroy = var.delete_contents_on_destroy
labels = each.value.labels
}
module "tables" {
source = "./tables"
for_each = { for k in var.dbs : k.db_id => k }
db_id = google_bigquery_dataset.database[each.key].dataset_id
labels = each.value.labels
tables = each.value.tables
}
module "materialized_views" {
source = "./materialized_views"
for_each = { for k in var.dbs : k.db_id => k if k.materialized_views != null }
db_id = google_bigquery_dataset.database[each.key].dataset_id
materialized_views = each.value.materialized_views
labels = each.value.labels
depends_on = [module.tables]
}
module "views" {
source = "./views"
for_each = { for k in var.dbs : k.db_id => k if k.views != null }
db_id = google_bigquery_dataset.database[each.key].dataset_id
views = each.value.views
labels = each.value.labels
depends_on = [module.tables]
}
module "iam" {
source = "./iam"
for_each = { for k in var.dbs : k.db_id => k }
db_id = google_bigquery_dataset.database[each.key].dataset_id
iam_members = each.value.iam_members
}
Now, since I’m constructing the keys based on “db_id”, terraform wants to destroy all resources and recreate them with non-index named resources in state. Since of course it’s undesirable to delete datasets, is there a way to avoid recreating the resources except using moved blocks?
Thanks in advance!