Terraform - Iterate through map of maps depending on value of internal map - for-loop

i'm trying to create a resource depending on a value of the internal map of a map.
If that specific value is true then create the resource
Please find below the code:
variable "ip_restrictions" {
type = map(map(string))
default = {
test01 = {
name = "test01"
start_ip_address = "0.0.0.0"
end_ip_address = "0.0.0.0"
is_firewall = false
},
test02 = {
name = "test02"
start_ip_address = "0.0.0.0"
end_ip_address = "0.0.0.0"
is_firewall = true
}
}
}
resource "azurerm_sql_firewall_rule" "sql_firewall_rules" {
for_each = {
for restr in var.ip_restrictions :
restr => restr
if restr.is_firewall == true
}
name = each.value.name
resource_group_name = azurerm_resource_group.rg.name
server_name = azurerm_sql_server.sqls.name
start_ip_address = each.value.start_ip_address
end_ip_address = each.value.end_ip_address
}

The correct form of your for_each should be:
for_each = {
for key, restr in var.ip_restrictions :
key => restr if restr.is_firewall == "true"
}
This will filter out all records with is_firewall of false.

Since your var.ip_restrictions is a map and not a list, you need to iterate over it like a map (returning a key and value). The shortest possible way in your situation:
for_each = {
for key, restr in var.ip_restrictions :
key => restr if restr.is_firewall
}
Gives:
"test02" = {
"end_ip_address" = "0.0.0.0"
"is_firewall" = "true"
"name" = "test02"
"start_ip_address" = "0.0.0.0"
}

Related

How to create multiple ec2 instance across different subnets and Az zones with private ip address

I have a slight issue. I have 3 subnet cidr blocks with 3 different az groups. I am passing a list of static Ip address for these instance to get assigned in order. My code however is throwing "ip address is out of range for the subnet" which makes sense because it's just going from ip 0 to the N instance how can i properly make sure the instance gets placed in the proper subnet. I hope my question makes sense and is clear please see the code below thank you all for the help! The code belows creates the instance
### Start of Radient FID Server ###
resource "aws_instance" "FID" {
depends_on = [aws_kms_key.aws-wm-wmad-prod]
disable_api_termination = false
count = var.How_many_FID
ami = var.windows_dc_ami_2016
availability_zone = element(var.availability_zones, count.index)
ebs_optimized = var.windows_dc_ebs_optimized
instance_type = var.windows_dc_instance_type_FID
key_name = var.Key_Pair_Ec2
monitoring = true
vpc_security_group_ids = [aws_security_group.Private01.id]
subnet_id = element(aws_subnet.private_subnet_cidr_blocks_Apps, count.index).id
private_ip = "${lookup(var.ips,count.index)}"
associate_public_ip_address = false
tags = merge(
{
Name = element(var.Radiant_FID_Server_Tags, count.index)
Project = var.project,
Environment = var.environment
},
var.tags
)
I have a variables file which i'm passing the private Ip addresses:
variable "ips" {
default = {
"0" = "10.7.90.79"
"1" = "10.7.90.80"
"2" = "10.7.90.81"
"3" = "10.7.90.82"
"4" = "10.7.90.90"
"5" = "10.7.90.84"
"6" = "10.7.90.85"
"7" = "10.7.90.86"
"8" = "10.7.90.87"
"9" = "10.7.90.88"
}
}
##how i create my subnets
resource "aws_subnet" "private_subnet_cidr_blocks_AD" {
count = length(var.private_subnet_cidr_blocks_AD) # count = 3
vpc_id = aws_vpc.default.id #id34odfjdf
cidr_block = var.private_subnet_cidr_blocks_AD[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(
{
Name = element(var.private_subnet_cidr_blocks_AD_NameTag, count.index),
Project = var.project,
Environment = var.environment
},
var.tags
)
}
So let's say you have the following CIDR block list defined:
private_subnet_cidr_blocks_AD = ["10.7.90.64/27", "10.7.90.96/27","10.7.90.160/27"]
Then you could define your IP list like this:
variable "ips" {
default = [
{ subnet=0, ip="10.7.90.79" },
{ subnet=0, ip="10.7.90.80" },
{ subnet=0, ip="10.7.90.81" },
# etc...
{ subnet=1, ip="10.7.90.100" },
{ subnet=1, ip="10.7.90.101" },
# etc...
{ subnet=2, ip="10.7.90.170" },
{ subnet=2, ip="10.7.90.171" },
]
}
Each subnet number being the index in the private_subnet_cidr_blocks_AD list that corresponds to the CIDR block that the IP belongs to.
Then your instance definition could look like this:
resource "aws_instance" "FID" {
for_each = toset( var.ips )
subnet_id = aws_subnet.private_subnet_cidr_blocks_Apps[each.value.subnet].id
private_ip = each.value.ip

terraform create list from yaml to be used in subject_alternative_names

I have this yaml structure and I need terraform to put apex_name and each record entry to be stored in a list which I can then use to create a san certificate. How can I achive this to be flat list for the subject_alternative_names? Any help is highly appreciated.
source_domains:
- apex_name: elastic2ls.com
records:
- elastic2ls.com
- www.elastic2ls.com
- apex_name: elastic2ls.ch
records:
- elastic2ls.ch
- www.elastic2ls.ch
- image.elastic2ls.ch
- m.elastic2ls.ch
- static.elastic2ls.ch
resource "aws_acm_certificate" "cert" {
for_each = var.subdomains
provider = aws.certificate_region
domain_name = var.target_domain
subject_alternative_names = sort(each.value)
validation_method = "DNS"
}
variable "source_domains" {
type = set(object({
apax_name = string
records = set(string)
}))
}
Load your yml into a local value, then use a for statement to massage it into something that fits your use case:
locals {
source_domains = yaml_decode(file("myYaml.yml"))
}
resource "aws_acm_certificate" "cert" {
for_each = tomap({ for d in local.source_domains :
d.apex_name => d })
provider = aws.certificate_region
domain_name = each.key
subject_alternative_names = sort(flatten([each.key, each.value.records]))
validation_method = "DNS"
}
I've assumed that you want to use apex_name as your domain name, and that apex_name was the correct value, and apax_name was a typo.
I was able to achive getting all domain names into the SAN certificate with this terraform code.
locals {
subject_alternative_names = flatten([
for d in var.source_domains :
contains(keys(d), "records") ? d.records: null
])
}
resource "aws_acm_certificate" "cert" {
provider = aws.certificate_region
domain_name = var.target_domain
subject_alternative_names = local.subject_alternative_names
validation_method = "DNS"
}
variable "source_domains" {
type = set(object({
apex_name = string
records = set(string)
}))
}

How to use environment variables in terraform using TF_VAR_name?

I'm trying to export list variables and use them via TF_VAR_name and getting error while combining them with toset function.
Success scenario:
terraform apply -auto-approve
# Variables
variable "sg_name" { default = ["SG1", "SG2", "SG3", "SG4", "SG5"] }
variable "Project" { default = "POC" }
variable "Owner" { default = "Me" }
variable "Environment" { default = "Testing" }
locals {
common_tags = {
Project = var.Project
Owner = var.Owner
Environment = var.Environment
}
}
# Create Security Group
resource "aws_security_group" "application_sg" {
for_each = toset(var.sg_name)
name = each.value
description = "${each.value} security group"
tags = merge(local.common_tags, { "Name" = each.value })
}
# Output the SG IDs
output "sg_id" {
value = values(aws_security_group.application_sg)[*].id
}
Failure scenario:
TF_VAR_sg_name='["SG1", "SG2", "SG3", "SG4", "SG5"]' terraform apply -auto-approve
# Variables
variable "sg_name" { }
variable "Project" { default = "POC" }
variable "Owner" { default = "Me" }
variable "Environment" { default = "Testing" }
locals {
common_tags = {
Project = var.Project
Owner = var.Owner
Environment = var.Environment
}
}
# Create Security Group
resource "aws_security_group" "application_sg" {
for_each = toset(var.sg_name)
name = each.value
description = "${each.value} security group"
tags = merge(local.common_tags, { "Name" = each.value })
}
# Output the SG IDs
output "sg_id" {
value = values(aws_security_group.application_sg)[*].id
}
Error
Error: Invalid function argument
on main.tf line 16, in resource "aws_security_group" "application_sg":
16: for_each = toset(var.sg_name)
|----------------
| var.sg_name is "[\"SG1\", \"SG2\", \"SG3\", \"SG4\", \"SG5\"]"
Invalid value for "v" parameter: cannot convert string to set of any single
type.
You'll need to specify the type (i.e. type = list(string) in your case) of your variable then it should work.
I tested it with the following configuration:
variable "sg_name" {
type = list(string)
}
resource "null_resource" "application_sg" {
for_each = toset(var.sg_name)
triggers = {
name = each.key
}
}
Then a TF_VAR_sg_name='["SG1", "SG2", "SG3", "SG4", "SG5"]' terraform apply works.
If I remove the type = list(string) it errors out as you say.

Terraform output object with multiple attributes for each of `for` resources?

I have terraform with a resource being created with for. As is typical, each instance of this resource has several attributes. At the moment I have a series of map outputs for this resource group but each consists of only a single key-value pair. I would like my terraform output to include a list or map of maps or objects with all of the attributes grouped by resource instance. How do I do this without using flatten; zipmap etc to construct them from my current outputs? This example is with aws_route53_record but this is a generic query:
Current code
output "r53record_zonal_fqdn" {
value = {
for entry in aws_route53_record.zonal :
entry.name => entry.fqdn
}
}
output "r53record_zonal_records" {
value = {
for entry in aws_route53_record.zonal :
entry.name => entry.records
}
}
output "r53record_zonal_zone_id" {
value = {
for entry in aws_route53_record.zonal :
entry.name => entry.zone_id
}
}
As you would expect, this renders three maps with aws_route53_record.zonal.name as the key and the other attribute(s) as the value.
What I would like is to have these outputs grouped by resource with a predefined key for each value, e.g. (pseudocode):
output "r53record_zonal_zone_id" {
value = {
for entry in aws_route53_record.zonal : {
instance[count.index] {
"name" = entry.name
"fqdn" = entry.fqdn
"records" = entry.records
"zone_id" = entry.zone_id
}
}
}
}
Producing a map or list of maps for each instance.
How can this or something like it be done?
I created a random route53_record resource block with two "name" arguments in for_each loop and tried to output something close to what you were looking for.
Assuming "mydomain.com" is the domain in Route53 as example....
resource "aws_route53_record" "zonal" {
for_each=toset(["site1","site2"])
name = each.key
zone_id = "ABCDZONEIDSTRING"
type = "A"
ttl = "300"
records = ["192.168.1.10"]
}
output "test" {
value = {
for dns, details in aws_route53_record.zonal:
dns => ({"fqdn" = details.fqdn , "zone_id" = details.zone_id , "records" = details.records})
}
}
this will create output in this fashion..
test = {
"site1" = {
"fqdn" = "site1.mydomain.com"
"records" = [
"192.168.1.10",
]
"zone_id" = "Z0630117NTQNSYTXYQ4Z"
}
"site2" = {
"fqdn" = "site2.mydomain.com"
"records" = [
"192.168.1.10",
]
"zone_id" = "Z0630117NTQNSYTXYQ4Z"
}
}
Suppose you passed the name values with domain name, like below...
for_each=toset(["site1.mydomain.com","site2.mydomain.com"])
the output would look like
test = {
"site1.mydomain.com" = {
"fqdn" = "site1.mydomain.com"
"records" = [
"192.168.1.10",
]
"zone_id" = "ABCDMYZONEIDSTRING"
}
"site2.mydomain.com" = {
"fqdn" = "site2.mydomain.com"
"records" = [
"192.168.1.10",
]
"zone_id" = "ABCDMYZONEIDSTRING"
}
}

How to use locals in terraform to repeat and merge blocks?

I have multiple docker_container resources:
resource "docker_container" "headerdebug" {
name = "headerdebug"
image = "${docker_image.headerdebug.latest}"
labels {
"traefik.frontend.rule" = "Host:debug.in.bb8.fun"
"traefik.port" = 8080
"traefik.enable" = "true"
"traefik.frontend.passHostHeader" = "true"
"traefik.frontend.headers.SSLTemporaryRedirect" = "true"
"traefik.frontend.headers.STSSeconds" = "2592000"
"traefik.frontend.headers.STSIncludeSubdomains" = "false"
"traefik.frontend.headers.customResponseHeaders" = "${var.xpoweredby}"
"traefik.frontend.headers.customFrameOptionsValue" = "${var.xfo_allow}"
}
}
And another one:
resource "docker_container" "cadvisor" {
name = "cadvisor"
image = "${docker_image.cadvisor.latest}"
labels {
"traefik.frontend.rule" = "Host:cadvisor.bb8.fun"
"traefik.port" = 8080
"traefik.enable" = "true"
"traefik.frontend.headers.SSLTemporaryRedirect" = "true"
"traefik.frontend.headers.STSSeconds" = "2592000"
"traefik.frontend.headers.STSIncludeSubdomains" = "false"
"traefik.frontend.headers.contentTypeNosniff" = "true"
"traefik.frontend.headers.browserXSSFilter" = "true"
"traefik.frontend.headers.customFrameOptionsValue" = "${var.xfo_allow}"
"traefik.frontend.headers.customResponseHeaders" = "${var.xpoweredby}"
}
}
I'm trying to use locals to re-use the common labels between both the containers. I have the following local defined:
locals {
traefik_common_labels {
"traefik.frontend.passHostHeader" = "true"
"traefik.frontend.headers.SSLTemporaryRedirect" = "true"
"traefik.frontend.headers.STSSeconds" = "2592000"
"traefik.frontend.headers.STSIncludeSubdomains" = "false"
"traefik.frontend.headers.customResponseHeaders" = "${var.xpoweredby}"
"traefik.frontend.headers.customFrameOptionsValue" = "${var.xfo_allow}"
}
}
But the documentation doesn't mention how to use locals for merging entire blocks, only maps.
I've tried the following:
labels "${merge(
local.traefik_common_labels,
map(
"traefik.frontend.rule", "Host:debug.in.bb8.fun",
"traefik.port", 8080,
"traefik.enable", "true",
)
)}"
which gives the following error:
tf11 plan
Error: Failed to load root config module: Error loading modules: module docker: Error parsing .terraform/modules/2f3785083ce0d0ac2dd3346cf129e795/main.tf: key 'labels "${merge(
local.traefik_common_labels,
map(
"traefik.frontend.rule", "Host:debug.in.bb8.fun",
"traefik.port", 8080,
"traefik.enable", "true",
)
)}"' expected start of object ('{') or assignment ('=')
There is a pretty diff of my attempts at this PR: https://git.captnemo.in/nemo/nebula/pulls/4/files
In Terraform 1.x+ you can use a dynamic block to achieve this
variable "xpoweredby" { default = "" }
variable "xfo_allow" { default = "" }
locals {
traefik_common_labels = {
"traefik.frontend.passHostHeader" = "true"
"traefik.frontend.headers.SSLTemporaryRedirect" = "true"
"traefik.frontend.headers.STSSeconds" = "2592000"
"traefik.frontend.headers.STSIncludeSubdomains" = "false"
"traefik.frontend.headers.customResponseHeaders" = var.xpoweredby
"traefik.frontend.headers.customFrameOptionsValue" = var.xfo_allow
}
}
resource "docker_image" "cadvisor" {
name = "google/cadvisor:latest"
}
resource "docker_container" "cadvisor" {
name = "cadvisor"
image = docker_image.cadvisor.latest
dynamic "labels" {
for_each = merge(local.traefik_common_labels,
{
"traefik.frontend.rule" = "Host:debug.in.bb8.fun",
"traefik.port" = 8080,
"traefik.enable" = "true",
}
)
content {
label = labels.key
value = labels.value
}
}
}
In Terraform 0.11 etc, this could be accomplised with the following:
You need to assign the value to labels like so
locals {
traefik_common_labels {
"traefik.frontend.passHostHeader" = "true"
"traefik.frontend.headers.SSLTemporaryRedirect" = "true"
"traefik.frontend.headers.STSSeconds" = "2592000"
"traefik.frontend.headers.STSIncludeSubdomains" = "false"
"traefik.frontend.headers.customResponseHeaders" = "${var.xpoweredby}"
"traefik.frontend.headers.customFrameOptionsValue" = "${var.xfo_allow}"
}
}
resource "docker_container" "cadvisor" {
name = "cadvisor"
image = "${docker_image.cadvisor.latest}"
labels = "${merge(
local.traefik_common_labels,
map(
"traefik.frontend.rule", "Host:debug.in.bb8.fun",
"traefik.port", 8080,
"traefik.enable", "true",
))}"
}

Resources