# Azure CLI Guide

Sign in once with `az`, then inspect and manage Azure subscriptions, App Services, SQL, Key Vault, and logs from the command line. This is the tool Claude uses under the hood when you ask it to diagnose an App Service issue, pull a Key Vault secret, or tail logs from a running app.

Good for:
- Pulling runtime state from any resource in any subscription you have access to
- Tailing live logs from an App Service without opening the Azure Portal
- Updating app settings or rotating Key Vault secrets
- Multi-step diagnostics (open firewall → query DB → close firewall)
- Answering "is this really configured the way I think it is?"

## 1. Install

**Windows:**
```bash
winget install Microsoft.AzureCLI
```

**macOS:**
```bash
brew install azure-cli
```

**Linux:** see https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux for distro-specific instructions.

Verify:
```bash
az --version
```

## 2. Sign In

```bash
az login
```

Opens a browser, authenticates, caches a token locally. Verify the active context:

```bash
az account show --query "{name:name, subscriptionId:id, tenantId:tenantId, user:user.name}" -o json
```

> **Terminology:** A **Tenant** (a.k.a. Directory) is your Entra ID organization. A **Subscription** is a billing/resource container inside a tenant. A **Resource Group** is a named collection of resources inside a subscription. You log in to a tenant, select a subscription, and address resources by their resource group.

## 3. Switch Subscriptions / Tenants

```bash
# List everything you can access
az account list --query "[].{name:name, id:id, tenantId:tenantId}" -o table

# Switch subscription within the current tenant
az account set --subscription "my-subscription"

# Switch to a different tenant (requires re-auth)
az login --tenant <tenant-id>
```

## 4. Everyday Commands

### Inspect resources

```bash
# Everything in a resource group
az resource list --resource-group my-resource-group -o table

# SQL servers + databases
az sql server list -o table
az sql db list --resource-group my-resource-group --server my-sql-server -o table

# App Services
az webapp list --resource-group my-resource-group \
  --query "[].{name:name, host:defaultHostName, state:state}" -o json

# Key Vaults
az keyvault list --resource-group my-resource-group -o table
```

### App Service configuration

```bash
# Read app settings (Key Vault refs appear as @Microsoft.KeyVault(...) strings)
az webapp config appsettings list \
  --name my-webapp --resource-group my-resource-group \
  --query "[?contains(name,'Stripe')]" -o json

# Set an appsetting (also re-resolves any Key Vault reference — see §7)
az webapp config appsettings set \
  --resource-group my-resource-group --name my-webapp \
  --settings "MySecret=@Microsoft.KeyVault(VaultName=my-key-vault;SecretName=my-secret)"

# Restart
az webapp restart --resource-group my-resource-group --name my-webapp
```

### Key Vault secrets

```bash
# Read a secret value
az keyvault secret show --vault-name my-key-vault --name my-secret --query value -o tsv

# Set a new version
az keyvault secret set --vault-name my-key-vault --name my-secret --value "NEW_VALUE"

# Version history
az keyvault secret list-versions --vault-name my-key-vault --name my-secret -o table
```

## 5. Reading Logs

### Live tail from a running App Service

```bash
az webapp log tail --name my-webapp --resource-group my-resource-group
```

Streams the container's `stdout` in real time. Blocks — `Ctrl+C` to exit. Works even if filesystem logging is off (reads live stdout, not persisted files). If the app is idle, trigger a request to generate output.

### Check current logging config

```bash
az webapp log show --name my-webapp --resource-group my-resource-group -o json
```

### Enable / tune filesystem logging

```bash
az webapp log config --resource-group my-resource-group --name my-webapp \
  --application-logging filesystem --level information
```

### Download persisted logs

```bash
az webapp log download --name my-webapp --resource-group my-resource-group \
  --log-file /tmp/app-logs.zip
unzip -q /tmp/app-logs.zip -d /tmp/app-logs
```

Rotated files may already be gone depending on retention.

### Query Application Insights / Log Analytics (KQL)

```bash
# Install once
az extension add --name log-analytics --yes

# Find the workspace
az monitor log-analytics workspace list \
  --query "[].{name:name, rg:resourceGroup, customerId:customerId}" -o table

# Query
az monitor log-analytics query \
  --workspace <customer-id> \
  --analytics-query "AppTraces | where TimeGenerated > ago(1h) | where Message has 'Error' | project TimeGenerated, Message | order by TimeGenerated desc" \
  -o table
```

> **Note:** "Not every App Service is wired to Application Insights. Check with `az webapp config appsettings list ... --query \"[?contains(name,'APPLICATIONINSIGHTS')]\"`. If empty, the app's logs only live in the container's stdout — use live tail or enable filesystem logging to see them."

## 6. Deep Move — Run Shell Commands Inside the Container (Kudu)

When you need to confirm what the running process actually sees (e.g., did my Key Vault update actually propagate to the container's environment?), use the Kudu command API.

```bash
az rest --method post \
  --url "https://my-webapp.scm.azurewebsites.net/api/command" \
  --resource "https://management.azure.com/" \
  --headers "Content-Type=application/json" \
  --body '{"command": "bash -lc \"printenv MyEnvVar | head -c 12\"", "dir": "site/wwwroot"}'
```

Returns `{"Output": "...", "Error": "", "ExitCode": 0}`. On Windows App Services, replace `bash -lc` with `cmd /c`.

> **Safety tip:** "To verify a secret matches an expected value without printing it, compare inside the shell and only emit MATCH/MISMATCH: `bash -lc '[ \"$VAR\" = \"expected\" ] && echo MATCH || echo MISMATCH; echo length=${#VAR}'`."

## 7. Key Vault Reference Re-Resolution

Updating a secret in Key Vault does **not** automatically propagate to a running App Service — App Service caches the resolved value until a config change forces re-resolution. The full dance:

```bash
# 1. Update the secret in Key Vault
az keyvault secret set --vault-name my-key-vault --name my-secret --value "NEW_VALUE"

# 2. Re-set the appsetting to the SAME reference string (triggers re-resolution)
az webapp config appsettings set \
  --resource-group my-resource-group --name my-webapp \
  --settings "MySecret=@Microsoft.KeyVault(VaultName=my-key-vault;SecretName=my-secret)"

# 3. Restart so the new process picks up the newly-resolved env var
az webapp restart --resource-group my-resource-group --name my-webapp

# 4. Verify via Kudu (§6) that the running container actually sees the new value
```

> **Important:** "A bare `az webapp restart` after a Key Vault update often leaves the new process reading the OLD cached value. Step 2 (re-set the appsetting) is required. Without it, the platform may wait up to 24 hours to refresh the reference."

## 8. SQL Firewall Dance

Pair this with `sqlcmd-guide.md` for the actual query step — this section just handles the open/close lifecycle.

```bash
MY_IP=$(curl -s https://api.ipify.org)

# Open
az sql server firewall-rule create \
  --resource-group my-resource-group --server my-sql-server \
  --name TempClaudeAccess \
  --start-ip-address $MY_IP --end-ip-address $MY_IP

# ... run your sqlcmd query ...

# Close
az sql server firewall-rule delete \
  --resource-group my-resource-group --server my-sql-server \
  --name TempClaudeAccess
```

> **Good hygiene:** "Always delete temp rules after. Leaving ad-hoc rules in place drifts the allow list over time and clutters audit."

## 9. Asking Claude

Natural-language prompts that trigger Claude to use `az`:

> "Which App Services are in the UAT resource group and what state are they in?"

> "What secret does Key Vault `my-key-vault` have under `my-secret` right now? Just show me the first 12 characters."

> "Tail the UAT API logs — I'm about to trigger a test event. Grab any lines matching 'webhook' or 'Skipping'."

> "Update the UAT webhook secret to the value I just pasted, re-set the appsetting to force KV re-resolution, restart the app, then verify via Kudu that the running container sees the new value."

> "Open a temp SQL firewall rule for my IP, run a 5-row sample from a table, close the rule."

Claude will chain the right `az` commands, check each step, and flag anything unexpected (permissions errors on resources you can't read, values that don't look right, etc.) instead of plowing ahead blindly.
