Creating valid JSON for container_definitions in Terraform - json

I’m creating an aws_ecs_task_definition resource. Within that resource, I need a container_definitions, which needs to be a JSON string. I’d like to add multiple secrets to that definition from a list of strings; [“var1”, “var2”].
The output I need looks like:
“secrets”: [
{
“name” = “var1”,
“valueFrom” = “arn:somestuffvar1”
},
{
“name” = “var2”,
“valueFrom” = “arn:somestuffvar2”
}
],
I have tried string interpolation and templatefile, this is the section from my .tftpl
  "secrets": [
   %{ for myvar in myvars ~}
    {
      "name": "${myvar}",
     "valueFrom”: “arn:somestuff${myvar}"
    }
    %{ endfor }
  ],
The problem is the commas. the above gives me
[
{
“name” = “var1”,
“valueFrom” = “arn:somestuffvar1”
}
{
“name” = “var2”,
“valueFrom” = “arn:somestuffvar2”
}
],
with no commas between the braces, if I add a comma, then i get a trailing comma
[
{
},
{
},
],
I’ve tried a zillion syntax variations, I’ve tried jsonencode on the interpolated string, I’ve tried stripping the trailing comma. Nothing gives me valid JSON. What am I missing?

The templatefile function documentation has a section specifically about Generating JSON or YAML from a template, which explicitly discourages using string templating to try to build valid JSON from string fragments like this.
Instead, you should use the jsonencode function as the entire definition of your template, and thus let Terraform be the one to worry about generating valid JSON syntax. You then only need to worry about writing an expression that describes the data structure that remote system expects.
In your case, a template generating a JSON object with just this "secrets" property would look like this:
${jsonencode({
secrets = [
for myvar in myvars : {
name = myvar
valueFrom = "arn:somestuff${myvar}"
}
],
})}
Notice that the entire template consists of a single interpolation sequence ${ ... } and the expression inside it is one big call to the jsonencode function, with the argument describing the data structure to serialize. Therefore inside that argument we're using normal Terraform expression syntax (like you'd write in a resource argument in a .tf file) rather than the special template interpolation/repetition syntaxes. In particular, the value of "secrets" is defined using a for expression.
With your { myvars = ["var1", "var2"] } template variables, this will produce a minified version of the following JSON structure, which I'm showing with manually-added indentation and newlines just so you can read it:
{
"secrets": [
{
"name": "var1",
"valueFrom": "arn:somestuffvar1"
},
{
"name": "var2",
"valueFrom": "arn:somestuffvar2"
}
]
}
I understand that you're only showing a fragment of the template here and so the above won't include all of the other properties included in your template, but hopefully you can see how to use Terraform expression syntax to describe those properties as Terraform object attributes too, so that the overall result of this template will be a valid JSON serialization of the total data structure.

Related

What is the ideal way to JSON.stringify in Terraform?

I'm working on my first Terraform project and I'm looking for the best way to stringify a JSON object. The resource I'm defining has a parameter that expects a JSON string. JSON structure is:
"document": {
"tag": "String Title",
"response": "There's a string response and perhaps a price like $[XX.XX]."
}
}
I don't think jsonencode or jsondecode do this. I could stringify them in advance but that isn't scalable in this case. I wasn't sure if I could do this with JavaScript or another language alongside Terraform, or if there's a function in HCL that will do it.
jsonencode in Terraform is exactly equivalent to JSON.stringify with only one argument in JavaScript.
For example, if you need to assign a string containing a JSON object to an argument called something_json, you could do that like this:
something_json = jsonencode({
document = {
tag = "String Title"
response = "There's a string response and perhaps a price like $[XX.XX]."
}
})
The above would set something_json to a minified version of the following JSON:
{
"document": {
"tag": "String Title",
"response": "There's a string response and perhaps a price like $[XX.XX]."
}
}
Terraform does not have an equivalent of the optional replacer and space arguments in JavaScript's JSON.stringify:
An equivalent of replacer isn't needed in Terraform because all possible values in the Terraform language have a defined JSON equivalent as described in the table in the jsonencode documentation.
space is for generating non-minified JSON; Terraform does not offer any way to do this because it is focused on generating JSON for machine consumption and so prefers to generate the most compact representation possible.

Length is not worked in JSON Extractor in Jmeter

I need to get the count of card from json file. For this I've used $.storedCards.cards.lenght
in JSON Extractor but it doesn't work. There is an error message:
Options AS_PATH_LIST and ALWAYS_RETURN_LIST are not allowed when using path functions!
After that I've tried JSR223 PostProcessor with next script on goovy
def jsonText = '''${AllCards}''' //${AllCards} has json value
def json = new JsonSlurper().parseText(jsonText)
log.info( "Json length---------->"+json.resource.size())
${CardsCount} = props.get("4") //vars.put(json.resource.size.toString())
but there is problem with set value to my variable. Or when i've created variable in Groovy it was impossible to use outside from script.
My json file
"storedCards":
{
"cards":
[
{
"CardId":"123",
"cardBrand":"Visa",
"lastFourDigits":"2968",
},
{
"CardId":"321",
"cardBrand":"Visa",
"lastFourDigits":"2968",
},
..........
],
How can i get the count of card and set to my Variables? what should i use for this?
Your JSON data seems to be invalid. Assuming you have the valid JSON like below, I'm answering your question.
{
"storedCards": {
"cards": [
{
"CardId": "123",
"cardBrand": "Visa",
"lastFourDigits": "2968"
},
{
"CardId": "321",
"cardBrand": "Visa",
"lastFourDigits": "2968"
}
]
}
}
You dont need to write Groovy code, you can resolve this using JSON Extractor. Instead of using length function, use JSON path predicate like this-
$.storedCards.cards[*]
Though Variable you used in JSON Extractor won't give the solution right away, another JMeter function helps - __RandomFromMultipleVars
Excerpt from documentation -
The RandomFromMultipleVars function returns a random value based on the variable values provided by Source Variables.
The variables can be simple or multi-valued as they can be generated by the following extractors:
Boundary Extractor
Regular Expression Extractor
CSS Selector Extractor
JSON Extractor
XPath Extractor
XPath2 Extractor
Multi-value vars are the ones that are extracted when you set -1 for
Match Numbers. This leads to creation of match number variable called
varName_matchNr and for each value to the creation of variable
varName_n where n = 1, 2, 3 etc.
So once you use the predicate, you will get the count in the yourVariableName_matchNr. Example:-
Hope this help.

Escape dots in Groovy GPath

I am using the RestAssured framework in Java, whose documentation contains this note
Note that the "json path" syntax uses Groovy's GPath notation and is not to be confused with Jayway's JsonPath syntax.
I need to validate the following JSON:
"_source": {
"logSource": {
"logger.name": "LogbackLogger",
},
}
And the selectors like
_source.logSource.logger.name or
_source.logSource.logger.name[0] return no result.
I assume this is due to the dot in the logger.name property.
If no escaping is done, logger.name is interpreted as if name was under the logger, which is not true.
How do I correctly escape the dot character in the GPath, so that logger.name is considered as a single property name?
Thanks!
You have a trivial issue.
Just wrap it in between single quote i.e., 'logger.name'
as there is special character.
Here is complete example :
def string = """{ "_source": {
"logSource": {
"logger.name": "LogbackLogger",
}
}
}"""
def json = new groovy.json.JsonSlurper().parseText(string)
println json.'_source'.logSource.'logger.name'

TJSONUnMarshal: how to track what is actually unmarshalled

Is there another way to track what is unmarshalled than write own reverter for each field?
I'm updating my local data based on json message and my problem is (simplified):
I'm expecting json like
{ "items": [ { "id":1, "name":"foobar", "price":"12.34" } ] }
which is then unmarshaled to class TItems by
UnMarshaller.TryCreateObject( TItems, TJsonObject( OneJsonElement ), TargetItem )
My problem is that I can't make difference between
{ "items": [ { "id":1, "name":"", "price":"12.34" } ] }
and
{ "items": [ { "id":1, "price":"12.34" } ] }
In both cases name is blank and i'd like to update only those fields that are passed on json message. Of course I could create a reverted for each field, but there are plenty of fields and messages so it's quite huge.
I tried to look REST.Jsonreflect.pas source, but couldn't make sense.
I'm using delphi 10.
In Rest.Json unit there is a TJson class defined that offers several convenience methods like converting objects to JSON and vice versa. Specifically, it has a class function JsonToObject where you can specify options like for example ignore empty strings or ignore empty arrays. I think the TJson class can serve you. For unmarshalling complex business objects you have to write custom converters though.
Actually, my problem was finally simple to solve.
Instead of using TJSONUnMarshal.tryCreateObject I use now TJSONUnMarshal.CreateObject. First one has object parameters declared with out modifier, but CreateObject has Object parameter var modifier, so I was able to
create object, initalize it from database and pass it to CreateObject which only modifies fields in json message.

Invalid JSON but validates on JSONLint

I have the following JSON which validates on JSONLint.com but when I pass it to JSON.parse() I get the error
SyntaxError: JSON.parse: unexpected character
...0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...
This is apparently the last "correct": line
var theJSON = JSON.parse({
"data": [
{
"wrong": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"correct": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
},
{
"wrong": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"correct": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
},
{
"wrong": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"correct": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
},
{
"wrong": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
"correct": "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
}
]
});
You, like many, have confused JavaScript's literal syntax with JSON. This happens a lot as JSON uses a subset of JavaScript's literal syntax so it looks a lot alike. JSON, however, is always a string. It is a serialized data scheme for porting data structures between langs/platforms.
Also confusing is that a string of JSON which has been output by any platform can be copied and pasted right into JavaScript and used. Again, this is because of the shared syntax. Having pasted such output right into JavaScript, however, one is no longer using JSON--they are now writing JavaScript in literal syntax. That is, unless, you pasted it between quotes and properly escaped the resulting string. But there's no sense in doing so as then it needs to be parsed in order to end up with what you already had.
JSON.parse() is a method for unserializing data which had been serialized into JSON. It expects a string because, well, JSON is a string. You're passing an object (in literal syntax). It does not need parsing...it is already the thing you want.
Wrapping your object literal in single quotes would make the code work, but it would be pointless to do so as the parse would simply result in what you already have.
Your code would be better written if you replaced the variable named theJSON with one named theObject and made it look as such:
var theObject = {
data: [
{
wrong: "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
correct: "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
},
{
wrong: "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
correct: "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
},
{
wrong: "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
correct: "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
},
{
wrong: "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
correct: "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
}
]
};
Whatever code wanted to use the parse result should be fine once you've done it.