The goal is to facilitate the interaction between commands, like how PowerShell uses variables to store objects.
For example, PowerShell CmdLet Add-AzVirtualNetworkSubnetConfig takes -VirtualNetwork <PSVirtualNetwork>
. See Create a virtual network using PowerShell.
$virtualNetwork = New-AzVirtualNetwork -ResourceGroupName rg1 -Name vnet1 -Location WestUS -AddressPrefix 10.0.0.0/16
$subnetConfig = Add-AzVirtualNetworkSubnetConfig -Name default -AddressPrefix 10.0.0.0/24 -VirtualNetwork $virtualNetwork
$virtualNetwork | Set-AzVirtualNetwork
Currently, with Azure CLI users need to append --query id --output tsv
to a command to extract the information from the output JSON and pass it to the next command, as documented in Tips for using Azure CLI effectively.
For example, we want to add a newly created subnet to a storage account's network rule. See Configure Azure Storage firewalls and virtual networks.
With CLI in Bash:
# Prepare resources
az group create -g rg1 -l westus
az network vnet create -g rg1 --name vnet1
az network vnet subnet create -g rg1 --vnet-name vnet1 --name subnet1 --address-prefixes 10.0.0.0/24 --service-endpoints Microsoft.Storage
az storage account create -g rg1 --name st0507
# Add the created subnet to storage account's network rule
subnet=$(az network vnet subnet show -g rg1 --vnet-name vnet1 --name subnet1 --query id --output tsv)
az storage account network-rule add -g rg1 --account-name st0507 --subnet $subnet
With Azure PowerShell:
$subnet = Get-AzVirtualNetwork -ResourceGroupName "myresourcegroup" -Name "myvnet" | Get-AzVirtualNetworkSubnetConfig -Name "mysubnet"
Add-AzStorageAccountNetworkRule -ResourceGroupName "myresourcegroup" -Name "mystorageaccount" -VirtualNetworkResourceId $subnet.Id
We want to eliminate the usage of --query id --output tsv
and achieve the same effect.
In this PR, a new factory get_json_query_type(query='id')
is added for command arguments. It has one parameter as the JMESPath query string, which is used to query the input JSON string. If the input is not a valid JSON, the original value is used.
c.argument('subnet', type=get_json_query_type(), help='Name or ID of subnet. If name is supplied, `--vnet-name` must be supplied.')
Then we can pipe the output JSON to the next command with either variable style or pipeline style.
https://www.gnu.org/software/bash/manual/html_node/Shell-Parameters.html
subnet=$(az network vnet subnet show -g rg1 --vnet-name vnet1 --name subnet1)
az storage account network-rule add -g rg1 --account-name st0507 --subnet "$subnet"
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_variables
Variable style doesn't work well with PowerShell due to a bug in PowerShell. See appendix for more information.
https://www.gnu.org/software/bash/manual/html_node/Pipelines.html
az network vnet subnet show -g rg1 --vnet-name vnet1 --name subnet1
| az storage account network-rule add -g rg1 --account-name st0507 --subnet @-
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pipelines
As symbol @
is interpreted by PowerShell as splatting symbol, so quote it or escape it.
az network vnet subnet show -g rg1 --vnet-name vnet1 --name subnet1
| az storage account network-rule add -g rg1 --account-name st0507 --subnet '@-'
az network vnet subnet show -g rg1 --vnet-name vnet1 --name subnet1
| az storage account network-rule add -g rg1 --account-name st0507 --subnet `@-
More information about quoting can be found at https://github.com/Azure/azure-cli/blob/dev/doc/use_cli_effectively.md#quoting-issues
Some problems can emerge with this approach:
-
The JSON can't be used to populate multiple parameters. For example, populating
--resource-group
and--vnet-name
when a vnet JSON is provided:az network vnet subnet create --resource-group rg1 --vnet-name "$vnet" --name subnet1
Possible solutions:
- Support
--vnet
which takes a resource ID by itself, likeThis solution is perhaps the best.az network vnet subnet create --vnet /subscriptions/0b1f6471-1bf0-4dda-aec3-cb9272f09590/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet1 --name subnet1
- Pass the JSON twice to the following command, like
This solution is verbose and counterintuitive.
az network vnet subnet create --resource-group "$vnet" --vnet-name "$vnet" --name subnet1
- Support
-
This can conflict with the newly introduced local context which may automatically populate
--resource-group
and--vnet-name
Due to issue PowerShell/PowerShell#1995, double quotes within the JSON string are lost when calling a native .exe
file.
# Note that the double quotes are lost
> python.exe -c "import sys; print(sys.argv)" '{"key": "value"}'
['-c', '{key: value}']
# Escape double quotes (") with backward-slashes (\), and quote the string with single quotes (')
> python.exe -c "import sys; print(sys.argv)" '{\"key": \"value\"}'
['-c', '{"key: "value"}']
# First escape double quotes with backticks (`), then escape double quotes with backward-slash (\)
> python.exe -c "import sys; print(sys.argv)" "{\`"key\`": \`"value\`"}"
['-c', '{"key": "value"}']
As you can see, the workaround makes the command awkward. Of course we can do some replacement to the JSON string to workaround the PowerShell issue but it takes more efforts, thus counteracting the benefit we gain from variable style.