I am trying to pick up both elements in my query_string tuple. However, only the last element is being picked up. Below, is my test/main.tf file:
terraform {
required_version = ">= 0.13.0"
required_providers {
aws = ">= 3.58"
}
}
provider "aws" {
region = "us-east-1"
}
module "test_module" {
source = "../"
lb = [
{
name = "lb-name"
enable_deletion_protection = true
internal = false
load_balancer_type = "application"
lb_listener = [
{
port = "8080"
protocol = "HTTP"
ssl_policy = "ELBSecurityPolicy-2016-08"
default_action = {
type = "redirect"
order = 1
redirect = {
status_code = "HTTP_302"
}
}
lb_listener_rule = [
{
action = {
type = "fixed-response"
fixed_response = {
content_type = "text/plain"
message_body = "This is a message body."
}
}
condition = {
query_string = [
{
key = "health"
value = "check"
},
{
value = "bar"
}
]
}
}
]
}
]
}
]
}
Here is my lb.tf file:
variable "lb" {
description = <<DESCRIPTION
The AWS Elastic Load Balancing v2 module.
DESCRIPTION
type = any
default = []
}
locals {
lb_listener_flat_list = flatten([
for lb in var.lb : [
for lb_listener in lookup(lb, "lb_listener", []) : {
lb_name = lb.name
lb_listener_load_balancer_arn = aws_lb.lb[lb.name].arn
lb_listener_port = lookup(lb, "load_balancer_type", "application") != "gateway" ? lookup(lb_listener, "port", null) : null
lb_listener_protocol = lookup(lb, "load_balancer_type", "application") != "gateway" || lookup(lb, "ip_address_type", null) != "dualstack" ? lookup(lb_listener, "protocol", null) : null
lb_listener_ssl_policy = lookup(lb_listener, "protocol", null) != "HTTPS" || lookup(lb_listener, "protocol", null) != "TLS" ? lb_listener.ssl_policy : lookup(lb_listener, "ssl_policy", null)
lb_listener_default_action_type = lb_listener.default_action.type
lb_listener_default_action_order = lookup(lb_listener.default_action, "order", null)
lb_listener_default_action_redirect = lookup(lb_listener.default_action, "redirect", {}) != {} ? {
lb_listener_default_action_redirect_status_code = lb_listener.default_action.redirect.status_code
} : null
}
]
])
lb_listener_rule_flat_list = flatten([
for lb in var.lb : [
for lb_listener in lookup(lb, "lb_listener", []) : [
for lb_listener_rule in lookup(lb_listener, "lb_listener_rule", []) : [
# I've recently added the line below in an attempte to loop through the list of maps for query_string
for index in range(length(lookup(lb_listener_rule.condition, "query_string", []))) : {
lb_name = lb.name
lb_listener_rule_index = index
lb_listener_rule_listener_arn = aws_lb_listener.lb_listener[lb.name].arn
lb_listener_rule_action_type = lb_listener_rule.action.type
lb_listener_rule_action_fixed_response = lookup(lb_listener_rule.action, "fixed_response", {}) != {} ? {
lb_listener_rule_action_fixed_response_content_type = lb_listener_rule.action.fixed_response.content_type
lb_listener_rule_action_fixed_response_message_body = lookup(lb_listener_rule.action.fixed_response, "message_body", null)
} : null
lb_listener_rule_condition_query_string = lookup(lb_listener_rule.condition, "query_string", {}) != {} ? {
lb_listener_rule_condition_query_string_key = lookup(lb_listener_rule.condition.query_string[index], "key", null)
lb_listener_rule_condition_query_string_value = lb_listener_rule.condition.query_string[index].value
} : null
}
]
]
]
])
lb_map = { for lb in var.lb : lb.name => lb }
lb_listener_map = { for lb_listener in local.lb_listener_flat_list : lb_listener.lb_name => lb_listener }
lb_listener_rule_map = { for lb_listener_rule in local.lb_listener_rule_flat_list : "${lb_listener_rule.lb_name}-[${lb_listener_rule.lb_listener_rule_index}]" => lb_listener_rule }
}
resource "aws_lb_listener" "lb_listener" {
for_each = local.lb_listener_map
load_balancer_arn = each.value.lb_listener_load_balancer_arn
port = each.value.lb_listener_port
protocol = each.value.lb_listener_protocol
ssl_policy = each.value.lb_listener_ssl_policy
default_action {
type = each.value.lb_listener_default_action_type
order = each.value.lb_listener_default_action_order
dynamic "redirect" {
for_each = each.value.lb_listener_default_action_redirect != null ? [{}] : []
content {
status_code = each.value.lb_listener_default_action_redirect.lb_listener_default_action_redirect_status_code
}
}
}
}
resource "aws_lb_listener_rule" "lb_listener_rule" {
for_each = local.lb_listener_rule_map
listener_arn = each.value.lb_listener_rule_listener_arn
action {
type = each.value.lb_listener_rule_action_type
dynamic "fixed_response" {
for_each = each.value.lb_listener_rule_action_fixed_response != null ? [{}] : []
content {
content_type = each.value.lb_listener_rule_action_fixed_response.lb_listener_rule_action_fixed_response_content_type
message_body = each.value.lb_listener_rule_action_fixed_response.lb_listener_rule_action_fixed_response_message_body
}
}
}
condition {
dynamic "query_string" {
for_each = each.value.lb_listener_rule_condition_query_string != null ? [{}] : []
content {
key = each.value.lb_listener_rule_condition_query_string.lb_listener_rule_condition_query_string_key
value = each.value.lb_listener_rule_condition_query_string.lb_listener_rule_condition_query_string_value
}
}
}
}
resource "aws_lb" "lb" {
for_each = local.lb_map
name = lookup(each.value, "name", null)
enable_deletion_protection = lookup(each.value, "enable_deletion_protection", null)
internal = lookup(each.value, "internal", null)
ip_address_type = lookup(each.value, "ip_address_type", null)
load_balancer_type = lookup(each.value, "load_balancer_type", null)
}
Here is how the condition block looks when I run a terraform plan:
condition {
query_string {
value = "bar"
}
}
Here is how I would like the condition block to look:
condition {
query_string {
key = "health"
value = "check"
}
query_string {
value = "bar"
}
}
Your code, as complex as it is creates the two query_string correctly:
The code, in the form posted in the question has number of issues and it will not work. But then as you wrote that "My original files are very long. ", I guess that your real code actually works. Thus I only focused on its verification, rather then fixing all possible errors as I don't know what is your factual code.
Related
Im trying to add retention policy but I want to enable it conditionally, as you can see from the code
buckets.tf
locals {
team_buckets = {
arc = { app_id = "20390", num_buckets = 2, retention_period = null }
ana = { app_id = "25402", num_buckets = 2, retention_period = 631139040 }
cha = { app_id = "20391", num_buckets = 2, retention_period = 631139040 } #20 year
}
}
module "team_bucket" {
source = "../../../../modules/gcs_bucket"
for_each = {
for bucket in flatten([
for product_name, bucket_info in local.team_buckets : [
for i in range(bucket_info.num_buckets) : {
name = format("%s-%02d", product_name, i + 1)
team = "ei_${product_name}"
app_id = bucket_info.app_id
retention_period = bucket_info.retention_period
}
]
]) : bucket.name => bucket
}
project_id = var.project
name = "teambucket-${each.value.name}"
app_id = each.value.app_id
team = each.value.team
retention_period = each.value.retention_period
}
root module is defined as follows
main.tf
resource "google_storage_bucket" "bucket" {
project = var.project_id
name = "${var.project_id}-${var.name}"
location = var.location
labels = {
app_id = var.app_id
ei_team = var.team
cost_center = var.cost_center
}
uniform_bucket_level_access = var.uniform_bucket_level_access
dynamic "retention_policy" {
for_each = var.retention_policy == null ? [] : [var.retention_period]
content {
retention_period = var.retention_period
}
}
}
but I can't seem to make the code pick up the value,
for example as you see below the value doesn't get implemented
~ resource "google_storage_bucket" "bucket" {
id = "teambucket-cha-02"
name = "teambucket-cha-02"
# (11 unchanged attributes hidden)
- retention_policy {
- is_locked = false -> null
- retention_period = 3155760000 -> null
}
}
variables.tf for retention policy is as follows
variable "retention_policy" {
description = "Configuation of the bucket's data retention policy for how long objects in the bucket should be retained"
type = any
default = null
}
variable "retention_period" {
default = null
}
Your var.retention_policy is always null, as its default value. You are not changing the default value at all. Probably you wanted the following:
for_each = var.retention_period == null ? [] : [var.retention_period]
instead of
for_each = var.retention_policy == null ? [] : [var.retention_period]
How can I fetch public IP address for compute instances created by google_compute_instance_template using terraform.
google_compute_instance_template
There is no attribute to get name of the compute instances created . I can see only name_prefix which will be suffixed with some random string.
I was thinking of using google_compute_instance data source which as attribute network_interface.0.access_config.0.nat_ip , but it requires compute instance name.
eg.
data "google_compute_instance" "appserver" {
name = "primary-application-server"
zone = "us-central1-a"
}
Below is the code I am using , I am attaching compute instance group manager to the backend service of a load balancer.
I need the public ip of the instance for connecting to SQL database
resource "google_compute_region_instance_group_manager" "mig" {
project = var.project
name = "${var.name}-instance-group"
region = var.region
version {
instance_template = google_compute_instance_template.instance_template.id
name = "primary"
}
named_port {
name = "https"
port = 443
}
base_instance_name = "${var.name}-mig"
target_size = var.instance_count
}
resource "google_compute_instance_template" "instance_template" {
name_prefix = "${var.name}-instance"
project = var.project
machine_type = var.machine_type
labels = var.labels
tags = local.firewall_rules.target_tags
can_ip_forward = var.can_ip_forward
metadata_startup_script = file("${path.module}/${var.startup_script}")
region = var.region
#min_cpu_platform = var.min_cpu_platform
dynamic "disk" {
for_each = local.all_disks
content {
auto_delete = lookup(disk.value, "auto_delete", null)
boot = lookup(disk.value, "boot", null)
device_name = lookup(disk.value, "device_name", null)
disk_name = lookup(disk.value, "disk_name", null)
disk_size_gb = lookup(disk.value, "disk_size_gb", lookup(disk.value, "disk_type", null) == "local-ssd" ? "375" : null)
disk_type = lookup(disk.value, "disk_type", null)
interface = lookup(disk.value, "interface", lookup(disk.value, "disk_type", null) == "local-ssd" ? "NVME" : null)
mode = lookup(disk.value, "mode", null)
source = lookup(disk.value, "source", null)
source_image = lookup(disk.value, "source_image", null)
type = lookup(disk.value, "disk_type", null) == "local-ssd" ? "SCRATCH" : "PERSISTENT"
#labels = lookup(disk.value, "disk_labels", {})
dynamic "disk_encryption_key" {
for_each = compact([var.disk_encryption_key == null ? null : 1])
content {
kms_key_self_link = var.disk_encryption_key
}
}
}
}
dynamic "service_account" {
for_each = [var.service_account]
content {
email = lookup(service_account.value, "email", null)
scopes = lookup(service_account.value, "scopes", null)
}
}
dynamic "network_interface" {
for_each = var.network_interfaces
iterator = config
content {
network = config.value.network
subnetwork = "projects/${var.project}/regions/${var.region}/subnetworks/${config.value.subnetwork}"
network_ip = try(config.value.addresses.internal, null)
dynamic "access_config" {
for_each = config.value.nat ? [""] : []
content {
nat_ip = try(config.value.addresses.external, null)
}
}
dynamic "alias_ip_range" {
for_each = local.network_interface_options[config.key].alias_ips != null ? local.network_interface_options[config.key].alias_ips : {}
iterator = config_alias
content {
subnetwork_range_name = config_alias.key
ip_cidr_range = config_alias.value
}
}
nic_type = local.network_interface_options[config.key].nic_type
}
}
lifecycle {
create_before_destroy = "true"
}
# scheduling must have automatic_restart be false when preemptible is true.
scheduling {
automatic_restart = !var.options.preemptible
on_host_maintenance = local.on_host_maintenance
preemptible = var.options.preemptible
}
dynamic "shielded_instance_config" {
for_each = var.shielded_config != null ? [var.shielded_config] : []
iterator = config
content {
enable_secure_boot = config.value.enable_secure_boot
enable_vtpm = config.value.enable_vtpm
enable_integrity_monitoring = config.value.enable_integrity_monitoring
}
}
dynamic "confidential_instance_config" {
for_each = var.confidential_compute ? [""] : []
content {
enable_confidential_compute = true
}
}
I think this can be done by the following:
#Get the list of instances
data "google_compute_region_instance_group" "mig_data" {
name = google_compute_region_instance_group_manager.mig.name
region = var.region
}
#Get each instance data
data "google_compute_instance" "intance_data" {
count = length(data.google_compute_region_instance_group.mig_data.instances)
self_link = data.google_compute_region_instance_group.mig_data.instances[count.index].instance
}
#Print the data needed
output "public_ips" {
value = [
for instance in data.google_compute_instance.intance_data:
[
instance.name, instance.network_interface.0.access_config.0.nat_ip
]
]
}
Basically we need to get the list of instances created in the MIG using the data.google_compute_region_instance_group and then iterate over this list to get the data for each instance.
I'm using output just to print the IPs but of course you can use those values in any other resource if you want.
I'm trying to take each value from the list in the map and iterate over it with the description in a map for a prefix list but can't work out how.
Variable:
users = {
"user1" = {
description = ""
secret_key_value = {
username = "user1"
home_directory = "/user1/"
}
allowlist = ["200.0.0.1/32"]
},
"user2" = {
description = ""
secret_key_value = {
username = "user2"
home_directory = "/user2/"
}
allowlist = ["200.0.0.5/32", "200.0.0.10/32"]
}
Resource:
resource "aws_ec2_managed_prefix_list" "sftp" {
count = local.prefix_list_enabled ? 1 : 0
name = "User Whitelist"
address_family = "IPv4"
max_entries = 10
dynamic "entry" {
for_each = {
for k, v in var.users : k => v
if v.allowlist != "" || v.description != ""
}
content {
cidr = entry.value.allowlist
description = entry.value.description
}
}
}
With the above, I'm getting "Inappropriate value for attribute "cidr": string required.". I need to break up the list values in the allowlist variable key and iterate through them with the description. Does anyone know how I can achieve this?
You have to flatten your users:
locals {
users_flat = merge([
for k,v in var.users: {
for cidr in v.allowlist:
"${k}-${cidr}" => {
description = v.description
secret_key_value = v.secret_key_value
"cidr" = cidr
}
}
]...)
}
then
dynamic "entry" {
for_each = local.users_flat
content {
cidr = entry.value.cidr
description = entry.value.description
}
}
I am adding routes to route table using module. Below is my code. It runs successfully but routes don't get added.
module.tf: (This checks if the publicRoute & privateRoute has more than one item, it will add that many routes to route table)
resource "aws_route" "public_routes" {
count = length(var.ExtraRoutes.publicRoute) > 1 ? length(var.ExtraRoutes.publicRoute) : 0
route_table_id = aws_route_table.VPCPublicSubnetRouteTable[0].id
destination_cidr_block = length(regexall("^[0-9].*.[0-9].*",var.ExtraRoutes.publicRoute[count.index].destination)) != 0 ? var.ExtraRoutes.publicRoute[count.index].destination : null
gateway_id = length(regexall("^igw-.*",var.ExtraRoutes.publicRoute[count.index].target)) != 0 ? var.ExtraRoutes.publicRoute[count.index].target : null
}
resource "aws_route" "private_routes" {
count = length(var.ExtraRoutes.privateRoute) > 1 ? length(var.ExtraRoutes.privateRoute) : 0
route_table_id = aws_route_table.VPCPrivateSubnetRouteTable[0].id
destination_cidr_block = length(regexall("^[0-9].*.[0-9].*",var.ExtraRoutes.privateRoute[count.index].destination)) != 0 ? var.ExtraRoutes.privateRoute[count.index].destination : null
gateway_id = length(regexall("^igw-.*",var.ExtraRoutes.privateRoute[count.index].target)) != 0 ? var.ExtraRoutes.privateRoute[count.index].target : null
}
module_var.tf (I am keeping it only a map)
variable "ExtraRoutes" {
type = map
default = {
publicRoute = []
privateRoute = []
}
}
main.tf (As I need the first item in ExtraRoutes for something else I want from count.index + 1)
module "ExtraVPCs" {
source = "./modules/VPC"
count = length(var.ExtraRoutes)
ExtraRoutes = {
publicRoute = var.ExtraRoutes[count.index + 1].publicRoute
privateRoute = var.ExtraRoutes[count.index + 1].privateRoute
}
}
main_var.tf
variable "ExtraRoutes" {
type = list(object({
publicRoute = list(object({
destination = string
target = string
})
)
privateRoute = list(object({
destination = string
target = string
}))
}))
}
init.tfvars (There are 2 items in ExtraRoutes. It should add the 2nd item in Route table but it's not working as expected.
ExtraRoutes = [
{
publicRoute = [
{
destination = "10.0.0.0/32"
target = "igw-092aba6c187183f48"
}
]
privateRoute = [
{
destination = "10.0.0.0/32"
target = "igw-092aba6c187183f48"
}
]
},
{
publicRoute = [
{
destination = "10.0.0.0/32"
target = "igw-0acf4f7ac1e7eba47"
}
]
privateRoute = [
{
destination = "10.0.0.0/32"
target = "igw-0acf4f7ac1e7eba47"
}
]
}
]
You check the length of a list using >0, not >1:
count = length(var.ExtraRoutes.publicRoute) > 0 ? length(var.ExtraRoutes.publicRoute) : 0
TF counts items from 0. When you use >1, in your case you end up with count = 0.
I wanted to create a for_each loop that loops only over objects in array that have some specific key : value pair.
My input variables are:
inputs = {
names = ["first", "second"]
lifecycle_rules = [
{
name = first
condition = {
age = "1"
}
action = {
type = "Delete"
}
},{
condition = {
age = "2"
}
action = {
type = "Delete"
}
},
{
name = second
condition = {
age = "3"
}
action = {
type = "Delete"
}
},{
condition = {
age = "4"
}
action = {
type = "Delete"
}
}
]
and in my main.tf (btw for deploying gcp bucket for reference), I wanted to separate the lifecycle per bucket and wanted to apply only the rules that have the buckets name in it.
So if anyone has idea how to modify for_each code below to work, I would highly appreciate. I believe only the for_each needs to be changed to loop over the right elements (let's say only objects in that list that have name = first). from the var.lifecycle_rules set
resource "google_storage_bucket" "buckets" {
count = length(var.names)
name = "${lower(element(var.names, count.index))}"
...
dynamic "lifecycle_rule" {
#for_each = length(lookup(lifecycle_rules[lookup(element(var.names, count.index))])
for_each = lifecycle_rules
content {
action {
type = lifecycle_rule.value.action.type
storage_class = lookup(lifecycle_rule.value.action, "storage_class", null)
}
condition {
#age = lifecycle_rule.value.name == element(var.names, count.index) ? lookup(lifecycle_rule.value.condition, "age", null) : null
age = lookup(lifecycle_rule.value.condition, "age", null) : null
...
I think that this "wierd" look can be obtained in two stages.
Reorganize lifecycle_rules into a map based on names
variable "input" {
default = {
names = ["first", "second"],
lifecycle_rules = [
{
name = "first",
condition = {
age = "1"
},
action = {
type = "Delete"
}
},
{
condition = {
age = "2"
},
action = {
type = "Delete"
}
},
{
name = "second",
condition = {
age = "3"
},
action = {
type = "Delete"
}
},
{
condition = {
age = "4"
},
action = {
type = "Delete"
}
}
]
}
}
locals {
new = {
for name in var.input.names:
name => [for rule in var.input.lifecycle_rules:
contains(keys(rule), "name" ) ?
rule.name == name ? rule: null :
null ]
}
}
which will give local.new in the form of:
{
"first" = [
{
"action" = {
"type" = "Delete"
}
"condition" = {
"age" = "1"
}
"name" = "first"
},
null,
null,
null,
]
"second" = [
null,
null,
{
"action" = {
"type" = "Delete"
}
"condition" = {
"age" = "3"
}
"name" = "second"
},
null,
]
}
Perform the for_each
resource "google_storage_bucket" "buckets" {
for_each = toset(var.input.names)
name = each.key
dynamic "lifecycle_rule" {
# iterate for each name skipping null values
for_each = [for v in local.new[each.key]: v if v != null]
content {
action {
type = lifecycle_rule.value["action"].type
storage_class = lookup(lifecycle_rule.value["action"], "storage_class", null)
}
condition {
age = lookup( tag.value["condition"], "age", null)
}
}
}
}
I could only verify the first step and partial second (using aws_autoscaling_group and its multiple tag components). I don't access to google cloud to fully test the code.