I have a module that outputs two values, key and value. The module contains a third value is_needed, which is a boolean. I can run this module any number of times and get a map of the outputs. Is there a way to conditionally add values to a map based on the is_needed boolean?
For example, I can create a map with all of the values like this:
locals {
map_of_values = tomap({
for instance in module.my_module : instance.key => instance.value
})
}
Can I create a map with only some of the values? Something akin to this pseudo-code:
locals {
map_of_needed_values = tomap({
for instance in module.my_module if is_needed: instance.key => instance.value
})
}
Yes. In fact your pseudo code is very close to the correct syntax.
locals {
map_of_needed_values = tomap({
for instance in module.my_module :
instance.key => instance.value if instance.is_needed
})
}
Here's a full block of functioning code that will help you see it all working. I was not completely sure of the structure of your module.my_module outputs, so I guessed.
variable "my_module" {
default = {
"instance_1" = {
"key" = "hello"
"value" = "world"
"is_needed" = false
}
"instance_2" = {
"key" = "foo"
"value" = "bar"
"is_needed" = true
}
}
}
locals {
map_of_needed_values = tomap({
for instance in var.my_module :
instance.key => instance.value if instance.is_needed
})
}
output "map_of_needed_values" { value = local.map_of_needed_values }
Related
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.
I am trying to create users in terraform cloud based off a simple csv, structured like so:
email,access_level
test#gmail.com,readonly
test2#gmail.come,owners
I can create users easily enough using the following resource block:
locals {
users_csv = csvdecode(file("${path.module}/users.csv"))
}
resource "tfe_organization_membership" "users_csv" {
for_each = { for r in local.users_csv : r.email => r }
organization = var.ppl_organisation
email = each.value.email
}
However, if I want to add them to teams then I need the "id" output from the above resource into the below resource:
resource "tfe_team_organization_member" "readonly_member" {
for_each = { for r in local.read_only : r.email => r }
team_id = each.value.access_level == "readonly" ? each.value.access_level : local.readonly_id
organization_membership_id = "${tfe_organization_membership.users_csv.id}"
}
Is there a way to pass this?
Thanks in advance.
I think the first thing I'd do here is to factor out the projection of your CSV data from a list to a map, like this:
locals {
users_csv = csvdecode(file("${path.module}/users.csv"))
users = { for r in local.users_csv : r.email => r }
}
By defining local.users like this we can make it more concise to refer to that value multiple times elsewhere in the config:
resource "tfe_organization_membership" "users_csv" {
for_each = local.users
organization = var.ppl_organisation
email = each.value.email
}
resource "tfe_team_organization_member" "readonly_member" {
for_each = local.users
team_id = (
each.value.access_level == "readonly" ?
each.value.access_level :
local.readonly_id
)
organization_membership_id = tfe_organization_membership.users_csv[each.key].id
}
Any resource that has for_each set is represented in expressions as a map of objects whose keys are the same as the for_each input map, so the tfe_organization_membership.users_csv[each.key] expression refers to an object representation of the corresponding instance of the other resource, correlating by the map keys.
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)) )}
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
)
}
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}"
}