Merge fails in terraform - for-loop

Consider I have a variable -
[
{
"outer_key_1" = [
{
"ip_cidr" = "172.16.6.0/24"
"range_name" = "range1"
},
{
"ip_cidr" = "172.16.7.0/24"
"range_name" = "range2"
},
{
"ip_cidr" = "172.17.6.0/24"
"range_name" = "range3"
},
{
"ip_cidr" = "172.17.7.0/24"
"range_name" = "range4"
},
]
},
{
"outer_key_2" = [
{
"ip_cidr" = "172.16.5.0/24"
"range_name" = "range5"
},
{
"ip_cidr" = "172.17.5.0/24"
"range_name" = "range6"
},
]
},
]
I am able to merge the maps inside the list to get this output -
{
"outer_key_1" = [
{
"ip_cidr" = "172.16.6.0/24"
"range_name" = "range1"
},
{
"ip_cidr" = "172.16.7.0/24"
"range_name" = "range2"
},
{
"ip_cidr" = "172.17.6.0/24"
"range_name" = "range3"
},
{
"ip_cidr" = "172.17.7.0/24"
"range_name" = "range4"
},
]
"outer_key_2" = [
{
"ip_cidr" = "172.16.5.0/24"
"range_name" = "range5"
},
{
"ip_cidr" = "172.17.5.0/24"
"range_name" = "range6"
},
]
}
I have done this using
result = merge(variable[0], variable[1])
But when I try this
result = merge(variable[*])
I get an error saying
Call to function "merge" failed: arguments must be maps or objects, got
"tuple".
Why does merge fail when I use the splat operator?
Is there a better way to merge maps in list as required above?

The merge function is defined to take one or more separate arguments that are each map or object values. When you call merge(variable) (which is the same as merge(variable[*]) if variable is already a list) you're instead giving it one list argument, leading to this error.
To pass the elements of a list or tuple as multiple individual arguments, you can use argument expansion syntax:
result = merge(variable...)
The ... symbol, when given after the last argument in a function call, tells Terraform to evaluate the expression as a list or tuple before calling the function, and then use the elements in that result a separate argument each.
In other words, merge(variable...) is the same as merge(variable[0], variable[1], variable[2]) etc for each element in the list, without needing to know ahead of time how many elements are in the list.
Applying the splat operator [*] to a value that is already a list is redundant; the splat operator is useful in this situation only when it's followed by at least one additional attribute name or index, like example[*].id or example[*][0].
There is one situation where a lonely [*] with no following steps can be useful: if you apply it to anything that isn't a list or tuple then Terraform will either wrap it in a single-element list (if it's non-null) or return an empty list (if it's null), which can be useful in some unusual cases where you're given a single value that might be null and need to adapt it into a zero-or-one length list to use with features like resource for_each and in dynamic blocks.

Related

elasticsearch sort by price with currency

I have data
{
"id": 1000,
"price": "99,01USA",
},
{
"id": 1001,
"price": "100USA",
},
{
"id": 1002,
"price": "780USA",
},
{
"id": 1003,
"price": "20USA",
},
How I sort order by price (ASC , DESC)
You can alter it a little to parse price to integer and then sort it
You can create a dynamic sort function that sorts objects by their value that you pass:
function dynamicSort(property) {
var sortOrder = 1;
if(property[0] === "-") {
sortOrder = -1;
property = property.substr(1);
}
return function (a,b) {
/* next line works with strings and numbers,
* and you may want to customize it to your needs
*/
var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
return result * sortOrder;
}
}
So you can have an array of objects like this:
var People = [
{Name: "Name", Surname: "Surname"},
{Name:"AAA", Surname:"ZZZ"},
{Name: "Name", Surname: "AAA"}
];
...and it will work when you do:
People.sort(dynamicSort("Name"));
People.sort(dynamicSort("Surname"));
People.sort(dynamicSort("-Surname"));
Actually this already answers the question. Below part is written because many people contacted me, complaining that it doesn't work with multiple parameters.
Multiple Parameters
You can use the function below to generate sort functions with multiple sort parameters.
function dynamicSortMultiple() {
/*
* save the arguments object as it will be overwritten
* note that arguments object is an array-like object
* consisting of the names of the properties to sort by
*/
var props = arguments;
return function (obj1, obj2) {
var i = 0, result = 0, numberOfProperties = props.length;
/* try getting a different result from 0 (equal)
* as long as we have extra properties to compare
*/
while(result === 0 && i < numberOfProperties) {
result = dynamicSort(props[i])(obj1, obj2);
i++;
}
return result;
}
}
Which would enable you to do something like this:
People.sort(dynamicSortMultiple("Name", "-Surname"));
Subclassing Array
For the lucky among us who can use ES6, which allows extending the native objects:
class MyArray extends Array {
sortBy(...args) {
return this.sort(dynamicSortMultiple(...args));
}
}
That would enable this:
MyArray.from(People).sortBy("Name", "-Surname");

Validate complex type using terraform

Im trying to validate a terraform complex map. The map contains a list of objects with multiple attributes. I was trying to validate each attribute and unfortunately, it seems that whatever condition I put, it is ok. It's like the validation is always true.
Could anyone have a look and see what I'm doing wrong? Also, would it be possible to validate map keys?
variable "complexmap" {
type = map(object(
{
reference = string
type = string
count = string
}
))
default = {
"key1" = {
count = "5"
reference = "refcode1"
type = "novel"
},
"key2" = {
count = "2"
reference = "REFcode2"
type = "comics"
},
}
validation {
condition = (
alltrue([for v in var.complexmap : (
can(regex("[a-z0-9]{4,10}", v["reference"]))
&& v["count"] >= 2 && v["count"] < 10
&& (v["type"] == "novel" || v["type"] == "comics")
)])
)
error_message = "Validation of an object failed."
}
}

Terraform output object with multiple attributes for each of `for` resources?

I have terraform with a resource being created with for. As is typical, each instance of this resource has several attributes. At the moment I have a series of map outputs for this resource group but each consists of only a single key-value pair. I would like my terraform output to include a list or map of maps or objects with all of the attributes grouped by resource instance. How do I do this without using flatten; zipmap etc to construct them from my current outputs? This example is with aws_route53_record but this is a generic query:
Current code
output "r53record_zonal_fqdn" {
value = {
for entry in aws_route53_record.zonal :
entry.name => entry.fqdn
}
}
output "r53record_zonal_records" {
value = {
for entry in aws_route53_record.zonal :
entry.name => entry.records
}
}
output "r53record_zonal_zone_id" {
value = {
for entry in aws_route53_record.zonal :
entry.name => entry.zone_id
}
}
As you would expect, this renders three maps with aws_route53_record.zonal.name as the key and the other attribute(s) as the value.
What I would like is to have these outputs grouped by resource with a predefined key for each value, e.g. (pseudocode):
output "r53record_zonal_zone_id" {
value = {
for entry in aws_route53_record.zonal : {
instance[count.index] {
"name" = entry.name
"fqdn" = entry.fqdn
"records" = entry.records
"zone_id" = entry.zone_id
}
}
}
}
Producing a map or list of maps for each instance.
How can this or something like it be done?
I created a random route53_record resource block with two "name" arguments in for_each loop and tried to output something close to what you were looking for.
Assuming "mydomain.com" is the domain in Route53 as example....
resource "aws_route53_record" "zonal" {
for_each=toset(["site1","site2"])
name = each.key
zone_id = "ABCDZONEIDSTRING"
type = "A"
ttl = "300"
records = ["192.168.1.10"]
}
output "test" {
value = {
for dns, details in aws_route53_record.zonal:
dns => ({"fqdn" = details.fqdn , "zone_id" = details.zone_id , "records" = details.records})
}
}
this will create output in this fashion..
test = {
"site1" = {
"fqdn" = "site1.mydomain.com"
"records" = [
"192.168.1.10",
]
"zone_id" = "Z0630117NTQNSYTXYQ4Z"
}
"site2" = {
"fqdn" = "site2.mydomain.com"
"records" = [
"192.168.1.10",
]
"zone_id" = "Z0630117NTQNSYTXYQ4Z"
}
}
Suppose you passed the name values with domain name, like below...
for_each=toset(["site1.mydomain.com","site2.mydomain.com"])
the output would look like
test = {
"site1.mydomain.com" = {
"fqdn" = "site1.mydomain.com"
"records" = [
"192.168.1.10",
]
"zone_id" = "ABCDMYZONEIDSTRING"
}
"site2.mydomain.com" = {
"fqdn" = "site2.mydomain.com"
"records" = [
"192.168.1.10",
]
"zone_id" = "ABCDMYZONEIDSTRING"
}
}

How to check an empty JSONArray in swiftyJSON

I have a JSON that have a JSONArray as a value in one of the json inside it. here is the example of it.
[
{
"id": 1,
"symptoms" : [{\"key\":\"sample1\",\"value\":5},{\"key\":\"sample2\",\"value\":5}]
},
{
"id": 2,
"symptoms" : [{\"key\":\"sample3\",\"value\":1}]
},
{ "id": 3,
"symptoms" : []
},
{
"id": 4,
"symptoms": [{\"key\":\"sample4\",\"value\":1}]
}
]
So what I am doing is that I am parsing the inner JSON and place it in a String Array. But whenever I look up to symptoms it skips the empty JSONArray. So whenever i print the String Array it goes like this (with the given sample on top) ["sample1", "sample2", "sample3", "sample4"]. But i want to do is to append an "" to the String Array whenever the JSONArray is empty so it should be like this ["sample1", "sample2", "sample3", "", "sample4"]. Anyone can help me with this? Here is my code
var arrayHolder: [String] = []
var idHolder: [Int] = []
for item in swiftyJSON.arrayValue {
idHolder.append(item["id"].intValue)
//for the inner JSON
let innerJSON = JSON(data: item["symptoms"].dataUsingEncoding(NSUTF8StringEncoding)!)
for symptoms in innerJSON.arrayValue {
arrayHolder.append(symptoms["key"].stringValue)
}
}
print(idHolder) // [1,2,3,4]
print(arrayHolder) // ["sample1","sample2","sample3","sample4"]
Just check if innerJSON is empty:
for item in swiftyJSON.arrayValue {
idHolder.append(item["id"].intValue)
//for the inner JSON
let innerJSON = item["symptoms"].arrayValue // non need to create a new JSON object
if innerJSON.isEmpty {
arrayHolder.append("")
} else {
for symptoms in innerJSON {
arrayHolder.append(symptoms["key"].stringValue)
}
}
}

Extracting and processing from JSON response

I am doing a hotel search by entering the hotel names.Below is the valid JSON response. This is a response for one hotel search. I have run 15 threads and I have 14 more JSON response for different hotels similar to this one. In this response you can see "providers" and "results" that refer to the providers with array values. My requirement is to find out how many offers ie., results each provider has in total across all the 15 hotels.
"providers":
[
{
"MM_logofile":"agd.svg",
"MM_isOfficialWithoutLogo":false,
"code":"AGD",
"name":"Agoda.com",
"logo":"AGD.png",
"isOfficial":false
},
{
"MM_logofile":"ian.svg",
"MM_isOfficialWithoutLogo":false,
"code":"IAN",
"name":"Hotels.com",
"logo":"IAN-Other.png",
"isOfficial":false
},
{
"MM_logofile":"gar.svg",
"MM_isOfficialWithoutLogo":false,
"code":"GAR",
"name":"getaroom.com",
"logo":"GAR.png",
"isOfficial":false
},
{
"MM_logofile":"exp.svg",
"MM_isOfficialWithoutLogo":false,
"code":"EXP",
"name":"Expedia.dk",
"logo":"EXP-DK.png",
"isOfficial":false
},
{
"MM_logofile":"acc.svg",
"MM_isOfficialWithoutLogo":false,
"code":"ACC",
"name":"AccorHotels.com",
"logo":"ACC.png",
"isOfficial":true
},
],
"results":
[
{
"roomName":"Standard Twin Rm Special Offer - Best price guarantee",
"totalRate":918.0,
"isCheapestRate":true,
"hasFreeCancelation":false,
"inclusions":
[
],
"availableRooms":null,
"providerIndex":0,
"includesAllTaxes":false,
"excludedCharges":
[
0
],
"bookUri":"https://72750.api.hotelscombined.com/ProviderRedirect.ashx?key=0.11648360.-378376995.495.USD.123155627&source=202-0&a_aid=72750&brandID=177977"
},
{
"roomName":"Standard Double Rm Special Offer - Best price guarantee",
"totalRate":918.0,
"isCheapestRate":false,
"hasFreeCancelation":false,
"inclusions":
[
],
"availableRooms":null,
"providerIndex":0,
"includesAllTaxes":false,
"excludedCharges":
[
0
],
"bookUri":"https://72750.api.hotelscombined.com/ProviderRedirect.ashx?key=0.11648360.-378376995.496.USD.1523114518&source=202-1&a_aid=72750&brandID=177977"
},
{
"roomName":"Standard Double Room Hot Deal - Best price guarantee",
"totalRate":918.0,
"isCheapestRate":false,
"hasFreeCancelation":false,
"inclusions":
[
],
"availableRooms":null,
"providerIndex":1,
"includesAllTaxes":false,
"excludedCharges":
[
0
],
"bookUri":"https://72750.api.hotelscombined.com/ProviderRedirect.ashx?key=0.11648360.-378376995.497.USD.573302441&source=202-2&a_aid=72750&brandID=177977"
},
{
"roomName":"Standard Twin Room Hot Deal - Best price guarantee",
"totalRate":918.0,
"isCheapestRate":false,
"hasFreeCancelation":false,
"inclusions":
[
],
"availableRooms":null,
"providerIndex":2,
"includesAllTaxes":false,
"excludedCharges":
[
0
],
"bookUri":"https://72750.api.hotelscombined.com/ProviderRedirect.ashx?key=0.11648360.-378376995.498.USD.1523907592&source=202-3&a_aid=72750&brandID=177977"
},
{
"roomName":"Standard Room, 1 Double Bed",
"totalRate":926.2,
"isCheapestRate":false,
"hasFreeCancelation":false,
"inclusions":
[
],
"availableRooms":null,
"providerIndex":3,
"includesAllTaxes":false,
"excludedCharges":
[
0
],
"bookUri":"https://72750.api.hotelscombined.com/ProviderRedirect.ashx?key=0.13476094.-378377052.1210.USD.1325439035&source=202-4&a_aid=72750&brandID=177977"
},
],
Following is the beanshell code:
import java.util.HashMap;
import java.util.Map;
results_count = Integer.parseInt(vars.get("results_matchNr"));
providers_count = Integer.parseInt(vars.get("providers_matchNr"));
log.info("total results " + results_count);
Map results = new HashMap();
Map providers = new HashMap();
for(i=1; i<=providers_count; i++){
log.info("iteration " + i);
temp = vars.get("providers_"+i);
log.info("provider_name " + temp);
providers.put(i-1, temp);
}
log.info("providers: " + providers);
int provider_index = -1;
String provider_name = "";
for(i=1; i<=results_count; i++){
log.info("iteration " + i);
provider_index = Integer.parseInt(vars.get("results_"+i));
log.info("provider_index " + provider_index);
provider_name = providers.get(provider_index);
log.info("provider name :" + provider_name);
if(results.get(provider_name) == null){
log.info("ading key for the first time " + provider_name);
results.put(provider_name, 1);
}
else{
log.info("second time " + provider_name);
int existing = results.get(provider_name);
log.info("exisiting value " + existing);
int updateValue = existing+1;
log.info("updated value: " + updateValue);
results.put(provider_name, updateValue);
}
}
log.info("results-providers mapping " + results);
int threadNum = ctx.getThreadNum();
// if you want to log something to jmeter.log file
// Pass true if you want to append to existing file othewise false.
f = new FileOutputStream("G:\\naveen\\mywork\\testing\\performance\\tools\\jmeter\\examples\\result.csv", true);
p = new PrintStream(f);
this.interpreter.setOut(p);
String output = "thread number#" + threadNum + " " + results;
print(output);
f.close();
Add BeanShell PostProcessor to the sampler and add the above code.
Add JSONPATH Extractor to get the providers and add the following syntax:
$.providers[*].name
Add another JSONPATH Extractor to get the results/offers and add the following syntax:
$.results[*].providerIndex
Note: change the file location to store the results as per your machine in the beanshell code.
Following is the results which will be saved into the file:
thread number#0 {Expedia.dk=1, Agoda.com=2, Hotels.com=1, getaroom.com=1}
thread number#1 {Expedia.dk=1, Agoda.com=2, Hotels.com=1, getaroom.com=1}
// two rows for two threads. here, Agoda.com provider has two offers, so the count is 2 and remaining providers has one offer each.
Note: verified the script for 2 users. there will be two entries present in the results file (one thread -> one row). chnage the foramt as per your requirements on beanshell code (code related to file writing).

Resources