-
Notifications
You must be signed in to change notification settings - Fork 10k
Description
Terraform Version
1.13.0
Use Cases
Consider the following:
- Your Terraform configuration has multiple
.tfvars
files, each one dedicated to a specific environment - Your Terraform configuration requires
locals
, because some resource configurations need dynamic inputs based on other variables or references to data sources, etc. - Your Terraform configuration cannot be generalized enough such that global
locals
and environment-specifictfvars
are sufficient without significant use of conditionals or other functions
For example, say I'm creating an Application Gateway in Azure, and I have listeners that are different in dev and prod and require me to reference different web application firewall policy ID's on a per-listener basis; my listeners are also not the same in dev and prod. Take the following (very simplified) configuration:
main.tf
:
resource "azurerm_application_gateway" "network" {
name = "example-appgateway"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
firewall_policy_id = azurerm_web_application_firewall_policy.default.id
dynamic "http_listener" {
for_each = local.http_listeners
content {
frontend_ip_configuration_name = coalesce(http_listener.value.frontend_ip_configuration_name, local.frontend_ip_configuration_name, local.frontend_ip_configuration_private_name)
frontend_port_name = http_listener.value.frontend_port_name
name = http_listener.value.name
protocol = http_listener.value.ssl_certificate_name == null ? "Http" : "Https"
firewall_policy_id = http_listener.value.firewall_policy_id
}
}
}
dev.tfvars
:
http_listeners = {
listener_1 = {
frontend_ip_configuration_name = "feip-1"
frontend_port_name = "fep-80"
name = "listener1"
}
}
Desired final configuration:
http_listeners = {
listener_1 = {
frontend_ip_configuration_name = "feip-1"
frontend_port_name = "fep-80"
name = "listener1"
}
}
prod.tfvars
:
http_listeners = {
listener_3 = {
frontend_ip_configuration_name = "feip-1"
frontend_port_name = "fep-80"
name = "listener3"
}
listener_4 = {
frontend_ip_configuration_name = "feip-1"
frontend_port_name = "fep-80"
name = "listener3"
}
}
If I use locals, like this:
locals.tf
:
locals {
http_listeners = {
for k, v in var.http_listeners : k => {
name = v.name
frontend_port_name = v.frontend_port_name
frontend_ip_configuration_name = v.frontend_ip_configuration_name
firewall_policy_id = v.firewall_policy_id
}
}
}
And my desired final configuration for local.http_listeners
would then be:
dev
:
http_listeners = {
listener_1 = {
frontend_ip_configuration_name = "feip-1"
frontend_port_name = "fep-80"
name = "listener1"
}
}
prod
:
http_listeners = {
listener_3 = {
frontend_ip_configuration_name = "feip-1"
frontend_port_name = "fep-80"
name = "listener3"
firewall_policy_id = azurerm_web_application_firewall_policy.listener_3.id
}
listener_4 = {
frontend_ip_configuration_name = "feip-1"
frontend_port_name = "fep-80"
name = "listener3"
firewall_policy_id = azurerm_web_application_firewall_policy.listener_4.id
}
}
As you can see I have no easy way of defining the var.http_listeners
input variable in a .tfvars
file in such a way that I could correctly reference any of the azurerm_web_application_firewall_policy
resources and have the final output be unique for dev
and prod
or whatever else I decide to create in the future.
Attempted Solutions
A few solutions I've considered:
- Hard code ID's into the variables, which doesn't work if this module is also creating those resources plus it risks encoding information into the code that may be considered secret (like subscription IDs)
- Really complicated conditionals and other things. This is hard to read, and doesn't scale well partly due to the lack of
switch
statements (hey, there's another idea for a feature) but even then, the scale is very limited - Use a module for this resource, and call it multiple times, but using a conditional where it only creates that configuration if e.g.,
environment = dev
. This violates DRY, and forces configuration out of variables and into the code directly. - Use
_override.tf
files, which would be reasonable in an environment where pipelines are in use as they could be created/dropped in per environment, but causes difficulties when run locally - Requesting that
.tfvars
files be able to reference resources created in the configuration, which seems like the sort of thing that would lead to impossible race conditions, cycle errors, and other nightmares
Proposal
Currently, Terraform allows us to specify which tfvars
file(s) to use by passing a -var-file
CLI parameter (to some/most commands). The proposal here is to either:
- Extend that same behaviour into
locals
by defining a new file type,.tflocals
, into which locals are placed, and a CLI parameter,-local-file
, which works the same as-var-file
.- Importantly, this should not break regular
locals
usage; just like withtfvars
files, Terraform should ignore their existence unless they are specifically passed into the CLI at runtime except e.g.,terraform.tfvars
and similarlyterraform.tflocals
or perhapslocals.tflocals
.
- Importantly, this should not break regular
- Alternatively, do the same thing with a new
.tfoverride
file type, which works just like_override.tf
files except again, is only processed by Terraform when explicitly passed in via anoverride-file
CLI parameter. There should be no change to regular_override.tf
file processing.
It's relevant to call out that I stated above when considering multiple modules called conditionally that
This violates DRY, and forces configuration out of variables and into the code directly
which arguably could apply here too if one considers locals
to be part of the code rather than part of the variable definitions for the code. Personally I take the latter view, which is why I think creating a .tflocals
file type for locals
makes a certain amount of sense.
Thank you for considering this.
References
No response