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

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",
))}"
}

Related

how to deploy escloud extension in terraform

I deploy escloud with terraform.
I want to add an existing extension, analysis-icu, how can I configure it?
resource "ec_deployment_extension" "icu" {
name = "analysis-icu"
version = "*"
extension_type = "bundle"
download_url = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-nori/analysis-nori-8.6.1.zip"
}
module "escloud_default" {
source = "./escloud"
name = "${var.environment}-test"
...
elasticsearch_config = {
topologies = [
{
id = "hot_content"
size = var.environment == "prod" ? "2g" : "1g"
size_resource = "memory"
zone_count = var.environment == "prod" ? 2 : 1
autoscaling = {
min_size = ""
min_size_resource = ""
max_size = "116g"
max_size_resource = "memory"
}
},
]
extensions = [
{
name = ec_deployment_extension.nori.name
type = "bundle"
version = "*"
url = ec_deployment_extension.nori.url
}
]
}
...
This code does not apply existing icu plugin, just create custom bundle.
i solved it. There is config.plugins arguments.
https://registry.terraform.io/providers/elastic/ec/latest/docs/resources/ec_deployment#plugins

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. How to identify element (key) using for_each

I'm building AWS Network LB with target groups. And I stuck on aws_lb_listener adding several target_group_arn in several http_tcp_listeners
I.e. I have two aws_lb_target_group for 80 and 443 ports and two http_tcp_listeners for these same ports.
But I have got this error message:
in resource "aws_lb_listener" "frontend_http_tcp":
│ 172: target_group_arn = each.value.arn
│ ├────────────────
│ │ each.value is map of string with 4 elements
│
│ This map does not have an element with the key "arn".
variable "aws_lb_target_group" {
description = "aws_lb_target_group"
type = map(any)
default = {
http = {
name = "http"
target_type = "instance"
port = 80
protocol = "TCP"
protocol_version = "HTTP1"
type = "source_ip"
enabled = false
path_health_check = "/health.html"
matcher_health_check = "200" # has to be HTTP 200 or fails
},
https = {
name = "https"
target_type = "instance"
port = 443
protocol = "TCP"
protocol_version = "HTTP2"
type = "source_ip"
enabled = false
path_health_check = "/health.html"
matcher_health_check = "200" # has to be HTTP 200 or fails
}
}
}
variable "http_tcp_listeners" {
description = "aws_lb_listener"
type = map(any)
default = {
http = {
port = "80"
protocol = "TCP"
action_type = "forward"
alpn_policy = "HTTP1Only"
},
https = {
port = "443"
protocol = "TCP"
action_type = "forward"
certificate_arn = "data.terraform_remote_state.acm.outputs.acm_certificate_arn"
alpn_policy = "HTTP2Preferred"
}
}
}
resource "aws_lb_target_group" "main" {
for_each = var.aws_lb_target_group
name = "test-group-${random_pet.this.id}-${each.value.name}"
target_type = each.value.target_type
port = each.value.port
protocol = each.value.protocol
protocol_version = each.value.protocol_version
vpc_id = local.vpc_id
stickiness {
type = "source_ip"
enabled = false
}
health_check {
path = each.value.path_health_check
port = each.value.port
healthy_threshold = 3
unhealthy_threshold = 3
interval = 30
}
depends_on = [
aws_lb.main,
]
}
resource "aws_lb_listener" "frontend_http_tcp" {
for_each = var.http_tcp_listeners
load_balancer_arn = aws_lb.main.arn
port = each.value.port
protocol = each.value.protocol
certificate_arn = data.terraform_remote_state.acm.outputs.acm_certificate_arn
alpn_policy = each.value.alpn_policy
dynamic "default_action" {
for_each = aws_lb_target_group.main
content {
type = "forward"
target_group_arn = each.value.arn
}
}
depends_on = [
aws_lb.main,
aws_lb_target_group.main,
]
}
When you use dynamic blocks, your key is not each, but the name of the block. So I think it should be:
target_group_arn = default_action.value.arn
To have only one default_action, try:
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.main[each.key].arn
}

How to add additional tag for EBS volume base on variable?

I'm using this EC2 module with lite alteration to create EC2 instances and EBS volumes, Code is working without an issue, But I have requirement to add mount point as a tag in EBS, So I can use data filter to get that value and mount it using Ansible.
Im trying to add tag value to "dynamic "ebs_block_device" through depoy-ec2.tf configuration file. As per the Terraform documentation tags is an optional value. Anyway, when I executing this it provided Unsupported argument error for tags value. Appreciate your support to understand issue here.
My Code as below.
Module main.tf
locals {
is_t_instance_type = replace(var.instance_type, "/^t(2|3|3a){1}\\..*$/", "1") == "1" ? true : false
}
resource "aws_instance" "this" {
count = var.instance_count
ami = var.ami
instance_type = var.instance_type
user_data = var.user_data
user_data_base64 = var.user_data_base64
subnet_id = length(var.network_interface) > 0 ? null : element(
distinct(compact(concat([var.subnet_id], var.subnet_ids))),
count.index,
)
key_name = var.key_name
monitoring = var.monitoring
get_password_data = var.get_password_data
vpc_security_group_ids = var.vpc_security_group_ids
iam_instance_profile = var.iam_instance_profile
associate_public_ip_address = var.associate_public_ip_address
private_ip = length(var.private_ips) > 0 ? element(var.private_ips, count.index) : var.private_ip
ipv6_address_count = var.ipv6_address_count
ipv6_addresses = var.ipv6_addresses
ebs_optimized = var.ebs_optimized
dynamic "root_block_device" {
for_each = var.root_block_device
content {
delete_on_termination = lookup(root_block_device.value, "delete_on_termination", null)
encrypted = lookup(root_block_device.value, "encrypted", null)
iops = lookup(root_block_device.value, "iops", null)
kms_key_id = lookup(root_block_device.value, "kms_key_id", null)
volume_size = lookup(root_block_device.value, "volume_size", null)
volume_type = lookup(root_block_device.value, "volume_type", null)
}
}
dynamic "ebs_block_device" {
for_each = var.ebs_block_device
content {
delete_on_termination = lookup(ebs_block_device.value, "delete_on_termination", null)
device_name = ebs_block_device.value.device_name
encrypted = lookup(ebs_block_device.value, "encrypted", null)
iops = lookup(ebs_block_device.value, "iops", null)
kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null)
snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null)
volume_size = lookup(ebs_block_device.value, "volume_size", null)
volume_type = lookup(ebs_block_device.value, "volume_type", null)
tags = lookup(ebs_block_device.value, "mount", null)
}
}
dynamic "ephemeral_block_device" {
for_each = var.ephemeral_block_device
content {
device_name = ephemeral_block_device.value.device_name
no_device = lookup(ephemeral_block_device.value, "no_device", null)
virtual_name = lookup(ephemeral_block_device.value, "virtual_name", null)
}
}
dynamic "metadata_options" {
for_each = length(keys(var.metadata_options)) == 0 ? [] : [var.metadata_options]
content {
http_endpoint = lookup(metadata_options.value, "http_endpoint", "enabled")
http_tokens = lookup(metadata_options.value, "http_tokens", "optional")
http_put_response_hop_limit = lookup(metadata_options.value, "http_put_response_hop_limit", "1")
}
}
dynamic "network_interface" {
for_each = var.network_interface
content {
device_index = network_interface.value.device_index
network_interface_id = lookup(network_interface.value, "network_interface_id", null)
delete_on_termination = lookup(network_interface.value, "delete_on_termination", false)
}
}
source_dest_check = length(var.network_interface) > 0 ? null : var.source_dest_check
disable_api_termination = var.disable_api_termination
instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior
placement_group = var.placement_group
tenancy = var.tenancy
tags = merge(
{
"Name" = var.instance_count > 1 || var.use_num_suffix ? format("%s${var.num_suffix_format}-EC2", var.name, count.index + 1) : format("%s-EC2",var.name)
},
{
"ResourceName" = var.instance_count > 1 || var.use_num_suffix ? format("%s${var.num_suffix_format}-EC2", var.name, count.index + 1) : format("%s-EC2",var.name)
},
{"Account" = var.Account,
"Environment" = var.Environment,
"ApplicationName" = var.ApplicationName,
"ApplicationID" = var.ApplicationID,
"Project" = var.Project,
"ProjectCode" = var.ProjectCode,
"Workload" = var.Workload,
"Division" = var.Division,
"Purpose" = var.Purpose,
"VersionNumber" = var.VersionNumber,
"RelVersion" = var.RelVersion,
"OSVersion" = var.OSVersion,
"DBVersion" = var.DBVersion,
"DataClassification" = var.DataClassification,
"Automation" = var.Automation,
"AWSResoureceType" = "EC2",
"BusinessEntitiy" = var.BusinessEntitiy,
"CostCentre" = var.CostCentre,
"BaseImageName" = var.BaseImageName},
var.tags,
)
volume_tags = merge(
{
"Name" = var.instance_count > 1 || var.use_num_suffix ? format("%s${var.num_suffix_format}-EBS", var.name, count.index + 1) : format("%s-EBS",var.name)
},
{
"ResourceName" = var.instance_count > 1 || var.use_num_suffix ? format("%s${var.num_suffix_format}-EBS", var.name, count.index + 1) : format("%s-EBS",var.name)
},
{"Account" = var.Account,
"Environment" = var.Environment,
"ApplicationName" = var.ApplicationName,
"ApplicationID" = var.ApplicationID,
"Project" = var.Project,
"ProjectCode" = var.ProjectCode,
"Workload" = var.Workload,
"Division" = var.Division,
"Purpose" = var.Purpose,
"VersionNumber" = var.VersionNumber,
"RelVersion" = var.RelVersion,
"OSVersion" = var.OSVersion,
"DBVersion" = var.DBVersion,
"DataClassification" = var.DataClassification,
"Automation" = var.Automation,
"AWSResoureceType" = "EC2",
"BusinessEntitiy" = var.BusinessEntitiy,
"CostCentre" = var.CostCentre,
"BaseImageName" = var.BaseImageName},
var.volume_tags,
)
credit_specification {
cpu_credits = local.is_t_instance_type ? var.cpu_credits : null
}
}
deploy-ec2.tf
module "mn-ec2" {
source = "../../../terraform12-modules/aws/ec2-instance"
instance_count = var.master_nodes
name = "${var.Account}-${var.Environment}-${var.ApplicationName}-${var.Project}-${var.Division}-${var.Purpose}-MN"
ami = var.ami_id
instance_type = var.master_node_ec2_type
subnet_ids = ["${data.aws_subnet.primary_subnet.id}","${data.aws_subnet.secondory_subnet.id}","${data.aws_subnet.tertiary_subnet.id}"]
vpc_security_group_ids = ["${module.sg-application-servers.this_security_group_id}"]
iam_instance_profile = "${var.iam_instance_profile}"
key_name = var.key_pair_1
Project = upper(var.Project)
Account = var.Account
Environment = var.Environment
ApplicationName = var.ApplicationName
ApplicationID = var.ApplicationID
ProjectCode = var.ProjectCode
Workload = var.Workload
Division = var.Division
RelVersion = var.RelVersion
Purpose = var.Purpose
DataClassification = var.DataClassification
CostCentre = var.CostCentre
Automation = var.Automation
tags = {
node_type = "master"
}
volume_tags = {
node_type = "master"
}
root_block_device = [
{
encrypted = true
kms_key_id = var.kms_key_id
volume_type = "gp2"
volume_size = 250
},
]
ebs_block_device = [
{
device_name = "/dev/sdc"
encrypted = true
kms_key_id = var.kms_key_id
volume_type = "gp2"
volume_size = 500
mount = "/x02"
},
{
device_name = "/dev/sdd"
encrypted = true
kms_key_id = var.kms_key_id
volume_type = "gp2"
volume_size = 1000
mount = "/x03"
},
{
device_name = "/dev/sde"
encrypted = true
kms_key_id = var.kms_key_id
volume_type = "gp2"
volume_size = 10000
mount = "/x04"
},
]
}
The issue with AWS provider, which didn't have much options, So I have upgraded to terraform-provider-aws_3.24.0_linux_amd64.zip and now can be added specific tags for each EBS volume
I ran into a similar problem. Changing from terraform-provider-aws=2 to terraform-provider-aws=3 worked.

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

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"
}

Resources