Azure CLI and JMESPath Query -Part 2
On
We continue with our post on Azure CLI and JMESPath. In our previous post, we looked at basic expressions, slicing, list projections, slice projections and object projections.
More than one projection can be used in a JMESPath expression.
Consider
{
"reservations": [
{
"instances": [
{"state": "running"},
{"state": "stopped"}
]
},
{
"instances": [
{"state": "terminated"},
{"state": "running"}
]
}
]
}
We can do
reservations[*].instances[*].state => [
[
"running",
"stopped"
],
[
"terminated",
"running"
]
]
//the same result as reservations[:2].instances[*]
This expression is saying that the top level key reservations
has an array as a value.
reservations[*] =>[
{
"instances": [
{
"state": "running"
},
{
"state": "stopped"
}
]
},
{
"instances": [
{
"state": "terminated"
},
{
"state": "runnning"
}
]
}
]
For each of those array elements, project the instances[*].state
expression. Within each list element, there’s an instances
key which itself is a value, and we create a sub projection for each each list element in the list.
reservations[*].instances[*] => [
[
{
"state": "running"
},
{
"state": "stopped"
}
],
[
{
"state": "terminated"
},
{
"state": "runnning"
}
]
]
We then project .state
over the each sub list
What if we just want a list of all the states of our instances? We’d ideally like a result [“running”, “stopped”, “terminated”, “running”]. This is where flatten projections become useful.
Flatten Projections
The Flatten projection uses the flatten operator, []
, to flatten sublists into the parent list (not recursively, just one level).
Take
[
[0, 1],
2,
[3],
4,
[5, [6, 7]]
]
The expression [ ] will result in
[ 0, 1, 2, 3, 4, 5, [ 6, 7 ] ]
The sublists have been flatten into the parent list. This creates a projection, so anything on the RHS of the flatten projection can be projected onto the newly created flattened list.
[].[] => [ 0, 1, 2, 3, 4, 5, 6, 7 ]
The expression reservations[ ] will not change the reservation JSON document much (since it does not have direct sublist), but
reservations[*].instances[] => [
{
"state": "running"
},
{
"state": "stopped"
},
{
"state": "terminated"
},
{
"state": "running"
}
]
Which returns a projection list, and with the expression .state, we get
[
"running",
"stopped",
"terminated",
"running"
]
Filter Expressions
A filter expression allows you to filter the LHS of the projection before evaluating the RHS of a projection.
This is comparable to the pseudocode, finding all machines that has a state of running
result = []
foreach machine in inputData['machines']
if machine['state'] == 'running'
result.insert_at_end(machine['name'])
return result
The JMESPath equivalent is
machines[?state=='running'].name
Given a JSON document
{
"machines": [
{"name": "a", "state": "running"},
{"name": "b", "state": "stopped"},
{"name": "b", "state": "running"}
]
}
the result is
[
"a",
"b"
]
JMESPath offers the standard comparison and logical operators. These include <
, <=
, >
, >=
, ==
, and !=
. JMESPath also supports logical and (&&
), or (||
), and not (!
).
The following Azure CLI command
az vm list -g demo --query "[?storageProfile.osDisk.osType=='Linux'].{Name:name, admin:osProfile.adminUsername}" --output table
will produce
Name Admin
------ ---------
Test-2 sttramer
TestVM azureuser
Pipe Expressions
Projections always return an array. The Pipe expression is useful when you want to operate on the result of a projection rather than projecting an expression onto each element in the array.
From our earlier people JSON document example,
{
"people": [
{"name": "James", "age": 30,"state": {"name": "up"}},
{"name": "Jacob", "age": 40,"state": {"name": "down"}},
{"name": "Jayden", "age": 50,"state": {"name": "down"}},
{"missing": "different"}
],
"foo": {"bar": "baz"}
}
we can use the pipe expression to get the name of the person at index 0 of our projected list
people[*].name | [0] => "James"
MultiSelect List & MultSelect Hashes
Up to this point, we have been able to select a property from our projected list using the RHS expression.
MultiSelect allows us to create JSON elements that don’t exist in a JSON document. A multiselect list creates a list and a multiselect hash creates a JSON object.
MultiSelect List
Up to this point, we have been able to select a property from our projected list using the RHS expression.
To get more than one property, we put the RHS expressions in square brackets [ ]
(a multiselect list) as a comma-separated list.
The expression people[].[name,state.name]
will produce
[
[
"James",
"up"
],
[
"Jacob",
"down"
],
[
"Jayden",
"up"
]
]
With our Azure CLI comand, we can get the VM name, admin user, and SSH key all at once using the command:
az vm show -g demo -n text-vm --query '[name, osProfile.adminUsername, osProfile.linuxConfiguration.ssh.publicKeys[0].keyData]' -o json
[
"test-vm",
"azureuser",
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMobZNJTqgjWn/IB5xlilvE4Y+BMYpqkDnGRUcA0g9BYPgrGSQquCES37v2e3JmpfDPHFsaR+CPKlVr2GoVJMMHeRcMJhj50ZWq0hAnkJBhlZVWy8S7dwdGAqPyPmWM2iJDCVMVrLITAJCno47O4Ees7RCH6ku7kU86b1NOanvrNwqTHr14wtnLhgZ0gQ5GV1oLWvMEVg1YFMIgPRkTsSQKWCG5lLqQ45aU/4NMJoUxGyJTL9i8YxMavaB1Z2npfTQDQo9+womZ7SXzHaIWC858gWNl9e5UFyHDnTEDc14hKkf1CqnGJVcCJkmSfmrrHk/CkmF0ZT3whTHO1DhJTtV stramer@contoso"
]
MultiSelect Hashes
To get a dictionary instead of an array when querying for multiple values, use the { }
(multiselect hash) operator.
From our previous example,
people[].{Name: name, State: state.name}
will produce
[
{
"Name": "James",
"State": "up"
},
{
"Name": "Jacob",
"State": "down"
},
{
"Name": "Jayden",
"State": "up"
}
]
With our Azure JSON document, the following command
az vm list -g demo --query "[].{Name:name, Storage:storageProfile.osDisk.managedDisk.storageAccountType}" -o json
will give us
[
{
"Name": "test-vm",
"Storage": "StandardSSD_LRS"
},
{
"Name": "WinTest",
"Storage": "StandardSSD_LRS"
}
]
Because a multselect hash produces a a list of JSON object element, we can use a filter expression on the results.
az vm list -g demo --query "[].{Name:name, Storage:storageProfile.osDisk.managedDisk.storageAccountType}[?Name =='test-vm']" -o json
[
{
"Name": "test-vm",
"Storage": "StandardSSD_LRS"
}
]
Functions
JMESPath supports function expressions, for example:
length(people) => 3
Functions can be used to;
Transform Data
Functions can be used to transform data on the LHS. The following example prints the name of the oldest person in the people array:
max_by(people, &age).name => "a"
The following Azure CLI command
az vm list -g demo --query "sort_by([].{Name:name, Size:storageProfile.osDisk.diskSizeGb}, &Size)" --output table
will result
Name Size
------- ------
TestVM 30
Test-2 32
WinTest 127
Filter expressions
Functions can also be combined with filter expressions.
myarray[?contains(@, 'foo')] //finds all elements in myarray that contains the string foo.
myarray[?!contains(@, 'foo')] //finds all elements in myarray that does not contains the string foo.
The @ symbol, called current-node token can be used to represent the current node being evaluated
The following Azure CLI command
az vm list -g demo --query "[?contains(storageProfile.osDisk.managedDisk.storageAccountType,'SSD')].{Name:name, Storage:storageProfile.osDisk.managedDisk.storageAccountType}" -o json
outputs
[
{
"Name": "TestVM",
"Storage": "StandardSSD_LRS"
},
{
"Name": "WinTest",
"Storage": "StandardSSD_LRS"
}
]
In MultiSelect
people[].[length(@), name]
//equivalent to
people[].[length(@), @.name]
The expression creates an array containing the total number of elements in each people object followed by the value of name.
[
[
3,
"James"
],
[
3,
"Jacob"
],
[
3,
"Jayden"
],
[
1,
null
]
]
Other posts in this series
- Azure CLI and JMESPath Query
- Azure CLI and JMESPath Query -Part 2