---
name: azv-azure-to-bicep
description: Reverse-engineer a live Azure scope (resource group or filtered subscription) into deployment-ready, modular Bicep templates with parameter files. Use when the user wants to bring existing Azure infrastructure under Bicep/IaC management.
license: MIT
compatibility: Requires an authenticated Azure session (CLI, Az PowerShell, or Azure MCP).
metadata:
  version: "1.1"
  project: AzVerify
---

Discover resources in a live Azure scope, extract their full configuration, and generate deployment-ready Bicep templates with modular structure, user-editable parameter files, and dependency documentation for external resources.

**Input**: An Azure scope — a resource group name (primary) or a subscription ID with optional resource type filter. Optionally, a target environment hint (dev/prod) to influence default sizing in the parameter file.

**Tools used**: File system tools (read/write files), Terminal (for running `az` CLI commands and PowerShell 7 / `pwsh` shared scripts), Azure Best Practices MCP (`azure-get_azure_bestpractices`), Bicep Schema MCP (`azure-bicepschema`), and Azure Documentation MCP (`azure-documentation`).

### Tool Preflight

Before discovery, verify the capabilities used by this workflow:

1. Call `azure-get_azure_bestpractices` with `get_azure_bestpractices_get` for general code generation guidance.
2. Call `azure-bicepschema` with `bicepschema_get` for every resource type whose API version or deployable schema is uncertain.
3. Use `azure-documentation` search and fetch for current service guidance when a schema call does not answer the question.

If a capability is unavailable, continue only when the matching shared reference plus Bicep CLI validation can provide the same check; report the fallback in the verification summary.


## Output Budget Rules

Follow `.github/skills/shared/procedures/output-budget.md` strictly — this skill frequently handles 20-40+ resources and can hit the LLM response length limit. In addition to the shared rules:

- **Build Bicep files directly.** Write generated code to files. Do NOT echo full Bicep/bicepparam content in the response — show only file paths and a summary of what was generated.
- **Delete intermediate files on the skill's own schedule.** Intermediate extraction files (`extract-*.json`, `resource-list-raw.json`, and the temporary resource model JSON file) are never deliverables. Keep them available until Step 11 has written the README (it needs the resource counts and extraction stats), then delete them all in Step 12. Never leave the resource model JSON behind after the skill completes.

## Fallback: pwsh Unavailable

If `pwsh`/`powershell.exe` or a shared script cannot be executed, use the fallback that matches the step you are on, then continue the workflow normally:

| Step | Fallback source |
|------|-----------------|
| 1 — Auth check | MCP auth probe fallback in `.github/skills/shared/procedures/azure-authentication.md` |
| 3 — Discovery | "Script/pwsh Unavailable — MCP Fallback" in `.github/skills/shared/azure-resource-configs.md` |
| 4 — Filtering | Inline fallback in `.github/skills/shared/procedures/resource-filtering.md` |
| 6a — Property extraction | "Script/pwsh Unavailable — MCP Fallback" in `.github/skills/shared/azure-resource-configs.md` |
| 6b — Read-only stripping and secrets | Manual strip rules listed in Step 6b, using `.github/skills/shared/data/arm-readonly-properties.json` |
| 7a — Relationships | "Manual Relationship Inference — Script Unavailable Fallback" in `.github/skills/shared/azure-resource-model.md` |

Stop only if Azure MCP is also unavailable, using the prerequisite message in `.github/skills/shared/azure-resource-configs.md`.

## Steps

### 1. Check Azure Authentication

Run `pwsh .github/skills/shared/scripts/Test-AzureAuth.ps1` — see `.github/skills/shared/procedures/azure-authentication.md` for the script contract. The script writes a JSON status object to stdout and exits non-zero when no Azure session is found. A **non-zero exit code is a HARD GATE**: present the authentication instructions from the contract doc and stop. *(If pwsh or the script is unavailable, see "Fallback: pwsh Unavailable".)*

### 2. Accept Inputs

Identify the Azure scope to discover resources from.

#### 2a. Identify the Azure Scope

**If the user specifies a resource group name:**
- Use that resource group as the discovery scope
- Verify the resource group exists: run `az group show --name <name>` — if this fails, report an error and stop

**If the user specifies a subscription ID:**
- Use that subscription as the discovery scope
- Note: subscription-level discovery can produce many resources — the skill will apply filtering and warnings (see Step 4)

**If no scope is specified:**
- Ask the user:
```
Which Azure resource group should I generate Bicep templates from?

If you want subscription-level discovery, provide a subscription ID instead.
```
- Wait for user input

#### 2b. Identify Optional Parameters

**Target environment hint:**
If the user specifies a target environment (e.g., "generate for dev" or "this will be production"):
- Use this to influence default sizing in the `.bicepparam` comments (e.g., note the current size and suggest dev/prod alternatives)
- If not specified, default to documenting the **current** Azure configuration as-is

**Resource type filters:**
If the user provides a resource type filter (e.g., "only compute and networking resources"):
- Map the filter to Azure resource type prefixes (e.g., `Microsoft.Compute/*`, `Microsoft.Network/*`)
- Apply these filters during discovery

**Resource exclusions:**
If the user wants to exclude specific resources or types:
- Accept a list of resource names or type patterns to skip
- Apply exclusions during the filtering step

### 3. Discover Azure Resources

Enumerate all resources in the specified Azure scope.

**3a. Resource group scope**

Run the shared discovery script and write the resource model to a temporary JSON file in the output folder:

```
pwsh .github/skills/shared/scripts/Get-AzureResourceModel.ps1 -ResourceGroup <rg-name> -OutFile <output-folder>/resource-model.json
```

The script emits the shared resource model contract (`id`, `name`, `type`, `location`, `tags`, `sku`) documented in `.github/skills/shared/azure-resource-model.md`. Treat the emitted JSON as the source of truth for Steps 4-7. Do not print the model contents.

Display progress:
```
⏳ Discovering resources in resource group `<rg-name>`...
```

**3b. Subscription scope**

Run the same script with `-SubscriptionId <sub-id>` instead of `-ResourceGroup`. Pass user-specified resource type filters from Step 2b as a `-ResourceTypeFilter` parameter to the script if supported; otherwise filter the emitted JSON before writing to the temporary model file. These user filters are distinct from the Step 4 standard exclusion rules applied by `-Mode bicep`, which never handles user filters.

**3c. Script/pwsh unavailable — MCP fallback**

If `pwsh`/`powershell.exe` or the script cannot be executed, build the same resource model through Azure MCP as described in "Fallback: pwsh Unavailable" (list resources with `mcp_azure_group_resource_list`, then assemble the model shape by hand).

**3d. Handle empty results**

If no resources are found after discovery:
```
## No Resources Found

No resources were found in `<scope-name>`.

If you expected resources here, verify:
- The resource group name is spelled correctly
- You're connected to the correct subscription (`az account show`)
- Resources have been deployed to this scope
- The authenticated identity has read permissions on this resource group
```



- Stop execution

### 4. Filter Non-Deployable Resources

Run `pwsh .github/skills/shared/scripts/Select-AzureResources.ps1 -InputFile <resource-model.json> -Mode bicep` — see `.github/skills/shared/procedures/resource-filtering.md` for the script contract. The script applies the shared exclusion rules, writes the filtered resource model JSON to stdout, and should be treated as the source of truth for the remaining steps. *(If pwsh or the script is unavailable, see "Fallback: pwsh Unavailable".)*

If the script exits non-zero or produces unparseable output, report the error message to the user and stop. Do not proceed with an empty or partial resource list.

Also apply any user-specified exclusion filters from Step 2b.

If the filtered resource list is empty after all exclusion rules have been applied, stop and present:
```
All discovered resources were excluded by filtering rules. No Bicep templates can be generated.
Review the exclusion rules in `.github/skills/shared/procedures/resource-filtering.md` or adjust your resource type filter.
```

Display the filtered resource list:
- If the filtered count is **10 or fewer**: display the full table inline with columns: #, Resource, Type, Location, SKU.
- If the filtered count **exceeds 10**: print only the count and a resource-type breakdown summary in the chat response. Create `original-request.md` in the output folder (recording the original user request and scope details at the top), then append the full resource table to it. Reference the file in the chat response.

### 5. Check for Large Scope

If the filtered resource count exceeds **30**, print a warning that lists unique resource types with counts, then automatically proceed to generate all resources. Templates will be split into modules by category. Do not pause for user confirmation.

### 6. Deep Property Extraction

For each discovered resource, extract the **full** resource configuration from Azure.

**6a. Extraction method**

Prefer running the shared script over manual per-resource extraction:

```
pwsh .github/skills/shared/scripts/Get-AzureResourceModel.ps1 -ResourceGroup <rg> -Enrich -Mode bicep -StripReadOnly -OutFile <path>
```

`-Enrich` fetches full resource detail (`az resource show` per resource) and extracts per-resource-type properties using the mappings in `.github/skills/shared/data/azure-property-paths.json` (`mcpTool` preferred, `fallback` CLI command per resource type, plus `armJsonPath`/composite rules for individual properties). `-Mode bicep` also applies the Step 4 filtering rules automatically.

**Generation boundary:** Azure responses contain a mix of deployable configuration and computed state. Generate Bicep only from each model resource's `deployableProperties`, plus explicitly preserved deployable top-level values (`location`, `tags`, `sku`, `kind`, and managed identity configuration). Use the raw `properties` bag only for relationship discovery. Never serialize an unreviewed raw `properties` bag into generated Bicep.

If `pwsh` or the script is unavailable, see "Fallback: pwsh Unavailable" to enrich and extract properties by hand.

After all resources are extracted, print a single batch summary (e.g., `✅ Extracted 34 resources (3 partial, 2 skipped)`). Do not print per-resource progress lines.

**6b. Property filtering and secrets detection**

The `-StripReadOnly` switch in the Step 6a command removes read-only, computed, and ARM-internal properties (`provisioningState`, `resourceGuid`, `etag`, timestamps, property-level `id`/`name`/`type`, `identity.principalId`, `privateEndpointConnections`, …) and flags secret-bearing properties. The rules live in `.github/skills/shared/data/arm-readonly-properties.json`.

If `pwsh` or the script is unavailable, apply the same rules by hand: strip the `alwaysRemoveAnyDepth` names at any depth and the `alwaysRemoveAtRoot` names at the root of each `properties` bag, apply `removePaths`, and never strip `keepPaths` (`identity.type`, `identity.userAssignedIdentities`) or the deployable `sku`, `kind`, `location`, and `tags`.

**6c. Handling flagged secrets**

The script writes an optional `secrets` array of dotted property paths on each affected resource (see `.github/skills/shared/azure-resource-model.md`). For every flagged path:
- Generate a `@secure()` parameter whose `.bicepparam` value uses `readEnvironmentVariable()` — see `.github/skills/shared/bicep-best-practices.md`
- Add it to the output summary as a "requires manual configuration" item

**6d. Graceful fallback**

If a resource-specific tool fails or is unavailable:
- Log a note: `⚠️ Could not fully extract <resource-name> (<resource-type>) — using list-level information`
- Use the properties available from the initial discovery (Step 3)
- Mark the resource as "partially extracted" in the output
- Do **not** stop execution due to extraction failures

### 7. Analyze Dependencies

Analyze extracted properties to identify relationships between resources — both within the scope and to external resources.

**7a. Internal relationships (resources within the scope)**

Use the `relationships` array already present on each resource in the model produced by `Get-AzureResourceModel.ps1` (`contains`, `connects`, `depends`, `secures` — see `.github/skills/shared/azure-resource-model.md`). These determine module structure and resource ordering in Bicep. *(If pwsh or the script was unavailable in Step 6, see "Fallback: pwsh Unavailable" to detect relationships and their Bicep implications by hand.)*

**7b. External dependencies (resources OUTSIDE the scope)**

These are resources that the in-scope resources depend on but that live in other resource groups, subscriptions, or tenants. They **cannot** be deployed by the generated Bicep — they need separate coordination.

Detect them using the detection patterns and record them in the field shape defined in the "External Dependency Detection" section of `.github/skills/shared/azure-resource-model.md`. The `type` values recorded there drive the dependency templates generated in Step 9.

**7c. Present relationship summary**

Show two tables:
- **Internal Dependencies (N relationships)**: columns Source, Relationship, Target
- **External Dependencies (M dependencies)**: columns External Resource, Type, Required Action, Depended On By

If external dependencies exist, warn: "⚠️ M external dependencies require out-of-scope changes. See `dependencies/README.md` after generation."

### 8. Generate Bicep Templates and Bicepparam File

Generate **all** Bicep files and the `.bicepparam` file in a single pass. Do not wait for user confirmation.

**Before writing any files:**
- Sanitize the scope name for use as a folder name: replace spaces with hyphens, remove characters not in `[a-zA-Z0-9_\-.]`, and truncate to 64 characters. Use the sanitized name as the output folder name and record the original scope name in the README.
- Write all output files to `./<sanitized-scope-name>/` relative to the workspace root.
- If a directory with that name already exists, warn the user and ask whether to overwrite or choose an alternate folder name before writing any files. This overwrite check is the only user confirmation pause permitted during generation. All other steps proceed without confirmation.

Use `.github/skills/shared/azure-resource-configs.md` for per-resource defaults and `.github/skills/shared/bicep-best-practices.md` for generation rules. Use the Tool Preflight capabilities to validate uncertain resource schemas and API versions.

**Output structure:**
```
<scope-name>/
├── README.md                     # Summary: verification results, file list, deploy commands, next steps
├── main.bicep                    # Entry point — orchestrates all modules
├── <scope-name>.bicepparam       # User-editable parameter values with comments
├── modules/
│   ├── networking.bicep          # VNets, subnets, NSGs, private endpoints, NICs
│   ├── compute.bicep             # VMs, App Services, Container Apps, Function Apps
│   ├── data.bicep                # SQL, Cosmos DB, Storage Accounts, Key Vault, Redis
│   ├── identity.bicep            # User-assigned managed identities
│   ├── monitoring.bicep          # Log Analytics, Application Insights, action groups
│   └── other.bicep               # Resources not mapping to any above category (generated only if needed)
└── dependencies/
    ├── README.md                 # Summary of all external dependencies
    ├── <dependency-type>.bicep   # Deployable Bicep for each external dependency
    └── <dependency-type>.bicepparam
```

**Bicep generation rules:**

Follow the **Template Structure** and **Bicepparam Comment Guidelines** sections of `.github/skills/shared/bicep-best-practices.md` for `main.bicep`, module, and `.bicepparam` structure. The following rules are specific to reverse-engineering a live environment and override the shared defaults:

| Area | Rule |
|------|------|
| Defaults | Match **current Azure values** — the goal is to reproduce the existing environment, not to apply cost-effective sizing. Still enable secure defaults (HTTPS, TLS 1.2, deny public access with PEs) and note any insecure current value with an upgrade comment |
| `.bicepparam` comments | Extend the shared comment block with the **current Azure value** for the parameter, alongside 2–3 sizing or tier alternatives with relative cost notes. No hard line limit |
| ARM-to-Bicep | Camel-case property names; ARM arrays to Bicep array syntax; `"true"`/`"false"` strings to booleans; inline resource IDs to symbolic refs |

### 9. Generate Out-of-Scope Dependency Bicep Templates

For each external dependency from Step 7b, generate a deployable `.bicep` + `.bicepparam` pair in `dependencies/` and document in `dependencies/README.md`.

**`dependencies/README.md`**: Summary table (External Resource, Resource Group, Dependency Type, Required Action, Depended On By), links to `.bicep`/`.bicepparam`, per-dependency description with deploy/verify commands, and deployment order instructions.

**Per-dependency templates:**

| Dependency Type | Bicep Resources | Key Params |
|-----------------|----------------|------------|
| VNet Peering | Two `virtualNetworkPeerings` resources with `existing` parent VNets | Local/remote VNet names, remote VNet resource ID |
| Private DNS Zone | A record + VNet link with `existing` DNS zone | Zone name, record name, IP, VNet resource ID |
| Log Analytics | RBAC role assignment on workspace | Workspace name, principal ID, role definition ID |
| Key Vault Access | RBAC role assignment on vault | Vault name, principal ID, role definition ID |
| External Subnet | Subnet with `existing` parent VNet | VNet name, subnet name, address prefix, delegations |
| Container Registry | AcrPull role assignment | Registry name, principal ID |
| RBAC Assignment | `Microsoft.Authorization/roleAssignments` | Target resource ID, principal ID, role definition ID |
| DNS Zone | `existing` DNS zone with CNAME or A record | Zone name, record name, value, TTL, record type |
| Hub Route Table | `existing` route table with routes | Route table name, route name, address prefix, next-hop IP/type |
| Any other type | Stub `.bicep` with a comment block explaining the required manual action; no deployable resources | See comment in stub file |

Each template: `targetScope = 'resourceGroup'`, top-of-file comment explaining the dependency, `existing` blocks for parents, follows all Bicep best practices, outputs key resource ID. Only generate templates for detected dependencies.

### 10. Validate Generated Bicep

Run the **full verification ruleset** from `.github/skills/shared/azure-deployment-verification.md`. This is mandatory — do not skip. Check all rule categories: SKU dependencies, resource compatibility, networking, security, regional availability, version currency, Bicep best practices, missing dependencies, and parameter completeness. Present results using the shared verification output format. Auto-fix errors where possible. Do not present code with known errors.

If errors remain after auto-fix attempts, halt delivery of the affected files, present the specific errors to the user with remediation suggestions, and ask whether to proceed with the warnings-only files or stop entirely.

### 11. Write README and Present Output Summary

Write a `README.md` to the output root directory (alongside `main.bicep`) containing:
- **Source** (resource group, subscription)
- **Generated date**
- **Pre-deployment verification results** (pass/warning/error counts and details)
- **Generated Files** table (file path + description for every generated file)
- **Resource counts** (full/partial extraction, excluded)
- **External dependency count** and secret count
- **Deployment commands** (`az deployment group create` and `New-AzResourceGroupDeployment` examples)
- **Next Steps** section pointing to `dependencies/README.md`, post-deploy RBAC steps, DNS verification, and related skills (azv-bicep-whatif, azv-azure-to-diagram, azv-bicep-policy-check)

After writing the README, present the same summary in the chat response.

### 12. Clean Up Intermediate Files

Delete all intermediate extraction files from the output folder. These were used during discovery and property extraction but are not deliverables:

- `extract-*.json` — per-resource CLI output
- `resource-list-raw.json` — initial resource list
- Temporary resource model JSON file — structured resource model used during generation only; delete it before finishing because its content is captured in the Bicep templates and README

Only final deliverables should remain: `main.bicep`, `.bicepparam`, `modules/`, `dependencies/`, `README.md`, and `original-request.md`.

## Important Notes

- This skill operates **independently** — no diagram or prior AzVerify output required
- `.bicepparam` defaults to **current Azure values** — deploying recreates the same resources
- External dependencies are standalone `.bicep`/`.bicepparam` pairs in `dependencies/`
- All files generated in a **single pass** — no intermediate confirmation
