Terrafrom datasource aws_vpcs - count.index error - amazon-web-services

I am trying to use data source aws_vpcs to get the vpc id having specific tag.
For reference:
https://www.terraform.io/docs/providers/aws/d/vpcs.html
Below is my terraform yaml file.
Terrafrom version used is: 0.12.3
data "aws_vpcs" "foo" {
tags = {
Name = "test1-VPC"
}
}
resource "aws_security_group" "cluster" {
count = "${length(data.aws_vpcs.foo.ids)}"
vpc_id = "${tolist(data.aws_vpcs.foo.ids)[count.index]}"
}
resource "aws_security_group_rule" "cluster-ingress-node-https" {
description = "Rule to do xyz"
from_port = 443
protocol = "tcp"
security_group_id = "${aws_security_group.cluster.id}"
to_port = 443
type = "ingress"
}
I am getting below error. Request for help to fix this
terraform plan
Error: Missing resource instance key
on modules/eks/eks-cluster.tf line 40, in resource "aws_security_group_rule" "cluster-ingress-node-https":
40: security_group_id = "${aws_security_group.cluster.id}"
Because aws_security_group.cluster has "count" set, its attributes must be
accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
aws_security_group.cluster[count.index]

You are creating a list of aws_security_group as you are using count on the aws_security_group resource. The error even mentions it:
Because aws_security_group.cluster has "count" set, its attributes
must be accessed on specific instances.
So either you need to include count on the aws_security_group_rule resource and create one aws_security_group_rule for each aws_security_group created, or in the case you expect only one VPC to be returned, create only one aws_security_group by accessing the returned aws_vpcs.foo.ids with index 0.

You will need to convert the list of security group.
Terraform provides flatten function to do that https://nedinthecloud.com/2018/07/16/terraform-fotd-flatten/
You should not get this error afterwards

I know this was posted a while ago. Stumbled upon this issue.
${aws_security_group.cluster.*.id} should do it.
Since the resource aws_security_group is creating multiple security groups with count, resource block aws_security_group_rule needs to reference the correct index in the list.

Related

Mapping multiple Security Groups into ELB

I'm trying to attach multiple security groups containing Cloudfront CIDRs to my AWS ALB.
locals {
chunks = chunklist(data.aws_ip_ranges.cloudfront.cidr_blocks, 60)
chunks_map = { for i in range(length(local.chunks)): i => local.chunks[i] }
}
resource "aws_security_group" "sg" {
for_each = local.chunks_map
name = "{each.key}"
egress {
....
}
}
resource "aws_elb" "load" {
name = "test"
security_groups = aws_security_group.sg.id // This is wrong
My error that I'm receiving is
Because aws_security_group.sg has for_each se, its attributes must be access on specific instances
Using for_each again doesn't make sense because i don't want to create multiple resources, I just want to ensure that all security groups created are attached to the load balancer. Any ideas?
Since you've used for_each there will be more than instance of aws_security_group.sg. To get id from all of them you can use splat operator:
security_groups = values(aws_security_group.sg)[*].id

Terraform Inappropriate value for attribute "route"

relatively new to terraform and currently trying to build cloud infrastructure in AWS.
I get an error when I use an official example (little bit changed) from the documentation for the resource aws_route_table (https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table)
resource "aws_route_table" "prod-route-table" {
vpc_id = aws_vpc.prod-vpc.id
route = [{
# Route all Traffic to the internet gateway
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
},{
ipv6_cidr_block = "::/0"
gateway_id = aws_internet_gateway.gw.id
}]
}
I get the following error message
Error: Incorrect attribute value type
│ Inappropriate value for attribute "route": element 0: attributes "carrier_gateway_id",
│ "destination_prefix_list_id", "egress_only_gateway_id", "instance_id", "ipv6_cidr_block",
│ "local_gateway_id", "nat_gateway_id", "network_interface_id", "transit_gateway_id", "vpc_endpoint_id",
│ and "vpc_peering_connection_id" are required.
Adding all these attributes solves the error however this blows up the code massively.
Writing it differently (see the following) results in no errors, is the terraform AWS documentation incorrect, as they clearly state the first way of writing it?
resource "aws_route_table" "prod-route-table" {
vpc_id = aws_vpc.prod-vpc.id
route {
# Route all Traffic to the internet gateway
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
route{
ipv6_cidr_block = "::/0"
gateway_id = aws_internet_gateway.gw.id
}
}
I am using terraform v1.0.10 and aws provider version = "3.63.0"
Thanks in advance
The documentation for that argument mentions that it's using the legacy attributes as blocks mode, which is a holdover from Terraform v0.12 for some situations where providers were depending on being able to write certain arguments in both the nested block syntax (like in your second example) and in the attribute syntax (like in your first example).
The syntax currently shown in the doc example -- and your first example in your question -- is contrary to the advice about how to write a fixed value (as opposed to a dynamic value), so indeed the second example you showed here would be the preferred way as far as the general Terraform documentation is concerned.
resource "aws_route_table" "example" {
vpc_id = aws_vpc.example.id
route {
cidr_block = "10.0.1.0/24"
gateway_id = aws_internet_gateway.example.id
}
route {
ipv6_cidr_block = "::/0"
egress_only_gateway_id = aws_egress_only_internet_gateway.example.id
}
tags = {
Name = "example"
}
}
Possibly the AWS provider documentation author here used the attribute syntax in order to show symmetry with the special case of setting route = [] to explicitly state that there should be no routes at all, because entirely omitting this argument unfortunately (for historical reasons) means to ignore any existing routes in the remote API, rather than to remove all existing routes in the remote API.
There's a little more about the behavior you saw in the subsequent section arbitrary expressions with argument syntax:
Because of the rule that argument declarations like this fully override any default value, when creating a list-of-objects expression directly the usual handling of optional arguments does not apply, so all of the arguments must be assigned a value, even if it's an explicit null:
example = [
{
# Cannot omit foo in this case, even though it would be optional in the
# nested block syntax.
foo = null
},
]
Over time providers will gradually phase out this legacy mode, but must do so cautiously because it can be a breaking change for some existing configurations. Until then this is unfortunately a confusing rough edge for some particular provider attributes, though they should at least all link to the relevant documentation page I linked above to note that their behavior doesn't match the normal Terraform argument-handling behaviors.

Security groups should be able to communicate to other security groups

My company requires that I we expressly specify all allowed ports and protocols in security group ingress rules. I would like to have a long list of ports protocols and security groups to allow ingress/egress for
from_port, to_port, protocol, security_group_that_port_protocol_restriction_applies_to
The below example has the problem that the "master-sg-ingress-security-groups" variable needs to have the security groups to be defined.
resource "aws_security_group" "master_lb_sg" {
....
}
resource "aws_security_group" "worker_sg" {
......
}
########
####### list of port protocols and security groups to create ingress blocks for. Problem is that security groups to not exist at variable creation time.
########
variable "master-sg-ingress-security-groups" {
depends_on = [aws_security_group.master_lb_sg, aws_security_group.worker_sg]
description = "List of port numbers for specific security group. company bans allowing all ports and protocols. "
type = map(any)
default = {
"ingress1" = [80, 80, "TCP", aws_security_group.master_lb_sg],
"ingress2" = [443, 443, "TCP", aws_security_group.master_lb_sg],
"ingress3" = [3398,3398, "RDP", aws_security_group.bastion_host_sg],
....
"ingress4" = [1024, 1024, "UDP", aws_security_group.worker_sg]
}
}
#####
#### I want to iterate over the above list of security groups and create dynamic ingress rules but other security groups do not exist
####
resource "aws_security_group" "test" {
depends_on = [aws_security_group.master_lb_sg, aws_security_group.worker_sg]
provider = aws.region_master
name = "master-sg"
description = "security group for Jenkins master"
vpc_id = aws_vpc.vpc_master.id
dynamic "ingress" {
# this for_each is not identical to for_each in line 21
for_each = var.master-sg-ingress-security-groups
content {
from_port = ingress.value[0]
to_port = ingress.value[1]
protocol = ingress.value[2]
security_group = ingress[3]
}
}
}
I am think ing I have to just copy paste blocks of text for each ingress
Is there a way to get around the problem of aws_security_group.worker_sg in a variable
Sadly not from TF itself. Variables must be fully defined when you run your script. But you could maybe modify master-sg-ingress-security-groups into a local variable. This way you could construct your map which includes other variables.
So depending exactly on your use-case, you could maybe have a base variable called base-master-sg-ingress-security-groups, and then in locals construct a final map which would containe references to other existing SGs.
Alternatively, you could split your TF script into two parts. The first one would deploy core SGs and output their IDs. Then these IDs would be used as input variables for the second part which would deploy SGs that reference the core ones.

How do I iterate over a 'count' resource in Terraform?

In this example, I'm trying to create 3 EC2 instances which each have an elastic IP assigned. I want to achieve this by saying the following.
resource "aws_instance" "web_servers" {
ami = "ami-09e67e426f25ce0d7"
instance_type = "t3.micro"
...
count = 3
}
and, along with other networking instances,
resource "aws_eip" "elastic_ip" {
for_each = aws_instance.web_servers
instance = each.key
vpc = true
}
However, this is saying the following:
The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type tuple.
I have tried wrapping the for_each in a toset() which also says there is an issue with an unknown number of instances - I know there are 3 though. Is there something I'm missing around the count & for_each keywords?
If you really want to use for_each, rather then count again, it should be:
resource "aws_eip" "elastic_ip" {
for_each = {for idx, val in aws_instance.web_servers: idx => val}
instance = each.value.id
vpc = true
}
But since you are using count in the first place, it would be probably better to use count for your aws_eip as well:
resource "aws_eip" "elastic_ip" {
count = length(aws_instance.web_servers)
instance = aws_instance.web_servers[count.index].id
vpc = true
}
In AWS is your instance field = EC2 ID = ARN?
If so you may be able to access the arn attribute in the aws_instance resource block.
You could try toset(aws_instance.web_servers.arn) and keep instance = each.key. I generally just use each.value but they should be the same when dealing with a set.

Terraform outs from a resource called via a for_each

I'm wondering if anyone can help me with the following I have a base resource to create aws subnets
resource aws_subnet subnet {
vpc_id = var.vpc_id
cidr_block = var.cidr_block
}
output subnetId {
value = aws_subnet.subnet.id
}
module private_subnet {
source = "linktoresourcedetailedabove"
for_each = var.privateSubnet
vpd.id = var.vpc_id
cidr_block = each.value.cidr_block
}
I have a module which calls using a for_each loop based on a var based in, my question is this resource might be called 10 times and I want to store each id and then access this from another module but I seem to be hitting issues here, I tried updating aws_subnet.subnet.id to aws_subnet.subnet.*.id but am still not having anyluck and can't seem to find anything out there that can help me.
If your private_subnet modules has output
output subnetId {
value = aws_subnet.subnet.id
}
then once you create your private_subnet modules, you can get the list of all subnetId creates as:
values(module.private_subnet)[*].subnetId