I want to get a unique storage size through Terraform output - azure

I created a terraform code using "for_each". And I made the code as below. I thought the entire code was not important here, so I omitted the entire code. And then, The terraform resource code I want to get is as follows.
resource "kubernetes_persistent_volume_claim" "pvc" {
for_each = var.az_fileshare
metadata {
name = each.value.pvc_name # PVC 이름
}
spec {
access_modes = ["ReadWriteMany"]
storage_class_name = each.value.storage_class
resources {
requests = {
storage = each.value.pv_size # PVC 볼륨
}
}
volume_name = kubernetes_persistent_volume.pv[each.key].id
}
}
I want to output the size of the unique "storage" in the above code as terraform output.
output "kubernetes_pvc_size" {
value = [
for pvc_size in kubernetes_persistent_volume_claim.pvc : pvc_size.spec
]
}
However, unwanted values were also printed. What should I do? I ask for your help.
kubernetes_pvc_size = [
tolist([
{
"access_modes" = toset([
"ReadWriteMany",
])
"resources" = tolist([
{
"limits" = tomap({})
"requests" = tomap({
"storage" = "10Gi"
})
},
])
"selector" = tolist([])
"storage_class_name" = "azurefile-csi"
"volume_name" = "TEST-1-pv"
},
]),
tolist([
{
"access_modes" = toset([
"ReadWriteMany",
])
"resources" = tolist([
{
"limits" = tomap({})
"requests" = tomap({
"storage" = "10Gi"
})
},
])
"selector" = tolist([])
"storage_class_name" = "azurefile-csi"
"volume_name" = "TEST-2-pv"
},
]),
tolist([
{
"access_modes" = toset([
"ReadWriteMany",
])
"resources" = tolist([
{
"limits" = tomap({})
"requests" = tomap({
"storage" = "10Gi"
})
},
])
"selector" = tolist([])
"storage_class_name" = "azurefile-csi"
"volume_name" = "TEST-3-pv"
},
]),
]
I only want to print "storage" = "10 Gi".

Thank you for your reply. First of all, your answer didn't work well in my environment. However, I got a hint from your answer and generated the code as below.
output "kubernetes_pvc_size" {
value = {
for k,v in kubernetes_persistent_volume_claim.pvc : k => v.spec[*].resources[*].requests.storage
}
}
This works as follows.
kubernetes_pvc_size = {
"fileshare_1" = tolist([
tolist([
"100Gi",
]),
])
"fileshare_2" = tolist([
tolist([
"100Gi",
]),
])
"fileshare_3" = tolist([
tolist([
"100Gi",
]),
])
}
To be a little over-motivated, I want to remove the "tolist([])" item from this result value and print only the value. Is this possible?
The reason why I'm not satisfied with just printing the contents of a variable is that I want to leave the value of the variable null in the future and receive input from the user. It would be nice if the input value is output as terraform output.

Related

did I do something wrong? or I found a bug?

this is to prove that there might be a bug in setintersection() or keys() in terraform v1.2.1
that it treats local and var differently.
I hope someone can help to confirm, or point out if I did something wrong?
# this file is to prove that there might be a bug in setintersection() or keys() in terraform v1.2.1
# that it treats local and var differently.
# I hope someone can help to confirm, or point out if I did something wrong?
# local.regions is to serve as the setintersection control set of strings
# as the keys(s_v) has non-region-related keys
locals {
regions = ["region-1", "region-2"]
}
# local.local_obj has the exact same data structure as of var.var_obj
locals {
local_obj = {
collection_local = {
subnet_local = {
other_keys = "somthing"
region-1 = "cidr-for-region-1-for-local-obj"
# region-2 = "cidr-for-region-2-for-local-obj"
}
}
}
}
# var.var_obj is a map(map(object())) data structure, outter map is the collection of subnets,
# inner map is the subnets within a collection, object is the subnet config
variable "var_obj" {
type = map(
map (
object ({
other_keys = "whatever"
region-1 = optional(string)
region-2 = optional(string)
})
)
)
default = {
collection_var = {
subnet_var = {
other_keys = "something_else"
region-1 = "cidr-for-region-1-for-var-obj"
# region-2 = "cidr-for-region-2-for-var-obj"
}
}
}
}
# local.transform-local and local.transform-var are two transforming locals to suite subnet resource needs
locals {
transform-local = flatten([
for c_k,c_v in local.local_obj : [
for s_k, s_v in c_v : [
for rg in setintersection(local.regions, keys(s_v)) : {
name = "${rg}-${s_k}"
region = rg
ip_cidr_range = s_v[rg]
}
]
]
])
transform-var = flatten([
for c_k,c_v in var.var_obj : [
for s_k, s_v in c_v : [
for rg in setintersection(local.regions, keys(s_v)) : {
name = "${rg}-${s_k}"
region = rg
ip_cidr_range = s_v[rg]
}
]
]
])
}
# output.contrast is to show the different behavior of setintersection() and keys() on local and var
# I think it is a bug?
# as commenting out the region-2 in local and var has different behavior,
# local behaves corretly, not producing region-2 record at all,
# but var transformer produced a region-2 record without ip_cidr_range, which will break the resource code execution
output "contrast" {
value = {
transform-local = local.transform-local
transform-var = local.transform-var
}
}
if uncommenting the region-2 line, setintersection() and keys() behave exactly the same on local and var:
output:
contrast : {
transform-local : [
{
name : "region-1-subnet_local"
ip_cidr_range : "cidr-for-region-1-for-local-obj"
region : "region-1"
}
{
name : "region-2-subnet_local"
ip_cidr_range : "cidr-for-region-2-for-local-obj"
region : "region-2"
}
]
transform-var : [
{
name : "region-1-subnet_var"
ip_cidr_range : "cidr-for-region-1-for-var-obj"
region : "region-1"
}
{
name : "region-2-subnet_var"
ip_cidr_range : "cidr-for-region-2-for-var-obj"
region : "region-2"
}
]
}
but if I comment out the region-2 line (as shown in code above), transform-var output is not desirable as it produces something that will cause malfunctioning
output:
contrast : {
transform-local : [
{
name : "region-1-subnet_local"
ip_cidr_range : "cidr-for-region-1-for-local-obj"
region : "region-1"
}
]
transform-var : [
{
name : "region-1-subnet_var"
ip_cidr_range : "cidr-for-region-1-for-var-obj"
region : "region-1"
}
{
name : "region-2-subnet_var"
region : "region-2"
}
]
}

terraform combine 2 objects with 2 attributes for aws instance_ids

Following hashicorp doc to leverage for_each to provision multiple instance using local var map.
I am unable to get the instance_ids into a single lists for output:
output "instance_ids" {
description = "IDs of EC2 instances"
value = { for p in sort(keys(var.project)) : p => module.ec2_instances[p].instance_ids }
}
This is the output:
instance_ids = {
"client-webapp" = [
"i-0e11fcc341e6ce292",
"i-0b7ddd178c0590116",
"i-0c570628d3997874b",
"i-0a1642d7cc173f329",
]
"internal-webapp" = [
"i-0e65c8569f2d2c6f5",
"i-0c62e911e9446c53b",
]
}
Looking to get both objects lists of instance_ids into single list. Any good recommendation? Attempt to use merge, flatten, concat fail with various errors.
The var context for ids in above output loops thru the KEYs 'client-webapp' & 'internal-webapp'
variable "project" {
description = "Map of project names to configuration."
type = map
default = {
client-webapp = {
public_subnets_per_vpc = 2,
private_subnets_per_vpc = 2,
instances_per_subnet = 2,
instance_type = "t2.micro",
environment = "dev"
},
internal-webapp = {
public_subnets_per_vpc = 1,
private_subnets_per_vpc = 1,
instances_per_subnet = 2,
instance_type = "t2.nano",
environment = "test"
}
}
}
Any suggestions?
You can concatenate both lists together.
locals {
instance_ids = {
"client-webapp" = [
"i-0e11fcc341e6ce292",
"i-0b7ddd178c0590116",
"i-0c570628d3997874b",
"i-0a1642d7cc173f329",
]
"internal-webapp" = [
"i-0e65c8569f2d2c6f5",
"i-0c62e911e9446c53b",
]
}
new-list = concat(local.instance_ids["client-webapp"], local.instance_ids["internal-webapp"])
}
output "new-list" {
description = "my new list"
value = local.new-list
}
Here is the output
Changes to Outputs:
+ new-list = [
+ "i-0e11fcc341e6ce292",
+ "i-0b7ddd178c0590116",
+ "i-0c570628d3997874b",
+ "i-0a1642d7cc173f329",
+ "i-0e65c8569f2d2c6f5",
+ "i-0c62e911e9446c53b",
]
Instead of creating a map with the p =>, can you just return the array? And flatten.
Something like...
output "instance_ids" {
description = "IDs of EC2 instances"
value = flatten({ for p in sort(keys(var.project)) : module.ec2_instances[p].instance_ids })
}

terraform flatten loop for 3 times

Has any one tried to get flatten for 3 loops working? I keep getting an error when i try the 3rd one:
I am trying to loop through a list within a data resource - data.instances.sg.ids
Variable example:
alarms = [
{
instances = data.instances.ids ## a list of instance ids
config = [
metric_name = "disk_free"
threshold = "GreaterThan"
]
}
]
locals {
configs = flatten([
for config_key, config_list in var.alarms : [
for instance in config_list.instances : {
for config in config_list.configs : {
instance_id = instance
metric_name = config.name
threshold = config.threshold
}
}
]
])
}
how can i properly loop through and flatten this list with data instances list.
Thanks
Not sure what exactly you want to construct, but I think it should be:
locals {
configs = flatten([
for config_key, config_list in var.alarms : [
for instance in config_list.instances : [
for config in config_list.configs :
{
instance_id = instance
metric_name = config.name
threshold = config.threshold
}
]
]
])
}

Transform an object (map) into a distinct list with terraform

With Terraform, is it possible to convert an object something like
locals {
data = {
"project1" = {
user_assigned = ["user1", "user2", "user3"]
}
"project2" = {
user_assigned = ["user2", "user3", "user4"]
}
}
to an output like
user1 = ["project1"]
user2 = ["project1","project2"]
user3 = ["project1","project2"]
user4 = ["project2"]
Note that data is an object with maps of keys(projects) and values(user_assigned)
Here's another way to do it, which I'm sharing just in case it's interesting -- the accepted answer is a fine approach too.
locals {
data = {
"project1" = {
user_assigned = ["user1", "user2", "user3"]
}
"project2" = {
user_assigned = ["user2", "user3", "user4"]
}
}
project_user = flatten([
for proj_name, proj in local.data : [
for username in proj.user_assigned : {
project_name = proj_name,
username = username
}
]
])
}
output "example" {
value = {
for pu in local.project_user :
pu.username => pu.project_name...
}
}
Outputs:
example = {
"user1" = [
"project1",
]
"user2" = [
"project1",
"project2",
]
"user3" = [
"project1",
"project2",
]
"user4" = [
"project2",
]
}
I typically use this sort of approach because a data structure like that intermediate local.project_user value -- which is a list with an element for each project/user pair -- often ends up being useful when declaring resources that represent those pairings.
There wasn't any context in the question about what these projects and users represent or which provider they might related to, so I'm going to use github_team and github_team_membership as an example to illustrate what I mean:
resource "github_team" "example" {
for_each = local.data
name = each.key
}
resource "github_team_membership" "example" {
for_each = {
for pu in local.project_user : "${pu.username}:${pu.project_name}" => pu
}
team_id = github_team.example[each.value.project_name].id
username = each.value.username
}
Lots of providers have resources that represent a relationship between two objects like this, and so having an intermediate data structure that contains an element for each pair is a useful building block for those cases, and then you can derive from that mappings in either direction as I did in the output "example" in my original snippet.
You can't dynamically create fully independent variables. Instead you can create a map in few ways. One way would be with the help of transpose and zipmap:
output "test1" {
value = transpose(zipmap(keys(local.data), values(local.data)[*].user_assigned))
}
Resulting in:
test1 = tomap({
"user1" = tolist([
"project1",
])
"user2" = tolist([
"project1",
"project2",
])
"user3" = tolist([
"project1",
"project2",
])
"user4" = tolist([
"project2",
])
})

Outputting null_data_source output as plain maps instead of array of maps in terraform

Terraform v0.11.11
+ provider.null v2.0.0
variable "vpc1" {
type = "list"
default = [
"name1:ip1:az1:dedicated",
"name2:ip2:az2:dedicated",
]
}
variable vpc2 {
type = "list"
default = [
"foo:10.1:east:yes",
"bar:10.2:west:no",
]
}
data "null_data_source" "values" {
count = "${length(var.vpc1)}"
inputs = "${
map (
"${element(split(":",var.vpc1[count.index]),0)}",
"${element(split(":", var.vpc2[count.index]),0)}"
)
}"
}
output "mappings" {
value = "${data.null_data_source.values.*.outputs}"
}
How to get the desired output , What am i missing
You need function of flatten()
output "mappings" {
value = "${flatten(data.null_data_source.values.*.outputs)}"
}
And there is easy way to get what you need with function concat()
variable "vpc1" {
type = "list"
default = [
"name1:ip1:az1:dedicated",
"name2:ip2:az2:dedicated",
]
}
variable vpc2 {
type = "list"
default = [
"foo:10.1:east:yes",
"bar:10.2:west:no",
]
}
output "mappings" {
value = "${flatten(concat(var.vpc1, var.vpc2))}"
}
Here is the output
$ terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
mappings = [
name1:ip1:az1:dedicated,
name2:ip2:az2:dedicated,
foo:10.1:east:yes,
bar:10.2:west:no
]
reference:
https://www.terraform.io/docs/configuration/interpolation.html#flatten-list-of-lists-
https://www.terraform.io/docs/configuration/interpolation.html#concat-list1-list2-

Resources