I need to create a several different policies this is my code:
This is my main.tf
resource "aws_iam_policy" "policy" {
count = length(var.name) != [] ? length(var.name) : 0
name = var.name[count.index]
path = var.path
description = var.description
policy = jsonencode(var.policy[count.index])
This is my variables.tf
variable "policy" {
description = "The policy in IAM (tpl file)"
type = list(any)
default = []
}
variable "name" {
description = "The name of the policy"
type = list
default = []
}
And for examples my var.tfvars
policy = [policy1,policy2]
This is the error
Error: Invalid value for module argument
│
│ on main.tf line 14, in module "test":
│ 14: policy = var.policy
│
│ The given value is not suitable for child module variable "policy" defined at ../policy/variables.tf:19,1-18: all list
│ elements must have the same type.
Here is how I would approach that code you posted:
variable "policies" {
description = "The policies in IAM"
type = map(object({
path = string
description = string
file = string
}))
default = {
"foo": {path : ".", description : "", file : "foo.json"},
"bar": {path : ".", description : "", file : "bar.json"}
}
}
resource "aws_iam_policy" "role_policy" {
for_each = var.policies
name = each.key
path = each.value.path
description = each.value.description
policy = file( each.value.file)
}
You can see that now the variables are all in one
with type = map(object({
We can loop over that, and that is exactly what I do in the resource:
for_each = var.policies
and of course the default in my code is just an example
I strongly recommend against type = list(any) that could makes the next developer what type is the list in use:
...and error looks straight forward:
Invalid value
all list elements must have the same type.
Related
I am trying to add tags to a launch template so that the ec2 nodes are tagged and named..
When I add the hardcoded tags inside the module it works, but the idea is to have dynamic tags and be able to merge with the local ones.
module
resource "aws_autoscaling_group" "ecs_asg" {
name = var.name_asg
max_size = var.max_size
min_size = var.min_size
.
.
.
service_linked_role_arn = var.service_linked_role_arn
tags = var.asg_tags
launch_template {
id = aws_launch_template.launch_template.id
version = "$Latest"
}
}
variables.tf
variable "asg_tags" {
type = map(string)
default = {}
}
main.tf
name_asg = "leo-nombre-asg"
max_size = var.max_size
min_size = var.min_size
.
.
.
asg_tags = merge(
local.tags,
{
propagate_at_launch=true,
},
)
locals.tf
locals {
tags = {
"Accountable" = "business"
"Deploy" = "terraform"
"Role" = "services"
}
}
terraform validate
│ Error: Incorrect attribute value type
│
│ on modules\ecs\main.tf line 38, in resource "aws_autoscaling_group" "ecs_asg":
│ 38: tags = var.asg_tags
│ ├────────────────
│ │ var.asg_tags is a map of string
│
│ Inappropriate value for attribute "tags": set of map of string required.
The two fixes necessary here are both for the type in the asg_tags parameter argument value:
asg_tags = [merge(local.tags, { "propagate_at_launch" = "true" })]
Here we use the list/set constructor to cast the type set(map(string)). Terraform will coerce to set instead of list with the constructor as long as the type is specified to be set instead. Since we need to fix the type declaration anyway to be compatible with the resource attribute schema, this is convenient to do:
variable "asg_tags" {
type = set(map(string))
default = {}
}
I want to append some text to the Name tag for each resource created. I want the name to be the 'key' name + "a string"
resource "aws_vpc_peering_connection" "commerce_vpc_pc" {
for_each = local.requester_vpcs
peer_vpc_id = data.aws_vpc.tf_commerce_vpc.id
vpc_id = each.value.vpc_id
auto_accept = true
tags = {
Name = [each.key]+"_pc_commerce"
}
}
This give the error:
│ Error: Invalid operand
│
│ on main.tf line 34, in resource "aws_vpc_peering_connection" "commerce_vpc_pc":
│ 34: Name = [each.key]+"-to-commerce_vpc"
│
│ Unsuitable value for right operand: a number is required.
which makes sense. However is it possible to append some text to the each.key key?
resource "aws_vpc_peering_connection" "commerce_vpc_pc" {
for_each = local.requester_vpcs
peer_vpc_id = data.aws_vpc.tf_commerce_vpc.id
vpc_id = each.value.vpc_id
auto_accept = true
tags = {
Name = "${each.key}_pc_commerce"
}
}
See this another example of provisioning a customer gateway.
################################################################################
# Customer Gateways
################################################################################
resource "aws_customer_gateway" "this" {
for_each = var.customer_gateways
bgp_asn = each.value["bgp_asn"]
ip_address = each.value["ip_address"]
device_name = lookup(each.value, "device_name", null)
type = "ipsec.1"
tags = merge(
{ Name = "${var.name}-${each.key}" },
var.tags,
var.customer_gateway_tags,
)
}
I am new to terraform and trying to change the existing script were we used to create 1 route 53 zone and corresponding route 53 record , now the requirement is to add one more zone and 53 record (correspondingly) , i am trying multi level map , i need your help correct my code
tf.vars
variable "facade_hostname" = {
type = "map"
default = {
old_mobile_facade_hostname = "xxx.morgen.nl"
new_mobile_facade_hostname = "xxx.test.nl"
}
}
dns_config = {
old_dns_records = {
mobile_facade = {
name = "xxx.morgen.nl",
ttl = "5",
type = "A",
records = [
"1.2.3.4"]
}
},
new_dns_records = {
mobile_facade = {
name = "xxx.test.nl",
ttl = "5",
type = "A",
records = [
"5.6.7.8"]
}
}
}
varibles.tf
variable "dns_config" {
type = map(object({
name = string
ttl = string
type = string
records = string
}))
default = {}
}
variable "facade_hostname" {
type = map(object({
old_mobile_facade_hostname = string
new_mobile_facade_hostname = string
}))
default = {}
}
and finally my resource creation
resource "aws_route53_zone" "private" {
for_each = var.facade_hostname
count = var.dns_config != "" && var.facade_hostname != "" ? 1 : 0
name = var.facade_hostname
force_destroy = true
vpc {
vpc_id = module.vpc_private.vpc_id
}
}
resource "aws_route53_record" "A" {
for_each = var.facade_hostname
count = var.dns_config != "" && var.facade_hostname!= "" ? 1 : 0
zone_id = aws_route53_zone.private[count.index].zone_id
name = var.dns_config.facade_hostname.name
ttl = var.dns_config.facade_hostname.ttl
type = var.dns_config.facade_hostname.type
records = var.dns_config.facade_hostname.records
allow_overwrite = true
}
Error i am encountering, when running the terraform init
╷
│ Error: Invalid combination of "count" and "for_each"
│
│ on route53.tf line 2, in resource "aws_route53_zone" "private":
│ 2: for_each = var.facade_hostname
│
│ The "count" and "for_each" meta-arguments are mutually-exclusive, only one
│ should be used to be explicit about the number of resources to be created.
╵
╷
│ Error: Invalid combination of "count" and "for_each"
│
│ on route53.tf line 12, in resource "aws_route53_record" "A":
│ 12: for_each = var.facade_hostname
│
│ The "count" and "for_each" meta-arguments are mutually-exclusive, only one
│ should be used to be explicit about the number of resources to be created.
╵
aws-vault: error: exec: Failed to wait for command termination: exit status 1
Thanks
Finally after spending some time , this seems a working solution incase if it helps any one in future , to create couple of hosted zone and create different A record based on hosted zone,
resource "aws_route53_zone" "private" {
for_each = var.mobile_facade_hostname
name = each.key
force_destroy = true
vpc {
vpc_id = module.vpc_private.vpc_id
}
}
resource "aws_route53_record" "A" {
for_each = aws_route53_zone.private
zone_id = each.value["zone_id"]
name = trimsuffix(each.value["name"], ".")
type = "A"
ttl = "5"
records = [var.mobile_facade_hostname[trimsuffix(each.value["name"], ".")]]
My tfvars
mobile_facade_hostname = { "x.y.nl" = "1.2.3.4", "a.b.nl" = "5.6.7.8" }
variables.tf
variable "mobile_facade_hostname" {
type = map(string)
default = {}
}
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.