How to convert list to map in terraform? - terraform

string vals=var.vals // which will contain string in the format below
//"hr:recruitment,department:human,tool:sap,fruit:apple"
labels={
hostname=var.hostname
monitored=var.monitored
customer=var.cust1
machine-type=var.machinetype
}
I need to set labels(key,value) pairs for google_compute_instance by combining the above 2 properties to form a map.
So i converted vals to list by this code split(",",var.vals)
This gives me a list
tolist([
"hr:recruitment",
"department:human",
"tool:sap",
"fruit:apple",
])
Expected Output consisting of only map(string) combining labels and vals
labels:
{
hostname:var.hostname
monitored:var.monitored
customer:var.cust1
machine-type:var.machinetype
hr:"recruitment",
department:"human",
tool:"sap",
fruit:"apple"
}
How to convert this list to a map and combine it with labels?

I tried below & it seems to be working but it heavily relies on data quality of vals string (shouldn't contain duplicate keys). At it's core it relies on zipmap to build the map from keys & values lists.
// Having vals as a string variable
variable "vals" {
description = "The name of availability set"
type = string
default = "hr:recruitment,department:human,tool:sap,fruit:apple"
}
// hostname, monitored, cust1 & machinetype vars
variable "hostname" {
type = string
default = "dummy.net"
}
variable "monitored" {
type = bool
default = true
}
variable "cust1" {
type = string
default = "xyz"
}
variable "machinetype" {
type = string
default = "linux"
}
locals {
// parsing locally to split the vals string to fetch the keys
keys = [
for s in split(",", var.vals) : split(":", s)[0]
]
// parsing locally to split the vals string to fetch the values
values = [
for s in split(",", var.vals) : split(":", s)[1]
]
//I used labels also as vars
labels = {
hostname = var.hostname
monitored = var.monitored
customer = var.cust1
machine-type = var.machinetype
}
}
// Finally, zipmap to construct map from keys & values & merge with local.labels
output "final_map" {
value = merge(local.labels, zipmap(local.keys, local.values))
}
Finally, the output looks like below ::
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
final_map = {
"customer" = "xyz"
"department" = "human"
"fruit" = "apple"
"hostname" = "dummy.net"
"hr" = "recruitment"
"machine-type" = "linux"
"monitored" = true
"tool" = "sap"
}

Related

How to remove values from a map in Terraform that match a list of keys?

I want to remove values from a map in Terraform that match a list of keys e.g. remove keys_b and keys_c from the map below.
Input
map = {
key_a = value_a,
key_b = value_b,
key_c = value_c
...
key_m = value_m
}
Desired Output
filtered_map = {
key_a = value_a,
...
key_m = value_m
}
I've tried using the contains function but that passes through only one value and not a list of values. I am not sure how to loop over this either.
output "test" {
value = { for k, v in var.map: k => v if ! contains(values(v), var.exclude) }
}
You were almost there. You had wrong arguments in contains. It should be:
output "test" {
value = { for k, v in var.map: k => v if contains(var.exclude, k) }
}

Update Terraform map function to "tomap"

I inherited some older TF code and I noticed that some of the code is using deprecated functions like map(object){}. I would like to update this code since map is no longer used as per this document.
Here is an excerpt from the old code:
variable "application_gateways" {
type = map(object({
name = string
webapp_firewall_policy_key = string
zones = list(string)
enable_http2 = bool
sku = object({
name = string
tier = string
capacity = number
})
Can I update it using tomap like this or is it more complicated?
variable "application_gateways" {
type = tomap({
name = string
webapp_firewall_policy_key = string
zones = list(string)
enable_http2 = bool
sku = object({
name = string
tier = string
capacity = number
})
Yes, that is all you need to do.
No further config is needed.
I think we need to use just 'object' keyword outside as well:
variable "application_gateways" {
type = object({
variable "application_gateways" {
type = object({
name = string
webapp_firewall_policy_key = string
zones = list(string)
enable_http2 = bool
sku = object({
name = string
tier = string
capacity = number
})
})

Terraform Loop with Range

Is there a way to loop through a range or list for a variable in terraform.
Below is what I would like to accomplish but I am not sure how to do it.
module "vlan_list" {
depends_on = [module.vlan_pools]
source = "../modules/add_vlans"
vlan_list = {
for i in range(1,100): {
"access" = {
vlan_pool = module.vlan_pools.vlan_pool["access"]
from = i
to = i
}
}
}
}
Let me add more information to it because unfortunately that didn't work. I get:
Error: Invalid value for module argument
on pools_vlan.tf line 34, in module "vlan_list":
34: vlan_list = {
35: for i in range(1, 100):
36: "access" => {
37: vlan_pool = module.vlan_pools.vlan_pool["access"]
38: from = i
39: to = i
40: }...
41: }
The given value is not suitable for child module variable "vlan_list" defined
at ../modules/add_vlans/variables.tf:5,1-21: element "access": object
required.
So I have created a module with the following
resource "aci_ranges" "add_vlan" {
for_each = local.vlan_list
alloc_mode = each.value["alloc_mode"]
annotation = each.value["annotation"]
name_alias = each.value["name_alias"]
vlan_pool_dn = each.value["vlan_pool"]
role = each.value["role"]
from = "vlan-${each.value["from"]}"
to = "vlan-${each.value["to"]}"
}
From Here I have defined a variables file to make it so users don't have to enter every variable... they can accept defaults
terraform {
experiments = [module_variable_optional_attrs]
}
variable "vlan_list" {
description = "Add VLANs to VLAN Pools"
type = map(object({
alloc_mode = optional(string)
annotation = optional(string)
from = optional(number)
name_alias = optional(string)
role = optional(string)
to = optional(number)
vlan_pool = optional(string)
}))
}
locals {
vlan_list = {
for k, v in var.vlan_list : k => {
alloc_mode = coalesce(v.alloc_mode, "static")
annotation = (v.annotation != null ? v.annotation : "")
from = (v.from != null ? v.from : 1)
name_alias = (v.name_alias != null ? v.name_alias : "")
role = coalesce(v.role, "external")
to = coalesce(v.to, 1)
vlan_pool = (v.vlan_pool != null ? v.vlan_pool : "")
}
}
}
So what I shared above is what someone would enter to consume the module... Here is a more complete example that I would like to do:
module "vlan_list" {
depends_on = [module.vlan_pools]
source = "../modules/add_vlans"
vlan_list = {
for i in range(1, 100):
"access" => {
vlan_pool = module.vlan_pools.vlan_pool["access"]
from = i
to = i
}...
for i in ranges([1000-1200], [1300-1400]):
"vmm_dynamic" => {
alloc_mode = "dynamic"
vlan_pool = module.vlan_pools.vlan_pool["vmm_dynamic"]
from = i
to = i
}...
for i in list[4, 100, 101]:
"l3out" => {
vlan_pool = module.vlan_pools.vlan_pool["l3out"]
from = i
to = i
}...
}
}
I know the second range on the vmm_dynamic key is not correct at all... I am just trying to show what I would like to be able to do if possible.
When the resource creates the entries, from the API, if I do it in a range as shown below; if someone needed to change the range for the first pool (in example) to 1-50,52-99, it would delete the entry and re-create it. whereas if they are creating the entry with each entry being created individually it wouldn't delete all of the individual entries, optimally.
I can do the following and it works fine... but being able to add the VLANs individually from a loop would be preferable.
module "vlan_list" {
depends_on = [module.vlan_pools]
source = "../modules/add_vlans"
vlan_list = {
"access" = {
vlan_pool = module.vlan_pools.vlan_pool["access"]
from = 1
to = 99
},
"vmm_dynamic" = {
alloc_mode = "dynamic"
vlan_pool = module.vlan_pools.vlan_pool["vmm_dynamic"]
from = 1000
to = 1199
},
"l3out_1" = {
vlan_pool = module.vlan_pools.vlan_pool["l3out"]
from = 4
to = 4
},
"l3out_2" = {
vlan_pool = module.vlan_pools.vlan_pool["l3out"]
from = 100
to = 101
},
}
}
Thanks in advance for help on this.
Just as one more point of reference... This is how I had previously accomplished this with Python, but I am trying to move this to native Terraform
def vlan_list_full(vlan_list):
full_vlan_list = []
if re.search(r',', str(vlan_list)):
vlist = vlan_list.split(',')
for v in vlist:
if re.fullmatch('^\\d{1,4}\\-\\d{1,4}$', v):
a,b = v.split('-')
a = int(a)
b = int(b)
vrange = range(a,b+1)
for vl in vrange:
full_vlan_list.append(vl)
elif re.fullmatch('^\\d{1,4}$', v):
full_vlan_list.append(v)
elif re.search('\\-', str(vlan_list)):
a,b = vlan_list.split('-')
a = int(a)
b = int(b)
vrange = range(a,b+1)
for v in vrange:
full_vlan_list.append(v)
else:
full_vlan_list.append(vlan_list)
return full_vlan_list
def vlan_pool
for z in range(1, 3):
vgroup = 'VLAN_Grp%s' % (z)
vgrp = 'VGRP%s_Allocation' % (z)
templateVars['Allocation_Mode'] = templateVars[vgrp]
if re.search(r'\d+', str(templateVars[vgroup])):
vlan_list = vlan_list_full(templateVars[vgroup])
for v in vlan_list:
vlan = str(v)
if re.fullmatch(r'\d+', vlan):
templateVars['VLAN_ID'] = int(vlan)
# Add VLAN to VLAN Pool File
create_tf_file('a+', dest_dir, dest_file, template, **templateVars)
I can't seem to find any examples of how to do this without Python.
If you want to create a map with a single key of access and a list of values, then you can use ellipsis operator (...). Also your syntax is incorrect. Thus, the following should be used in this case:
vlan_list = {
for i in range(1, 100):
"access" => {
vlan_pool = module.vlan_pools.vlan_pool["access"]
from = i
to = i
}...
}
Here is how I recently accomplished what I asked above. It was about a year to figure it out but this is the way I could accomplish it.
# Map of Object input Variables is as shown below
variable "vlan_pools" {
default = {
"default" = {
alias = ""
allocation_mode = "dynamic"
description = ""
encap_blocks = {
"default" = {
allocation_mode = "inherit"
description = ""
role = "external"
vlan_range = "**REQUIRED**"
}
}
}
}
description = <<-EOT
key - name of the VLAN Pool
* alias: A changeable name for a given object. While the name of an object, once created, cannot be changed, the alias is a field that can be changed.
* allocation_mode: The allocation mode. The values can be:
- dynamic (default): Managed internally by the APIC to allocate VLANs for endpoint groups (EPGs). A vCenter Domain can associate only to a dynamic pool.
- static: One or more EPGs are associated with a domain, and that domain is associated with a static range of VLANs. You must configure statically deployed EPGs within that range of VLANs.
When you create VLAN ranges, you can also assign the allocation mode to be inherited from the parent.
* description: Description to add to the Object. The description can be up to 128 alphanumeric characters.
* encap_blocks:
- allocation_mode: The allocation mode. The values can be:
* dynamic: Managed internally by the APIC to allocate VLANs for endpoint groups (EPGs). A vCenter Domain can associate only to a dynamic pool.
* inherit (default): The inherited mode from the parent device.
* static: One or more EPGs are associated with a domain, and that domain is associated with a static range of VLANs. You must configure statically deployed EPGs within that range of VLANs.
- description: Description to add to the Object. The description can be up to 128 alphanumeric characters.
- role: Role of the VLAN range. The options are:
* external (Default): Used for allocating VLANs for each EPG assigned to the domain. The VLANs are used when packets are sent to or from leafs.
* Internal: Used for private VLAN allocations in the internal vSwitch by the Cisco ACI Virtual Edge (AVE). The VLANs are not seen outside the ESX host or on the wire.
- vlan_range: single vlan; i.e. 1. range of vlans; i.e. 1-5. Or List of Vlans; i.e. 1-5,10-15
EOT
type = map(object(
{
alias = optional(string)
allocation_mode = optional(string)
description = optional(string)
encap_blocks = map(object(
{
allocation_mode = optional(string)
description = optional(string)
role = optional(string)
vlan_range = string
}
))
}
))
}
# Which I then run some conditional modifications in locals to format the variables.
locals {
#__________________________________________________________
#
# VLAN Pools Variables
#__________________________________________________________
# This first loop is to handle optional attributes and return
# default values if the user doesn't enter a value.
vlan_pools = {
for k, v in var.vlan_pools : k => {
alias = v.alias != null ? v.alias : ""
allocation_mode = v.allocation_mode != null ? v.allocation_mode : "dynamic"
description = v.description != null ? v.description : ""
encap_blocks = v.encap_blocks != null ? v.encap_blocks : {}
}
}
# Loop 1 is to determine if the encap_blocks are:
# A Single number 1
# A Range of numbers 1-5
# A List of numbers 1-5,10-15
# And then to return these values as a list
vlan_ranges_loop_1 = flatten([
for key, value in local.vlan_pools : [
for k, v in value.encap_blocks : {
allocation_mode = v.allocation_mode != null ? v.allocation_mode : "inherit"
description = v.description != null ? v.description : ""
key1 = key
key2 = k
role = v.role != null ? v.role : "external"
vlan_split = length(regexall("-", v.vlan_range)) > 0 ? tolist(split(",", v.vlan_range)) : length(
regexall(",", v.vlan_range)) > 0 ? tolist(split(",", v.vlan_range)
) : [v.vlan_range]
vlan_range = v.vlan_range
}
]
])
# Loop 2 takes a list that contains a "-" or a "," and expands those values
# into a full list. So [1-5] becomes [1, 2, 3, 4, 5]
vlan_ranges_loop_2 = {
for k, v in local.vlan_ranges_loop_1 : "${v.key1}_${v.key2}" => {
allocation_mode = v.allocation_mode
description = v.description
key1 = v.key1
key2 = v.key2
role = v.role
vlan_list = length(regexall("(,|-)", jsonencode(v.vlan_range))) > 0 ? flatten([
for s in v.vlan_split : length(regexall("-", s)) > 0 ? [for v in range(tonumber(
element(split("-", s), 0)), (tonumber(element(split("-", s), 1)) + 1)
) : tonumber(v)] : [s]
]) : v.vlan_split
}
}
# Loop 3 will take the vlan_list created in Loop 2 and expand this
# out to a map of objects per vlan.
vlan_ranges_loop_3 = flatten([
for k, v in local.vlan_ranges_loop_2 : [
for s in v.vlan_list : {
allocation_mode = v.allocation_mode
description = v.description
key1 = v.key1
role = v.role
vlan = s
}
]
])
# And lastly loop3's list is converted back to a map of objects
vlan_ranges = { for k, v in local.vlan_ranges_loop_3 : "${v.key1}_${v.vlan}" => v }
# End of Local Loops
}
# And lastly these values are consumed by the resources with for_each loops.
resource "aci_vlan_pool" "vlan_pools" {
for_each = local.vlan_pools
alloc_mode = each.value.allocation_mode
description = each.value.description
name = each.key
name_alias = each.value.alias
}
resource "aci_ranges" "vlans" {
depends_on = [
aci_vlan_pool.vlan_pools
]
for_each = local.vlan_ranges
description = each.value.description
alloc_mode = each.value.allocation_mode
from = "vlan-${each.value.vlan}"
to = "vlan-${each.value.vlan}"
role = each.value.role
vlan_pool_dn = aci_vlan_pool.vlan_pools[each.value.key1].id
}
I am sure someone with more experience might be able to accomplish this with fewer steps in locals but this worked for me at least.

If conditions in terraform map and objects to index based on key

I have a strange situation as shown below
variable is declared as :
variable "subnet_info" {
type = map(object({vpc_name=string, subnet_name=string, subnet_ip_range=string, region=string}))
default = {
abc = {
vpc_name = "abc"
subnet_name = "abc"
subnet_ip_range = ""
region = "europe-west3"
}
bcd = {
vpc_name = "bcd"
subnet_name = "bcd"
subnet_ip_range = ""
region = "europe-west3"
}
}
}
I have declared a modules as follow
module "abc" {
source = "./modular_approach/modules/general_network/"
for_each = var.subnet_info
individual_vpc_name = each.value
individual_subnet_name = each.value
}
I want to pass the value of vpc and subnet from the variable subnet_info. The problem is that there will be many values in that variable. If I run the for_each I want to condition in the module that if the key is "abc" then access the internal (key,value) other wise skip. Is this possible in terraform?
Thanks in advance!
If you really wish to filter like that, the following construct should do :
for_each = {for k,v in var.subnet_info: k=>v if k == "abc"}

Override variables in Terraform

I've started to use Terraform lately, and i need some help. I hope it's not too basic. I've the following Terraform data structure.
abc_template = {
a = var.a
b = var.b
c = var.c
d = var.d
....
....
....
k = var.k
}
And then i run:
resource "local_file" "aaa" {
count = 1
content = templatefile("${path.module}/templates/abc.tmpl", local.abc_template)
....
....
}
I need to create a new template (xyz_template), Which should be very similar to abc_template, While only a few variables will changed from the original template. What can i do instead of duplicating so many code lines? Is there a way to inherit abc_template, and just to override the relevant variables, Instead of creating xyz_template which might be very similar to abc_template?
Please advise.
You could use a map:
variable "global" {
type = "map"
default = {
name = "TEST"
addr = "Test123"
}
}
output "example" {
value = templatefile("${path.module}/web.tpl", {
global = var.global
})
}
template:
My name is ${global.name}.
And you can override values in a map using the merge() function.
You can use the merge function to produce a new map using a blend of elements from multiple maps.
For example:
locals {
abc_template = {
a = var.a
b = var.b
c = var.c
d = var.d
}
xyz_template = merge(
local.abc_template,
{
d = var.other_d
x = var.x
y = var.y
z = var.z
},
)
}
In the above example, local.xyz_template would have all of the same elements as local.abc_template except that d is overridden, and it would additionally have elements x, y, and z.

Resources