Terraform reduce the amount of loops at the moment of generating a JSON - terraform

I have this terraform code that is generating me this JSON.
{
host = {
path = "/xxxx/yyyy"
}
name = "NAME"
}
Currently it's working, but I have 3 loops, consider it not efficient, wondering if I can reduce it to 2 or probably 1 loop? Or this isn't possible.
My first loop validates that container_mounts isn't empty. Don't want to generate it, if that comes empty. The second and the third is specific for getting the information as container_mounts is a map of strings.
variable "container_mounts" {
type = map(string)
default = { "app/data" = "/xxxx/yyyyy" }
}
json = jsonencode(
[
for i in range(length(var.container_mounts)) :
{
name = [for sourceVolume in keys(var.container_mounts) :
replace(substr(sourceVolume, 1, length(sourceVolume)), "/", "-")][0]
host = {
sourcePath = [for key, value in var.container_mounts : value][0]
}
}
]
)
Is there a way to improve it? I assume that yes, but running into different scenarios were it's not working.

So after talking with a friend, looks I complicated my life doing what I did.
json = jsonencode(
[
for key, value in var.container_mounts :{
host = { "sourcePath" = value }
name = replace(substr(key, 1, length(key)), "/", "-")
}
]
)
It can be done only with one loop.

Related

Terraform map transformation

I have been banging my head for a few days trying to do this. I know I have probably seen it before but google is failing me.
I have created a local from a data source.:
locals {
my_new_map = [for group in data.okta_groups.aws_groups.groups: {
id = group.id
name = group.name
} if contains(local.groups_not_to_check, group.name) == false]
}
the result is
[
{
id = <group1id>
name = <group1name>
},
{
id = <group2id>
name = <group2name>
},
{
...
},
]
What i need is to transform it to this:
[
<group1name> = <group1id>,
<group2name> = <group2id>,
...
]
groupnames and ids are all unique so can be used as keys
Don’t know if it is possible in my for statement or not but am willing to create a local to do it.
Any help would be awesome.
Have tried many combinations of the FOR expression but to no avail.
Maybe i am missing a function of some sort that could help or maybe i just haven't hit the correct way to output the For expression.
The following should do what you want:
locals {
my_new_map = {for group in data.okta_groups.aws_groups.groups:
group.name => group.id if contains(local.groups_not_to_check, group.name) == false }
}

Remove duplicates from output

I have the following output:
output "regions_data" {
value = regions({
for region, data in var.regions :
regions => "${region}/${data.postcode}"
})
}
Which contains duplicates like(it is intentional):
regions = {
reg1 = {
postcode = "1"
},
reg1 = {
postcode = "1"
},
reg2 = {
postcode = "2"
}
}
How can I remove the duplicates from the output?
Your code does not comply to the basic rules of maps or objects. Nor there is any regions function you use in the code. The provided code is not a proper Terraform syntax.
I believe however, you might have meant the following example:
variable "regions" {
default = {
reg1 = [
{
postcode = 1
area = "oak-county"
},
{
postcode = 2
area = "birch-county"
}
],
reg2 = [
{
postcode = 1
area = "fir-county"
},
{
postcode = 2
area = "pine-county"
}
],
}
}
In a case, when the two maps have the same keys, you can use flatten to break up everything to pieces, then rejoin everything back together:
locals {
flatten = flatten([
for region_key, region in var.regions : [
for area in region :
{
key = "${region_key}-${area.postcode}"
value = area.area
}
]
])
}
output "flattened_regions" {
value = local.flatten
}
output "remap" {
value = { for key, data in local.flatten :
data.key => data.value
}
}
Even if the code above doesn't exactly fit your case, please experiment in a similar manner - or, provide more complete example of variables you have and the outcome you need.
Source: https://www.terraform.io/language/functions/flatten
Probably what you want
I have no idea what you've meant by regions in value = regions({ but I assume that this code will do what you want:
locals {
regions = {
reg1 = {
postcode = "1"
},
reg1 = {
postcode = "1"
},
reg2 = {
postcode = "2"
}
}
}
output "regions_data" {
value = {
for region, data in local.regions :
region => "${region}/${data.postcode}"
}
}
Keep in mind that I replaced var with local to have one file.
And output of such is:
regions_data = {
"reg1" = "reg1/1"
"reg2" = "reg2/2"
}
Thought be warned that it will use one of keys. It doesn't check for duplicates. It just takes first one.
But why you shouldn't want it
This solution is quite bad for multiple reasons:
You provide variables - why the heck would you put duplicates? :)
As I said this merge will not necessarily provide the output you what
If var is provided by some terraform code (e.g. this regions_data is in module) then logic of merging should be done outside of module and probably terraform's own merge would be the answer.

Dynamic JSON in Terraform

I am using Terraform to Invoke a lambda function, and need to pass an input JSON which includes a list of string values.
data "aws_lambda_invocation" "invo6" {
function_name = "my_function"
input = <<JSON
{
"pairs":[
{
"principal":"arn:aws:iam::12345678901:role/myRole",
"databases":[
"my_db_apple", "my_db_banana", "my_db_orange"
]
}
]
}
JSON
}
Instead of hard-coding these database names, I want to pull in from a map that already exists elsewhere in my tf files.
variable "gluedb_map" {
type = map(map(string))
default = {
"apple" = {
description = "my apple db"
catalog = ""
location_uri = "s3://mybucket/"
params = ""
}
"banana" = {
description = "my banana db"
catalog = ""
location_uri = "s3://anotherpath/"
params = ""
}
I tried swapping out the 'databases' code for this :
input = <<JSON
{
"pairs":[
{
"principal":"arn:aws:iam::12345678901:role/myRole",
${jsonencode("databases": [for each in var.gluedb_map : "my_db_${each}"], )}
}
]
}
JSON
but i then get error :
A comma is required to separate each function argument from the next.
Can anyone spot where I'm going wrong ?
Thanks
If you're just interested in accessing the keys of the map then you can use the keys function to return a list of keys. You can then combine that with formatlist to interpolate each list item with a string.
I'd also recommend using a HCL map for the wider data structure and then encoding to JSON rather than trying to JSON encode a section of it and having to mangle things to get it in a suitable shape.
A fully worked example then looks something like this:
variable "gluedb_map" {
type = map(map(string))
default = {
"apple" = {
description = "my apple db"
catalog = ""
location_uri = "s3://mybucket/"
params = ""
}
"banana" = {
description = "my banana db"
catalog = ""
location_uri = "s3://anotherpath/"
params = ""
}
}
}
output "json" {
value = jsonencode({
pairs: [
{
principal = "arn:aws:iam::12345678901:role/myRole"
databases = formatlist("my_db_%s", keys(var.gluedb_map))
}
]
})
}
Applying this will output the following:
json = {"pairs":[{"databases":["my_db_apple","my_db_banana"],"principal":"arn:aws:iam::12345678901:role/myRole"}]}
You can try to use keys, formatlist and join to get:
${jsonencode("databases": [join("," , formatlist("my_db_%s", keys(var.gluedb_map)) )}

flattening output contents of a composite map

I have two module that output respectively
output "discovery_service_hostname" {
value = "${aws_appmesh_virtual_service.service.name}"
}
and
output "discovery_service_arn" {
value = zipmap( aws_service_discovery_service.sd[*].name, aws_service_discovery_service.sd[*].arn)
}
Both are used in the main script that outputs
output "services" {
value = {
"web" = "${module.web.discovery_service_hostname}"
"wwb-backend" = "${module.web_backend.discovery_service_hostname}"
"wwb-backend-n" = "${module.web_backend_n.discovery_service_hostname}"
}
}
in this case I used the 1st module for web andweb-backend, while I used the 2nd module for web-backend-n
I need to access the service arn via lookup function in a 3rd script, but I would avoid duplicating the whole code to handle the two cases
final output like this
discovery_service = {
"web" = "arn:xxx1"
"web-backend" = "arn:xxx2"
"web-backend-n" = {
"web-backend-n-1" = "arn:xxx3"
"web-backend-n-2" = "arn:xxx4
"web-backend-n-3" = "arn:xxx5"
}
Is there a way to have an output like
discovery_service = {
"web" = "arn:xxx1"
"web-backend" = "arn:xxx2"
"web-backend-n-1" = "arn:xxx3"
"web-backend-n-2" = "arn:xxx4
"web-backend-n-3" = "arn:xxx5"
}
thanks!
I will answer my own question. Solution is to always output a map (even from the module with single outputs) like this:
output "discovery_service_arn" {
value = zipmap( [ aws_service_discovery_service.sd.name ], [ aws_service_discovery_service.sd.arn ])
}
and
output "discovery_service_arn" {
value = zipmap( aws_service_discovery_service.sd[*].name, aws_service_discovery_service.sd[*].arn)
}
then in the final script use merge to get a single map like
output "discovery_service" {
value = merge(
module.web.discovery_service_arn,
module.web_backend.discovery_service_arn,
module.web_backend_n.discovery_service_arn
)
}

Look for the resource to match a key word

Suppose I have two kineses, I'd like to get the one of them with the key word _consumer.
variable "kinesis" {
default = ["kinesis_publisher", "kinesis_consumer"]
}
resource "aws_kinesis_stream" "test_stream" {
count = "${length(var.kinesis)}"
name = "${var.kinesis[count.index]}"
shard_count = 1
retention_period = 48
shard_level_metrics = [
"IncomingBytes",
"OutgoingBytes",
]
tags = {
Environment = "test"
}
}
How do I get consumer arn only?
output "kinesis_consumer_arn" {
value = "??? lookup or matchkeys with _consumer ???"
}
It is not always the same sequence and will be many kinesis. So I can't use 0 or 1 directly.
You may create modules for each kafka stream , thereby giving more control on variables passed into and derived from the resources.
module "kinesis_publisher" {
source = "../modules/test_stream"
stream_name = "kinesis_publisher"
}
module "kinesis_consumer" {
source = "../modules/test_stream"
stream_name = "kinesis_consumer"
}
And then output can be filtered on basis of modules.
output "kinesis_consumer_arn" {
value = "{module.kinesis_consumer.arn}"
}

Resources