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
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}
}
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)
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
}
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
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.