operate-azure
Use when operating Azure by CLI — confirm sign-in, tenant and active subscription first, choose read-only or IaC or a recorded one-off, query with az graph, avoid az rest and RBAC traps, and keep Terr
Files2
View on GitHubSKILL.mdskills/ops/operate-azure/SKILL.md
GitHubOperate Azure
Read-only by default. Never change prd by hand when the resource is IaC-managed; change the code and promote it (set-up-terraform-pipeline). A CLI session is for investigation, small CLI-managed fixes with a record, and emergencies — with the revert plan written before the command runs.
1. Audit — signed in, and where (change nothing)
az version # CLI present, version line
az account show -o json | jq -r '.tenantId, .id, .name' # ACTIVE tenant and subscription
az account list --query '[].{sub:name, id:id, default:isDefault}' -o table
az ad signed-in-user show --query userPrincipalName -o tsv # who am I
az graph query -q "Resources | summarize count() by type | top 5 by count_" -o table # needs extension resource-graph
Also check the track before touching anything (step 3). Most "wrong resource" incidents start with the right command in the wrong subscription.
2. Decide
- Read-only investigation (default) —
show,list,az graph query,az rest --method GET. - The resource is IaC-managed and needs a change — change the code, open a PR, promote it. No hand edits on prd; on dev/stg a hand edit creates drift — still say no.
- CLI-managed resource, one-off change justified — smallest command that does it, JSON output pasted into the ticket as the record, revert command named before running. Firewall IP rules and role assignments made this way are removed again (step 5, step 7).
3. Detect the track
| Signal | Track | Then |
|---|---|---|
terraform/ covers it — tofu -chdir=terraform/envs/<env> state list | grep -i <name>, or it appears in *.tf |
IaC | Read-only here; change via set-up-terraform-pipeline |
Repo has *.bicep files or az deployment group scripts |
Bicep | Change the template, deploy through the repo's deployment flow |
| Neither, and tags/history say CLI | CLI-managed | One-off change with a record (step 2) |
tofu -chdir=terraform/envs/<env> state list 2>/dev/null | grep -i <name>
grep -rn "azurerm_" terraform/ --include='*.tf' 2>/dev/null | grep -i <name> | head -5
find . -name '*.bicep' -not -path '*/node_modules/*' | head -5
az resource show --ids <resource-id> --query tags -o json # a `managed-by`-style tag tells you who owns it
4. Install only what is missing
brew install azure-cli jq # verify version before pinning (../_shared/stack-versions.md)
az extension add --name resource-graph # for az graph query
az extension add --name account
azd and Bicep only when the project already uses them; the full table is in
cli-toolbox.
5. Do the work
Sign in per tenant — never rely on the default subscription:
az login --tenant <tenant-id>
az account set --subscription <subscription-id>
az account show --query id -o tsv # confirm the subscription you intended
Find things:
az graph query -q "Resources | where name contains '<app>' | project name, resourceGroup, type, location" -o table
az resource list --resource-group <rg> -o table # ARM resources of the ACTIVE subscription only
az resource list sees only ARM resources. Service principals, groups and users are Entra
objects, not ARM resources — use operate-entra-and-exchange.
RBAC:
az role assignment list --assignee <object-id> --scope <scope> -o table
az role assignment create --assignee <object-id> --role "<role-name>" --scope <scope>
az role assignment delete --id <assignment-id> # the explicit counterpart; create does not clean up
Propagation takes 2–5 minutes — a "still forbidden" right after a grant is usually not an error.
Key Vault secrets need a data-plane role (Key Vault Secrets User), not a management role;
"secret not found" is usually a missing data-plane role or the wrong subscription, not a missing
secret.
az rest — GET, merge, PUT:
# endpoint with no CLI command — e.g. static web app app settings; exact path and api-version per resource docs
az rest --method GET --url "https://management.azure.com<resource-id>/<settings-path>?api-version=<api-version>" -o json > settings.json
jq '.properties.appSettings.newKey = "newValue"' settings.json > settings.merged.json # merge in jq, never by hand
# then PUT settings.merged.json back at the same URL — a PUT replaces the WHOLE set
Output: -o tsv prints booleans capitalised (True/False). Compare case-insensitively or
use -o json | jq.
Firewall-gated data stores (SQL, storage, Key Vault): add a temporary IP rule, work, remove it in the same session — the remove command written down before the add runs (patterns file).
Hygiene: name <app>-<env>-<type>, tag app/env, resource locks CanNotDelete on prd:
az lock create --name lock-<app>-prd --lock-type CanNotDelete --resource-group <rg>
az consumption usage list --top 5 -o table # current-month cost; budgets per subscription
6. Wire
- Identifiers (
<tenant-id>,<subscription-id>, resource IDs) live in the IaC repo'senvs/*.tfvarsand backend config — real values there, placeholders everywhere else. - Secrets never in the shell profile or repo: manage-secrets.
Locally,
az loginbeats any stored token — no secret to leak. - IaC changes go through set-up-terraform-pipeline, with OIDC per environment instead of local credentials.
- Tools and versions: cli-toolbox, stack-versions.
7. Verify
az account show --query id -o tsv | grep -q <subscription-id> && echo right-subscription
az resource show --ids <resource-id> --query tags -o json # tags name the owning system
az graph query -q "Resources | where name == '<name>' | project name, type" -o table # exactly one row
az role assignment list --assignee <object-id> --scope <scope> -o table # the grant exists
az storage account network-rule list --account-name <account> -o table # no leftover client-IP rule
Expected: right-subscription, one row for the resource, the new role listed, and no temporary
IP rule left behind.
References
- azure-patterns.md: the traps — default subscription, RBAC propagation,
data-plane roles,
az restPUT semantics, TSV booleans, firewall rules, locks and cost. - cli-toolbox, stack-versions.
- operate-entra-and-exchange: everything not an ARM resource.
- manage-secrets, set-up-terraform-pipeline.
- security-baseline.