Loading values dynamically from Terraform into a map - azure

I'm trying to load some external data from a json file into Terraform to merge into an appSettings map
It's loading it in as a tuple - and no matter what conversion I do, I can't get a map out of it:
Call to function "merge" failed: arguments must be maps or objects, got "tuple".
Json File
[
{
"appCode": "value",
"containerName": "value",
"databaseName": "value",
"referer": "bvalue",
"shortCode": "value",
"user": "value"
},
{
"appCode": "value",
"containerName": "value",
"databaseName": "value",
"referer": "value",
"shortCode": "value",
"user": "value"
}
]
Locals:
customerSettings = jsondecode(file("vars/${var.environment}.json"))
customerAppSettingsFromJson = {
for index, externalCustomer in local.customerSettings :
externalCustomer => {
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__AppCode" = "${externalCustomer.appCode}"
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__ContainerName" = "${externalCustomer.containerName}"
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__DatabaseName" = "${externalCustomer.databaseName}"
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__Referer" = "${externalCustomer.databaseName}"
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__ShortCode" = "${externalCustomer.shortCode}"
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__User" = "${externalCustomer.user}"
}
}
Main.tf appSettings block, has inferred ones, ones from vars and ones from json
app_settings = merge({}, var.app_settings, local.customerAppSettingsFromJson)

You were very close to the solution, but here is how to convert the list of objects into a map:
customerSettings = jsondecode(file("vars/${var.environment}.json"))
customerAppSettingsFromJson = {
for index, externalCustomer in local.customerSettings :
index => {
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__AppCode" = "${externalCustomer.appCode}"
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__ContainerName" = "${externalCustomer.containerName}"
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__DatabaseName" = "${externalCustomer.databaseName}"
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__Referer" = "${externalCustomer.databaseName}"
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__ShortCode" = "${externalCustomer.shortCode}"
"DynamicCosmosDbSettings__CosmosHostSettings__${index}__User" = "${externalCustomer.user}"
}
}

Related

Terraform: How can I read a value from a json file?

I have the json file (map.json):
[
{
"QueueName": "queue1",
"TypeName": "type1"
},
{
"QueueName": "queue2",
"TypeName": "type2"
},
{
"QueueName": "queue3",
"TypeName": "type2"
}
]
which I can load into the following variable:
locals {
maps = jsondecode(file("maps.json"))
}
How can I read the TypeName value for QueueName = "queue2"?
I think you are looking at something like this:
type = [for el in local.maps : el["TypeName"] if el["QueueName"] == "queue2"]
Output
$ terraform console
> local.response
[
"type1",
]
Logically this will return a list of elements, but if you want to retrieve only the first result, then you can use:
response = [for el in local.maps : el["TypeName"] if el["QueueName"] == "queue1"]
type = length(local.response) > 0 ? local.response[0] : ""
Outputs
$ terraform console
> local.type
"type1"
>
Or just:
type = local.maps[index(local.maps.*.QueueName, "queue1")]["TypeName"]
But this will throw an exception if the element queueName does not exist, something like this:
Call to function "index" failed: item not found

Is it possible to perform nested iterations in HCL resulting in a flat list without calling flatten?

Is it possible with HCL to have nested iterations returning a flat list(map) without resorting to flatten?
I have this:
locals {
mappings = flatten([
for record_type in var.record_types : [
for host in var.hosts : {
type = record_type,
host = host
}
]
])
}
I would like to remove the need for flatten like this:
locals {
mappings = [
for record_type in var.record_types :
for host in var.hosts : {
type = record_type,
host = host
}
]
}
But it seems like each for .. in must return data.
One alternative I could think of to only have a single for-loop is using setproduct():
variable "record_types" {
default = ["type1", "type2"]
}
variable "hosts" {
default = ["host1", "host2"]
}
locals {
mappings = [
for i in setproduct(var.record_types, var.hosts) : {
type = i[0],
host = i[1],
}
]
}
output "mappings" {
value = local.mappings
}
after terraform apply resulting in:
Outputs:
mappings = [
{
"host" = "host1"
"type" = "type1"
},
{
"host" = "host2"
"type" = "type1"
},
{
"host" = "host1"
"type" = "type2"
},
{
"host" = "host2"
"type" = "type2"
},
]
Of course, the two variables need to be independent sets here.
If you want to support duplicates or have dependent inputs, flatten() with two loops is the way.

Power Query Expression error: A value of type "Record" cannot converted into type "Text"

I have the issue that I'm not able to execute the following code. The syntax seems to be okay, but when I try to execute it, I get the response, that:
Expression.Error: We cannot convert a value of type Record to type "Text".
Details:
Value=[Record]
Type=[Type]
let
body="{
""page"": ""1"",
""pageSize"": ""100"",
""requestParams"": {
""deviceUids"": [
""xxx-yyy-xxx-yyyy-xxxx"",
""yyy-xxx-yyy-xxxx-yyyy"",
""aaa-bbb-aaa-bbbb-aaaa"",
""ccc-ddd-ccc-dddd-cccc""
],
""entityColumns"": [
{
""entityId"": ""144"",
""joinColumnName"": ""device_uid"",
""columnName"": ""device_random_date""
}
],
""columnNames"": [
""ts"",
""device_uid"",
""1"",
""32"",
""55"",
""203"",
""204""
],
""startUnixTsMs"": ""1583413637000"",
""endUnixTsMs"": ""1583413640000"",
""columnFilters"": [
{
""filterType"": ""eq"",
""columnName"": ""55"",
""value"": ""1234""
}
],
""sortOrder"": [
{
""column"": ""ts"",
""order"": ""DESC""
},
{
""column"": ""55"",
""order"": ""ASC""
}
],
""entityFilters"": [
{
""entityId"": ""144"",
""entityEntryIds"": [
""12345-221-232-1231-123456""
]
}
]
}
}",
Parsed_JSON = Json.Document(body),
BuildQueryString = Uri.BuildQueryString(Parsed_JSON),
Quelle = Json.Document(Web.Contents("http://localhost:8101/device-data-reader-api/read-paginated/xxx-xxx-yyyy-yyyy", [Headers=[#"Content-Type"="application/json"], Content = Text.ToBinary(BuildQueryString)]))
in
Quelle
I tried to remove the quotes of the numbers, but this leads to the same issue, as system complains it cannot convert numbers into text.
I need the body which needs to be handed over with the request in order to do a POST request. What I'm doing wrong?
Since you seem to want to send this as application/json, I think you would change this bit in your code:
Content = Text.ToBinary(BuildQueryString)
to:
Content = Text.ToBinary(body)
and then you'd also get rid of the lines below (since you don't need them):
Parsed_JSON = Json.Document(body),
BuildQueryString = Uri.BuildQueryString(Parsed_JSON),
I don't think you would need Uri.BuildQueryString unless you wanted to send as application/x-www-form-urlencoded (i.e. URL encoded key-value pairs).
Unrelated: If it helps, you can build the structure in M and then use JSON.FromValue to turn the structure into bytes which can be put directly into the POST body. Untested example is below.
let
body = [
page = "1",
pageSize = "100",
requestParams = [
deviceUids = {
"xxx-yyy-xxx-yyyy-xxxx",
"yyy-xxx-yyy-xxxx-yyyy",
"aaa-bbb-aaa-bbbb-aaaa",
"ccc-ddd-ccc-dddd-cccc"
},
entityColumns = {
[
entityId = "144",
joinColumnName = "device_uid",
columnName = "device_random_date"
]
},
columnNames = {
"ts",
"device_uid",
"1",
"32",
"55",
"203",
"204"
},
startUnixTsMs = "1583413637000",
endUnixTsMs = "1583413640000",
columnFilters = {
[
filterType = "eq",
columnName = "55",
value = "1234"
]
},
sortOrder = {
[
column = "ts",
order = "DESC"
],
[
column = "55",
order = "ASC"
]
},
entityFilters = {
[
entityId = "144",
entityEntryIds = {
"12345-221-232-1231-123456"
}
]
}
]
],
Quelle = Json.Document(
Web.Contents(
"http://localhost:8101/device-data-reader-api/read-paginated/xxx-xxx-yyyy-yyyy",
[
Headers = [#"Content-Type" = "application/json"],
Content = Json.FromValue(body)
]
)
)
in
Quelle
It might look a little weird (since M uses [] instead of {}, {} instead of [] and = instead of :), but just mentioning in case it helps.

terraform output map value from lists of maps

I want to get a specific map element from a lists of maps in Terraform's output.
For example, how would I access account in properties.json for each map item
and print a list as output conditionally for type also.
I tried for loops and splat expressions as below but it does not return exact value.
It seems that the props below is also a list.
output "resources_by_name" {
description = "Resource name of all machine type resources from a vRA deployment"
value = [
for props in deployment.deploy[*].resources.*.properties_json:
jsondecode(props).account
if jsondecode(props).type == "vsphere"
]
}
I am not sure how to use nested for loops or access map items within the lists.
properties.json
[
[
{
"id" = "b5336bf7-07fb-4026-aa3d-479bd974ca45"
"name" = "test1"
"properties_json" = "{"account":"test0","constraints":"anothertest4"}"
"type" = "vsphere"
},
{
"id" = "67a3380b-8008-4f9c-9c13-2a1a935d5820"
"name" = "test2"
"properties_json" = "{"account":"test1","constraints":"anothertest3"}"
"type" = "gcp"
},
],
[
{
"id" = "eeddd127-cba2-4b34-a2d7-e56dda5d2974"
"name" = "test3"
"properties_json" = "{"account":"test2","constraints":"anothertest2"}"
"type" = "aws"
},
{
"id" = "81de1857-c0c9-4c9e-8fbd-d8a1da64fa3c"
"name" = "test4"
"properties_json" = "{"account":"test3","constraints":"anothertest1"}"
"type" = "az"
},
],
]
Here is working example. I had to fill out the blanks missing from your question, thus you may need to modify it to suit your needs:
locals {
properties = [
[
{
"id" = "b5336bf7-07fb-4026-aa3d-479bd974ca45"
"name" = "test1"
"properties_json" = "{\"account\":\"test0\",\"constraints\":\"anothertest4\"}"
"type" = "vsphere"
},
{
"id" = "67a3380b-8008-4f9c-9c13-2a1a935d5820"
"name" = "test2"
"properties_json" = "{\"account\":\"test1\",\"constraints\":\"anothertest3\"}"
"type" = "gcp"
},
],
[
{
"id" = "eeddd127-cba2-4b34-a2d7-e56dda5d2974"
"name" = "test3"
"properties_json" = "{\"account\":\"test2\",\"constraints\":\"anothertest2\"}"
"type" = "aws"
},
{
"id" = "81de1857-c0c9-4c9e-8fbd-d8a1da64fa3c"
"name" = "test4"
"properties_json" = "{\"account\":\"test3\",\"constraints\":\"anothertest1\"}"
"type" = "az"
},
],
]
}
output "resources_by_name" {
value = [for props in flatten(local.properties):
jsondecode(props.properties_json).account
if props.type == "vsphere"
]
}
Outcome:
resources_by_name = [
"test0",
]

Using a list variable for ECS task in container_definitions with terraform

In terraform I am attempting to pass a variable (list) to a module that we built. This variable needs to be used within a aws_ecs_task_definition resource in the container_definitions.
Right now I am just starting with an empty default list defined as a variable:
variable "task_enviornment" {
type = "list"
default = []
}
My ECS task definition looks like this:
resource "aws_ecs_task_definition" "ecs_task_definition" {
family = "${var.ecs_family}"
network_mode = "awsvpc"
task_role_arn = "${aws_iam_role.iam_role.arn}"
execution_role_arn = "${data.aws_iam_role.iam_ecs_task_execution_role.arn}"
requires_compatibilities = ["FARGATE"]
cpu = "${var.fargate_cpu}"
memory = "${var.fargate_memory}"
container_definitions =<<DEFINITION
[
{
"cpu": ${var.fargate_cpu},
"image": "${var.app_image}",
"memory": ${var.fargate_memory},
"name": "OURNAME",
"networkMode": "awsvpc",
"environment": "${jsonencode(var.task_enviornment)}",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group" : "${aws_cloudwatch_log_group.fargate-logs.name}",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "demo"
}
},
"portMappings": [
{
"containerPort": ${var.app_port},
"hostPort": ${var.app_port}
}
]
}
]
DEFINITION
}
The part I am having a problem with with the "environment" part:
"environment": "${jsonencode(var.task_enviornment)}",
I have tried a few different ways to get this to work.
If I do "environment": "${jsonencode(var.task_enviornment)}",
I get ECS Task Definition container_definitions is invalid: Error decoding JSON: json: cannot unmarshal string into Go struct field ContainerDefinition.Environment of type []*ecs.KeyValuePair
If I do "environment": "${var.task_enviornment}", or "environment": ["${var.task_enviornment}"],
I get At column 1, line 1: output of an HIL expression must be a string, or a single list (argument 8 is TypeList) in:
Then it just outputs the contents of container_definitions
I did also try adding default values and I was getting similar error messages. However I do need to be able to handle no values being sent in, so an empty list.
variable "task_enviornment" {
type = "list"
default = [
{
"name" = "BUCKET",
"value" = "test"
}
]
}
After a lot of investigation and a fresh set of eyes looking at this figured out the solution. I am unsure why this fixes it, and I feel like this is likely a bug.
Needed to do 2 things to fix this.
Remove type = "list" from the variable definition.
variable "task_environment" {
default = []
}
Remove the quotes when using the variable:
"environment": ${jsonencode(var.task_environment)},
The below solution should work
in variable.tf
variable "app_environments_vars" {
type = list(map(string))
default = []
description = "environment variable needed by the application"
default = [
{
"name" = "BUCKET",
"value" = "test"
},{
"name" = "BUCKET1",
"value" = "test1"
}]
and in your task definition, you can use ${jsonencode(var.app_environments_vars)} similar to
container_definitions =<<DEFINITION
[
{
"cpu": ${var.fargate_cpu},
"image": "${var.app_image}",
"memory": ${var.fargate_memory},
"name": "OURNAME",
"networkMode": "awsvpc",
"environment": ${jsonencode(var.app_environments_vars)},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group" : "${aws_cloudwatch_log_group.fargate-logs.name}",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "demo"
}
},
"portMappings": [
{
"containerPort": ${var.app_port},
"hostPort": ${var.app_port}
}
]
}
]
My guess is that you are trying to use the type "map" instead of lists, as showed above, the removal from type specification will work.
Example:
List_sample = [1,2,3]
Map_sample = { key_name = "value" }
Reference: Terraform - Type Constraints

Resources