In my main.tf im using a module, in the module there's this snippet:
resource "aws_lb_listener" "ip_https" {
count = length(var.ip_https_listener) > 0 ? 1 : 0
load_balancer_arn = aws_lb.default.arn
port = var.ip_https_listener.https_port
protocol = "HTTPS"
ssl_policy = var.https_ssl_policy
certificate_arn = var.certificate_arn
default_action {
target_group_arn = aws_lb_target_group.ip[0].arn
type = "forward"
}
depends_on = [aws_lb_target_group.ip]
}
My problem with this that the listener will always have the same default action. on my main.tf id like to create a boolean variable for example fixed in case fixed == true id like to be able to use the module the same only change the default action:
default_action {
{
type = "fixed-response"
fixed_response = {
content_type = "text/plain"
message_body = "FORBIDDEN"
status_code = "403"
}
what the easiet way to do that?
CodePudding user response:
This can be done with for_each
meta-argument [1] and dynamic
[2]:
dynamic "default_action" {
for_each = var.fixed ? [1] : []
content {
type = "fixed-response"
fixed_response = {
content_type = "text/plain"
message_body = "FORBIDDEN"
status_code = "403"
}
}
}
[1] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each
[2] https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks
CodePudding user response:
Unfortunately this isn't as easy as it might first appear because the "fixed-response" example in your question isn't valid. According to the provider documentation, a "fixed-response" action should look like this:
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "FORBIDDEN"
status_code = "403"
}
}
Note that fixed_response
is a nested block rather than an argument, which means that dynamically choosing the number of fixed_response
blocks (either zero or one) will require using a dynamic
block to generate a dynamic number of these blocks.
Since there are only two possible cases for default_action
I would implement this as a lookup table in a local value which shows each of the possible cases as a clear literal data structure, separate from the complexity of generating different nested blocks using dynamic
blocks.
For example:
variable "ip_https_listener" {
type = list(object({
https_port = number
fixed = boolean
}))
}
locals {
lb_listener_default_actions = {
forward_to_ip = {
type = "forward"
target_group_arn = aws_lb_target_group.ip[0].arn
}
fixed_forbidden = {
type = "fixed_response"
fixed_response = {
content_type = "text/plain"
message_body = "FORBIDDEN"
status_code = "403"
}
}
}
# This extends the var.ip_https_listener objects with an
# additional attribute "default_action", so we can use
# local.ip_https_listeners instead of var.ip_https_listener
# below to access this conveniently.
ip_https_listeners = [
for l in var.ip_https_listener :
merge(
l,
{
default_action = local.lb_listener_default_actions[l.fixed ? "fixed_response" : "forward_to_ip"]
},
]
}
resource "aws_lb_listener" "ip_https" {
for_each = length(local.ip_https_listener)
load_balancer_arn = aws_lb.default.arn
port = local.ip_https_listener[count.index].https_port
# (...and all of your other arguments)
# Default actions for each listener are selected in the
# definition of local.ip_https_listeners, by looking up
# one of the possible default actions in
# local.lb_listener_default_actions .
default_action {
type = local.ip_https_listeners[count.index].default_action.type
target_group_arn = try(local.ip_https_listeners[count.index].default_action.target_group_arn, null)
dynamic "fixed_response" {
for_each = try(local.ip_https_listeners[count.index].default_action.fixed_response, null)[*]
content {
content_type = fixed_response.value.content_type
message_body = fixed_response.value.message_body
status_code = fixed_response.value.status_code
}
}
}
}
There are three key parts to the above:
local.lb_listener_default_actions
describes the two possible "default actions" that any LB listener can have. I arbitrarily named themforward_to_ip
andfixed_forbidden
here, but you can choose any name that you find descriptive as long as thelocal.ip_https_listeners
condition results match.local.ip_https_listeners
is an extension ofvar.ip_https_listener
which adds the new attributedefault_action
to each of the objects in the list.This works by looking up one of the two members of
local.lb_listener_default_actions
based on whether thefixed
attribute is true or false.The
resource "aws_lb_listener" "ip_https"
block now useslocal.ip_https_listeners
instead ofvar.ip_https_listener
, and itsdefault_action
block is now dynamic based on thedynamic_action
attribute of each listener object.I used
try
to concisely tolerate certain attributes being unset in the default action object, usingnull
to represent absense instead. These expressions then each conditionally include thetarget_group_arn
argument and thefixed_response
nested block based on whether their corresponding attributes are set in the sourcedefault_action
object.
There's a subjective design tradeoff here which I want to be explicit about. I chose to factor out the two possible sets of values for default_action
into a separate local value because I think that'll make it easier to read and update them in future, but that does come at the expense of some extra indirection: it's no longer clear just from reading the resource block exactly how the default_action
will be populated, and instead requires working backwards through all of these expressions to find the local value to update.
I added a comment above the default_action
block in the resource in an attempt to mitigate that by directing the future maintainer to the appropriate local value, but it would also be possible to write all of the values inline as part of all of these dynamic expressions and thus remove the indirection at the expense of making it (subjectively) harder to find and update a specific value.
The repeated references to local.ip_https_listeners[count.index]
are also unfortunate but come as a consequence of using a list of listeners and the count
argument for repetition. If possible I would recommend changing the input variable to be a map of objects instead of a list of objects, and then using for_each
to describe the repetition so that you can use each.value
as a more concise way to refer to the current element. That is far beyond the scope of this question though, so I won't go into the details about it here.