I have a question on null values. The following is the tricky one for me. Can anyone suggest to me how can we check if the value is null?
locals {
application_vars = {
"oke_build" = [
"121",
"121",
"121",
]
"ipa" = [
"101.10.2,1",
"101.10.2,2",
null
]
"size" = [
"c4.8xlarge",
null,
null,
]
}
}
I want to verify if the list contains null values.
You can use compact function to return only non-null elements from a list.
Then, you could compare the length of the original list with the length of the list returned by the compact function.
locals {
application_vars = {
"oke_build" = [
"121",
"121",
"121",
]
"ipa" = [
"101.10.2,1",
"101.10.2,2",
null
]
"size" = [
"c4.8xlarge",
null,
null,
]
}
}
output "not_null_check_oke_build" {
value = length(compact(local.application_vars.oke_build)) == length(local.application_vars.oke_build) ? "has no null elements" : "has null elements"
}
output "not_null_check_ipa" {
value = length(compact(local.application_vars.ipa)) == length(local.application_vars.ipa) ? "has no null elements" : "has null elements"
}
output "not_null_check_size" {
value = length(compact(local.application_vars.size)) == length(local.application_vars.size) ? "has no null elements" : "has null elements"
}
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
not_null_check_ipa = "has null elements"
not_null_check_oke_build = "has no null elements"
not_null_check_size = "has null elements"
If you have too many lists to check, this would get repetitive & you gotta loop these with another for a loop. But for 2-3 lists, it's okay to have them this way.
Related
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
How can we generate an yaml out from a json
{
"users":[
{
"name":"rock",
"age":33,
"city":"paris"
},
{
"name":"austin",
"age":45,
"city":"greece"
}
]
}
Expected output with a additional field with random password
users:
- key: 'user[0].name'
value: rock
- key: 'user[0].age'
value: '33'
- key: 'user[0].city'
value: paris
- key: 'user[0].password'
value: '5]L+J7rA*<7+:PO6'
- key: 'user[1].name'
value: austin
- key: 'user[1].age'
value: '45'
- key: 'user[1].city'
value: greece
- key: 'user[1].password'
value: P=x&385YGMI0?!Is
The files are used only on a local machine.
A straight conversion from JSON to YAML would involve just passing the result of jsondecode to yamlencode.
However, the meat of your question seems to not be about conversion between serialization formats but rather about transforming from a typical nested data structure into a flattened structure using a compound key syntax.
Transforming from a multi-level structure into a single-level structure is a good job for flatten:
locals {
users = flatten([
for idx, u in local.users : [
for k, v in u : {
key = "user[${idx}].${v}"
value = v
}
]
])
}
The result would be a Terraform value corresponding to the data structure you illustrated in YAML:
[
{
key = "user[0].name"
value = "rock"
},
{
key = "user[0].age"
value = 33
},
# ...etc...
]
You can then pass the result to yamlencode to produce the YAML serialization of that data structure.
You can use file function of terraform to read the content of your json.
You can then use jsondecode to decode/represent the string as json.
The last step is to use , yamlencode function to encode returned json representation in yaml format.
Please see below terraform config.
locals {
test = yamlencode(jsondecode(file("${path.module}/test.json")))
}
output "test" {
value = local.test
}
The result from the above tf config is as below
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
test = "users":
- "age": 33
"city": "paris"
"name": "rock"
- "age": 45
"city": "greece"
"name": "austin"
locals {
users = jsondecode(file("${path.module}/user.json"))
}
resource "random_string" "password" {
count = length(local.users.users)
length = 16
special = true
}
output "pass" {
value = random_string.password
}
locals {
user_flat = yamlencode(flatten([for i,j in local.users.users:[
for k,v in j : {
key = "user.[${i}].${k}"
"value" = v
}
]
]
)
)
out = yamlencode({"users"= local.user_flat})
}
output "Desired_output" {
value = local.out
}
Try this code.
Outputs:
Desired_output = <<EOT
"users": |
- "key": "user.[0].age"
"value": 23
- "key": "user.[0].city"
"value": "barcelona"
- "key": "user.[0].name"
"value": "john"
- "key": "user.[1].age"
"value": 29
- "key": "user.[1].city"
"value": "london"
- "key": "user.[1].name"
"value": "bob"
EOT
pass = [
{
"id" = "XEXj*svXm#d&_%Fn"
"keepers" = tomap(null) /* of string */
"length" = 16
"lower" = true
"min_lower" = 0
"min_numeric" = 0
"min_special" = 0
"min_upper" = 0
"number" = true
"override_special" = tostring(null)
"result" = "XEXj*svXm#d&_%Fn"
"special" = true
"upper" = true
},
{
"id" = "Ews%V%Xk[YBd]D_M"
"keepers" = tomap(null) /* of string */
"length" = 16
"lower" = true
"min_lower" = 0
"min_numeric" = 0
"min_special" = 0
"min_upper" = 0
"number" = true
"override_special" = tostring(null)
"result" = "Ews%V%Xk[YBd]D_M"
"special" = true
"upper" = true
},
]
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.
I'm struggling with this interpolation, I have variables below like.
primary = ["foo.dev","all.bar.com"]
secondary = ["module.foo.dev","*.foo.dev","all.bar.com"]
I want my output to be
{
"foo.dev" = ["module.foo.dev","*foo.dev"]
"all.bar.com" = ["all.bar.com"]
}
Using Terraform 0.12.20
I tried in terraform console , I'm not able to achieve my desired output. Is there any easy solution?
[for i in ["foo.dev","all.bar.com"]: map(i,[for j in ["module.foo.dev","*foo.dev","all.bar.com"]: replace(j,i,"")!=j == true ? j : 0])]
[
{
"foo.dev" = [
"module.foo.dev",
"*foo.dev",
"0",
]
},
{
"all.bar.com" = [
"0",
"0",
"all.bar.com",
]
},
]
You can do this with the following expression:
{ for i in ["foo.dev", "all.bar.com"] : i => [for j in ["module.foo.dev", "*foo.dev", "all.bar.com"] : j if can(regex(i, j))] }
This results in the following value:
{
"all.bar.com" = [
"all.bar.com",
]
"foo.dev" = [
"module.foo.dev",
"*foo.dev",
]
}
which is exactly what you requested.
The iterative explanation follows:
First, specify the type is a map:
{}
Now, we should iterate over the elements in the first list:
{ for i in ["foo.dev", "all.bar.com"] : }
Next, we assign the i value to the key of the map, and initialize a list for the value:
{ for i in ["foo.dev", "all.bar.com"] : i => [] }
However, we need to assign the values from the elements in the second list to the list for the key of the resulting map (the below example is sub-optimal because we do not need a for expression, but it is illustrative for the next step):
{ for i in ["foo.dev", "all.bar.com"] : i => [for j in ["module.foo.dev", "*foo.dev", "all.bar.com"] : j] }
Finally, we want to filter the elements in the list value based on the key. We can use the can function outside the regex function to return a boolean based on whether there is a match. We use this for the conditional on whether the element of the second list should be added to the list in the value:
{ for i in ["foo.dev", "all.bar.com"] : i => [for j in ["module.foo.dev", "*foo.dev", "all.bar.com"] : j if can(regex(i, j))] }
and we have arrived at the solution.
I have stored my Data in the give formate in AreangoDB, My collection name in DSP:
"data": {
"1": [ {"db_name": "DSP"}, {"rel": "2"} ],
"2": [ {"rel_name": "DataSource"}, {"tuple": "201"}, {"tuple": "202"}, {"tuple": "203"} ],
"201": [ {"src_id": "Pos201510070"}, {"src_name": "Postgres"}, {"password": "root"}, {"host": "localhost"}, {"created_date": "20151007"}, {"user_name": "postgres"}, {"src_type": "Structured"}, {"db_name": "postgres"}, {"port": "None"} ],
"202": [ {"src_id": "pos201510060"}, {"src_name": "Postgres"},{"password": "root"}, {"host": "localhost"}, {"created_date": "20151006"}, {"user_name": "postgres"}, {"src_type": "Structured"}, {"db_name": "DSP"}, {"port": "5432"} ],
"203": [ {"src_id": "pos201510060"}, {"src_name": "Postgres"}, {"password": "root"}, {"host": "localhost"}, {"created_date": "20151006"}, {"user_name": "postgres"},{"src_type": "Structured"},{"db_name": "maindb"},{"port": "5432"} ]
}
I am executing a query with the above data in the following format:
FOR p IN NestedDSP
LET attributes = ATTRIBUTES(p)
FOR attribute IN attributes
LET key = ATTRIBUTES(attribute)[0]
LET value = attribute[key]
RETURN { subject: attribute, predicate: key, object: value }
When I submit my query to ArangoDB, it returns the response as:
Warnings:
[1542], 'invalid argument type in call to function 'ATTRIBUTES()''
[1542], 'invalid argument type in call to function 'ATTRIBUTES()''
[1542], 'invalid argument type in call to function 'ATTRIBUTES()''
[1542], 'invalid argument type in call to function 'ATTRIBUTES()''
Result:
[
{
"subject": "data",
"predicate": null,
"object": null
},
{
"subject": "_id",
"predicate": null,
"object": null
},
{
"subject": "_rev",
"predicate": null,
"object": null
},
{
"subject": "_key",
"predicate": null,
"object": null
}
]
Please tell me what is the problem with this query, and why the answer is like the above..I am working in ArangoDB-2.7.3-win64.
Thanks
Let me demonstrate how to construct such a complex query digging deep into nested data structures. I start out taking the outer parts of the query, to have an inner result:
FOR p IN NestedDSP
LET attributes = ATTRIBUTES(p)
FOR attribute IN attributes
RETURN attribute
which gives me:
[
"data",
"_rev",
"_key",
"_id"
]
So lets dive deeper into the next layer. I guess you're only interested in the values present underneath data key right? so we pick p.data:
FOR p IN NestedDSP
LET attributes = ATTRIBUTES(p.data)
FOR attribute IN attributes
RETURN attribute
which then gives me the keys for your next inner array:
[
"203",
"202",
"201",
"2",
"1"
]
We now explore what we find attached to these nodes:
FOR p IN NestedDSP
LET attributes = ATTRIBUTES(p.data)
FOR oneAttribute IN attributes
LET keys = p.data[oneAttribute]
RETURN keys
Its Again an array, which we need to iterate into using a FOR loop over keys:
[
[
{
"src_id" : "pos201510060"
},
{
"src_name" : "Postgres"
}, ...
We add this additional FOR-loop:
FOR p IN NestedDSP
LET attributes = ATTRIBUTES(p.data)
FOR oneAttribute IN attributes
LET keys = p.data[oneAttribute]
FOR key IN keys
RETURN key
we get the inner most objects:
[
{
"src_id" : "pos201510060"
},
{
"src_name" : "Postgres"
},
{
"password" : "root"
},
...
You wanted to use the ATTRIBUTES function, but the objects only have one member, so we can access [0]:
FOR p IN NestedDSP
LET attributes = ATTRIBUTES(p.data)
FOR oneAttribute IN attributes
LET keys = p.data[oneAttribute]
FOR key IN keys
LET keyAttributes=ATTRIBUTES(key)
RETURN keyAttributes
Which gives us the object keys, one per inner most object:
[
[
"src_id"
],
[
"src_name"
],
We inspect whether we now get only the object keys of the inner most structure; we pick the variable names a little more clever than above:
FOR p IN NestedDSP
LET attributes = ATTRIBUTES(p.data)
FOR oneAttribute IN attributes
LET pairs = p.data[oneAttribute]
FOR onePair IN pairs
LET pairKey=ATTRIBUTES(onePair)[0]
RETURN pairKey
YES:
[
"src_id",
"src_name",
"password",
"host",
...
So now its time to construct the results object as you wanted them:
FOR p IN NestedDSP
LET attributes = ATTRIBUTES(p.data)
FOR oneAttribute IN attributes
LET pairs = p.data[oneAttribute]
FOR onePair IN pairs
LET pairKey=ATTRIBUTES(onePair)[0]
RETURN { subject: oneAttribute, predicate: pairKey, object: onePair[pairKey] }
The subject is the number identifying the outermost item, the predicate is the object key, and the object is the value in it:
[
{
"subject" : "203",
"predicate" : "src_id",
"object" : "pos201510060"
},
{
"subject" : "203",
"predicate" : "src_name",
"object" : "Postgres"
}
Which is hopefully what you wanted?