Have you ever used Azure CLI? Azure CLI is probably the best way to interact with Microsoft Azure and to automate recurring tasks in the context of Microsoft’s public cloud. However, I often see people writing fundamental shell scripts to execute more advanced queries or filter simple lists of elements returned from Azure CLI.
Although using shell scripts is an excellent way to get things done, it’s more time-consuming compared to learning all the capabilities that Azure CLI brings to the table. Microsoft provides some good documentation on how to write fundamental queries with Azure CLI. However, more advanced, real-world queries are not documented. This is why people start tinkering with shell scripts instead of using built-in capabilities. So let’s quickly catch up with fundamental JMESPath queries that every Azure CLI user should know.
Projecting a property from an object
Let’s say you want to get the resource identifier of an Azure Container Registry instance to attach that particular instance to an Azure Kubernetes Service (AKS). You can do so by combining --query
and -otsv
:
# store ACR resource identifier in acrId
acrId=$(az acr show -n myacr --query "id" -otsv)
echo $acrId
# /subscriptions/<SUB_ID>/resourceGroups/rg-sample/providers/Microsoft.ContainerRegistry/registries/myacr
Note that this command lacks the -g (--resource-group)
flag. It’s not required for az acr
.
Project a property from objects in an array
Now let’s grab all resource identifiers of storage accounts within a certain Resource Group:
az storage account list -g rg-sample --query "[].{myId:id}" -ojson
The query
consists of multiple parts. First, we specify that we expect the command to return an array that we want to do further processing on []
to project properties from objects in that array; we specify an alias of myId
which should be set to the id
property of every object.
[
{
"myId": ""
},
{
"myId": ""
}
]
Project multiple properties from objects in an array
We can extend the previous example to grab also the name of the storage account and the stock keeping unit (sku):
az storage account list -g rg-sample \
--query "[].{myId:id,name:name,sku:sku.name}" \
-ojson
Take a look at sku
. We use dot-notation (.
) to access nested properties. Which will return the list of all storage accounts in rg-sample
as JSON:
[
{
"myId": "",
"name": "",
"sku": ""
},
{
"myId": "",
"name": "",
"sku": ""
}
]
Filtering arrays
Filtering arrays in Azure CLI can become more complicated depending on the scenario. Microsoft provides some samples on filtering arrays using operators and built-in functions using string
fields.
However, in reality, there is more than just strings :). Again, let’s review basic filtering:
az storage account list -g rg-samples \
--query "[?accessTier == 'Hot']" \
-otable
We extend our intention to process an array []
by specifying the filter condition, which must evaluate as true
. The condition is introduced by ?
followed by the actual condition.
Use logical operators in filters
In the previous example, we queried for all Storage Accounts with accessTier
set to Hot
, let’s extend the query by using the logical and operator &&
to filter also on the sku.tier
property:
az storage account list -g rg-samples \
--query "[?accessTier == 'Hot' && sku.tier == 'Standard']" \
-otable
Combine filters and projections
Obviously, we can combine filters and projections in a single query:
az storage account list -g rg-samples \
--query "[?accessTier == 'Hot' && sku.tier == 'Standard]{myId:id,name:name,sku:sku.name}" \
-otable
Filter arrays (booleans)
To filter arrays on fields of type boolean, we must escape the desired field value. See the following query, escaping false
by using a combination of ````` (backslash + back-tick):
az storage account list -g rg-samples \
--query "[?allowBlobPublicAccess==\`false\`]" \
-otable
We can simplify the query if we want to check if the boolean property is true
by removing the comparison:
az storage account list -g rg-samples \
--query "[?allowBlobPublicAccess]" \
-otable
Filter arrays (numbers)
Filtering arrays on fields of type number works the same way as filtering on fields of type boolean. Again, we must use proper escaping syntax. For demonstration purposes, let’s examine some information about the secure score calculated by Microsoft Defender for Cloud:
az security secure-score-controls list \
--query "[?unhealthyResourceCount>=\`1\`]" \
-otable
This works also for floating-point numbers:
az security secure-score-controls list \
--query "[?current>=\`3.3\`]"
Sorting arrays by a property
We can sort arrays using the built-in sort_by
function. For example, let’s sort all secure score control details returned from the previous query by the number of healthy resources (healthyResourceCount
):
az security secure-score-controls list \
--query "sort_by([? current >= \`3.30\`], &healthyResourceCount)" \
-otable
You’ve to prefix the desired property (healthyResourceCount
) with &
to tell Azure CLI that the desired property is a nested in every object of the array.
Working with nested arrays
Sometimes, requirements are a bit more sophisticated. Depending on the actual resource type, received responses may become pretty big. Required information is always in some nested array or property (at least for me :D). Luckily, we can extend our JMESPath queries to support more complex situations.
Project properties from nested arrays
Have you ever wondered which permissions are associated with a given principal in an instance of Azure Key Vault? You can get information like this quickly by using proper JMESPath queries as shown here:
az keyvault show -n kv-sample -g rg-sample \
--query "properties.accessPolicies[*].{objectId:objectId,permissions:permissions}[]" \
-ojson
This query will print all object identifiers and their associated permissions in the format of
[
{
"objectId": "",
"permissions":
{
"certificates": [],
"keys": [],
"secrets": [],
"storage": []
}
},
{
"objectId": "",
"permissions":
{
"certificates": [],
"keys": [],
"secrets": [],
"storage": []
}
}
]
Filter and project properties from nested arrays
We can take the previous sample further and ask Azure CLI to show only object identifiers and their permissions if the underlying principal can delete secrets from Azure Key Vault. To do so, we add a filter that uses the JMESPath built-in function contains
.
az keyvault show -n kv-sample -g rg-sample \
--query "properties.accessPolicies[*].{objectId:objectId,permissions:permissions}[? contains(permissions.secrets, 'Delete')]" \
-ojson
Again, we will receive the same response structure. However, we’ll only receive access policies with the Delete
permission set for secrets
this time.
Recap
As you can see, JMESPath is quite powerful. It can do more than just projecting a single property from a JSON object. JMESPath offers more than I’ve covered in this article. However, I wanted to document some practical examples which are not part of the official Azure CLI documentation.
Don’t get me wrong! I’m not saying stop using shell scripts. Quite the reverse! Script everything, but know the capabilities of the tools you are using. Using more advanced JMESPath queries allows you to address requirements without increasing complexity.