Retrieve one key value from another file at another depth - jq

I have two json files (these are from AWS). One is returned from amazon following a server state change (state.json), the other has details of the instance including a specific tag (The tag is called "Name" and the value has the hostname of the server) - file is called instance.json in my example. I'm trying to write some jq which uses the instanceID retrieved from the state to query the instance details document (with the same instanceID key). I think if I could get the hostname tag added to the state document in the right place, that would be ideal...
This would normally be something I'd possibly be capable of, but the keys are at different depths in the json and I can't figure out how to retrieve different depth/matching keys. (If you're familiar with aws you'll know the server state is also in the instance, however I'm changing the state and don't wish to make 3 amazon calls).
Some sample json below:
instance.json (this is a huge file, I've edited out all the useless bits and maintained the structure):
{
"Reservations": [
{
"Instances": [
{
"InstanceId": "i-1",
"Tags": [
{
"Value": "hostname1",
"Key": "Name"
}
],
"AmiLaunchIndex": 0
}
],
"ReservationId": "r-1",
"Groups": []
},
{
"Instances": [
{
"InstanceId": "i-2",
"Tags": [
{
"Value": "hostname2",
"Key": "Name"
}
],
"AmiLaunchIndex": 0
}
],
"ReservationId": "r-1",
"Groups": []
},
{
"Instances": [
{
"InstanceId": "i-3",
"Tags": [
{
"Value": "hostname3",
"Key": "Name"
}
],
"AmiLaunchIndex": 0
}
],
"ReservationId": "r-1",
"Groups": []
}
]
}
state.json:
{
"StoppingInstances": [
{
"CurrentState": {
"Code": 80,
"Name": "stopped"
},
"InstanceId": "i-1",
"PreviousState": {
"Code": 80,
"Name": "stopped"
}
},
{
"CurrentState": {
"Code": 80,
"Name": "stopped"
},
"InstanceId": "i-2",
"PreviousState": {
"Code": 80,
"Name": "stopped"
}
},
{
"CurrentState": {
"Code": 80,
"Name": "stopped"
},
"InstanceId": "i-3",
"PreviousState": {
"Code": 80,
"Name": "stopped"
}
}
]
}
Desirable output (if possible):
{
"StoppingInstances": [
{
"CurrentState": {
"Code": 80,
"Name": "stopped"
},
"InstanceId": "i-1",
"Hostname": "hostname1",
"PreviousState": {
"Code": 80,
"Name": "stopped"
}
},
{
"CurrentState": {
"Code": 80,
"Name": "stopped"
},
"InstanceId": "i-2",
"Hostname": "hostname2",
"PreviousState": {
"Code": 80,
"Name": "stopped"
}
},
{
"CurrentState": {
"Code": 80,
"Name": "stopped"
},
"InstanceId": "i-3",
"Hostname": "hostname3",
"PreviousState": {
"Code": 80,
"Name": "stopped"
}
}
]
}

Here's a straightforward approach that just uses INDEX. It assumes an invocation of the following form, though different variations are of course possible:
jq -n --argfile state state.json --argfile instance instance.json -f program.jq
where program.jq contains:
INDEX($instance.Reservations[].Instances[]; .InstanceId)
| map_values(.Tags|from_entries.Name) as $dict
| $state
| .StoppingInstances |= map(. + {Hostname: $dict[.InstanceId]})
If you're not sure where .InstanceId is located in instance.json, you could modify the above as follows:
INDEX($instance | .. | objects | select(has("Instances")) | .Instances[];
.InstanceId)
| map_values(.Tags|from_entries.Name) as $dict
| $state
| .StoppingInstances |= map(. + {Hostname: $dict[.InstanceId]})

To query specific tags in instance.json based on ids shared with state.json, you can iterate over the targets by comparing their common ids using a variable and the select function.
jq -r --argfile state state.json --arg tag "Name" '
$state.StoppingInstances[].InstanceId as $id
| .Reservations[].Instances[]
| select(.InstanceId == $id).Tags[]
| select(.Key == $tag).Value
' instance.json
hostname1
hostname2
hostname3
To join records from instance.json with items in state.json, you could use the INDEX and JOIN builtins:
jq --argfile instance instance.json '
.StoppingInstances |= JOIN(
INDEX($instance.Reservations[].Instances[]; .InstanceId); .InstanceId
)
' state.json
{
"StoppingInstances": [
[
{"CurrentState":{"Code":80,"Name":"stopped"},"InstanceId":"i-1","PreviousState":{"Code":80,"Name":"stopped"}},
{"InstanceId":"i-1","Tags":[{"Value":"hostname1","Key":"Name"}],"AmiLaunchIndex":0}
],
[
{"CurrentState":{"Code":80,"Name":"stopped"},"InstanceId":"i-2","PreviousState":{"Code":80,"Name":"stopped"}},
{"InstanceId":"i-2","Tags":[{"Value":"hostname2","Key":"Name"}],"AmiLaunchIndex":0}
],
[
{"CurrentState":{"Code":80,"Name":"stopped"},"InstanceId":"i-3","PreviousState":{"Code":80,"Name":"stopped"}},
{"InstanceId":"i-3","Tags":[{"Value":"hostname3","Key":"Name"}],"AmiLaunchIndex":0}
]
]
}

Related

Simplify array of objects with children using JQ

From Wikidata, I get the following json:
# Sparql query
query=$(cat ./myquery.sparql)
response=$(curl -G --data-urlencode query="${query}" https://wikidata.org/sparql?format=json)
echo "${response}" | jq '.results.bindings'
[
{
"language": {
"type": "uri",
"value": "https://lingualibre.org/entity/Q100"
},
"wikidata": {
"type": "literal",
"value": "Q36157"
},
"code": {
"type": "literal",
"value": "lub"
}
},
{
"language": {
"type": "uri",
"value": "https://lingualibre.org/entity/Q101"
},
"wikidata": {
"type": "literal",
"value": "Q36284"
},
"code": {
"type": "literal",
"value": "srr"
}
}
]
I would like to have the keys directly paired with their values, such as :
[
{
"language": "https://lingualibre.org/entity/Q100",
"wikidata": "Q36157",
"iso": "lub"
},
{
"language": "https://lingualibre.org/entity/Q101",
"wikidata": "Q36284",
"iso": "srr"
}
]
I currently have a non-resilient code, which will break whenever the key names change :
jq 'map({"language":.language.value,"wikidata":.wikidata.value,"iso":.code.value})'
How to pair the keys with their values in a resilient way (not naming the keys) ?
I want to "prune" the child objects so to only keep the value.
You could use map_values which works like the outer map but for objects, i.e. it retains the object structure, including the field names:
jq 'map(map_values(.value))'
[
{
"language": "https://lingualibre.org/entity/Q100",
"wikidata": "Q36157",
"code": "lub"
},
{
"language": "https://lingualibre.org/entity/Q101",
"wikidata": "Q36284",
"code": "srr"
}
]
Note that this solution lacks the name conversion from code to iso.

jq error cannot iterate over null but need to make a different choice

I have a jq filter that selects the rows I need. But sometimes these lines can be empty, and then everything breaks and the rule does not work. I tried to use the if-then-else construct but to no avail.
A rule that works if you process the following json:
.metadata.namespace as $ns | (.spec.rules[0].match.any[].resources.kinds[] / "/") | [select(.[1])[0] // null, select(.[2])[1] // null, last] as [$version,$group,$kind] | {namespace: $ns, kind: $kind, group: $version, version: $group} | with_entries(select(.value!=null))
suitable json:
{
"apiVersion": "kyverno.io/v1",
"kind": "posdfsdf",
"metadata": {
"name": "e-eion",
"namespace": "kke",
"annotations": {
"policies.kyverno.io/title": "Dation",
"policies.kyverno.io/category": "Pod Security Standards (Restricted)",
"policies.kyverno.io/severity": "medium",
"policies.kyverno.io/subject": "Pod",
"kyverno.io/kyverno-version": "1.6.0",
"kyverno.io/kubernetes-version": "1.22-1.23",
"policies.kyverno.io/description": "se`. "
}
},
"spec": {
"validationFailureAction": "audit",
"background": true,
"rules": [
{
"name": "tion",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"validate": {
"message": "Prisd",
"pattern": {
"spec": {
"=(eners)": [
{
"secxt": {
"altion": "false"
}
}
],
"=(i)": [
{
"sext": {
"alcalation": "false"
}
}
],
"containers": [
{
"setext": {
"an": "false"
}
}
]
}
}
}
}
]
}
}
example on which the rule stops working:
{
"apiVersion": "k/v1",
"kind": "Picy",
"metadata": {
"name": "denylation",
"namespace": "what",
},
"spec": {
"validationFailureAction": "audit",
"background": true,
"rules": [
{
"name": "deny-privilege-escalation",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "Priviles[*].securityContext.allowPrind spec.initContalse`.",
"pattern": {
"spec": {
"=(iners)": [
{
"=(seext)": {
"=(aln)": "false"
}
}
],
"containers": [
{
"=(stext)": {
"=(al)": "false"
}
}
]
}
}
}
}
]
}
}
how can this be fixed? I need the rule to work out in any cases and give output
This should work :
.metadata.namespace as $ns |
((.spec.rules[0].match | .. | (objects | .resources.kinds[]?)) / "/") |
[select(.[1])[0] // null, select(.[2])[1] // null, last] as [$version,$group,$kind] |
{namespace: $ns, kind: $kind, group: $version, version: $group} |
with_entries(select(.value!=null))
The failing json gives the following error:
parse error: Expected another key-value pair at line 7, column 3
This is caused by the trailing comma found here:
"metadata": {
"name": "denylation",
"namespace": "what",
},
Removing that comma, the following error is thrown:
jq: error (at :44): Cannot iterate over null (null)
This is caused by the missing any key inside the match object.
We can 'catch' that error using a ? (docs):
(.spec.rules[0].match.any[]?.resources.kinds[] / "/")
^
But due to the missing key, the filter does not find anything and the output is empty.
Updated jqPlay

Remove Quotation from HTTP reponse(string datatype) to pass in post API

I have an API returning data(string format) like this
84, 101, 115, 116
But when I call this in power automate the response I get thru HTTP post is with quotation marks
"84, 101, 115, 116"
Is there any way I can get it without quotation mark " ", As I need the output to send in another API which fails if I send it
with "".
The reason being it needs to pass the response to another API that accepts byte array
like this [84, 101, 115, 116]
currently, inflow it is being sent as ["84, 101, 115, 116"] which fails the API
I tried replace function but it doesn't work for my case.
I think you need to split up your texts into an array.
Start with initialising three variables as shown below ...
... as you can see, I initialised a string variable with your values, it doesn't show the quotes but it's declared as a string so when it treats it, the quotes will be there.
In the next steps, I initialise an array variable that has the following expression ...
split(replace(variables('Response'), ' ', ''), ',')
... and then a blank array that will store the resulting number values.
After the above, loop through each value that was found and add it to the number array, this will convert the value as a string to a numeric value.
The expression above is ... int(item())
That then produces this result and it's that array that can then be passed through to your next API call.
This is the JOSN definition that you can load into your own tenant to see the answer in its entirety.
{
"definition": {
"$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
"actions": {
"For_each": {
"actions": {
"Append_to_array_variable": {
"inputs": {
"name": "Number Array",
"value": "#int(item())"
},
"runAfter": {},
"type": "AppendToArrayVariable"
}
},
"foreach": "#variables('String Array')",
"runAfter": {
"Initialize_Number_Array": [
"Succeeded"
]
},
"runtimeConfiguration": {
"concurrency": {
"repetitions": 1
}
},
"type": "Foreach"
},
"Initialize_Number_Array": {
"inputs": {
"variables": [
{
"name": "Number Array",
"type": "array"
}
]
},
"runAfter": {
"Initialize_String_Array": [
"Succeeded"
]
},
"type": "InitializeVariable"
},
"Initialize_String_Array": {
"inputs": {
"variables": [
{
"name": "String Array",
"type": "array",
"value": "#split(replace(variables('Response'), ' ', ''), ',')"
}
]
},
"runAfter": {
"Initialize__Response": [
"Succeeded"
]
},
"type": "InitializeVariable"
},
"Initialize__Response": {
"inputs": {
"variables": [
{
"name": "Response",
"type": "string",
"value": "84, 101, 115, 116"
}
]
},
"runAfter": {},
"type": "InitializeVariable"
},
"Initialize_variable": {
"inputs": {
"variables": [
{
"name": "Result",
"type": "array",
"value": "#variables('Number Array')"
}
]
},
"runAfter": {
"For_each": [
"Succeeded"
]
},
"type": "InitializeVariable"
}
},
"contentVersion": "1.0.0.0",
"outputs": {},
"parameters": {},
"triggers": {
"Recurrence": {
"evaluatedRecurrence": {
"frequency": "Month",
"interval": 12
},
"recurrence": {
"frequency": "Month",
"interval": 12
},
"type": "Recurrence"
}
}
},
"parameters": {}
}

mapping array of keys to array of values for substitution

Given this data, which is two distinct objects, one having an array of keys and the other a dictionary of key value pairs:
{
"servers": [
{
"location": "server4",
"services": [
"srv07",
"srv06",
"srv01",
"srv04"
]
},
{
"location": "server2",
"services": [
"srv07",
"srv02",
"srv05",
"srv03"
]
}
],
"release": {
"id": "release1",
"services": [
{
"service": "srv01",
"URL": "/srv01_service/v1.20.0"
},
{
"service": "srv02",
"URL": "/srv02_service/v1.14.0"
},
{
"service": "srv03",
"URL": "/srv03_service/v1.15.0"
},
{
"service": "srv04",
"URL": "/srv04_service/v1.18.0"
},
{
"service": "srv05",
"URL": "/srv05_service/v1.14.0"
},
{
"service": "srv06",
"URL": "/srv06_serv/v1.13.0"
},
{
"service": "srv07",
"URL": "/srv07_service/v1.19.0"
}
]
}
}
I am trying to produce this, the first object with the keys replaced with the values from the dictionary. NOTE: I would be fine with renaming services[] to URLs[] if it makes things easier.
{
"servers": [
{
"location": "server4",
"services": [
"/srv07_service/v1.19.0",
"/srv06_serv/v1.13.0",
"/srv01_service/v1.20.0",
"/srv04_service/v1.18.0"
]
},
{
"location": "server2",
"services": [
"/srv07_service/v1.19.0",
"/srv02_service/v1.14.0",
"/srv05_service/v1.14.0",
"/srv03_service/v1.15.0"
]
}
]
}
My latest attempt is close but returns something akin to a Cartesian.
. | .servers[].services[] = (.servers[] as $s | .release.services[] | select(.service as $v | $s.services[] | index($v)).URL) | {servers}
Create an INDEX to lookup in, then map each element you want to change according to the index.
jq '
INDEX(.release.services[]; .service) as $index
| {servers} | .servers[].services[] |= $index[.].URL
'
{
"servers": [
{
"location": "server4",
"services": [
"/srv07_service/v1.19.0",
"/srv06_serv/v1.13.0",
"/srv01_service/v1.20.0",
"/srv04_service/v1.18.0"
]
},
{
"location": "server2",
"services": [
"/srv07_service/v1.19.0",
"/srv02_service/v1.14.0",
"/srv05_service/v1.14.0",
"/srv03_service/v1.15.0"
]
}
]
}
Demo

jq - extract multiple fields from a list, with a nested list of key/value pairs

I have the following structure:
{
"Subnets": [
{
"SubnetId": "foo1",
"Id": "bar1",
"Tags": [
{
"Key": "Name",
"Value": "foo"
},
{
"Key": "Status",
"Value": "dev"
}
]
},
{
"SubnetId": "foo2",
"Id": "bar2",
"Tags": [
{
"Key": "Name",
"Value": "foo"
},
{
"Key": "Status",
"Value": "dev"
}
]
}
]
}
I can extract multiple keys at the "top level" like so:
cat subnets.json| jq '.Subnets[] | "\(.Id) \(.SubnetId)"'
Anyone know how I can also display one of the tags by key name, let's say I also want the Status tag displayed on the same line as the Id and SubnetId.
Thx for any help,
Is this what you are looking for?
jq '.Subnets[] | "\(.Id) \(.SubnetId) \(.Tags | from_entries | .Status)"' subnets.json

Resources