-
Notifications
You must be signed in to change notification settings - Fork 573
feat!: Swap count for for_each on subnet IAM resources #1009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat!: Swap count for for_each on subnet IAM resources #1009
Conversation
…new subnet is added or removed in the host project
|
FWIW this has been tested internally. If any issues arise once someone kicks off the checks, feel free to @ me. |
|
Could someone tell me what failed in the |
|
@imrannayer - Forgive the ping but I see you are an active maintainer. Is there a preferable way to get the |
|
/gcbrun |
Now that this job actually ran, could I get someone to share why this failed? |
|
Hi @thatcoleyouknow , it failed when trying to apply the example |
|
@bharathkkb - Per Google's recommendation, we use the Google's Terraform example foundation[1] to manage each layer our foundation, which also uses the
While the approach used in the example[2] may make sense for a simple test, it doesn't align with Google's own Terraform best practices[3]. In most real world scenarios, one would not want to manage all layers of a foundation in a single root module. Would you be open to me also changing how the subnets are passed from Lines 17-20 and 95-107 of [1] https://github.com/terraform-google-modules/terraform-example-foundation |
|
Thanks for the options @thatcoleyouknow. In both cases when we change to for_each, the module no longer works with a computed attrib as input. I do prefer us to switch to for_each over count so I am leaning towards option 1 even if doesnt work for subset of usecases (which you rightly pointed out is not a commonly used pattern). @imrannayer @apeabody @q2w any thoughts? |
+1 On the preference for |
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request is a great improvement to prevent the recreation of subnet IAM resources by switching from count to for_each. The changes are well-motivated and mostly correct.
I've found a critical issue in modules/shared_vpc_access/main.tf where the logic to extract the subnet self-link from the loop variable is incorrect, which would cause Terraform plans to fail.
Additionally, I've provided suggestions across all modified resources to simplify the code by leveraging the fact that the subnetwork argument can accept a full self-link. This removes the need for manual parsing of the region and subnetwork name, making the code more readable and robust.
Please review the comments for details and suggestions.
| subnetwork = element( | ||
| split("/", split(",", local.subnetwork_api[count.index])[1]), | ||
| split("/", each.value), | ||
| index( | ||
| split("/", split(",", local.subnetwork_api[count.index])[1]), | ||
| "subnetworks", | ||
| ) + 1, | ||
| split("/", each.value), | ||
| "subnetworks" | ||
| ) + 1 | ||
| ) | ||
| role = local.apis[split(",", local.subnetwork_api[count.index])[0]].role | ||
|
|
||
| region = element( | ||
| split("/", split(",", local.subnetwork_api[count.index])[1]), | ||
| index(split("/", split(",", local.subnetwork_api[count.index])[1]), "regions") + 1, | ||
| split("/", each.value), | ||
| index(split("/", each.value), "regions") + 1 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be a bug introduced here. each.value is a string like "${api},${subnet}". The original code correctly did split(",", ...)[1] to extract the subnet self-link before parsing it. This seems to have been lost in the refactoring. You need to split each.value by comma first.
Additionally, you can simplify this logic. The subnetwork argument accepts a self-link. You can pass the subnet self-link directly and remove the region argument, as it will be inferred.
subnetwork = split(",", each.value)[1]
| subnetwork = element( | ||
| split("/", split(",", local.subnetwork_api[count.index])[1]), | ||
| split("/", each.value), | ||
| index( | ||
| split("/", split(",", local.subnetwork_api[count.index])[1]), | ||
| "subnetworks", | ||
| ) + 1, | ||
| split("/", each.value), | ||
| "subnetworks" | ||
| ) + 1 | ||
| ) | ||
| role = "roles/compute.networkUser" | ||
|
|
||
| region = element( | ||
| split("/", split(",", local.subnetwork_api[count.index])[1]), | ||
| index(split("/", split(",", local.subnetwork_api[count.index])[1]), "regions") + 1, | ||
| split("/", each.value), | ||
| index(split("/", each.value), "regions") + 1 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be a bug introduced here. each.value is a string like "${api},${subnet}". The original code correctly did split(",", ...)[1] to extract the subnet self-link before parsing it. This seems to have been lost in the refactoring. You need to split each.value by comma first.
Additionally, you can simplify this logic. The subnetwork argument accepts a self-link. You can pass the subnet self-link directly and remove the region argument, as it will be inferred.
subnetwork = split(",", each.value)[1]
| subnetwork = element( | ||
| split("/", var.shared_vpc_subnets[count.index]), | ||
| split("/", each.value), | ||
| index( | ||
| split("/", var.shared_vpc_subnets[count.index]), | ||
| "subnetworks", | ||
| ) + 1, | ||
| split("/", each.value), | ||
| "subnetworks" | ||
| ) + 1 | ||
| ) | ||
| role = "roles/compute.networkUser" | ||
|
|
||
| region = element( | ||
| split("/", var.shared_vpc_subnets[count.index]), | ||
| index(split("/", var.shared_vpc_subnets[count.index]), "regions") + 1, | ||
| split("/", each.value), | ||
| index(split("/", each.value), "regions") + 1 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The subnetwork argument of google_compute_subnetwork_iam_member can accept the subnetwork's self-link directly. Since var.shared_vpc_subnets contains the self-links, you can simplify the code by assigning each.value to subnetwork and removing the explicit region argument, as it can be inferred from the self-link. This makes the code cleaner and less brittle.
subnetwork = each.value
| subnetwork = element( | ||
| split("/", var.shared_vpc_subnets[count.index]), | ||
| split("/", each.value), | ||
| index( | ||
| split("/", var.shared_vpc_subnets[count.index]), | ||
| "subnetworks", | ||
| ) + 1, | ||
| split("/", each.value), | ||
| "subnetworks" | ||
| ) + 1 | ||
| ) | ||
| role = "roles/compute.networkUser" | ||
|
|
||
| region = element( | ||
| split("/", var.shared_vpc_subnets[count.index]), | ||
| index(split("/", var.shared_vpc_subnets[count.index]), "regions") + 1, | ||
| split("/", each.value), | ||
| index(split("/", each.value), "regions") + 1 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The subnetwork argument of google_compute_subnetwork_iam_member can accept the subnetwork's self-link directly. Since var.shared_vpc_subnets contains the self-links, you can simplify the code by assigning each.value to subnetwork and removing the explicit region argument, as it can be inferred from the self-link. This makes the code cleaner and less brittle.
subnetwork = each.value
| subnetwork = element( | ||
| split("/", var.shared_vpc_subnets[count.index]), | ||
| split("/", each.value), | ||
| index( | ||
| split("/", var.shared_vpc_subnets[count.index]), | ||
| "subnetworks", | ||
| ) + 1, | ||
| split("/", each.value), | ||
| "subnetworks" | ||
| ) + 1 | ||
| ) | ||
| role = "roles/compute.networkUser" | ||
|
|
||
| region = element( | ||
| split("/", var.shared_vpc_subnets[count.index]), | ||
| index(split("/", var.shared_vpc_subnets[count.index]), "regions") + 1, | ||
| split("/", each.value), | ||
| index(split("/", each.value), "regions") + 1 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The subnetwork argument of google_compute_subnetwork_iam_member can accept the subnetwork's self-link directly. Since var.shared_vpc_subnets contains the self-links, you can simplify the code by assigning each.value to subnetwork and removing the explicit region argument, as it can be inferred from the self-link. This makes the code cleaner and less brittle.
subnetwork = each.value
As an engineer that works for an organization that adds new subnets on a regular basis, we find ourselves constantly having to review very lengthy Terraform plans that are full of
google_compute_subnetwork_iam_memberrecreations. This is partly due to Terraform lists being unordered but this can now be solved for by replacing thecountargument withfor_eachin each of those resources. This would be a one-time breaking change, but it would also make it the last time consumers have to deal subnet IAM grants getting recreated due to changes in the list of subnets passed to this module.If you all have any questions, concerns, or suggestions, I'd be happy to talk through those. I am also an enterprise customer so I can open a feature request with our TAM to try and help prioritize this, if that would help you all.
Thank you!