I have a number of Terraform data sources and a locals block created as such
data "aws_subnets" "subs" {
for_each = toset(["a", "b", "c"])
filter {
name = "vpc-id"
values = [data.aws_vpc.vpc.id]
}
filter {
name = "availability-zone"
values = ["${data.aws_region.region.name}${each.key}"]
}
}
data "aws_vpc" "vpc" {
default = false
}
data "aws_region" "region" {}
locals {
ids = [for az in data.aws_subnets.subs : az.ids[1]]
}
And an output block
output "main" {
value = local.ids
}
But when I run a terraform apply I get the error
The given key does not identify an element in this collection value: the given index is greater than or equal to the length of the collection
When I take out the index value [1] from my locals block, I can see the output as
+ main = [
+ [
+ "subnet-1234567f3f5d95987",
+ "subnet-123456797f61f831e",
+ "subnet-123456791ec481316",
],
+ [
+ "subnet-12345674da33e8064",
+ "subnet-12345676030bc7040",
],
+ [],
]
How can I extract a particular subnet ID from this list?
You have to consider two things here:
You are using for_each for the data source, so that means the return result will have key value pairs
The return result for each key will be a list
In order to achieve what you want, you need to change to the following code:
data "aws_subnets" "subs" {
for_each = toset(["a", "b", "c"])
filter {
name = "vpc-id"
values = [data.aws_vpc.vpc.id]
}
filter {
name = "availability-zone"
values = ["${data.aws_region.region.name}${each.key}"]
}
}
data "aws_vpc" "vpc" {
default = false
}
data "aws_region" "region" {}
locals {
ids = values(data.aws_subnets.subs)[*].ids
}
Here, the built-in values [1] function is used to get all the values for all the keys. The return result is also a list, so this will be a list of lists.
[1] https://developer.hashicorp.com/terraform/language/functions/values
Related
Variable
ip_set = [
{
name: "test-ip-set-1"
ip_list: ["10.0.0.1/32", "10.0.0.1/32"]
}
]
I want to loop through the ip_set variable and create IP sets per the length of ip_set and loop through ip_list within that dictionary
For e.g.
resource "aws_waf_ipset" "ipset" {
for_each = {for name, ip_list in var.ip_set: name => ip_list}
name = each.value.name
ip_set_descriptors {
type = "IPV4"
value = each.value.ip_list[ip_list_element_1]
}
ip_set_descriptors {
type = "IPV4"
value = each.value.ip_list[ip_list_element_2]
}
Error
If I do
ip_set_descriptors {
type = "IPV4"
value = tostring(each.value[*].ip_list)
}
I get
Invalid value for "v" parameter: cannot convert tuple to string.
FYI. value in ip_set_descriptors needs to be a string and I don't know how many elements are there
You can use dynamic blocks:
resource "aws_waf_ipset" "ipset" {
for_each = {for name, ip_list in var.ip_set: name => ip_list}
name = each.value.name
dynamic "ip_set_descriptors" {
for_each = each.value.ip_list
content {
type = "IPV4"
value = ip_set_descriptors.value
}
}
I want to get subnet values from another repository. For this purpose I added data aws_subnet part. However I'm having a problem with the filtering part. At the end of the values line I need to count for each subnets. I tried to use count.index and different things. But I get this error: The "count" object can only be used in "module", "resource", and "data" blocks, and only when the "count" argument is set. So how can I use * for the filter values part. Such as: ${var.vpcname}-Public-*
My Subnets:
myvpc-Private-0
myvpc-Private-1
myvpc-Private-2
myvpc-Public-0
myvpc-Public-1
myvpc-Public-2
My data part:
data "aws_subnet" "public" {
filter {
name = "tag:Name"
values = ["${var.vpcname}-Public-"]
}
}
data "aws_subnet" "private" {
filter {
name = "tag:Name"
values = ["${var.vpcname}-Private-"]
}
}
Want to see all subnets with below output part.
output "private" {
value = data.aws_subnet.private.*.id
}
output "public" {
value = data.aws_subnet.public.*.id
}
You should be using aws_subnets, not aws_subnet:
data "aws_subnets" "public" {
filter {
name = "tag:Name"
values = ["${var.vpcname}-Public-*"]
}
}
data "aws_subnets" "private" {
filter {
name = "tag:Name"
values = ["${var.vpcname}-Private-*"]
}
}
then
output "private" {
value = data.aws_subnets.private.ids
}
output "public" {
value = data.aws_subnets.public.ids
}
I would like to create a dynamic resource with for_each as well I would like to implement a resource creation condition, however by some reason which I don't understand, the resource creation condition is not working as expected.
The logic is very simple: I've a list of objects, each object is resource, therefore in each object a have a bool variable that enables creation of a resource.
resource "aws_autoscaling_group" "aws_asg" {
for_each = { for key, value in var.parameters : key => value if flatten([ for x in var.parameters : x.init ]) }
}
The problem in flatten([ for x in var.parameters : x.init ]), the resource takes first element, and ignores next element:
+ x = [
+ true,
+ false,
]
Here is var.parameters
parameters = [
# Runner 1
{
init = true
name = "test-runner-1"
scaling = {
desired = 3
maximum = 9
minimum = 3
}
},
# Runner 2
{
init = false
name = "test-runner-2"
scaling = {
desired = 3
maximum = 9
minimum = 3
}
}
]
}
Any idea how to map each boolean to resource creation?
If you want to conditionally loop through your parameters, then it should be:
resource "aws_autoscaling_group" "aws_asg" {
for_each = { for key, value in var.parameters : key => value if value.init }
}
I'm trying to create GCP SQL DBs by iterating a list of string using Terraform's for_each and count parameter and the other loop is for the map keys (maindb & replicadb).
Unfortunately, I get the error that appears below.
Is it possible to do this is Terraform?
variables.tf
variable "sql_var" {
default = {
"maindb" = {
"db_list" = ["firstdb", "secondsdb", "thirddb"],
"disk_size" = "20",
},
"replicadb" = {
"db_list" = ["firstdb"],
"disk_size" = "",
}
}
}
main.tf
resource "google_sql_database_instance" "master_sql_instance" {
...
}
resource "google_sql_database" "database" {
for_each = var.sql_var
name = "${element(each.value.db_list, count.index)}"
instance = "${google_sql_database_instance.master_sql_instance[each.key].name}"
count = "${length(each.value.db_list)}"
}
Error Message
Error: Invalid combination of "count" and "for_each"
on ../main.tf line 43, in resource
"google_sql_database" "database": 43: for_each =
var.sql_var
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.
What the error message tells you is that you cannot use count and for_each together. It looks like you are trying to create 3 main databases and 1 replica database am I correct? What I would do is create your 2 master instances and then transform your map variable to create the databases.
terraform {
required_version = ">=0.13.3"
required_providers {
google = ">=3.36.0"
}
}
variable "sql_instances" {
default = {
"main_instance" = {
"db_list" = ["first_db", "second_db", "third_db"],
"disk_size" = "20",
},
"replica_instance" = {
"db_list" = ["first_db"],
"disk_size" = "20",
}
}
}
locals {
databases = flatten([
for key, value in var.sql_instances : [
for item in value.db_list : {
name = item
instance = key
}
]
])
sql_databases = {
for item in local.databases :
uuid() => item
}
}
resource "google_sql_database_instance" "sql_instance" {
for_each = var.sql_instances
name = each.key
settings {
disk_size = each.value.disk_size
tier = "db-f1-micro"
}
}
resource "google_sql_database" "sql_database" {
for_each = local.sql_databases
name = each.value.name
instance = each.value.instance
depends_on = [
google_sql_database_instance.sql_instance,
]
}
Then, first run terraform apply -target=google_sql_database_instance.sql_instance and after this run terraform apply.
I have a few EKS node groups. I am trying to access the autoscaling_groups name of all the node groups (eg name = eks-e214c586716a). It's much easier in 0.12, but we are still using 0.11.
[
[
{
"autoscaling_groups" = [
{
"name" = "eks-e214c586716a"
},
]
"remote_access_security_group_id" = "sg-name1"
},
],
[
{
"autoscaling_groups" = [
{
"name" = "eks-c866f3f2edb5"
},
]
"remote_access_security_group_id" = "sg-name2"
},
],
]
This works: aws_eks_node_group.node-group.resources.0.autoscaling_groups.0.name
However, when I iterate through, I am unsuccessful.
count = "${length(var.nodegroups)}"
autoscaling_group_name = "${element(aws_eks_node_group.node-group.resources.*.autoscaling_groups.*.name, count.index)}"
It looks like you're misreading the complex data structure you have there.
You have a list of node groups which in turn contain another list that always has one element that is an object with the keys autoscaling_groups and remote_access_security_group_id. The autoscaling_groups key is then also another list with one element that contains an object with the key name.
Your attempt at doing this was to use:
"${element(aws_eks_node_group.node-group.resources.*.autoscaling_groups.*.name, count.index)}"
Which is saying to loop over the outermost list and then attempt to get the autoscaling_groups key from an object there when at that point it's a list with 1 element in it. You then also tried to loop over the inner most list which only has one element in it so you'd over index that if you went with:
"${element(aws_eks_node_group.node-group.resources.*.*.autoscaling_groups.*.name, count.index)}"
Because element allows wrap around you wouldn't get an out of bound index here when trying to access the second element of a 1 element list, however Terraform doesn't let you use nested splat expressions:
Error: Nested splat expression not allowed
on main.tf line 33, in resource "local_file" "asg_names":
33: content = "${element(local.eks_node_group.*.*.autoscaling_groups.*.name, count.index)}"
A splat expression (*) cannot be used inside another attribute-only splat
expression.
So to get the autoscaling group names you want to loop over the outermost list and then take the first element of that list, get the autoscaling_groups key, take the first element of that list and then finally get the value from the name key.
Here's a basic worked example of accessing this data structure, using locals for the input and a local_file resource as an output to allow us to loop over it with count:
locals {
eks_node_group = [
[
{
"autoscaling_groups" = [
{
"name" = "eks-e214c586716a"
},
]
"remote_access_security_group_id" = "sg-name1"
},
],
[
{
"autoscaling_groups" = [
{
"name" = "eks-c866f3f2edb5"
},
]
"remote_access_security_group_id" = "sg-name2"
},
],
]
}
resource "local_file" "asg_names" {
count = "${length(local.eks_node_group)}"
filename = "${count.index}.output"
content = "${element(local.eks_node_group.*.0.autoscaling_groups.0.name, count.index)}"
}
Running the plan shows the following expected output:
# local_file.asg_names[0] will be created
+ resource "local_file" "asg_names" {
+ content = "eks-e214c586716a"
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "0.output"
+ id = (known after apply)
}
# local_file.asg_names[1] will be created
+ resource "local_file" "asg_names" {
+ content = "eks-c866f3f2edb5"
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "1.output"
+ id = (known after apply)
}
Note that unless you want the wrap around functionality of element as mentioned above you should really be using the more straightforward indexing syntax as list[index]:
resource "local_file" "asg_names" {
count = "${length(local.eks_node_group)}"
filename = "${count.index}.output"
content = "${local.eks_node_group[count.index].0.autoscaling_groups.0.name}"
}