I get an error message │ An argument named "default_action" is not expected here. Did you mean to define a block of type "default_action"? trying to apply this part of the tf template
resource "aws_lb_listener" "HTTPS443" {
load_balancer_arn = aws_lb.alb.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS-1-0-2015-04"
certificate_arn = "${aws_acm_certificate.acm.arn}"
default_action {
type = "fixed-response"
fixed_response = {
content_type = "application/json"
message_body = "Nothing to see here"
status_code = "200"
}
}
}
However, based on the terraform documentation, this is how you define fixed response.
Am I missing something? My Terraform version is v1.2.9.
You need to remove the = sign from the fixed_response [1] configuration block:
default_action {
type = "fixed-response"
fixed_response {
content_type = "application/json"
message_body = "Nothing to see here"
status_code = "200"
}
}
As a side note, make sure to replace certificate_arn = "${aws_acm_certificate.acm.arn}" with certificate_arn = aws_acm_certificate.acm.arn as if you do not remove it terraform will treat it as a string literal.
[1] https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener#fixed-response-action
Related
I'm trying to add the tag "Project" to the resource "aws_ec2_transit_gateway_vpc_attachment" to make it match the rest of the resources in this project, but when I do so Terraform throws an error.
Relevant Terraform code:
resource "aws_ec2_transit_gateway" "tgw" {
description = "TGW for inter-region connectivity"
auto_accept_shared_attachments = "disable"
default_route_table_association = "disable"
default_route_table_propagation = "disable"
dns_support = "enable"
tags = {
"Name" = "labs tgw",
"Terraform" = "true",
"Project" = var.project_id
}
}
## Create new TGW Attachments
resource "aws_ec2_transit_gateway_vpc_attachment" "tgw_attachments" {
subnet_ids = aws_subnet.tgw[*].id
transit_gateway_id = aws_ec2_transit_gateway.tgw.id
vpc_id = aws_vpc.vpc.id
dns_support = "enable"
transit_gateway_default_route_table_association = "false"
transit_gateway_default_route_table_propagation = "false"
tags = {
"Name" = "labs_tgw_attachment",
"Terraform" = "true",
#"Project" = var.project_id
}
}
## Accept attachment if it hasn't already been accepted
data "aws_ec2_transit_gateway_attachment" "attachment_data" {
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.tgw_attachments.id
}
resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "accept" {
count = (data.aws_ec2_transit_gateway_attachment.attachment_data.state == "pendingAcceptance") ? 1 : 0
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.tgw_attachments.id
depends_on = [
aws_ec2_transit_gateway_vpc_attachment.tgw_attachments
]
}
The purpose of the data block and count is due to terraform throwing an error that the attachment is in an inconsistent state after it's already been accepted, so it checks to see if the attachment is in "pendingAcceptance" state, and builds the accepter only if that's true.
Currently, the attachment is accepted, everything else is working as expected, but if I uncomment "Project" = var.project_id, then I get this error:
Error: Invalid count argument
│
│ on modules/main.tf line 74, in resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "accept":
│ 74: count = (data.aws_ec2_transit_gateway_attachment.attachment_data.state == "pendingAcceptance") ? 1 : 0
│
│ The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.
I've tried adding the tags with merge, like so:
tags = merge({
"Name" = "labs tgw attachment",
"Terraform" = "true"
},
{
"Project" = var.project_id
}
)
}
But got the same error, presumably since that formatting isn't required and doesn't really change the way the tags are evaluated.
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?
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 them forward_to_ip and fixed_forbidden here, but you can choose any name that you find descriptive as long as the local.ip_https_listeners condition results match.
local.ip_https_listeners is an extension of var.ip_https_listener which adds the new attribute default_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 the fixed attribute is true or false.
The resource "aws_lb_listener" "ip_https" block now uses local.ip_https_listeners instead of var.ip_https_listener, and its default_action block is now dynamic based on the dynamic_action attribute of each listener object.
I used try to concisely tolerate certain attributes being unset in the default action object, using null to represent absense instead. These expressions then each conditionally include the target_group_arn argument and the fixed_response nested block based on whether their corresponding attributes are set in the source default_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.
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
I want to call list variables from below code. But, It is throwing error instead after mentioning the default value in variables.tf
Terraform Service Folder (/root/terraform-ukg-smtp).
main.tf
module "google_uig" {
source = "/root/terraform-google-vm/modules/compute_engine_uig"
depends_on = [
module.google_vm
]
project = var.project
count = var.num_instances
zone = var.zone == null ? data.google_compute_zones.available.names[count.index % length(data.google_compute_zones.available.names)] : var.zone
name = "apoc-uig-${random_integer.integer[count.index].result}"
instances = element((module.google_vm[*].google_instance_id), count.index)
named_ports = var.named_ports
}
variables.tf
variable "named_ports" {
description = "Named name and named port"
type = list(object({
port_name = string
port_number = number
}))
default = [{
port_name = "smtp"
port_number = "33"
}]
}
Terraform Core Folder (/root/terraform-google-vm/modules/compute_engine_uig).
main.tf
# Instance Group
resource "google_compute_instance_group" "google_uig" {
count = var.num_instances
project = var.project
zone = var.zone
name = var.name
instances = var.instances
dynamic "named_port" {
for_each = var.named_ports != null ? toset([1]) : toset([])
content {
name = named_port.value.port_name
port = named_port.value.port_number
}
}
}
variables.tf
variable "named_ports" {
description = "Named name and named port"
type = list(object({
port_name = string
port_number = number
}))
default = null
}
ERROR
╷
│ Error: Unsupported argument
│
│ on main.tf line 66, in module "google_uig":
│ 66: port_number = each.value["port_number"]
│
│ An argument named "port_number" is not expected here.
The error actually lies in the file /root/terraform-google-vm/modules/compute_engine_uig/main.tf, which you have not added to your question. But from the error message, I think to know what is wrong.
The resource google_compute_instance_group.google_uig in compute_engine_uig/main.tf should look like this:
resource "google_compute_instance_group" "google_uig" {
other_keys = other_values
dynamic "named_port" {
for_each = var.named_ports
content {
name = named_port.value.name
port = named_port.value.port
}
}
}
From the error message, it seems that you have written
name = named_ports.value.name
i.e., with a plural s instead of
name = named_port.value.name
in the content block.
If this doesn't solve it, please add the file that throws the error.
Edit from 30.05.2022:
Two more problems are now visible:
You set for_each = var.named_ports != null ? toset([1]) : toset([]), which is not correct. You have to iterate over var.named_ports (as I have written above), not over a set containing the number 1. Just copy it word by word from the code above.
Additionaly, you have defined the type of port_number in your variable named_ports as "number", but you have given a string "33". This may be fine for terraform since it does a lot of conversion in the background, but better change it too.
This is my waf.tf terraform file:
resource "aws_wafv2_web_acl" "waf_acl-dev" {
name = "waf_log4j_Protections-dev"
description = "WAFv2 for dev"
scope = "REGIONAL"
default_action {
allow {}
}
rule {
name = "AWSManagedRulesKnownBadInputsRule"
priority = 1
override_action {
count {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesKnownBadInputsRuleSet"
vendor_name = "AWS"
# Excluding all these leaves only Log4JRCE
excluded_rule {
name = "Host_localhost_HEADER"
}
excluded_rule {
name = "PROPFIND_METHOD"
}
excluded_rule {
name = "ExploitablePaths_URIPATH"
}
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesKnownBadInputsRule"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedKnownBadInputsRule"
sampled_requests_enabled = true
}
}
variable "lb_arn" {
type = string
default = ""
}
data "aws_lb" "all_alb" {
tags = {
Environment = "Dev"
}
# arn = var.lb_arn
# for_each = data.aws_lb.all_alb
# id = each.value
}
output "all_alb" {
value = data.aws_lb.all_alb
}
resource "aws_wafv2_web_acl_association" "waf_acl-association-dev" {
for_each = data.aws_lb.all_alb.arn
resource_arn = each.value
web_acl_arn = aws_wafv2_web_acl.waf_acl-dev.arn
}
My objective is to create a WAF with rules (that portion works fine), and attach multiple ALBs to the WAF using tags on each ALB. Im running it inside a teamcity loop that loops into multiple AWS accounts (That is out of scope). Each account can have one or multiple ALBs with the tag provided. When I run this code I get the following error:
Error: Search returned 2 results, please revise so only one is returned
17:39:18 │
17:39:18 │ with data.aws_lb.all_alb,
17:39:18 │ on xa-waf-inow.tf line 49, in data "aws_lb" "all_alb":
17:39:18 │ 49: data "aws_lb" "all_alb" {
I also tried a few other alternatives like putting [0] at the end of line 49 but Im still getting hit with some syntax error or the other. Can someone please help?Thanks
Edit:
I also tried for_each in data block:
data "aws_lb" "all_alb" {
for_each = {
tags = {
Environment = "Dev"
}
}
arn = each.key
}
output "all_alb" {
value = data.aws_lb.all_alb
}
resource "aws_wafv2_web_acl_association" "waf_acl-association-dev" {
# for_each = data.aws_lb.all_alb.arn
resource_arn = data.aws_lb.all_alb[each.key]
web_acl_arn = aws_wafv2_web_acl.waf_acl-dev.arn
}
But got this error:
Error: Reference to "each" in context without for_each
18:24:01 │
18:24:01 │ on xa-waf-inow.tf line 65, in resource "aws_wafv2_web_acl_association" "waf_acl-association-dev":
18:24:01 │ 65: resource_arn = data.aws_lb.all_alb[each.key]
18:24:01 │
18:24:01 │ The "each" object can be used only in "module" or "resource" blocks, and
18:24:01 │ only when the "for_each" argument is set.
I was able to get this to work for myself. My issue was a mixture of lists and sets. I believe I can change my variable to a set, and not have to deal with any sets here, but I know the below works as is.
VARIABLES.TF
variable "list_of_alb" {
type = list(string)
}
MAIN.TF
list_of_alb = [
"${terraform.workspace}-unique-1",
"${terraform.workspace}-unique-2"
]
DATA.TF - the problem child
data "aws_lb" "main" {
for_each = toset( var.list_of_alb )
name = each.value
}
WAF.TF
resource "aws_wafv2_web_acl_association" "main" {
for_each = toset(var.list_of_alb)
resource_arn = data.aws_lb.main[each.value].arn
web_acl_arn = aws_wafv2_web_acl.main.arn
}
The aws_lb data source must return only one result. You can't change that, as this is how it was destined.
If you want to return multiple ALBs you have two choices:
Use for_each with the data source. This way your data source will run for each id of your alb. This means that you have to provide the alb ids as an input variable.
Or, create your own custom data source. Since this is fully custom code that you have to write, it can overcome any limitations of TF's build in data sources.
I'm using Terraform with GCP ... I have the groups variable that I have not been able to get to work. Here's the definitions:
resource "google_compute_instance_group" "vm_group" {
name = "vm-group"
zone = "us-central1-c"
project = "myproject-dev"
instances = [google_compute_instance.east_vm.id, google_compute_instance.west_vm.id]
named_port {
name = "http"
port = "8080"
}
named_port {
name = "https"
port = "8443"
}
lifecycle {
create_before_destroy = true
}
}
data "google_compute_image" "debian_image" {
family = "debian-9"
project = "debian-cloud"
}
resource "google_compute_instance" "west_vm" {
name = "west-vm"
project = "myproject-dev"
machine_type = "e2-micro"
zone = "us-central1-c"
boot_disk {
initialize_params {
image = data.google_compute_image.debian_image.self_link
}
}
network_interface {
network = "default"
}
}
resource "google_compute_instance" "east_vm" {
name = "east-vm"
project = "myproject-dev"
machine_type = "e2-micro"
zone = "us-central1-c"
boot_disk {
initialize_params {
image = data.google_compute_image.debian_image.self_link
}
}
network_interface {
network = "default"
}
}
And here are the variables:
http_forward = true
https_redirect = true
create_address = true
project = "myproject-dev"
backends = {
"yobaby" = {
description = "my app"
enable_cdn = false
security_policy = ""
custom_request_headers = null
custom_response_headers = null
iap_config = {
enable = false
oauth2_client_id = ""
oauth2_client_secret = ""
}
log_config = {
enable = false
sample_rate = 0
}
groups = [{group = "google_compute_instance_group.vm_group.id"}]
}
}
... this is my latest attempt to get a group value that works, but this one won't work for me either; I still get
Error 400: Invalid value for field 'resource.backends[0].group': 'google_compute_instance_group.vm_group.id'. The URL is malformed., invalid
I've tried this with DNS FQDNs and variations on the syntax above; still no go.
Thanks much for any advice whatsoever!
There are couple clues that can lead in this direction based from the error message reported by Terraform Error 400: Invalid value for field 'resource.backends[0].group': 'google_compute_instance_group.vm_group.id'. The URL is malformed., invalid:
Error code 400 means the request was actually sent to the server, who rejected it as malformed (HTTP error code 400 is for client-side errors); this implies that Terraform itself has no problem with the syntax, i.e., the configuration file is correct and actionable from TF's PoV
Value of field resource.backends[0].group is reported as being literally 'google_compute_instance_group.vm_group.id' which strongly suggests that a variable substitution did not take place.
The quotes around the code block makes it into a literal value instead of a variable reference. The solution is to change this:
groups = [{group = "google_compute_instance_group.vm_group.id"}]
To this:
groups = [{group = google_compute_instance_group.vm_group.id}]
I gave up on Terraform and used gcloud scripts to do what I needed to do, based on this posting.