Skip to content

Instantly share code, notes, and snippets.

@petevb
Last active January 9, 2024 19:47
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save petevb/19f1b72775e8bae45636ff1b0b529d57 to your computer and use it in GitHub Desktop.
Save petevb/19f1b72775e8bae45636ff1b0b529d57 to your computer and use it in GitHub Desktop.
Azure CLI cheatsheet

az CLI cheatsheet / FAQ

set defaults

$ az config set core.output=table
Command group 'config' is experimental and under development. Reference and support levels: https://aka.ms/CLI_refstatus

$ az config set extension.use_dynamic_install=yes_prompt
Command group 'config' is experimental and under development. Reference and support levels: https://aka.ms/CLI_refstatus

$ az config set extension.run_after_dynamic_install=yes
Command group 'config' is experimental and under development. Reference and support levels: https://aka.ms/CLI_refstatus

https://learn.microsoft.com/en-us/cli/azure/azure-cli-configuration

Persistent parameters

# turn persisted parameters on
az config param-persist on

# Create a resource group which will store "resource group" and "location" in persisted parameter.
az group create --name RGlocalContext --location westeurope

# Create an Azure storage account omitting location and resource group.
az storage account create \
  --name sa1localcontext \
  --sku Standard_LRS

# Create a serverless function app in the resource group omitting storage account and resource group.
az functionapp create \
  --name FAlocalContext \
  --consumption-plan-location westeurope \
  --functions-version 2

# See the stored parameter values
az config param-persist show```
<https://learn.microsoft.com/en-us/cli/azure/param-persist-howto>

## How to use Fiddler (web proxy) with the CLI?

```bash
export HTTP_PROXY="http://localhost:8888" HTTPS_PROXY="http://localhost:8888"

export AZURE_CLI_DISABLE_CONNECTION_VERIFICATION=1

How to set your environment and defaults?

az login
az account list
az account set -s <subscription>

az configure --defaults location=<location_name>
az configure --defaults group=<resourceGroupName>

List all the secrets in KeyVault?

Set an environment variable VAULT_NAME to your the name of your Key Vault resource,e.g.:

set VAULT_NAME=pvb-uat-euw-kv

Then run these commands:

del %VAULT_NAME%.csv

for /f %i in ('az keyvault secret list --vault-name %VAULT_NAME%') do @az keyvault secret show --vault-name %VAULT_NAME% --name %i -o tsv --query "{name:name, value:value}" >> %VAULT_NAME%.csv

more %VAULT_NAME%.csv

List app services?

az webapp list [-g group] [-s sub]

View app settings?

set APP_NAME=pvb-live-euw-admin-as
az webapp config appsettings list -g  pvb-live -n  %APP_NAME% -o tsv > %APP_NAME%.settings.csv

az functionapp config appsettings list -g  pvb-live -n  pvb-live-euw-admin-as

Backup all app settings?

set RG=pvb-uat

for /f %i in ('az webapp list -g %RG%' -o tsv --query "[].{name:name}"') do @az webapp config appsettings list -g %RG% -n %i -o tsv >> %RG%.%i.settings.csv

List resources in a group?

az account set -s Playground
RG=pvb-uat

az resource list -g $RG --query "[].{ name: name, kind: kind, type: type, location: location }" --output table

IAM

IAM for a RG?

az account set -s Playground
RG=pvb-uat

az role assignment list --resource-group $RG --output json --query '[].{principalName:principalName, roleDefinitionName:roleDefinitionName, scope:scope}'

IAM for resources in a RG?

az account set -s Playground
RG=pvb-uat

# NB bash on Windows (WSL) may need the CR trimming:
# for scope in "$(az resource list -g $1 -o tsv --query '[].{ id: id }' | tr -d '\r')"; do
for scope in "$(az resource list -g $RG -o tsv --query '[].{ id: id }')"; do
    echo IAM for $scope
    az role assignment list --scope $scope -o table
done

IAM for groups in subscription?

for rg in $(az group list -o tsv --query "[].{name:name}" | tr -d '\r'); do
  #echo resources in $rg

  for scope in "$(az resource list -g $rg -o tsv --query '[].{ id: id }' | tr -d '\r')"; do
      echo "$scope"
      az role assignment list --scope "$scope" -o table
  done
done

List all your types of cosmos db?

You can list all your Cosmos DBs in a subscription with something like this:

az cosmosdb list --subscription %subscriptionId% --query "[].{name:name, kind:kind, location:location, resourceGroup:resourceGroup}"

Given all your subscriptions IDs are:

az account list -o tsv --query "[].{id:id}"

Then all your "Cosmoses" can be queried like so:

for /f %s in ('az account list -o tsv --query "[].{id:id}"') do @az cosmosdb list --subscription %s --query "[].{name:name, kind:kind, location:location, resourceGroup:resourceGroup}" -o tsv

Find a particular type* of Cosmos DB?

* In these examples, the type is "MongoDB". You could remove that ?kind=='MongoDB' filter from the query and list all as above.

Windows

for /f %s in ('az account list -o tsv --query "[].{id:id}"') do @az cosmosdb list --subscription %s --query "[?kind=='MongoDB'].{name:name, kind:kind, location:location, resourceGroup:resourceGroup}" -o json

bash

for sub in $(az account list -o tsv --query '[].{id:id}' | tr -d '\r'); do
  az cosmosdb list --subscription $sub --query "[?kind=='MongoDB'].{name:name, kind:kind, location:location, resourceGroup:resourceGroup}" -o json
done

Find Elastic Pools?

for sub in $(az account list -o tsv --query '[].{id:id}' | tr -d '\r'); do
  for sql in $(az sql server list --subscription $sub --query "[].{name:name}" | tr -d '\r'); do
    echo $sql
    # az sql elastic-pool list --subscription $sub -o table
  done
done

Deploy local website (or other) ?

$ az webapp deployment source config-zip -g pvb-qat-rg -n pvb-qat-euw-myapp-as --src .\bin\Release\netcoreapp3.1\publish.zip

Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
Active    Author    Author_email    Complete    Deployer    End_time                      Is_readonly    Is_temp    Last_success_end_time         Log_url                                                                             Message                        Progress    Received_time               Site_name                  Start_time                    Status    Status_text    Url
--------  --------  --------------  ----------  ----------  ----------------------------  -------------  ---------  ----------------------------  ----------------------------------------------------------------------------------  -----------------------------  ----------  --------------------------  -------------------------  ----------------------------  --------  -------------  ------------------------------------------------------------------------------
True      N/A       N/A             True        ZipDeploy   2021-01-30T11:53:11.3202611Z  True           False      2021-01-30T11:53:11.3202611Z  https://pvb-qat-euw-myapp-as.scm.azurewebsites.net/api/deployments/latest/log  Created via a push deployment              2021-01-30T11:53:09.85145Z  pvb-qat-euw-myapp-as  2021-01-30T11:53:10.3827205Z  4                        https://pvb-qat-euw-myapp-as.scm.azurewebsites.net/api/deployments/latest

$

Create alerts?

az monitor metrics alert create -n "InfoCapchaSqlDtuOver80pct" -g "pvb-qat-rg" --scopes "dtu_used" --condition "avg Percentage DTU > 80" --description "Check to see if the Avg DTU exceeds 80%."

List alerts?

for group in "$(az group list -o tsv --query '[].{name:name}' | tr -d '\r')"; do
  az monitor metrics alert list -g $group;
done

Using Azure CLI to add cert and hostname to web apps

These notes assume that you already have an app service plan running a number of sites (web apps) that ALL want the same wildcard cert.

The problem being solved was how to point a number of sites to a company domain.

The aliases (e.g. alias trainer.foo-uk.com onto CNAME pvb-live-eun-trainer-as.azurewebsites.net) MUST already be configured in DNS -- Azure will check!

First, how to get the interesting App Service Plan

You might instead want to query a Resource Group. I chose a plan because I had another plan in the same resource group that I didn't want to configure. In this exmaple I want the LIVE sites running in EU North, i.e. pvb-live-eun-asp.

$ az webapp list -g $resourceGroup --query "[?ends_with(appServicePlanId, '-eun-live-asp')].{name: name, plan: appServicePlanId}" -o table

Name                     Plan
-----------------------  -------------------------------------------------------------------------------------------------
 pvb-live-eun-trainer-as  /subscriptions/.../resourceGroups/foo-live/providers/Microsoft.Web/serverfarms/ pvb-live-eun-asp
 pvb-live-eun-manager-as  /subscriptions/.../resourceGroups/foo-live/providers/Microsoft.Web/serverfarms/ pvb-live-eun-asp
 pvb-live-eun-booking-as  /subscriptions/.../resourceGroups/foo-live/providers/Microsoft.Web/serverfarms/ pvb-live-eun-asp
 pvb-live-eun-admin-as    /subscriptions/.../resourceGroups/foo-live/providers/Microsoft.Web/serverfarms/ pvb-live-eun-asp
 pvb-live-eun-driver-as   /subscriptions/.../resourceGroups/foo-live/providers/Microsoft.Web/serverfarms/ pvb-live-eun-asp

In a bash script create a mapping (an associative array) between the webapp name and the live subdomain

declare -A aliases=( \
  [" pvb-live-eun-trainer-as"]="trainer" \
  [" pvb-live-eun-manager-as"]="app-manager" \
  [" pvb-live-eun-booking-as"]="booking" \
  [" pvb-live-eun-admin-as"]="admin" \
  [" pvb-live-eun-driver-as"]="app" \
)

Next loop around those Web Apps

pfxPassword=********
domain=pvb-uk.com
pfxPath=pvb-uk.pfx
resourceGroup=pvb-live

...

for webappname in $(az webapp list -g $resourceGroup --query "[?ends_with(appServicePlanId, 'eun-live-asp')].{name: name}" -o tsv); do
    # Map the prepared custom domain name to the web app, lookiing up the mapping in dictionary (associative array) above
    fqdn=${aliases[$webappname]}.$domain
    echo Adding hostname $fqdn to $webappname...
    az webapp config hostname add --webapp-name $webappname --resource-group $resourceGroup --hostname $fqdn
    echo $fqdn added to $webappname.

    # Upload the SSL certificate and get the thumbprint.
    echo Uploading cert tp $webappname...
    thumbprint=$(az webapp config ssl upload --certificate-file $pfxPath --certificate-password $pfxPassword --name $webappname --resource-group $resourceGroup --query thumbprint --output tsv)
    echo Cert uploaded.

    # Binds the uploaded SSL certificate to the web app.
    echo Binding $thumbprint to $webappname...
    # read -p "Press [Enter] key when ready ..."
    az webapp config ssl bind --certificate-thumbprint $thumbprint --ssl-type SNI --name $webappname --resource-group $resourceGroup
    echo "You can now browse to https://$fqdn"

    read -p "Press [Enter] key to continue..."
done

Consider adding error handling and whether you want to exit or continue

if [ $? -ne 0 ]; then
    echo "Failed to add hostname to $webappname"
    # exit 1
fi

Further Reading

set VAULT_NAME=%1
set SUB=%2
:: NB Example commands to compare all **AppSettings** (as opposed to this cmd which is for KeyVault) :
::
:: az webapp config appsettings list --name pvb-live-euw-myapp-as --subscription d45315f6-cafe-4537-93c2-f526f2d9755f -g
:: pvb-live-rg > pvb-live-euw-myapp-as.appsettings.txt
::
:: az functionapp config appsettings list --name pvb-live-euw-myapp-fn --subscription df65c18f-cafe-4951-8a1a-d7a23a97d649 -g pvb-live-rg > pvb-live-euw-myapp-fn.appsettings.txt
::
echo %VAULT_NAME% %SUB% > %VAULT_NAME%.txt
for /f %%i in ('az keyvault secret list --subscription %SUB% --vault-name %VAULT_NAME% -o tsv --query "[].{id:id}"') do @az keyvault secret show --id %%i -o tsv --query "{name:name, value:value}" >> %VAULT_NAME%.txt
echo written to %VAULT_NAME%
@echo off
for /f %%i in ('az account list --query "[].id" -o tsv') do call :get_subs %%i
goto :eof
:get_subs
set sub=%1
:: @echo sub: %sub%
for /f %%j in ('az group list --subscription %sub% --query "[].name" -o tsv') do call :get_service_plan %sub% %%j
goto :eof
:get_service_plan
set sub=%1
set rg=%2
::az appservice plan list -g %rg% --subscription %SUB% --query "[].{ Name: name, Sku: sku.name }" -o tsv
call az appservice plan list -g %rg% --subscription %SUB% --query "[].{ Name: name, Sku: sku.name }" -o tsv
goto :eof
:eof
echo Done.
#!/bin/bash
#
# query-app-settings.sh
#
# List Appsettings in every slot of every webapp and function app for a given sub
#
# Optionally filter results that contain a given string.
#
# EXAMPLES:
#
# ./query-app-settings.sh NewOrbit
#
# - show all settings in the NewOrbit subscription
#
# ./query-app-settings.sh pvb Logging
#
# - show all "pvb" settings that contain the string "Logging"
#
# petevb@NOPVBP51:/mnt/c/Users/PetevanBlerk/code$ ./query-app-settings.sh
# RG APP SLOT NAME VALUE
# -------- ----------------------- ------- ------------------------------- ----------
# pvb-live pvb-eun-live-booking-as staging Logging:Serilog:MinimumLevel Verbose
# pvb-uat pvb-eun-uat-booking-as staging Logging:Serilog:MinimumLevel Verbose
# pvb-uat pvb-eun-uat-admin-as Staging Logging:Serilog:MinimumLevel Verbose
#
# az group list --subscription foo --query [].name -o table
sub=${1:-foo}
# echo $sub
for rg in $(az group list --subscription $sub --query [].name -o tsv); do
# echo $rg.
for webapp in $(az webapp list --subscription $sub -g $rg --query [].name -o tsv); do
# echo $webapp..
for slot in $(az webapp deployment slot list --subscription $sub -g $rg -n $webapp -o tsv --query "[].{ Name:name }"); do
# echo $slot...
az webapp config appsettings list --subscription $sub -g $rg -n $webapp --slot $slot --query "[?contains(name,'$2')].{ Group:'$rg', App:'$webapp', Slot:'$slot', Name:name, Value:value }" -o tsv
done
done
for fnapp in $(az functionapp list --subscription $sub -g $rg --query [].name -o tsv); do
# echo $fnapp..
for slot in $(az functionapp deployment slot list --subscription $sub -g $rg -n $fnapp -o tsv --query "[].{ Name:name }"); do
# echo $slot...
az webapp config appsettings list --subscription $sub -g $rg -n $fnapp --slot $slot --query "[?contains(name,'$2')].{ Group:'$rg', App:'$fnapp', Slot:'$slot', Name:name, Value:value }" -o tsv
done
done
done
#!/bin/bash
#
# query-cosmos-failover.sh
#
# List cosmos DBs in every resource group of a subscription and find their locations
#
sub=$1
# az group list --subscription $1 --query [].name -o table
for resourceGroup in $(az group list --subscription $1 --query [].name -o tsv); do
# echo RESOURCE GROUP $resourceGroup\:
for database in $(az cosmosdb list --subscription $1 -g $resourceGroup --query [].name -o tsv); do
# echo Found '$database' Cosmos DB database. Querying ...
az cosmosdb list --subscription $1 -g $resourceGroup -o tsv --query "[].readLocations[].locationName | { Group:'$resourceGroup', Database:'$database', readLocations: join(', ', @), count: length(@) }"
done
done
#!/bin/bash
#
# query-cosmos-throughput.sh
#
# List cosmos DBs in every resource group of a subscription and find their current thoughput, their max (ever) throughput, and whether they have per minute RU enabled.
#
# az group list --subscription pvb --query [].name -o table
for resourceGroup in $(az group list --subscription pvb --query [].name -o tsv); do
# echo RESOURCE GROUP $resourceGroup\:
for database in $(az cosmosdb list -g $resourceGroup --query [].name -o tsv); do
# echo Found '$database' Cosmos DB database. Querying ...
# az cosmosdb keys list -g $resourceGroup -n $database --type read-only-keys --query secondaryReadonlyMasterKey -o tsv
for collection in $(az cosmosdb collection list -d pvb -g $resourceGroup -n $database -o tsv --query [].id); do
az cosmosdb collection show -c $collection -d pvb -g $resourceGroup -n $database -o tsv \
--query "{ Group:'$resourceGroup', Database:'$database', Collection:collection.id, CurrentThroughput:offer.content.offerThroughput, MaxEver:offer.content.offerMinimumThroughputParameters.maxThroughputEverProvisioned, IsRUPerMinuteEnabled: offerIsRUPerMinuteThroughputEnabled }"
done
done
done

Trim trailing %0D from az CLI's reponse

I think there's been a change in behaviour in WSL or the Azure CLI that had me puzzled for a bit.

When you run an az command in bash on Windows you might get a CRLF (\r\n) in the response rather than just the LF (\n) you'd have on POSIX/linux environments.

I'm using Ubuntu 20.04 (Ubuntu 20.04.1 LTS) in WSL2 on Windows 10 (20H2 / 19042.746).

If you capture an az command into a variable, e.g.:

local slotId=$(az $resourceType deployment slot list --name pvb-live-euw-mywebapp-as --resource-group pvb-live-rg -o tsv --query [].id) >/dev/null

echo "stop deployment slot with id $slotId"
az resource invoke-action --action stop --ids $slotId

and try to use it in a command you'll get an error, e.g.:

BadRequestError: Operation failed with status: 'Bad Request'. Details: 400 Client Error: Bad Request for url:
https://management.azure.com/subscriptions/453dead-9ac1-cafe-2805-decafbad755f/resourceGroups/pvb-
live-rg/providers/Microsoft.Web/sites/pvb-live-euw-mywebapp-as/slots/staging%0D/stop?api-version=2020-09-01

See the %0D (CR) in that URL? (It's at the end just before the query string.) That's what's causing the HTTP 400. The reason it's there is because the slotId I got back from the previous command has the CR of CRLF in it 😲.

I'm not sure if that's a bug, but one solution is to pipe bash's transform, | tr -d '\r', to trim the trailing CR:

local slotId=$(az $resourceType deployment ... -o tsv --query [].id | tr -d '\r') >/dev/null

Note: This is not an issue with line endings in the script, but a problem with the line endings of the "response" to stdout.

Command az monitor metrics alert create : Create a metric-based alert rule.

Arguments --condition [Required] : The condition which triggers the rule. Usage: --condition {avg,min,max,total,count} [NAMESPACE.]METRIC {=,!=,>,>=,<,<=} THRESHOLD [where DIMENSION {includes,excludes} VALUE [or VALUE ...] [and DIMENSION {includes,excludes} VALUE [or VALUE ...] ...]]

    Dimensions can be queried by adding the 'where' keyword and multiple dimensions can be
    queried by combining them with the 'and' keyword.

    Values for METRIC, DIMENSION and appropriate THRESHOLD values can be obtained from `az
    monitor metrics list-definitions` command.

    Due to server limitation, when an alert rule contains multiple criterias, the use of
    dimensions is limited to one value per dimension within each criterion.

    Multiple conditions can be specified by using more than one `--condition` argument.
--name -n              [Required] : Name of the alert rule.
--resource-group -g    [Required] : Name of resource group. You can configure the default group
                                    using `az configure --defaults group=<name>`.
--scopes               [Required] : Space-separated list of scopes the rule applies to. The
                                    resources specified in this parameter must be of the same
                                    type and exist in the same location.
--action -a                       : Add an action group and optional webhook properties to fire
                                    when the alert is triggered.
    Usage:   --action ACTION_GROUP_NAME_OR_ID [KEY=VAL [KEY=VAL ...]]

    Multiple action groups can be specified by using more than one `--action` argument.
--auto-mitigate                   : Automatically resolve the alert.  Allowed values: false,
                                    true.
--description                     : Free-text description of the rule.
--disabled                        : Create the rule in a disabled state.  Allowed values: false,
                                    true.
--evaluation-frequency            : Frequency with which to evaluate the rule in "##h##m##s"
                                    format.  Default: 1m.
--region --target-resource-region : The region of the target resource(s) in scopes. This must be
                                    provided when scopes is resource group or subscription.
--severity                        : Severity of the alert from 0 (critical) to 4 (verbose).
                                    Default: 2.
--tags                            : Space-separated tags: key[=value] [key[=value] ...]. Use ""
                                    to clear existing tags.
--target-resource-type --type     : The resource type of the target resource(s) in scopes. This
                                    must be provided when scopes is resource group or
                                    subscription.
--window-size                     : Time over which to aggregate metrics in "##h##m##s" format.
                                    Default: 5m.

Global Arguments --debug : Increase logging verbosity to show all debug logs. --help -h : Show this help message and exit. --only-show-errors : Only show errors, suppressing warnings. --output -o : Output format. Allowed values: json, jsonc, none, table, tsv, yaml, yamlc. Default: table. --query : JMESPath query string. See http://jmespath.org/ for more information and examples. --subscription : Name or ID of subscription. You can configure the default subscription using az account set -s NAME_OR_ID. --verbose : Increase logging verbosity. Use --debug for full debug logs.

Examples

  • Create a high CPU usage alert on a VM with no action.
 az monitor metrics alert create -n alert1 -g {ResourceGroup} --scopes {VirtualMachineID} --condition "avg Percentage CPU > 90" --description "High CPU"
  • Create a high CPU usage alert on a VM with email and webhook actions.
az monitor metrics alert create -n alert1 -g {ResourceGroup} --scopes {VirtualMachineID} \
--condition "avg Percentage CPU > 90" --window-size 5m --evaluation-frequency 1m \
--action "/subscriptions/<subscriptionId>/resourceGroups/<resourceGroupName>/providers/Microsoft.Insights/actionGroups/<actionGroupName>" apiKey={APIKey} type=HighCPU \
--description "High CPU"
  • Create an alert when a storage account shows a high number of slow transactions, using multi-dimensional filters.
az monitor metrics alert create -g {ResourceGroup} -n alert1 --scopes {StorageAccountId} \
    --description "Storage Slow Transactions" \
    --condition "total transactions > 5 where ResponseType includes Success" \
    --condition "avg SuccessE2ELatency > 250 where ApiName includes GetBlob"
  • Create a metric-based alert rule that monitors a custom metric.
az monitor metrics alert create -n "metric alert rule on a custom metric" -g "Demos"
--scopes {VirtualMachineID} \
    --condition "max Azure.VM.Windows.GuestMetrics.Memory\Available Bytes > 90" \
--window-size 5m --evaluation-frequency 1m
  • Create a high CPU usage alert on several VMs with no actions.
az monitor metrics alert create -n alert1 -g {ResourceGroup} --scopes {VirtualMachineID1}
{VirtualMachineID2} {VirtualMachineID3} \
--condition "avg Percentage CPU > 90" --description "High CPU"

For more specific examples, use: az find "az monitor metrics alert create"


Please let us know how we are doing: https://aka.ms/azureclihats

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment