Azure Infra Preflight
A pre-deploy checklist that prevents the most common first-run failures in
Azure Terraform workflows. Run this before writing a terraform plan.
1. Verify toolchain
mcp__claude-skills-cli__list_available_clis
Confirm az and terraform are both available. If either is missing, stop
and tell the user which tool to install.
Check Terraform version:
mcp__claude-skills-cli__run_command { cli: "terraform", args: ["version"] }
Gate on >= 1.6 (required for import {} block syntax support).
2. Azure login + record subscription
mcp__claude-skills-cli__run_command { cli: "az", args: ["account", "show", "--output", "json"] }
If exitCode ≠ 0: stop. Tell the user to run az login manually (the MCP
server cannot open a browser).
If exitCode = 0: write the JSON to <workspace>/.claude/<project>/subscription.json
via mcp__filesystem__write_file so subsequent sessions skip this step.
3. SSH key type check (Azure rejects ed25519)
Before writing terraform.tfvars with an SSH public key:
- Check
~/.ssh/id_rsa.pub first (preferred for Azure).
- If only
~/.ssh/id_ed25519.pub exists, do not use it — Azure
azurerm_linux_virtual_machine rejects ed25519 at terraform plan time
with a hard error, even though init succeeds.
- If no RSA key exists, generate one:
mcp__claude-skills-cli__run_command {
cli: "ssh-keygen", // only if on allow-list, otherwise use Bash
args: ["-t", "rsa", "-b", "4096", "-f", "<workspace>/.claude/<project>/deploy-key", "-N", ""]
}
On Windows, use Git Bash via Bash tool if ssh-keygen is not on the CLI
allow-list:ssh-keygen -t rsa -b 4096 -f "<keydir>/deploy-key" -N "" && cat "<keydir>/deploy-key.pub"
4. Check whether resources already exist (import-before-apply)
mcp__claude-skills-cli__run_command {
cli: "az",
args: ["group", "exists", "--name", "<resource-group>"]
}
stdout = false: resources are new — proceed with terraform plan normally.
stdout = true: resources already exist. List them:
mcp__claude-skills-cli__run_command {
cli: "az",
args: ["resource", "list", "--resource-group", "<rg>", "--output", "json"]
}
For each resource, generate a native import {} block (TF >= 1.7):
import {
to = azurerm_<type>.<label>
id = "/subscriptions/.../resourceGroups/<rg>/providers/<provider>/<name>"
}
Write these to imports.tf in the Terraform root. Running terraform plan
with import blocks present will reconcile state without destroying resources.
Remove import blocks once terraform apply completes.
Why: skipping this step causes terraform apply to attempt creating
resources that already exist, producing ResourceAlreadyExists errors and
requiring manual terraform import calls for each resource.
⚠ Windows / Git Bash path mangling — if using the legacy terraform import
CLI (not import {} blocks), Git Bash converts /subscriptions/... to
C:/Program Files/Git/subscriptions/..., breaking the import. Always pass
env: { MSYS_NO_PATHCONV: "1" } in the run_command call:
mcp__claude-skills-cli__run_command {
cli: "terraform",
args: ["import", "azurerm_resource_group.rg",
"/subscriptions/<sub>/resourceGroups/<rg>"],
cwd: "<tf-root>",
env: { MSYS_NO_PATHCONV: "1" }
}
Prefer import {} blocks over CLI import loops — they are written as HCL
to imports.tf and are not subject to shell path mangling.
5. Write a run-log entry
Append to <workspace>/.claude/<project>/run-log.md:
## Preflight — <ISO date>
- az login: ✓ <subscription name>
- SSH key: RSA-4096 at <path>
- RG <name>: exists=<true|false>
- Resources found: <N> (import blocks written to imports.tf)
- TF version: <version>
6. Hand-offs
terraform plan errors after preflight → terraform-plan-review
- 403 / AuthorizationFailed →
azure-rbac-diagnostics
- CI pipeline failures →
ci-pipeline-debug
1---2name: azure-infra-preflight3description: Pre-flight checklist before any Azure Terraform deploy — verify az login, detect SSH key type (Azure only accepts RSA), check whether the target resource group already exists and list resources to generate import blocks, validate Terraform version, and write subscription context. Use before terraform plan/apply on a new or potentially pre-existing Azure environment, or when setting up Azure IaC files from scratch.4---56# Azure Infra Preflight78A pre-deploy checklist that prevents the most common first-run failures in9Azure Terraform workflows. Run this before writing a `terraform plan`.1011## 1. Verify toolchain1213```14mcp__claude-skills-cli__list_available_clis15```1617Confirm `az` and `terraform` are both available. If either is missing, stop18and tell the user which tool to install.1920Check Terraform version:21```22mcp__claude-skills-cli__run_command { cli: "terraform", args: ["version"] }23```24Gate on `>= 1.6` (required for `import {}` block syntax support).2526## 2. Azure login + record subscription2728```29mcp__claude-skills-cli__run_command { cli: "az", args: ["account", "show", "--output", "json"] }30```3132If exitCode ≠ 0: stop. Tell the user to run `az login` manually (the MCP33server cannot open a browser).3435If exitCode = 0: write the JSON to `<workspace>/.claude/<project>/subscription.json`36via `mcp__filesystem__write_file` so subsequent sessions skip this step.3738## 3. SSH key type check (Azure rejects ed25519)3940Before writing `terraform.tfvars` with an SSH public key:41421. Check `~/.ssh/id_rsa.pub` first (preferred for Azure).432. If only `~/.ssh/id_ed25519.pub` exists, do **not** use it — Azure44 `azurerm_linux_virtual_machine` rejects ed25519 at `terraform plan` time45 with a hard error, even though init succeeds.463. If no RSA key exists, generate one:47 ```48 mcp__claude-skills-cli__run_command {49 cli: "ssh-keygen", // only if on allow-list, otherwise use Bash50 args: ["-t", "rsa", "-b", "4096", "-f", "<workspace>/.claude/<project>/deploy-key", "-N", ""]51 }52 ```53 On Windows, use Git Bash via Bash tool if `ssh-keygen` is not on the CLI54 allow-list:55 ```bash56 ssh-keygen -t rsa -b 4096 -f "<keydir>/deploy-key" -N "" && cat "<keydir>/deploy-key.pub"57 ```5859## 4. Check whether resources already exist (import-before-apply)6061```62mcp__claude-skills-cli__run_command {63 cli: "az",64 args: ["group", "exists", "--name", "<resource-group>"]65}66```6768- **stdout = `false`**: resources are new — proceed with `terraform plan` normally.69- **stdout = `true`**: resources already exist. List them:70 ```71 mcp__claude-skills-cli__run_command {72 cli: "az",73 args: ["resource", "list", "--resource-group", "<rg>", "--output", "json"]74 }75 ```76 For each resource, generate a native `import {}` block (TF >= 1.7):77 ```hcl78 import {79 to = azurerm_<type>.<label>80 id = "/subscriptions/.../resourceGroups/<rg>/providers/<provider>/<name>"81 }82 ```83 Write these to `imports.tf` in the Terraform root. Running `terraform plan`84 with import blocks present will reconcile state without destroying resources.85 Remove import blocks once `terraform apply` completes.8687 **Why:** skipping this step causes `terraform apply` to attempt creating88 resources that already exist, producing `ResourceAlreadyExists` errors and89 requiring manual `terraform import` calls for each resource.9091 ⚠ **Windows / Git Bash path mangling** — if using the legacy `terraform import`92 CLI (not `import {}` blocks), Git Bash converts `/subscriptions/...` to93 `C:/Program Files/Git/subscriptions/...`, breaking the import. Always pass94 `env: { MSYS_NO_PATHCONV: "1" }` in the `run_command` call:95 ```96 mcp__claude-skills-cli__run_command {97 cli: "terraform",98 args: ["import", "azurerm_resource_group.rg",99 "/subscriptions/<sub>/resourceGroups/<rg>"],100 cwd: "<tf-root>",101 env: { MSYS_NO_PATHCONV: "1" }102 }103 ```104 **Prefer `import {}` blocks** over CLI import loops — they are written as HCL105 to `imports.tf` and are not subject to shell path mangling.106107## 5. Write a run-log entry108109Append to `<workspace>/.claude/<project>/run-log.md`:110111```markdown112## Preflight — <ISO date>113- az login: ✓ <subscription name>114- SSH key: RSA-4096 at <path>115- RG <name>: exists=<true|false>116- Resources found: <N> (import blocks written to imports.tf)117- TF version: <version>118```119120## 6. Hand-offs121122- `terraform plan` errors after preflight → `terraform-plan-review`123- 403 / AuthorizationFailed → `azure-rbac-diagnostics`124- CI pipeline failures → `ci-pipeline-debug`