Terraform Pass Variable (Object) to Templatefile - terraform

I have the following variable that I would like to have rendered in a template file. All values ​​of the variable are optional.
The template itself contains other variables, but these are usually just simple strings.
variable "security_context" {
type = object({
allow_privilege_escalation = optional(bool)
read_only_root_filesystem = optional(bool)
run_as_non_root = optional(bool)
privileged = optional(bool)
capabilities = optional(object({
add = optional(list(string))
drop = optional(list(string))
}))
})
default = null
}
resource "helm_release" "test" {
...
values = [templatefile("${path.module}/values-override.yaml.tpl",
{
...
sec_context = var.security_context
}
)]
}
My template file looks like this:
%{ if main_container_sec_context != null }
securityContext:
%{ if sec_context.allow_privilege_escalation != null }
allowPrivilegeEscalation: ${sec_context.allow_privilege_escalation}
%{ endif }
%{ if sec_context.read_only_root_filesystem != null }
readOnlyRootFilesystem: ${sec_context.read_only_root_filesystem}
%{ endif }
%{ if sec_context.run_as_non_root != null }
runAsNonRoot: ${sec_context.run_as_non_root}
%{ endif }
%{ if sec_context.privileged != null }
privileged: ${sec_context.privileged}
%{ endif }
%{ endif }
For the bool values, this approach works, but it's a lot of code. Can this be reduced by iterating over the values?
What would be the best way to integrate the lists (capabilities: add and drop)?

Related

jsonencode adding unwanted blackslash

Here is my code :
locals {
meta = "{\"ABC\":${var.value1},\"XYZ\":${var.value2}}"
}
variable value1 {
default = "google.com"
}
variable value2 {
default = "yahoo.com"
}
Output:
jsonencode(local.meta)
"{"ABC":google.com,"XYZ":yahoo.com}"
I get unwanted backslashes in the output
output must be the following:
{"ABC":"google.com","XYZ":"yahoo.com"}
Your local.meta should be:
locals {
meta = {ABC = var.value1, XYZ = var.value2}
}

Module composition between two modules using a conditional for_each on both ways

On my root module, I am declaring two modules (paired_regions_network and paired_regions_app), that both iterate a set of regions.
module "paired_regions_network" {
source = "./modules/network"
application_hostname = module.paired_regions_app[each.key].website_hostname
...
for_each = ( var.environment == "TEST" || var.environment == "PROD") ? { region1 = var.paired_regions.region1 } : { }
}
module "paired_regions_app" {
source = "./modules/multi-region"
wag_public_ip = module.paired_regions_network[each.key].wag_public_ip
...
for_each = (var.environment == "TEST" || var.environment == "PROD") ? var.paired_regions : { region1 = var.paired_regions.region1 }
}
output "network_outputs" {
value = module.paired_regions_network
}
output "app_outputs" {
value = module.paired_regions_app
}
The iterated regions are declared as follows:
variable "paired_regions" {
description = "The paired regions"
default = {
region1 = {
...
},
region2 = {
...
}
}
}
From the paired_regions_network module I want to have access to the output coming from the paired_regions_app module, namely the website_hostname value, which I want to assign to the application_hostname parameter, of the paired_regions_network module, as shown above.
output "website_hostname" {
value = azurerm_app_service.was_app.default_site_hostname
description = "The hostname of the website"
}
And from the paired_regions_app module I want to have access to the output coming from the paired_regions_network module, namely the wag_public_ip value, which I want to assign to the parameter with the same name, of the paired_regions_app module, as shown above.
output "wag_ip_address" {
value = azurerm_public_ip.network_ip.ip_address
description = "The Public IP address that will be used by the app gateway"
}
But this causes a dependency cycle, that I can't get rid off. The error is the following:
Error: Cycle: ...
Can I pass the output between the two modules, without causing the dependency cycle?
As per #Marcin's advice, I was able to overcome the issue by creating a third module containing only the Public IP resource. So, the paired_regions_app module would depend on the new module instead of depending on the paired_regions_network module. The paired_regions_network would then depend on both the other two modules. Besides removing the output from the paired_regions_network module, the code changes are as follows:
Root Module
module "paired_regions_ips" {
source = "./modules/public-ip"
...
for_each = ( var.environment == "TEST" || var.environment == "PROD") ? { region1 = var.paired_regions.region1 } : { }
}
module "paired_regions_app" {
source = "./modules/multi-region"
wag_public_ip = length(module.paired_regions_ips) > 0 ? (lookup(module.paired_regions_ips, each.key, "") != "" ? join("/", ["${module.paired_regions_ips[each.key].ip_obj.ip_address}", "32"]) : "" ) : ""
...
for_each = (var.environment == "TEST" || var.environment == "PROD") ? var.paired_regions : { region1 = var.paired_regions.region1 }
}
module "paired_regions_network" {
source = "./modules/network"
wag_public_ip_id = module.paired_regions_ips[each.key].ip_obj.id
application_hostname = module.paired_regions_app[each.key].website_hostname
...
for_each = ( var.environment == "TEST" || var.environment == "PROD") ? { region1 = var.paired_regions.region1 } : { }
}
output "network_outputs" {
value = module.paired_regions_ips
}
output "app_outputs" {
value = module.paired_regions_app
}
The new module
output "ip_obj" {
value = azurerm_public_ip.network_ip
description = "The Public IP address"
}
Some remarks:
Because the paired_regions_ips module has different conditions on the for_each loop when compared to the paired_regions_app module, I had to add some logic when fetching the output from the latter
The new module outputs a public IP object, so that I have access to both its ID (from the paired_regions_network module) and to the IP address (from the paired_regions_app module)

Terraform if statement with true or false

I need to prepare if statement. I am wondering what is the best solution for this?
locals {
prod_ingress_certyficate = "${prod_ingress_certyficate == "true" ? var.prod_cert : var.test_cert}"
}
Is it the correct way? If the variable is true then user prod_cert and if false then use test_cert.
You can't reference prod_ingress_certyficate before it is defined. But, you could create a variable called prod_ingress_certyficate which then you use in locals in your condition:
variable "prod_cert" {
default = "prod_cert"
}
variable "test_cert" {
default = "test_cert"
}
variable "prod_ingress_certyficate" {
default = true
}
locals {
prod_ingress_certyficate = var.prod_ingress_certyficate == true ? var.prod_cert : var.test_cert
}
output "test" {
value = local.prod_ingress_certyficate
}

Merging module output map

I'm trying out the new for_each function on a module, which itself outputs some values that I need to pass into another resource.
module "vnets" {
source = "../caf-virtual-network"
for_each = var.vnet_list
ARM_ENVIRONMENT = var.ARM_ENVIRONMENT
ARM_LOCATION = var.ARM_LOCATION
ARM_SUBSCRIPTION_ID = var.ARM_SUBSCRIPTION_ID
diagnostics_map = local.diagnostics_map
location = var.ARM_LOCATION
netwatcher = local.netwatcher
networking_object = each.value
tags = var.global_settings.tags
virtual_network_rg = "${module.names.standard["resource-group"]}-${each.value.vnet.resource_group_name}"
depends_on = [
module.resource_groups_networking
]
}
I can grab the output of the module for one or more of those objects by specifying something like this
output "subnets" { value = module.vnets["vnet_shared_services_object"].vnet_subnets } , which in turn looks like this:
"vnet_shared_services_object" = {
"sn-dev-uks-asdf-app-dynamic" = "/subscriptions/asdf/resourceGroups/asdf/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-app-dynamic"
"sn-dev-uks-asdf-artifactory" = "/subscriptions/asdf/resourceGroups/asdf/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-artifactory"
}
Here I'm specifying the output of ONE object, but I want to dynamically specify the output of both objects in one hit.
So I want this;
"vnet_shared_services_object" = {
"sn-dev-uks-asdf-app-dynamic" = "/subscriptions/asdf/resourceGroups/asdf/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-app-dynamic"
"sn-dev-uks-asdf-artifactory" = "/subscriptions/asdf/resourceGroups/asdf/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-artifactory"
}
"vnet_transit_object" = {
"AzureFirewallSubnet" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/AzureFirewallSubnet"
"GatewaySubnet" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/GatewaySubnet"
"sn-dev-uks-asdf-bind-dns" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/sn-dev-uks-asdf-bind-dns"
}
...output to look like this:
subnets = {
"sn-dev-uks-asdf-app-dynamic" = "/subscriptions/asdf/resourceGroups/asdf/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-app-dynamic"
"sn-dev-uks-asdf-artifactory" = "/subscriptions/asdf/resourceGroups/asdf/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-artifactory"
"AzureFirewallSubnet" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/AzureFirewallSubnet"
"GatewaySubnet" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/GatewaySubnet"
"sn-dev-uks-asdf-bind-dns" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/sn-dev-uks-asdf-bind-dns"
}
So i know doing the following will work, but the point i'm trying to make is that i don't know how many vnet modules i'm going to produce, and thus i need to make this dynamic:
output merge{
value = merge({
for key, value in module.vnets["vnet_shared_services_object"].vnet_subnets:
key => value
},
{
for key, value in module.vnets["vnet_transit_object"].vnet_subnets:
key => value
})
}
Using the guide on Terraform to flatten (https://www.terraform.io/docs/configuration/functions/flatten.html) the output object works, but it's not how i wish for it to be:
output stuff {
value = flatten([
for key, value in module.vnets: [
for subnet, id in value.vnet_subnets: {
"${subnet}" = id
}
]
])
}
...which equats to:
stuff = [
{
"sn-dev-uks-asdf-app-dynamic" = "/subscriptions/asdf/resourceGroups/rg-dev-uks-asdf-vnet-shared-services/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-app-dynamic"
},
{
"sn-dev-uks-asdf-artifactory" = "/subscriptions/asdf/resourceGroups/rg-dev-uks-asdf-vnet-shared-services/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-artifactory"
},
...and so on
]
an FYI, this does not help me :(
output {
value = merge(
for key, value in module.vnets:
key => value.vnets_subnets
)
}
Any help on this would be greatly appreciated!
I'm not sure if I correctly understand the input maps, but I tried to replicate the issue creating some mock variables.
For that I created the following variables:
variable "vnets" {
default = {
"vnet_shared_services_object" = {
"sn-dev-uks-asdf-app-dynamic" = "/subscriptions/asdf/resourceGroups/asdf/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-app-dynamic"
"sn-dev-uks-asdf-artifactory" = "/subscriptions/asdf/resourceGroups/asdf/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-artifactory"
}
}
}
variable "vnet_subnets" {
default = {
"vnet_transit_object" = {
"AzureFirewallSubnet" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/AzureFirewallSubnet"
"GatewaySubnet" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/GatewaySubnet"
"sn-dev-uks-asdf-bind-dns" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/sn-dev-uks-asdf-bind-dns"
}
}
}
Then the output was defiend as:
output stuff {
value = {for k,v in flatten([
for key, value in merge(var.vnets, var.vnet_subnets):
[for subkey1, subval1 in value: {"${subkey1}" = subval1}]
]): keys(v)[0] => values(v)[0]}
}
which resulted in:
stuff = {
"AzureFirewallSubnet" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/AzureFirewallSubnet"
"GatewaySubnet" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/GatewaySubnet"
"sn-dev-uks-asdf-app-dynamic" = "/subscriptions/asdf/resourceGroups/asdf/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-app-dynamic"
"sn-dev-uks-asdf-artifactory" = "/subscriptions/asdf/resourceGroups/asdf/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-shared-services/subnets/sn-dev-uks-asdf-artifactory"
"sn-dev-uks-asdf-bind-dns" = "/subscriptions/asdf/resourceGroups/qwer/providers/Microsoft.Network/virtualNetworks/vnet-dev-uks-asdf-transit/subnets/sn-dev-uks-asdf-bind-dns"
}
A colleague was able to answer this question with the following code:
locals {
subnet_list = {
for key, value in module.vnets:
key => value.vnet_subnets
}
subnet_map = merge(values(local.subnet_list)...)
}
it is the ... operator which is the key takeaway from this. you can look it up here; https://www.terraform.io/docs/configuration/expressions.html#expanding-function-arguments
... will expand a list of items to function parameters, hence you can call merge to merge a list of map

terraform how to describe variable type with changing keys in object

I've got an ever changing list of objects as variable and wanted to know how to properly describe its type
variable "lifecycle_rules" {
type = set(object({
# set(object({
# action = map(string)
# condition = map(string)
# }))
}))
default = [
{
first = [
{
condition = {
age = "1"
}
action = {
type = "Delete"
}
},
{
condition = {
age = "2"
}
action = {
type = "Delete"
}
}
]},
{
second = [
{
condition = {
age = "3"
}
action = {
type = "Delete"
}
},
{
condition = {
age = "4"
}
action = {
type = "Delete"
}
}
]
}
]
}
Here should be line with smth like this string = set(object({...
the first and second are always changing, so key value should be
string but can't really set it - any other thoguhts, how to write
type for the default below ?
You are almost there. I think the correct one is:
type = set(
map(
set(
object({condition = map(string),
action = map(string)})
)
)
)
In the map you don't specify attributes, as they can be different. In the most inner one you have object as condition and action are constant.

Resources