diff --git a/plugin/skills/azure-iac-generator/SKILL.md b/plugin/skills/azure-iac-generator/SKILL.md new file mode 100644 index 000000000..4c3fa2606 --- /dev/null +++ b/plugin/skills/azure-iac-generator/SKILL.md @@ -0,0 +1,84 @@ +--- +name: azure-iac-generator +description: "Generate deployment-ready Bicep templates from existing Azure environments or Draw.io architecture diagrams. Reverse-engineer live infrastructure into Infrastructure as Code. WHEN: generate bicep, azure to bicep, generate bicep from azure, bicep from diagram, diagram to bicep, create bicep templates from resources, export infrastructure as code, generate infrastructure code, reverse engineer azure, generate iac from azure. DO NOT USE FOR: creating new applications, deploying existing resources, comparing environments." +license: MIT +metadata: + author: Microsoft + version: "1.0.0" +--- + +# Azure IaC Generator + +Reverse-engineer live Azure resources or Draw.io diagrams into deployment-ready, modular Bicep. The goal is an **environment-identical** redeployment for supported configurations. When Azure uses an end-of-life runtime, preserve the extracted value in comments and default to the current supported upgrade path. + +## Prerequisites + +- Azure MCP and Bicep MCP servers available +- For diagram source: a `.drawio` file in the workspace +- For live Azure / `azure-to-bicep`: an authenticated Azure CLI session (`az login`) with access to the target subscription and resource group + +## When to Use This Skill + +- Generate Bicep from existing Azure resources or resource groups +- Reverse-engineer live infrastructure into Infrastructure as Code +- Convert Draw.io architecture diagrams to Bicep templates +- Export Azure infrastructure as code + +## Quick Reference + +| Property | Value | +|---|---| +| **MCP tools** | Azure MCP (`group_resource_list`, `appservice`, `compute`, `storage`, `keyvault`), Bicep MCP (`get_bicep_best_practices`, `get_az_resource_type_schema`) | +| **CLI fallback** | `az resource show --ids `, `az webapp show`, `az webapp config appsettings list` | +| **Output** | Project folder with `main.bicep`, `.bicepparam`, `modules/`, `dependencies/`, `README.md` | + +## Design Notes + +Named `azure-iac-generator` rather than `azure-bicep-generator` to leave room for future expansion to additional Infrastructure as Code targets. Bicep is the only supported target today. + +## Routing — MUST follow the matched workflow + +``` +User request +├── Live Azure ("resource group", "subscription", "reverse engineer") +│ └─► FOLLOW [azure-to-bicep-workflow.md](references/azure-to-bicep-workflow.md) — ALL steps are HARD GATES +│ +└── Draw.io diagram ("from diagram", ".drawio") + └─► FOLLOW [diagram-to-bicep-workflow.md](references/diagram-to-bicep-workflow.md) +``` + +## Mandatory References — MUST read before generating any Bicep + +- [bicep-best-practices.md](references/bicep-best-practices.md) — Generation rules +- [azure-resource-configs.md](references/azure-resource-configs.md) — Per-type property extraction +- [azure-deployment-verification.md](references/azure-deployment-verification.md) — Pre-deployment checks +- [version-currency.md](references/version-currency.md) — API + runtime version rules +- [bicep-parsing.md](references/procedures/bicep-parsing.md) — Parse existing Bicep and `.bicepparam` files when merging with generated output + +## Output Structure — MUST create this folder layout + +``` +/ +├── README.md # Original request, resource summary, verification, deploy commands +├── main.bicep # Orchestrator — module refs only, no inline resources +├── main.bicepparam # All param values with comments (alternatives, EOL dates) +├── modules/ # One file per resource category (only if resources exist) +│ ├── networking.bicep # VNets, subnets, NSGs, private endpoints +│ ├── compute.bicep # VMs, App Services, Functions, Container Apps +│ ├── data.bicep # SQL, Cosmos DB, Storage, Redis, Key Vault +│ ├── identity.bicep # Managed identities, role assignments +│ └── monitoring.bicep # App Insights, Log Analytics +└── dependencies/ # Out-of-scope external dependencies (if any) + └── README.md # What each dependency needs and who owns it +``` + +> ⚠️ **NEVER generate a single flat `main.bicep` with all resources inline.** Resources MUST be in `modules/`. + +## Error Handling + +| Error | Remediation | +|---|---| +| Not authenticated | Run `az login` — see [azure-authentication.md](references/procedures/azure-authentication.md) | +| Resource type unsupported | Placeholder with `// TODO:` and resource ID | +| Secrets detected | `@secure()` param + `readEnvironmentVariable()` in `.bicepparam` | +| API version missing | Latest stable from Bicep MCP or Microsoft docs | diff --git a/plugin/skills/azure-iac-generator/references/auto-detection-rules.md b/plugin/skills/azure-iac-generator/references/auto-detection-rules.md new file mode 100644 index 000000000..ea627ed2e --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/auto-detection-rules.md @@ -0,0 +1,18 @@ +# Auto-Detection Rules + +Settings automatically applied based on diagram topology during Bicep generation. + +| Condition | Auto-Setting | +|-----------|-------------| +| Resource has a Private Endpoint connection | Set `publicNetworkAccess: 'Disabled'` on target resource | +| App Service connected to a Subnet | Set `vnetIntegrationSubnet` to the subnet reference | +| Private Endpoint connected to SQL Server | Set `groupIds: ['sqlServer']` | +| Private Endpoint connected to Storage Account | Set `groupIds: ['blob']` | +| Private Endpoint connected to App Service | Set `groupIds: ['sites']` | +| Private Endpoint connected to Key Vault | Set `groupIds: ['vault']` | +| Private Endpoint connected to Cosmos DB | Set `groupIds: ['Sql']` | +| Private Endpoint exists in a subnet | Set `privateEndpointNetworkPolicies: 'Disabled'` on that subnet | +| VM exists without NIC in diagram | Auto-add NIC resource | +| App Service exists without App Service Plan | Auto-add App Service Plan | +| Subnet index N | Derive `addressPrefix` from the VNet `addressSpace` when present; otherwise require or prompt for a base CIDR before auto-assigning a non-overlapping subnet | + diff --git a/plugin/skills/azure-iac-generator/references/azure-deployment-verification.md b/plugin/skills/azure-iac-generator/references/azure-deployment-verification.md new file mode 100644 index 000000000..ea47662f3 --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/azure-deployment-verification.md @@ -0,0 +1,148 @@ +# Azure Deployment Verification Rules + +Shared pre-deployment verification rules for generated Bicep templates. These cover **gotcha-prone constraints** that are easy to miss — SKU dependencies, resource compatibility, and networking rules that cause deployment failures. + +For rules not listed here (security defaults like TLS 1.2, HTTPS enforcement, runtime version currency), verify against Bicep MCP `get_az_resource_type_schema`, [bicep-best-practices.md](bicep-best-practices.md), and Microsoft documentation. + +Any skill that generates or modifies Bicep for deployment MUST run these checks before presenting results. Failures block deployment; warnings are reported but don't block. + +--- + +## How to Use + +1. After generating or modifying Bicep files, run every applicable rule category below against the generated code and the `.bicepparam` values. +2. Present results as a checklist (see "Output Format" at the end). +3. **Errors** must be fixed automatically. If automatic fixing is not possible, notify the user and present the issue with a concrete recommended fix. Do not present generated code that has known unfixed errors. +4. **Warnings** are informational — present them so the user can decide. + +--- + +## 1. SKU Dependency Rules + +Certain SKUs require companion resources or specific configurations. Missing these causes deployment failures. + +### 1.1 Application Gateway WAF_v2 requires WAF Policy +- **Applies to**: `Microsoft.Network/applicationGateways` with `sku.tier == 'WAF_v2'` +- **Rule**: A `Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies` resource MUST exist and be referenced via the `firewallPolicy.id` property on the Application Gateway. +- **Fix**: Create a WAF policy resource with OWASP 3.2 managed rules in Prevention mode and link it to the gateway. +- **Severity**: Error + +### 1.2 Application Gateway v2 requires Standard Public IP +- **Applies to**: `Microsoft.Network/applicationGateways` with `sku.tier` ending in `_v2` +- **Rule**: The associated `Microsoft.Network/publicIPAddresses` MUST use `sku.name = 'Standard'` and `allocationMethod = 'Static'`. +- **Fix**: Set Public IP SKU to Standard and allocation to Static. +- **Severity**: Error + +### 1.3 App Service VNet Integration requires Standard+ SKU +- **Applies to**: `Microsoft.Web/sites` with VNet integration configured +- **Rule**: The associated `Microsoft.Web/serverfarms` MUST use SKU `S1` or higher. `F1` and `B1` do not support VNet integration. +- **Fix**: Upgrade App Service Plan SKU to at least `S1`. +- **Severity**: Error + +### 1.4 Private Endpoint requires Standard+ resources +- **Applies to**: `Microsoft.Network/privateEndpoints` connected to Storage, SQL, Key Vault, etc. +- **Rule**: The target resource must support private endpoints at its current SKU tier (e.g., Key Vault Standard, Storage all SKUs, SQL all SKUs, Redis Premium only, Service Bus Premium only). +- **Fix**: Upgrade the target resource SKU to one that supports private endpoints. +- **Severity**: Error + +### 1.5 AKS network policy requires compatible plugin +- **Applies to**: `Microsoft.ContainerService/managedClusters` with `networkPolicy` set +- **Rule**: If `networkPolicy = 'azure'`, then `networkPlugin` must be `'azure'`. If `networkPolicy = 'calico'`, `networkPlugin` can be `'azure'` or `'kubenet'`. +- **Fix**: Align network policy and plugin settings. +- **Severity**: Error + +### 1.6 Azure Firewall requires dedicated subnet +- **Applies to**: `Microsoft.Network/azureFirewalls` +- **Rule**: The firewall MUST be placed in a subnet named exactly `AzureFirewallSubnet` with a minimum size of `/26`. +- **Fix**: Add or rename subnet to `AzureFirewallSubnet` with at least `/26` prefix. +- **Severity**: Error + +### 1.7 Bastion requires dedicated subnet +- **Applies to**: `Microsoft.Network/bastionHosts` +- **Rule**: The bastion MUST be placed in a subnet named exactly `AzureBastionSubnet` with a minimum size of `/26`. +- **Fix**: Add or rename subnet to `AzureBastionSubnet` with at least `/26` prefix. +- **Severity**: Error + +--- + +## 2. Resource Compatibility Rules + +Resources that reference each other must be compatible in configuration. + +### 2.1 Backend protocol must match target +- **Applies to**: `Microsoft.Network/applicationGateways` backend HTTP settings +- **Rule**: If the backend is an App Service, `backendHttpSettings.protocol` should be `Https` and `pickHostNameFromBackendAddress` should be `true`. +- **Severity**: Warning + +### 2.2 VM NIC must exist in same subnet +- **Applies to**: `Microsoft.Compute/virtualMachines` +- **Rule**: Every VM must have at least one `Microsoft.Network/networkInterfaces` in the template, attached to a subnet in the same VNet. +- **Fix**: Generate a NIC resource if missing. +- **Severity**: Error + +### 2.3 Private DNS zone must match service +- **Applies to**: `Microsoft.Network/privateEndpoints` with DNS zone groups +- **Rule**: The private DNS zone name must match the expected zone for the service type: + - SQL Server → `privatelink.database.windows.net` + - Blob Storage → `privatelink.blob.core.windows.net` + - Key Vault → `privatelink.vaultcore.azure.net` + - App Service → `privatelink.azurewebsites.net` + - ACR → `privatelink.azurecr.io` + - Cosmos DB → `privatelink.documents.azure.com` +- **Fix**: Use the correct zone name for the target resource type. +- **Severity**: Error + +### 2.4 Private DNS zone must link to VNet +- **Applies to**: `Microsoft.Network/privateDnsZones` +- **Rule**: A `Microsoft.Network/privateDnsZones/virtualNetworkLinks` resource MUST exist linking the DNS zone to the VNet containing the private endpoint. +- **Fix**: Add a VNet link resource. +- **Severity**: Error + +--- + +## 3. Networking Rules + +### 3.1 No subnet address overlap +- **Applies to**: All subnets within a VNet +- **Rule**: Subnet address prefixes MUST NOT overlap with each other or exceed the VNet address space. +- **Fix**: Recalculate subnet prefixes to avoid overlap. +- **Severity**: Error + +### 3.2 Subnet sizing for delegations +- **Applies to**: Subnets with delegations +- **Rule**: Subnets delegated to `Microsoft.Web/serverFarms` (VNet integration) should be at least `/26` (64 addresses). App Gateway subnets should be at least `/24`. +- **Severity**: Warning + +### 3.3 Application Gateway subnet must be dedicated +- **Applies to**: `Microsoft.Network/applicationGateways` +- **Rule**: The App Gateway subnet must not contain any other resources (except other App Gateways). No delegations allowed. +- **Fix**: Move other resources to a different subnet. +- **Severity**: Error + +### 3.4 NSG cannot be applied to App Gateway subnet (v2) +- **Applies to**: `Microsoft.Network/applicationGateways` with v2 SKU +- **Rule**: NSGs on App Gateway v2 subnets require special rules (allow GatewayManager inbound, allow Azure Load Balancer, allow health probes on ports 65200-65535). If an NSG is attached, verify these rules exist. +- **Severity**: Warning + +--- + +## Output Format + +Present verification results after validation: + +``` +## Pre-Deployment Verification + +✅ N checks passed +⚠️ N warnings +❌ N errors + +### Errors (must fix before deployment) +- ❌ **Rule 1.1**: Application Gateway uses WAF_v2 SKU but no WAF policy is defined. + → Fix: Added `waf-policy-appgw` resource with OWASP 3.2 rules linked to `appgw-web`. + +### Warnings +- ⚠️ **Rule 2.1**: App Gateway backend uses HTTP — consider HTTPS with `pickHostNameFromBackendAddress: true`. +``` + +When errors are found and auto-fixed, re-run the affected checks to confirm the fix resolves the issue. diff --git a/plugin/skills/azure-iac-generator/references/azure-resource-configs.md b/plugin/skills/azure-iac-generator/references/azure-resource-configs.md new file mode 100644 index 000000000..8c7565b6e --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/azure-resource-configs.md @@ -0,0 +1,24 @@ +# Azure Resource Configuration Reference + +Per-resource-type property retrieval mapping for drift detection and Bicep generation. For per-resource defaults (SKUs, sizes, settings), derive from Bicep MCP `get_az_resource_type_schema`, Azure Verified Modules, or Microsoft documentation. Do not hardcode defaults — verify at generation time. + +## SKU Extraction Rules (Global) + +For all resource types with `skuName` / `skuTier` properties, extract from the top-level `sku` object: +- `sku.name` → `skuName` +- `sku.tier` → `skuTier` + +**Composite property** — `Microsoft.Compute/virtualMachines` `osImage`: assembled from `storageProfile.imageReference` as `publisher:offer:sku:version`. + +## Per-Resource Property Maps + +Detailed ARM field paths are split by category. Load the relevant file on demand: + +- **Compute & Containers**: [resource-configs-compute.md](resource-configs-compute.md) — VMs, VMSS, App Service, Functions, AKS, Container Apps, ACR +- **Networking**: [resource-configs-network.md](resource-configs-network.md) — VNet, Subnet, NSG, Load Balancer, App Gateway, Public IP, NIC, Private Endpoints, VNet Gateway, Firewall, Bastion, Private DNS +- **Data & Storage**: [resource-configs-data.md](resource-configs-data.md) — Storage Accounts, SQL Server/DB, Cosmos DB, Redis +- **Platform & Integration**: [resource-configs-platform.md](resource-configs-platform.md) — Key Vault, App Insights, Log Analytics, Service Bus, Event Hub, APIM + +## Auto-Detection Rules + +Topology-based settings applied automatically during generation: [auto-detection-rules.md](auto-detection-rules.md) diff --git a/plugin/skills/azure-iac-generator/references/azure-resource-model.md b/plugin/skills/azure-iac-generator/references/azure-resource-model.md new file mode 100644 index 000000000..0e36b312d --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/azure-resource-model.md @@ -0,0 +1,98 @@ +# Azure Resource Metadata Model + + + +## Schema + +Each Azure environment is represented as a **resource model** — a JSON structure with the following shape: + +```json +{ + "resources": [ + { + "id": "/subscriptions//resourceGroups//providers///", + "localId": "", + "type": "", + "name": "", + "resourceGroup": "", + "location": "", + "properties": {}, + "tags": {}, + "relationships": [ + { + "targetId": "/subscriptions//resourceGroups//providers///", + "type": "" + } + ] + } + ] +} +``` + +## Field Definitions + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `id` | string | Yes | **Canonical ARM resource ID** (for example, `/subscriptions//resourceGroups//providers/Microsoft.Compute/virtualMachines/vm-web-01`). This is the primary key for matching resources across workflows. | +| `localId` | string | No | Optional short slug for readability (for example, `vm-web-01`). Do not use as the canonical identifier. | +| `type` | string | Yes | Azure resource provider type (for example, `Microsoft.Compute/virtualMachines`). Must align with `id`. | +| `name` | string | Yes | Display name of the resource. | +| `resourceGroup` | string | No | Resource group the resource belongs to. | +| `location` | string | No | Azure region (e.g., `eastus`, `westeurope`). | +| `properties` | object | No | Resource-specific properties (SKU, tier, size, etc.). | +| `tags` | object | No | Azure resource tags as key-value pairs. | +| `relationships` | array | No | Connections to other resources in the model. | + +## Relationship Types + +| Type | Description | Example | +|------|-------------|---------| +| `contains` | Parent contains child resource | VNet contains Subnet | +| `connects` | Network or data flow connection | VM connects to Storage Account | +| `depends` | Deployment dependency | App Service depends on App Service Plan | +| `peers` | Bidirectional peering | VNet peers with VNet | +| `secures` | Security association | NSG secures Subnet | +| `routes` | Traffic routing | Load Balancer routes to VM | + +## Usage by Workflow + +- **Azure to Bicep workflow**: Builds a resource model from live Azure resources; generates Bicep from it. +- **Diagram to Bicep workflow**: Parses Draw.io XML into a resource model; enriches it with configuration manifest; generates Bicep. + +## Common Azure Resource Types + +| Resource Type | Short Name | +|---------------|------------| +| `Microsoft.Compute/virtualMachines` | VM | +| `Microsoft.Web/sites` | App Service | +| `Microsoft.Web/serverfarms` | App Service Plan | +| `Microsoft.Storage/storageAccounts` | Storage Account | +| `Microsoft.Sql/servers` | SQL Server | +| `Microsoft.Sql/servers/databases` | SQL Database | +| `Microsoft.Network/virtualNetworks` | VNet | +| `Microsoft.Network/virtualNetworks/subnets` | Subnet | +| `Microsoft.Network/networkSecurityGroups` | NSG | +| `Microsoft.Network/loadBalancers` | Load Balancer | +| `Microsoft.Network/applicationGateways` | App Gateway | +| `Microsoft.Network/publicIPAddresses` | Public IP | +| `Microsoft.Network/networkInterfaces` | NIC | +| `Microsoft.Network/privateDnsZones` | Private DNS Zone | +| `Microsoft.Network/privateEndpoints` | Private Endpoint | +| `Microsoft.Network/virtualNetworkGateways` | VPN Gateway | +| `Microsoft.KeyVault/vaults` | Key Vault | +| `Microsoft.ContainerRegistry/registries` | Container Registry | +| `Microsoft.ContainerService/managedClusters` | AKS | +| `Microsoft.App/containerApps` | Container App | +| `Microsoft.App/managedEnvironments` | Container App Environment | +| `Microsoft.DocumentDB/databaseAccounts` | Cosmos DB | +| `Microsoft.ServiceBus/namespaces` | Service Bus | +| `Microsoft.EventHub/namespaces` | Event Hub | +| `Microsoft.Cache/redis` | Redis Cache | +| `Microsoft.Insights/components` | Application Insights | +| `Microsoft.OperationalInsights/workspaces` | Log Analytics Workspace | +| `Microsoft.ApiManagement/service` | API Management | +| `Microsoft.SignalRService/signalR` | SignalR | +| `Microsoft.CognitiveServices/accounts` | Cognitive Services | +| `Microsoft.ManagedIdentity/userAssignedIdentities` | Managed Identity | +| `Microsoft.Authorization/roleAssignments` | Role Assignment | +| `Microsoft.Resources/resourceGroups` | Resource Group | diff --git a/plugin/skills/azure-iac-generator/references/azure-stencil-mapping.json b/plugin/skills/azure-iac-generator/references/azure-stencil-mapping.json new file mode 100644 index 000000000..0207a64cc --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/azure-stencil-mapping.json @@ -0,0 +1,180 @@ +{ + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines", + "imagePath": "img/lib/azure2/compute/Virtual_Machine.svg" + }, + { + "type": "Microsoft.Web/sites", + "imagePath": "img/lib/azure2/app_services/App_Services.svg" + }, + { + "type": "Microsoft.Web/serverfarms", + "imagePath": "img/lib/azure2/app_services/App_Service_Plans.svg" + }, + { + "type": "Microsoft.Storage/storageAccounts", + "imagePath": "img/lib/azure2/storage/Storage_Accounts.svg" + }, + { + "type": "Microsoft.KeyVault/vaults", + "imagePath": "img/lib/azure2/security/Key_Vaults.svg" + }, + { + "type": "Microsoft.Sql/servers", + "imagePath": "img/lib/azure2/databases/SQL_Server.svg" + }, + { + "type": "Microsoft.Sql/servers/databases", + "imagePath": "img/lib/azure2/databases/SQL_Database.svg" + }, + { + "type": "Microsoft.Network/virtualNetworks", + "imagePath": "img/lib/azure2/networking/Virtual_Networks.svg" + }, + { + "type": "Microsoft.Network/virtualNetworks/subnets", + "imagePath": "img/lib/azure2/networking/Subnets.svg" + }, + { + "type": "Microsoft.Network/networkSecurityGroups", + "imagePath": "img/lib/azure2/networking/Network_Security_Groups.svg" + }, + { + "type": "Microsoft.Network/privateEndpoints", + "imagePath": "img/lib/azure2/networking/Private_Link.svg" + }, + { + "type": "Microsoft.Network/privateDnsZones", + "imagePath": "img/lib/azure2/networking/Private_DNS_Zones.svg" + }, + { + "type": "Microsoft.Network/applicationGateways", + "imagePath": "img/lib/azure2/networking/Application_Gateways.svg" + }, + { + "type": "Microsoft.Network/loadBalancers", + "imagePath": "img/lib/azure2/networking/Load_Balancers.svg" + }, + { + "type": "Microsoft.Network/publicIPAddresses", + "imagePath": "img/lib/azure2/networking/Public_IP_Addresses.svg" + }, + { + "type": "Microsoft.ContainerRegistry/registries", + "imagePath": "img/lib/azure2/containers/Container_Registries.svg" + }, + { + "type": "Microsoft.ContainerService/managedClusters", + "imagePath": "img/lib/azure2/containers/Kubernetes_Services.svg" + }, + { + "type": "Microsoft.App/containerApps", + "imagePath": "img/lib/azure2/containers/Container_Apps.svg" + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts", + "imagePath": "img/lib/azure2/databases/Azure_Cosmos_DB.svg" + }, + { + "type": "Microsoft.Cache/redis", + "imagePath": "img/lib/azure2/databases/Azure_Cache_for_Redis.svg" + }, + { + "type": "Microsoft.ServiceBus/namespaces", + "imagePath": "img/lib/azure2/integration/Service_Bus.svg" + }, + { + "type": "Microsoft.EventHub/namespaces", + "imagePath": "img/lib/azure2/analytics/Event_Hubs.svg" + }, + { + "type": "Microsoft.Insights/components", + "imagePath": "img/lib/azure2/monitor/Application_Insights.svg" + }, + { + "type": "Microsoft.OperationalInsights/workspaces", + "imagePath": "img/lib/azure2/monitor/Log_Analytics_Workspaces.svg" + }, + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "imagePath": "img/lib/azure2/identity/Managed_Identities.svg" + }, + { + "type": "Microsoft.Sql/managedInstances", + "imagePath": "img/lib/azure2/databases/SQL_Managed_Instance.svg" + }, + { + "type": "Microsoft.ApiManagement/service", + "imagePath": "img/lib/azure2/integration/API_Management_Services.svg" + }, + { + "type": "Microsoft.SignalRService/signalR", + "imagePath": "img/lib/azure2/web/SignalR.svg" + }, + { + "type": "Microsoft.Web/sites[functionapp]", + "imagePath": "img/lib/azure2/compute/Function_Apps.svg" + }, + { + "type": "Microsoft.EventGrid/systemTopics", + "imagePath": "img/lib/azure2/integration/System_Topic.svg" + }, + { + "type": "Microsoft.EventGrid/topics", + "imagePath": "img/lib/azure2/integration/Event_Grid_Topics.svg" + }, + { + "type": "Microsoft.EventGrid/domains", + "imagePath": "img/lib/azure2/integration/Event_Grid_Domains.svg" + }, + { + "type": "Microsoft.Communication/communicationServices", + "imagePath": "img/lib/azure2/other/Azure_Communication_Services.svg" + }, + { + "type": "Microsoft.Communication/emailServices", + "imagePath": "img/lib/azure2/other/Azure_Communication_Services.svg" + }, + { + "type": "Microsoft.App/managedEnvironments", + "imagePath": "img/lib/azure2/other/Container_App_Environments.svg" + }, + { + "type": "Microsoft.Network/natGateways", + "imagePath": "img/lib/azure2/networking/NAT.svg" + }, + { + "type": "Microsoft.AppConfiguration/configurationStores", + "imagePath": "img/lib/mscae/App_Configuration.svg" + }, + { + "type": "Microsoft.Network/networkInterfaces", + "imagePath": "img/lib/azure2/networking/Network_Interfaces.svg" + }, + { + "type": "Microsoft.Network/virtualNetworkGateways", + "imagePath": "img/lib/azure2/networking/Virtual_Network_Gateways.svg" + }, + { + "type": "Microsoft.CognitiveServices/accounts", + "imagePath": "img/lib/azure2/ai_machine_learning/Cognitive_Services.svg" + }, + { + "type": "Microsoft.Resources/resourceGroups", + "imagePath": "img/lib/azure2/general/Resource_Groups.svg" + }, + { + "type": "Microsoft.Web/sites/functions", + "imagePath": "img/lib/azure2/compute/Function_Apps.svg" + }, + { + "type": "Microsoft.Network/azureFirewalls", + "imagePath": "img/lib/azure2/networking/Firewalls.svg" + }, + { + "type": "Microsoft.Network/bastionHosts", + "imagePath": "img/lib/azure2/networking/Bastions.svg" + } + ] +} diff --git a/plugin/skills/azure-iac-generator/references/azure-to-bicep-workflow.md b/plugin/skills/azure-iac-generator/references/azure-to-bicep-workflow.md new file mode 100644 index 000000000..6132d91d4 --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/azure-to-bicep-workflow.md @@ -0,0 +1,130 @@ +# Azure-to-Bicep Workflow + +Reverse-engineer live Azure resources into modular, deployment-ready Bicep. Every step is **mandatory** — do not skip or combine steps. + +> ⚠️ **Before starting**: Read [bicep-best-practices.md](bicep-best-practices.md) and [version-currency.md](version-currency.md). Call Bicep MCP `get_bicep_best_practices` to load current rules. + +## Step 1 — Authenticate (HARD GATE) + +Follow [azure-authentication.md](procedures/azure-authentication.md). **Stop** if not authenticated. + +## Step 2 — Accept Inputs + +Get target scope: resource group name(s) and subscription ID. If not specified, list resource groups and ask user to select. Optionally accept resource type filters or exclusions. + +## Step 3 — Discover Resources + +Use `group_resource_list` (or `az resource list --resource-group `) to enumerate all resources. Extract: `id`, `name`, `type`, `location`, `tags`, `sku`. If no resources found, stop with an error. + +## Step 4 — Filter Non-Deployable Resources + +Remove auto-created and hidden resources per [resource-filtering.md](procedures/resource-filtering.md) (use "Exclude for Bicep" column). Keep: Application Insights, Log Analytics, user-assigned identities, diagnostic settings. If >30 resources remain, warn user and offer to filter by type. + +## Step 5 — Deep Property Extraction (HARD GATE) + +**Do NOT skip this step.** For every resource, retrieve the **full** configuration: + +| Resource Type | Tool | Also Extract | +|---|---|---| +| `Microsoft.Web/sites` | Azure MCP or `az webapp show` | `az webapp config appsettings list --ids ` for app settings, `siteConfig.linuxFxVersion` or `windowsFxVersion` for runtime stack | +| `Microsoft.Web/serverfarms` | Azure MCP or `az appservice plan show` | `sku.name`, `sku.tier`, `kind`, `properties.reserved` (Linux flag) | +| `Microsoft.Compute/virtualMachines` | Azure MCP `compute` or `az vm show` | `storageProfile.imageReference` → assemble as `publisher:offer:sku:version` | +| `Microsoft.Storage/storageAccounts` | Azure MCP `storage` or `az storage account show` | `minimumTlsVersion`, `allowBlobPublicAccess`, `kind`, `accessTier` | +| `Microsoft.KeyVault/vaults` | Azure MCP `keyvault` or `az keyvault show` | `enableRbacAuthorization`, `softDeleteRetentionInDays` | +| All other types | `az resource show --ids -o json` | See [azure-resource-configs.md](azure-resource-configs.md) for per-type field maps | + +**Strip read-only ARM properties** — these must NOT appear in generated Bicep: +- `provisioningState`, `resourceGuid`, `etag`, `uniqueId` +- `id`, `name`, `type` at property level (derived from Bicep resource declaration) +- Timestamps: `createdTime`, `changedTime`, `lastModifiedTime`, `createdDate` +- Computed: `outboundIpAddresses`, `possibleOutboundIpAddresses`, `defaultHostName`, `hostNames`, `state`, `status` +- Identity outputs: `identity.principalId`, `identity.tenantId` (keep `identity.type` and `userAssignedIdentities`) + +**Detect secrets**: Scan for properties containing `password`, `key`, `secret`, `connectionString`, `accessKey`, or masked values (`****`). Record each for `@secure()` parameter generation. + +If extraction fails for a resource, log a warning and continue with list-level data — do not stop. + +## Step 6 — Analyze Dependencies + +Classify references between resources into two distinct buckets: + +- **Internal** (within scope) — populate the `relationships` array on each resource in the model per [azure-resource-model.md](azure-resource-model.md), using its defined relationship types: + - `contains` (e.g., VNet → Subnet) + - `depends` (e.g., App Service → Plan, NIC → VM) + - `connects` (e.g., Diagnostic → Log Analytics, PE → target) + - `secures` (e.g., NSG → Subnet) + - `peers` (e.g., VNet → VNet) + - `routes` (e.g., Load Balancer → backend) + + Apply [auto-detection-rules.md](auto-detection-rules.md). + +- **External** (outside scope) — references to resources in other resource groups/subscriptions (VNet peering, shared Log Analytics, external Key Vault, external DNS zones). These are NOT model relationships; they are tracked as a separate list that feeds the `dependencies/` folder in Step 10. Record each as: resource ID, type, required action, depended-on-by. + +## Step 7 — Create Output Folder + +Create a folder named after the scope (e.g., resource group name). All generated files go inside this folder. **Never** write files to the repository root. + +## Step 8 — Merge Existing Bicepparam (if present) + +If a `.bicepparam` file already exists in the output folder from a previous run, parse it per [bicep-parsing.md](procedures/bicep-parsing.md) and apply the merge rules below. If no existing file is present, skip this step. + +1. Read all existing parameter values. +2. For parameters that exist in both the existing file and the new generation: **keep the existing value** (the user may have customized it). +3. For new parameters: add with generated defaults and comments. +4. For parameters that no longer apply: comment them out with a `// REMOVED:` prefix rather than deleting them. +5. Always preserve user comments. + +## Step 9 — Generate Modular Bicep (HARD GATE) + +**MUST use modules.** `main.bicep` contains ONLY `param`, `module`, and `output` statements — no inline resource declarations. + +Follow [bicep-best-practices.md](bicep-best-practices.md) strictly. Call Bicep MCP `get_az_resource_type_schema` for each resource type to get the latest stable API version. + +| File | Contents | +|---|---| +| `main.bicep` | `targetScope = 'resourceGroup'`; all params with `@description()` and `@secure()` where needed; one `module` block per category; outputs for key endpoints/IDs | +| `main.bicepparam` | `using 'main.bicep'`; every param value matching current Azure config; 1-3 line comments per param (what it controls, alternatives with cost impact, version EOL dates); `readEnvironmentVariable()` for secrets | +| `modules/networking.bicep` | VNets, subnets, NSGs, private endpoints, NICs, firewalls | +| `modules/compute.bicep` | VMs, App Services, Functions, Container Apps — follow the runtime defaulting rules in [version-currency.md](version-currency.md) | +| `modules/data.bicep` | Storage, SQL, Cosmos DB, Redis, Key Vault | +| `modules/identity.bicep` | User-assigned managed identities, role assignments | +| `modules/monitoring.bicep` | App Insights, Log Analytics, action groups | + +Only create module files that have resources. Each module receives only the params it needs. Use `parent:` for child resources, `existing` blocks for cross-module refs, symbolic references (`foo.id`) — never `resourceId()`. + +Apply the runtime defaulting and comment rules from [version-currency.md](version-currency.md). That file is the single source of truth for supported-versus-EOL handling. + +## Step 10 — Generate Dependencies Folder + +If external dependencies were found in Step 6, create `dependencies/` with: +- `dependencies/README.md` — table of external dependencies (resource, type, required action, owner) +- One `.bicep` + `.bicepparam` pair per dependency type where a deployable template is possible + +If no external dependencies exist, skip this step. + +## Step 11 — Run Verification (HARD GATE) + +Apply **all** rules from [azure-deployment-verification.md](azure-deployment-verification.md): SKU dependencies, resource compatibility, networking, security, version currency. Automatically fix errors. If automatic fixing is not possible, notify the user and present the issue with a concrete recommended fix. Present results as: + +``` +## Pre-Deployment Verification +✅ N passed ⚠️ N warnings ❌ N errors +``` + +Do NOT present output with known unfixed errors. + +## Step 12 — Generate README.md + +Write `README.md` inside the output folder containing: +1. **Original request** — the user's exact prompt +2. **Source** — resource group, subscription ID, discovery date +3. **Verification results** — pass/warning/error summary +4. **Generated files** — table of every file with description +5. **Resource summary** — count by type, extraction status (full/partial) +6. **Secrets** — count of detected secrets requiring manual configuration +7. **Deploy commands** — `az deployment group create` and `New-AzResourceGroupDeployment` examples +8. **Next steps** — populate secrets, review `dependencies/`, post-deploy validation + +## Step 13 — Present Summary + +Show in chat: resource count, file count, verification results, and the path to the generated folder. Do NOT echo full Bicep content — only paths and summaries. diff --git a/plugin/skills/azure-iac-generator/references/bicep-best-practices.md b/plugin/skills/azure-iac-generator/references/bicep-best-practices.md new file mode 100644 index 000000000..493a2b1ee --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/bicep-best-practices.md @@ -0,0 +1,70 @@ +# Bicep Best Practices + +Mandatory rules for all Bicep generation. Before generating any Bicep, also call the Bicep MCP server's `get_bicep_best_practices` tool for the latest guidance. + +--- + +## Rules + +| # | Rule | Detail | +|---|------|--------| +| 1 | No `name` on module statements | Omit the `name` field on `module` blocks; Bicep generates it automatically | +| 2 | User-defined types over open types | Avoid `array`, unless it is an array of strings, or `object` params; define typed structures | +| 3 | `.bicepparam` files | Always generate `.bicepparam`, never JSON parameter files | +| 4 | `parent` property for child resources | Never build child names with `/`; use `parent:` with a symbolic ref | +| 5 | `existing` resource for parent lookups | If parent is not in the same file, add an `existing` resource block | +| 6 | Symbolic references | Use `foo.id` / `foo.properties.x`, not `resourceId()` or `reference()` | +| 7 | `@secure()` on sensitive params | Always decorate passwords, keys, connection strings | +| 8 | `@description()` on params and types | Describe every parameter; describe type properties where context is unclear | +| 9 | Resource-derived types | Prefer `resourceInput<>` / `resourceOutput<>` over hand-written types | +| 10 | Minimal comments | Only add comments beyond what `@description()` says. No `// ====` banners. Do use comments for complicated operations or ternaries | +| 11 | Safe-dereference | Use `.?` and `??` for null handling, not ternaries or `!.` | + +## Bicepparam Comment Guidelines + +If comments are needed, keep comments to max 1-3 lines per parameter. Cover: +- What the setting controls (plain language) +- 2-3 common alternatives with cost/capacity impact +- Security consequences where applicable + +```bicep +// VM size — CPU, memory, cost. +// Standard_B2s → 2 vCPU, 4 GB (~$30/mo) — dev/test +// Standard_D4s_v5 → 4 vCPU, 16 GB (~$140/mo) — production +param vmSize = 'Standard_B2s' +``` + +## API Version Rule + +Use the latest **stable** (non-preview) API version for each resource type. Call `get_az_resource_type_schema` to verify when uncertain. + +## Bicepparam File Pattern + +The `.bicepparam` file must include ALL parameter values with descriptive comments. Use `readEnvironmentVariable()` for secrets — never hardcode them. + +```bicep +using 'main.bicep' + +// Azure region. Options: eastus, westeurope, westus2 +param location = 'eastus' + +// VM size — cost, CPU, memory. +// Standard_B2s → 2 vCPU, 4 GB (~$30/mo) — dev/test +// Standard_D2s_v3 → 2 vCPU, 8 GB (~$70/mo) — general +param vmSize = 'Standard_B2s' + +// SQL admin password — reads from env var at deploy time. +param sqlAdminPassword = readEnvironmentVariable('SQLPASSWORD') +``` + +## Private Endpoint groupIds by Target + +| Target Resource | groupIds | +|---|---| +| App Service | `['sites']` | +| Storage (Blob) | `['blob']` | +| Storage (File) | `['file']` | +| SQL Server | `['sqlServer']` | +| Cosmos DB | `['Sql']` | +| Key Vault | `['vault']` | +| Redis | `['redisCache']` | diff --git a/plugin/skills/azure-iac-generator/references/diagram-to-bicep-workflow.md b/plugin/skills/azure-iac-generator/references/diagram-to-bicep-workflow.md new file mode 100644 index 000000000..33341c28e --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/diagram-to-bicep-workflow.md @@ -0,0 +1,28 @@ +# Diagram-to-Bicep Workflow + +Parse a Draw.io architecture diagram and generate deployment-ready Bicep templates. + +--- + +## Steps + +| # | Action | Details | +|---|--------|---------| +| 1 | **Accept Diagram** | Get the Draw.io file path from the user. Read the `.drawio` XML file. | +| 2 | **Parse to Resource Model** | Apply [diagram-parsing.md](procedures/diagram-parsing.md) to extract resources, containers, and relationships into a resource model per [azure-resource-model.md](azure-resource-model.md). | +| 3 | **Create Output Folder** | Create a folder named after the scope (the diagram filename without extension, e.g., `my-architecture/` for `my-architecture.drawio`). All generated files go inside this folder per the layout in [SKILL.md](../SKILL.md). **Never** write files to the repository root. | +| 4 | **Merge Existing Bicepparam** | If a `.bicepparam` file already exists in the output folder, parse it per [bicep-parsing.md](procedures/bicep-parsing.md) and apply the **Existing Bicepparam Merge Rules** below. If none exists, skip. | +| 5 | **Enrich Resource Properties** | For each resource in the model, derive recommended properties using [azure-resource-configs.md](azure-resource-configs.md) and Bicep MCP `get_az_resource_type_schema`. Apply auto-detection rules (e.g., Private Endpoint → disable public access on target). | +| 6 | **Generate Bicep** | Create modular Bicep following [bicep-best-practices.md](bicep-best-practices.md) and the folder layout in [SKILL.md](../SKILL.md). Use latest stable API versions per [version-currency.md](version-currency.md). Group into `modules/` by layer. Generate `main.bicep` and `main.bicepparam`. | +| 7 | **Run Deployment Verification** | Apply [azure-deployment-verification.md](azure-deployment-verification.md) checks. Automatically fix errors; if a fix is not possible, notify the user and present the issue. Present checklist. | +| 8 | **Write README** | Generate a `README.md` inside the output folder. MUST include the same explicit sections as the azure-to-bicep workflow: 1) Original request — the user's exact prompt; 2) Source — diagram file path, parse date; 3) Verification results — pass/warning/error summary; 4) Generated files — table of every file with description; 5) Resource summary — count by type; 6) Secrets — count of detected secrets requiring manual configuration; 7) Deploy commands — `az deployment group create` and `New-AzResourceGroupDeployment` examples; 8) Next steps — populate secrets, post-deploy validation. | +| 9 | **Save Output & Present Summary** | Write all files to the output folder. Show in chat: resource count, file count, verification results, and the path to the generated folder. Do NOT echo full Bicep content. | + +## Existing Bicepparam Merge Rules + +When a `.bicepparam` file already exists: +1. Read all existing parameter values +2. For parameters that exist in both old and new: keep the existing value (user may have customized) +3. For new parameters: add with generated defaults and comments +4. For removed parameters: comment them out with `// REMOVED:` prefix +5. Always preserve user comments diff --git a/plugin/skills/azure-iac-generator/references/procedures/azure-authentication.md b/plugin/skills/azure-iac-generator/references/procedures/azure-authentication.md new file mode 100644 index 000000000..15abfaefe --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/procedures/azure-authentication.md @@ -0,0 +1,31 @@ +# Azure Authentication Check + + +--- + +## Procedure + +1. **Verify session**: Run a lightweight Azure MCP call (e.g., `mcp_azure_mcp_subscription_list`), or run `az account show` or `Get-AzContext` in the terminal + +2. **If authenticated**: + - Display the active subscription name and ID + - Proceed to the next step + +3. **If NOT authenticated**: + - Present this message: + + ```markdown + ## Azure Authentication Required + + You need an active Azure session. + + **Option A — Azure CLI:** + az login + + **Option B — Azure PowerShell:** + Connect-AzAccount + + After authenticating, run this skill again. + ``` + + - **HARD GATE** — Stop execution. Do not proceed without authentication. \ No newline at end of file diff --git a/plugin/skills/azure-iac-generator/references/procedures/bicep-parsing.md b/plugin/skills/azure-iac-generator/references/procedures/bicep-parsing.md new file mode 100644 index 000000000..f033b8ce0 --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/procedures/bicep-parsing.md @@ -0,0 +1,60 @@ +# Bicep Parsing Procedure + + +Parse Bicep templates into a structured resource model for comparison. Referenced by skills that analyze existing Bicep files. + +--- + +## Procedure + +### 1. Read Parameter Values + +Read the `.bicepparam` file and extract all parameter values. These are needed to resolve name expressions in resource blocks. + +### 2. Read Template Files + +Read `main.bicep` and parse all `module` declarations to find referenced Bicep files. Module paths are relative to the file containing the `module` statement — they may reference files outside `modules/` (e.g., `'../shared/networking.bicep'`). Read each resolved module file recursively to discover nested module references. + +> **Registry modules** (`br:` references like `'br:myregistry.azurecr.io/bicep/networking:v1'`) are not local file paths. They require `bicep restore` to download into the local module cache before they can be read. If restoration is not possible, flag these modules as "external — cannot resolve locally" and skip recursive parsing for them. + +### 3. Extract Resources + +For each `resource` block found: + +| Field | How to Extract | +|---|---| +| `type` | Resource type string, sans API version (e.g., `Microsoft.Compute/virtualMachines`) | +| `apiVersion` | API version from the resource declaration | +| `symbolicName` | The Bicep symbolic name (left side of `=`) | +| `name` | The `name` property value — resolve parameter references using `.bicepparam` values | +| `sourceFile` | Relative path of the file containing this resource | +| `conditional` | `true` if the resource has an `if` condition, `false` otherwise | +| `parent` | Symbolic name of the `parent:` reference (if any) | + +### 4. Resolve Hierarchy + +- Match `parent:` references to their target resource blocks +- Build parent-child relationships (e.g., subnet → VNet) + +### 5. Output Model + +Output the parsed Bicep resource model in chat for user verification. Schema per resource: + +```json +{ + "symbolicName": "", + "type": "Microsoft.Provider/resourceType", + "name": "", + "sourceFile": "modules/networking.bicep", + "conditional": false, + "parent": null +} +``` + +## Depth Levels + +| Depth | What's Extracted | Use Case | +|---|---|---| +| Shallow | Type, name, file location | Quick comparison / drift detection | +| Standard | Above + params, parent refs, conditions | Sync, what-if | +| Deep | Above + all property values resolved | Policy check, detailed what-if | \ No newline at end of file diff --git a/plugin/skills/azure-iac-generator/references/procedures/diagram-parsing.md b/plugin/skills/azure-iac-generator/references/procedures/diagram-parsing.md new file mode 100644 index 000000000..90f4779e3 --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/procedures/diagram-parsing.md @@ -0,0 +1,54 @@ +# Diagram Parsing Procedure + +Parse a Draw.io XML file into a structured resource model. Referenced by all skills that consume Draw.io diagrams. + +--- + +## Procedure + +1. **Load the stencil mapping** from [azure-stencil-mapping.json](../azure-stencil-mapping.json) and build **reverse lookups** that map supported Draw.io style identifiers back to Azure resource types: + - `image=` → Azure resource type (for `image`-based cells) + - `shape=mxgraph.azure2...` → Azure resource type (for `shape`-based cells, using a normalized azure2 shape-key lookup) + + **If an icon's path is NOT in the mapping**, do not skip the cell. Infer the Azure service directly from the icon path itself: + - Paths follow the convention `img/lib/azure2//.svg` (or `img/lib/mscae/.svg`). + - Use the SVG filename (e.g. `Azure_Functions.svg`, `Front_Door.svg`, `Firewalls.svg`) plus the `` segment to identify the Azure service, then map it to the most appropriate `Microsoft./` ARM type. + - Examples: `…/networking/Firewalls.svg` → `Microsoft.Network/azureFirewalls`; `…/networking/Bastions.svg` → `Microsoft.Network/bastionHosts`; `…/web/Front_Door_and_CDN_Profiles.svg` → `Microsoft.Cdn/profiles`. + - If the filename is ambiguous or no plausible ARM type can be inferred, record the cell as an `unknown` resource (preserving its `id`, `value`, and `image` path) and surface it in the output so the user can resolve it — never silently drop it. + +2. **Identify resource cells**: For each `mxCell` in the diagram XML: + - If the style contains `image=` and the path matches the reverse lookup → this is an Azure resource + - Extract: cell `id`, `value` (display name), `parent` (container relationship), matched resource type + - **Skip icon cells**: Cells whose `id` ends in `-icon` are cosmetic container icons — do NOT add as resources + +3. **Identify container cells**: Cells with `container=1` that are NOT icon cells represent groupings. Match via their icon child cell's image path or container style: + + | Style Pattern | Container Type | + |---|---| + | `fillColor=#fff2cc` | Resource Group | + | `fillColor=#dae8fc` | VNet | + | `fillColor=#e1d5e7` | Subnet | + +4. **Build containment hierarchy** using the `parent` attribute: + - `parent="1"` → top-level (diagram root) + - `parent=""` → inside that container + - Map to `contains` relationships in the resource model + +5. **Extract connections**: For each `mxCell` with `edge="1"`: + - Extract `source` and `target` cell IDs + - Determine relationship type from edge style: + + | Edge Style | Relationship | + |---|---| + | `strokeColor=#0078D4` | `connects` (data flow) | + | `strokeColor=#E81123` | `secures` (security / private link) | + | `strokeColor=#999999` + `dashed=1` | `depends` (dependency) | + | `strokeColor=#00A4EF` + `dashed=1` | `peers` (network link) | + +6. **Output the resource model** in chat so the user can verify parsing is correct. + +--- + +## Output Schema + +The parsed model follows [azure-resource-model.md](../azure-resource-model.md). Each resource has: `id`, `type`, `name`, `location` (container), `relationships`, and `tags`. diff --git a/plugin/skills/azure-iac-generator/references/procedures/resource-filtering.md b/plugin/skills/azure-iac-generator/references/procedures/resource-filtering.md new file mode 100644 index 000000000..93dba8368 --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/procedures/resource-filtering.md @@ -0,0 +1,57 @@ +# Resource Filtering for Azure-to-Bicep + +Use this table during Azure-to-Bicep discovery to remove resources that should not become first-class Bicep resources in generated output. + +## Filtering Table + +| Resource Pattern or Type | Why It Is Filtered | Exclude for Bicep | +|---|---|---| +| `Microsoft.Resources/deployments` | Deployment history is operational metadata, not desired-state infrastructure. | Yes | +| `Microsoft.Insights/metricAlerts` generated by platform onboarding | Often auto-created during monitoring enablement and not part of the requested baseline. | Yes | +| `Microsoft.AlertsManagement/smartDetectorAlertRules` | Auto-generated smart detection rules are service-managed. | Yes | +| `Microsoft.Insights/webtests` created by availability defaults | Synthetic tests may be optional operational assets rather than core infrastructure. | Yes | +| Resources with `hidden-` prefixes or `hidden-link:` tags | Platform-generated link resources are derived artifacts, not author-managed infrastructure. | Yes | +| Extension resources whose lifecycle is entirely platform-managed | These resources are recreated by the parent service and should not be emitted directly. | Yes | +| `Microsoft.Insights/components` | Application Insights is deployable infrastructure and should be preserved. | No | +| `Microsoft.OperationalInsights/workspaces` | Log Analytics workspaces are deployable infrastructure and should be preserved. | No | +| `Microsoft.ManagedIdentity/userAssignedIdentities` | User-assigned identities are first-class deployable resources. | No | +| `Microsoft.Insights/diagnosticSettings` | Diagnostic settings are deployable and often required for parity. | No | + +## Exclusion Table + +| Resource Type | Exclude for Bicep | Rationale | +|---|---|---| +| `Microsoft.Network/networkWatchers` | ✅ | Auto-created by Azure | +| `Microsoft.Network/networkWatchers/connectionMonitors` | ✅ | Auto-created child | +| `Microsoft.AlertsManagement/smartDetectorAlertRules` | ✅ | Auto-created | +| `Microsoft.Portal/dashboards` | ✅ | Portal UI artifact | +| `Microsoft.Insights/autoscalesettings` (with `hidden-related:` tags) | ✅ | Auto-created | +| `Microsoft.Network/networkIntentPolicies` | ✅ | Auto-created | +| `Microsoft.Network/serviceEndpointPolicies` | ✅ | Auto-created | +| `Microsoft.Resources/deployments` | ✅ | Deployment history | +| `Microsoft.Resources/templateSpecs` | ✅ | Template metadata | +| `Microsoft.Authorization/policyAssignments` | ✅ | Policy — service-managed | +| `Microsoft.Authorization/policyDefinitions` | ✅ | Policy — service-managed | +| `Microsoft.Authorization/roleAssignments` (scoped to a user-assigned managed identity) | ❌ KEEP | Required for the identity to function; emit alongside the identity | +| `Microsoft.Authorization/roleAssignments` (all other scopes) | ✅ | RBAC assignments not tied to deployable identities are out of scope | +| `Microsoft.Insights/components` (Application Insights) | ❌ KEEP | Deployable infrastructure | +| `Microsoft.Insights/actionGroups` | ❌ KEEP | Deployable infrastructure | +| `Microsoft.OperationalInsights/workspaces` (Log Analytics) | ❌ KEEP | Deployable infrastructure | +| `Microsoft.ManagedIdentity/userAssignedIdentities` | ❌ KEEP | Explicitly created; used for resource authentication and RBAC | +| Diagnostic settings (child resources) | ❌ KEEP | Deployable as child | +| Resources with ALL tag keys starting with `hidden-` | ✅ | Fully Azure-managed | +| Resources with `hidden-related:` tag prefixes | Check individually | May be Azure-managed | + +## Application Rules + +1. Check resource type against the table (case-insensitive) +2. Check if ALL tag keys start with `hidden-` (fully managed) +3. Apply any user-specified exclusion filters +4. Remove matching resources from the working list + +## Procedure + +1. Start with the full `group_resource_list` output. +2. Exclude rows marked **Yes** in the table above. +3. Keep all rows marked **No**, even when they were created by portal workflows. +4. If a resource is ambiguous, prefer keeping it and note the uncertainty in the generated `README.md`. diff --git a/plugin/skills/azure-iac-generator/references/resource-configs-compute.md b/plugin/skills/azure-iac-generator/references/resource-configs-compute.md new file mode 100644 index 000000000..731529a45 --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/resource-configs-compute.md @@ -0,0 +1,130 @@ +# Compute Resource Property Mapping + +Per-resource property retrieval for compute and container resource types. + +**Fallback for all**: `az resource show --ids -o json` + +--- + +### Microsoft.Compute/virtualMachines + +**MCP Tool**: `mcp_azure_mcp_compute` +**Fallback**: `az vm show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| vmSize | `properties.hardwareProfile.vmSize` | | +| osType | `properties.storageProfile.osDisk.osType` | | +| osImage | *(composite — publisher:offer:sku:version from `storageProfile.imageReference`)* | | +| osDiskType | `properties.storageProfile.osDisk.managedDisk.storageAccountType` | | +| osDiskSizeGB | `properties.storageProfile.osDisk.diskSizeGB` | | +| dataDisks | `properties.storageProfile.dataDisks` | Array | +| adminUsername | `properties.osProfile.adminUsername` | | +| authenticationType | `properties.osProfile.linuxConfiguration.disablePasswordAuthentication` | `true`→`sshPublicKey`, `false`→`password` | +| enableBootDiagnostics | `properties.diagnosticsProfile.bootDiagnostics.enabled` | | +| availabilityZone | `zones[0]` | Top-level `zones` array | +| enableAcceleratedNetworking | *(from NIC resource)* | Query linked NIC via `properties.networkProfile.networkInterfaces[0].id` | + +### Microsoft.Compute/virtualMachineScaleSets + +**MCP Tool**: `mcp_azure_mcp_compute` +**Fallback**: `az vmss show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| vmSize | `sku.name` | In `sku` not `properties` | +| capacity | `sku.capacity` | | +| osImage | *(composite from `properties.virtualMachineProfile.storageProfile.imageReference`)* | | +| upgradePolicy | `properties.upgradePolicy.mode` | | + +### Microsoft.Web/serverfarms + +**MCP Tool**: `mcp_azure_mcp_appservice` +**Fallback**: `az appservice plan show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `sku.name` | | +| skuTier | `sku.tier` | | +| kind | `kind` | Top-level field | +| reserved | `properties.reserved` | | +| capacity | `sku.capacity` | | + +### Microsoft.Web/sites + +**MCP Tool**: `mcp_azure_mcp_appservice` +**Fallback**: `az webapp show --ids -o json` + `az webapp config show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| runtimeStack | `properties.siteConfig.linuxFxVersion` or `windowsFxVersion` | Format: `RUNTIME\|VERSION` | +| httpsOnly | `properties.httpsOnly` | | +| minTlsVersion | `properties.siteConfig.minTlsVersion` | config sub-call | +| alwaysOn | `properties.siteConfig.alwaysOn` | config sub-call | +| http20Enabled | `properties.siteConfig.http20Enabled` | config sub-call | +| ftpsState | `properties.siteConfig.ftpsState` | config sub-call | +| publicNetworkAccess | `properties.publicNetworkAccess` | | +| vnetIntegrationSubnet | `properties.virtualNetworkSubnetId` | | + +### Microsoft.Web/sites/functions + +**MCP Tool**: `mcp_azure_mcp_appservice` +**Fallback**: `az functionapp show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| runtimeStack | `properties.siteConfig.linuxFxVersion` | | +| hostingPlan | *(derived from linked serverfarm SKU)* | | +| httpsOnly | `properties.httpsOnly` | | +| minTlsVersion | `properties.siteConfig.minTlsVersion` | | + +### Microsoft.ContainerService/managedClusters + +**MCP Tool**: `mcp_azure_mcp_aks` +**Fallback**: `az aks show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| kubernetesVersion | `properties.kubernetesVersion` | | +| defaultNodePoolVmSize | `properties.agentPoolProfiles[0].vmSize` | | +| defaultNodePoolCount | `properties.agentPoolProfiles[0].count` | | +| networkPlugin | `properties.networkProfile.networkPlugin` | | +| networkPolicy | `properties.networkProfile.networkPolicy` | | +| enableRBAC | `properties.enableRBAC` | | +| enableManagedIdentity | *(derived)* | `true` if `identity.type` = `SystemAssigned` or `UserAssigned` | + +### Microsoft.App/managedEnvironments + +**MCP Tool**: `mcp_azure_mcp_containerapps` +**Fallback**: `az containerapp env show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| workloadProfileType | `properties.workloadProfiles[0].workloadProfileType` | | +| zoneRedundant | `properties.zoneRedundant` | | + +### Microsoft.App/containerApps + +**MCP Tool**: `mcp_azure_mcp_containerapps` +**Fallback**: `az containerapp show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| containerImage | `properties.template.containers[0].image` | | +| cpuCores | `properties.template.containers[0].resources.cpu` | | +| memoryGi | `properties.template.containers[0].resources.memory` | Strip `Gi` suffix | +| minReplicas | `properties.template.scale.minReplicas` | | +| maxReplicas | `properties.template.scale.maxReplicas` | | +| targetPort | `properties.configuration.ingress.targetPort` | | +| external | `properties.configuration.ingress.external` | | + +### Microsoft.ContainerRegistry/registries + +**MCP Tool**: `mcp_azure_mcp_acr` +**Fallback**: `az acr show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `sku.name` | | +| adminUserEnabled | `properties.adminUserEnabled` | | +| publicNetworkAccess | `properties.publicNetworkAccess` | | diff --git a/plugin/skills/azure-iac-generator/references/resource-configs-data.md b/plugin/skills/azure-iac-generator/references/resource-configs-data.md new file mode 100644 index 000000000..a5543df25 --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/resource-configs-data.md @@ -0,0 +1,76 @@ +# Data Resource Property Mapping + +Per-resource property retrieval for data and storage resource types. + +**Fallback for all**: `az resource show --ids -o json` + +--- + +### Microsoft.Storage/storageAccounts + +**MCP Tool**: `mcp_azure_mcp_storage` +**Fallback**: `az storage account show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `sku.name` | | +| kind | `kind` | Top-level field | +| accessTier | `properties.accessTier` | | +| minimumTlsVersion | `properties.minimumTlsVersion` | | +| supportsHttpsTrafficOnly | `properties.supportsHttpsTrafficOnly` | | +| allowBlobPublicAccess | `properties.allowBlobPublicAccess` | | +| publicNetworkAccess | `properties.publicNetworkAccess` | | +| enableHierarchicalNamespace | `properties.isHnsEnabled` | | + +### Microsoft.Sql/servers + +**MCP Tool**: `mcp_azure_mcp_sql` +**Fallback**: `az sql server show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| administratorLogin | `properties.administratorLogin` | | +| minimalTlsVersion | `properties.minimalTlsVersion` | | +| publicNetworkAccess | `properties.publicNetworkAccess` | | + +### Microsoft.Sql/servers/databases + +**MCP Tool**: `mcp_azure_mcp_sql` +**Fallback**: `az sql db show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `sku.name` | | +| skuTier | `sku.tier` | | +| maxSizeBytes | `properties.maxSizeBytes` | | +| zoneRedundant | `properties.zoneRedundant` | | +| readScale | `properties.readScale` | | +| backupRedundancy | `properties.requestedBackupStorageRedundancy` | | + +### Microsoft.DocumentDB/databaseAccounts + +**MCP Tool**: `mcp_azure_mcp_cosmos` +**Fallback**: `az cosmosdb show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| kind | `kind` | Top-level field | +| consistencyLevel | `properties.consistencyPolicy.defaultConsistencyLevel` | | +| capacityMode | `properties.capacityMode` | | +| enableFreeTier | `properties.enableFreeTier` | | +| enableAutomaticFailover | `properties.enableAutomaticFailover` | | +| publicNetworkAccess | `properties.publicNetworkAccess` | | +| backupPolicy | `properties.backupPolicy.type` | | + +### Microsoft.Cache/redis + +**MCP Tool**: `mcp_azure_mcp_redis` +**Fallback**: `az redis show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `properties.sku.name` | Nested under `properties.sku` | +| capacity | `properties.sku.capacity` | Nested under `properties.sku` | +| enableNonSslPort | `properties.enableNonSslPort` | | +| minimumTlsVersion | `properties.minimumTlsVersion` | | +| publicNetworkAccess | `properties.publicNetworkAccess` | | diff --git a/plugin/skills/azure-iac-generator/references/resource-configs-network.md b/plugin/skills/azure-iac-generator/references/resource-configs-network.md new file mode 100644 index 000000000..9807e4b59 --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/resource-configs-network.md @@ -0,0 +1,125 @@ +# Network Resource Property Mapping + +Per-resource property retrieval for network resource types. + +**MCP Tool**: *(none for all — use fallback)* + +--- + +### Microsoft.Network/virtualNetworks + +**Fallback**: `az network vnet show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| addressPrefixes | `properties.addressSpace.addressPrefixes` | Array | +| enableDdosProtection | `properties.enableDdosProtection` | | + +### Microsoft.Network/virtualNetworks/subnets + +**Fallback**: `az network vnet subnet show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| addressPrefix | `properties.addressPrefix` | | +| serviceEndpoints | `properties.serviceEndpoints` | Array; compare `service` values | +| delegations | `properties.delegations` | Array; compare `serviceName` values | +| privateEndpointNetworkPolicies | `properties.privateEndpointNetworkPolicies` | | + +### Microsoft.Network/networkSecurityGroups + +**Fallback**: `az network nsg show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| securityRules | `properties.securityRules` | Array; compare rule names and key attributes | + +### Microsoft.Network/loadBalancers + +**Fallback**: `az network lb show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `sku.name` | | +| type | *(derived)* | Has `frontendIPConfigurations[0].properties.publicIPAddress` → `Public`, else `Internal` | +| frontendPort | `properties.loadBalancingRules[0].properties.frontendPort` | | +| backendPort | `properties.loadBalancingRules[0].properties.backendPort` | | +| protocol | `properties.loadBalancingRules[0].properties.protocol` | | +| enableFloatingIP | `properties.loadBalancingRules[0].properties.enableFloatingIP` | | + +### Microsoft.Network/applicationGateways + +**Fallback**: `az network application-gateway show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `properties.sku.name` | | +| tier | `properties.sku.tier` | | +| capacity | `properties.sku.capacity` | | +| enableHttp2 | `properties.enableHttp2` | | +| frontendPort | `properties.frontendPorts[0].properties.port` | | + +### Microsoft.Network/publicIPAddresses + +**Fallback**: `az network public-ip show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `sku.name` | | +| allocationMethod | `properties.publicIPAllocationMethod` | | +| availabilityZone | `zones` | Array; `Zone-redundant` if multiple zones | + +### Microsoft.Network/networkInterfaces + +**Fallback**: `az network nic show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| enableAcceleratedNetworking | `properties.enableAcceleratedNetworking` | | +| enableIPForwarding | `properties.enableIPForwarding` | | +| privateIPAllocationMethod | `properties.ipConfigurations[0].properties.privateIPAllocationMethod` | | + +### Microsoft.Network/privateEndpoints + +**Fallback**: `az network private-endpoint show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| groupIds | `properties.privateLinkServiceConnections[0].properties.groupIds` | Array | +| privateDnsZoneGroup | `properties.privateDnsZoneGroups` | `true` if array non-empty | + +### Microsoft.Network/virtualNetworkGateways + +**Fallback**: `az network vnet-gateway show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `properties.sku.name` | | +| gatewayType | `properties.gatewayType` | | +| vpnType | `properties.vpnType` | | +| enableBgp | `properties.enableBgp` | | + +### Microsoft.Network/azureFirewalls + +**Fallback**: `az network firewall show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuTier | `properties.sku.tier` | | +| threatIntelMode | `properties.threatIntelMode` | | + +### Microsoft.Network/bastionHosts + +**Fallback**: `az network bastion show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `sku.name` | | + +### Microsoft.Network/privateDnsZones + +**Fallback**: `az network private-dns zone show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| zoneName | `name` | Top-level `name` field | diff --git a/plugin/skills/azure-iac-generator/references/resource-configs-platform.md b/plugin/skills/azure-iac-generator/references/resource-configs-platform.md new file mode 100644 index 000000000..2dc679af9 --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/resource-configs-platform.md @@ -0,0 +1,75 @@ +# Platform Resource Property Mapping + +Per-resource property retrieval for platform, security, and integration resource types. + +**Fallback for all**: `az resource show --ids -o json` + +--- + +### Microsoft.KeyVault/vaults + +**MCP Tool**: `mcp_azure_mcp_keyvault` +**Fallback**: `az keyvault show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `properties.sku.name` | | +| enableRbacAuthorization | `properties.enableRbacAuthorization` | | +| enableSoftDelete | `properties.enableSoftDelete` | | +| softDeleteRetentionInDays | `properties.softDeleteRetentionInDays` | | +| publicNetworkAccess | `properties.publicNetworkAccess` | | +| enablePurgeProtection | `properties.enablePurgeProtection` | | + +### Microsoft.Insights/components + +**MCP Tool**: `mcp_azure_mcp_applicationinsights` +**Fallback**: `az monitor app-insights component show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| applicationType | `properties.Application_Type` | | +| retentionInDays | `properties.RetentionInDays` | | +| ingestionMode | `properties.IngestionMode` | | + +### Microsoft.OperationalInsights/workspaces + +**MCP Tool**: `mcp_azure_mcp_monitor` +**Fallback**: `az monitor log-analytics workspace show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `properties.sku.name` | | +| retentionInDays | `properties.retentionInDays` | | + +### Microsoft.ServiceBus/namespaces + +**MCP Tool**: `mcp_azure_mcp_servicebus` +**Fallback**: `az servicebus namespace show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `sku.name` | | +| capacity | `sku.capacity` | | +| zoneRedundant | `properties.zoneRedundant` | | + +### Microsoft.EventHub/namespaces + +**MCP Tool**: `mcp_azure_mcp_eventhubs` +**Fallback**: `az eventhubs namespace show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `sku.name` | | +| capacity | `sku.capacity` | | +| isAutoInflateEnabled | `properties.isAutoInflateEnabled` | | + +### Microsoft.ApiManagement/service + +**Fallback**: `az apim show --ids -o json` + +| Property | ARM JSON Path | Notes | +|----------|---------------|-------| +| skuName | `sku.name` | | +| capacity | `sku.capacity` | | +| publisherEmail | `properties.publisherEmail` | | +| publisherName | `properties.publisherName` | | diff --git a/plugin/skills/azure-iac-generator/references/version-currency.md b/plugin/skills/azure-iac-generator/references/version-currency.md new file mode 100644 index 000000000..707ab0c6c --- /dev/null +++ b/plugin/skills/azure-iac-generator/references/version-currency.md @@ -0,0 +1,50 @@ +# Version Currency Rules + +version-selection guidance for Azure IaC skills. Keep local copies aligned when this rule changes. + + +All generated code must use current, supported versions. Stale defaults cause security vulnerabilities, missing features, and end-of-life surprises. + +--- + +## Verification Checklist + +| Category | Verification Method | Rule | +|---|---|---| +| **Bicep API versions** | Use Bicep MCP `get_az_resource_type_schema` | Latest **stable** (non-preview). Only use preview if no stable exists. | +| **Runtime stacks** | Extract from Azure, then verify against `az webapp list-runtimes` or current docs | Apply the hybrid rule below: keep the exact extracted runtime when it is still supported; if it is EOL or unsupported, default to the current supported version and document the extracted value, recommended upgrade, and EOL date in comments. Flag versions within 6 months of EOL. | +| **Kubernetes versions** | Azure supported versions list | Currently supported AKS version | +| **OS images** | Docs / `az vm image list` | Latest generation + LTS releases (e.g., latest Ubuntu LTS, latest Windows Server) | +| **SDK/tool versions** | Docs | Azure Functions host, runtime extensions — current versions | + +## Bicepparam Version Comments + +Always note the version choice in `.bicepparam` comments. Treat the example below as a template only: look up the current supported runtime and support window at generation time rather than copying literal versions or dates from this document. +```bicep +// Runtime stack for the App Service. +// Replace the placeholders below with the current supported values at generation time. +// → (, supported until ) +// → (, supported until ) +// → (, supported until ) +// → (, supported until ) +param appServiceRuntimeStack = '' +``` + +## Hybrid Runtime Rule + +When extracting runtime values from Azure: + +- **Still supported runtime**: use the exact Azure value as the default in generated code. +- **EOL or unsupported runtime**: use the current supported version as the default, and add a comment that includes: + - the extracted Azure value, + - the recommended upgrade target, + - the EOL date. + +This is the single source of truth for runtime defaults in generated Bicep and `.bicepparam` files. + +## Handling Outdated Values from Azure + +When extracting from Azure and the current value uses an outdated version: +- Use the **current supported version** as the default +- Add a comment noting the extracted version, the recommended upgrade, and the EOL date +- Never silently keep an end-of-life runtime as the generated default diff --git a/tests/azure-iac-generator/__snapshots__/triggers.test.ts.snap b/tests/azure-iac-generator/__snapshots__/triggers.test.ts.snap new file mode 100644 index 000000000..2929dc737 --- /dev/null +++ b/tests/azure-iac-generator/__snapshots__/triggers.test.ts.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`azure-iac-generator - Trigger Tests Trigger Keywords Snapshot skill description triggers match snapshot 1`] = ` +{ + "description": "Generate deployment-ready Bicep templates from existing Azure environments or Draw.io architecture diagrams. Reverse-engineer live infrastructure into Infrastructure as Code. WHEN: generate bicep, azure to bicep, generate bicep from azure, bicep from diagram, diagram to bicep, create bicep templates from resources, export infrastructure as code, generate infrastructure code, reverse engineer azure, generate iac from azure. DO NOT USE FOR: creating new applications, deploying existing resources, comparing environments.", + "extractedKeywords": [ + "app service", + "applications", + "architecture", + "authentication", + "azure", + "bicep", + "cli", + "code", + "comparing", + "container", + "cosmos", + "create", + "creating", + "deploy", + "deploying", + "deployment-ready", + "diagram", + "diagrams", + "draw", + "engineer", + "environments", + "existing", + "export", + "from", + "function", + "generate", + "generator", + "iac", + "identity", + "infrastructure", + "into", + "key vault", + "keyvault", + "live", + "mcp", + "monitor", + "networking", + "redis", + "resources", + "reverse", + "reverse-engineer", + "sql", + "storage", + "templates", + "when", + ], + "name": "azure-iac-generator", +} +`; + +exports[`azure-iac-generator - Trigger Tests Trigger Keywords Snapshot skill keywords match snapshot 1`] = ` +[ + "app service", + "applications", + "architecture", + "authentication", + "azure", + "bicep", + "cli", + "code", + "comparing", + "container", + "cosmos", + "create", + "creating", + "deploy", + "deploying", + "deployment-ready", + "diagram", + "diagrams", + "draw", + "engineer", + "environments", + "existing", + "export", + "from", + "function", + "generate", + "generator", + "iac", + "identity", + "infrastructure", + "into", + "key vault", + "keyvault", + "live", + "mcp", + "monitor", + "networking", + "redis", + "resources", + "reverse", + "reverse-engineer", + "sql", + "storage", + "templates", + "when", +] +`; diff --git a/tests/azure-iac-generator/integration.test.ts b/tests/azure-iac-generator/integration.test.ts new file mode 100644 index 000000000..4db8924db --- /dev/null +++ b/tests/azure-iac-generator/integration.test.ts @@ -0,0 +1,285 @@ +/** + * Integration Tests for azure-iac-generator + * + * Tests skill behavior with a real Copilot agent session. + * Runs prompts multiple times to measure skill invocation rate, + * and verifies end-to-end output structure for both workflows. + * + * Prerequisites: + * 1. npm install -g @github/copilot-cli + * 2. Run `copilot` and authenticate + * 3. az login (for live Azure resource access tests) + */ + +import * as fs from "node:fs"; +import * as path from "node:path"; +import { + useAgentRunner, + shouldSkipIntegrationTests, + getIntegrationSkipReason, + doesAssistantMessageIncludeKeyword, +} from "../utils/agent-runner"; +import { + softCheckSkill, + isSkillInvoked, + shouldEarlyTerminateForSkillInvocation, + doesWorkspaceFileIncludePattern, + expectFiles, + withTestResult, +} from "../utils/evaluate"; + +const SKILL_NAME = "azure-iac-generator"; +const RUNS_PER_PROMPT = 5; +const invocationRateThreshold = 0.8; + +// Check if integration tests should be skipped at module level +const skipTests = shouldSkipIntegrationTests(); +const skipReason = getIntegrationSkipReason(); + +if (skipTests && skipReason) { + console.log(`⏭️ Skipping integration tests: ${skipReason}`); +} + +const describeIntegration = skipTests ? describe.skip : describe; +const iacTestTimeoutMs = 1800000; + +// Minimal Draw.io XML representing an App Service + Storage Account architecture +const SAMPLE_DRAWIO_XML = ` + + + + + + + + +`; + +describeIntegration(`${SKILL_NAME} - Integration Tests`, () => { + const agent = useAgentRunner(); + + // ────────────────────────────────────────────────────────────────── + // Skill invocation rate tests + // ────────────────────────────────────────────────────────────────── + describe("skill-invocation", () => { + test( + "invokes skill for live-Azure reverse-engineering prompt", + () => + withTestResult(async ({ setSkillInvocationRate }) => { + let invocationCount = 0; + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + const agentMetadata = await agent.run({ + prompt: + "Generate Bicep templates by reverse engineering my Azure resource group named my-rg in subscription 00000000-0000-0000-0000-000000000000", + shouldEarlyTerminate: (metadata) => + shouldEarlyTerminateForSkillInvocation(metadata, SKILL_NAME), + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + if (isSkillInvoked(agentMetadata, SKILL_NAME)) { + invocationCount += 1; + } + } + const rate = invocationCount / RUNS_PER_PROMPT; + setSkillInvocationRate(rate); + expect(rate).toBeGreaterThanOrEqual(invocationRateThreshold); + }), + iacTestTimeoutMs + ); + + test( + "invokes skill for export infrastructure as code prompt", + () => + withTestResult(async ({ setSkillInvocationRate }) => { + let invocationCount = 0; + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + const agentMetadata = await agent.run({ + prompt: + "Export my Azure infrastructure as code using Bicep for my resource group", + shouldEarlyTerminate: (metadata) => + shouldEarlyTerminateForSkillInvocation(metadata, SKILL_NAME), + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + if (isSkillInvoked(agentMetadata, SKILL_NAME)) { + invocationCount += 1; + } + } + const rate = invocationCount / RUNS_PER_PROMPT; + setSkillInvocationRate(rate); + expect(rate).toBeGreaterThanOrEqual(invocationRateThreshold); + }), + iacTestTimeoutMs + ); + + test( + "invokes skill for diagram-to-Bicep prompt", + () => + withTestResult(async ({ setSkillInvocationRate }) => { + let invocationCount = 0; + for (let i = 0; i < RUNS_PER_PROMPT; i++) { + const agentMetadata = await agent.run({ + setup: async (workspace: string) => { + fs.writeFileSync( + path.join(workspace, "architecture.drawio"), + SAMPLE_DRAWIO_XML + ); + }, + prompt: + "Generate Bicep templates from my Draw.io architecture diagram at architecture.drawio", + shouldEarlyTerminate: (metadata) => + shouldEarlyTerminateForSkillInvocation(metadata, SKILL_NAME), + }); + + softCheckSkill(agentMetadata, SKILL_NAME); + if (isSkillInvoked(agentMetadata, SKILL_NAME)) { + invocationCount += 1; + } + } + const rate = invocationCount / RUNS_PER_PROMPT; + setSkillInvocationRate(rate); + expect(rate).toBeGreaterThanOrEqual(invocationRateThreshold); + }), + iacTestTimeoutMs + ); + }); + + // ────────────────────────────────────────────────────────────────── + // Response quality tests — diagram-to-Bicep (no Azure auth needed) + // ────────────────────────────────────────────────────────────────── + describe("response-quality", () => { + test( + "generates main.bicep from a Draw.io diagram", + () => + withTestResult(async () => { + let workspacePath: string | undefined; + + await agent.run({ + setup: async (workspace: string) => { + workspacePath = workspace; + fs.writeFileSync( + path.join(workspace, "architecture.drawio"), + SAMPLE_DRAWIO_XML + ); + }, + prompt: + "Generate Bicep templates from my architecture diagram at architecture.drawio. Output files into a folder named my-app.", + nonInteractive: true, + followUp: ["Continue until all files are written."], + }); + + expect(workspacePath).toBeDefined(); + // Assert main.bicep is written under the requested my-app/ folder, + // not at the workspace root (which the skill explicitly forbids). + expectFiles( + workspacePath!, + [/\/my-app\/main\.bicep$/], + [/^(?!.*\/my-app\/).*\/main\.bicep$/] + ); + const hasMainBicep = doesWorkspaceFileIncludePattern( + workspacePath!, + /targetScope|module\s+\w+|param\s+\w+/, + /main\.bicep$/ + ); + expect(hasMainBicep).toBe(true); + }), + iacTestTimeoutMs + ); + + test( + "places resources in modules/, not inline in main.bicep", + () => + withTestResult(async () => { + let workspacePath: string | undefined; + + await agent.run({ + setup: async (workspace: string) => { + workspacePath = workspace; + fs.writeFileSync( + path.join(workspace, "architecture.drawio"), + SAMPLE_DRAWIO_XML + ); + }, + prompt: + "Generate modular Bicep from my architecture.drawio diagram. Output to folder named my-app.", + nonInteractive: true, + followUp: ["Continue until all files are written."], + }); + + expect(workspacePath).toBeDefined(); + // Assert files land under my-app/ with a modules/ subfolder, and that + // no .bicep files (other than the diagram source) are written at the + // workspace root. + expectFiles( + workspacePath!, + [/\/my-app\/main\.bicep$/, /\/my-app\/modules\/.+\.bicep$/], + [/^(?!.*\/my-app\/).*\/main\.bicep$/] + ); + // main.bicep should reference modules, not declare resources directly + const mainBicepUsesModules = doesWorkspaceFileIncludePattern( + workspacePath!, + /module\s+\w+/, + /main\.bicep$/ + ); + expect(mainBicepUsesModules).toBe(true); + }), + iacTestTimeoutMs + ); + + test( + "response mentions authentication requirement for live Azure workflow", + () => + withTestResult(async () => { + const agentMetadata = await agent.run({ + prompt: + "Generate Bicep from my Azure resource group prod-rg in subscription 00000000-0000-0000-0000-000000000000", + }); + + const mentionsAuth = + doesAssistantMessageIncludeKeyword(agentMetadata, "az login") || + doesAssistantMessageIncludeKeyword(agentMetadata, "authenticated") || + doesAssistantMessageIncludeKeyword(agentMetadata, "authentication"); + expect(mentionsAuth).toBe(true); + }), + iacTestTimeoutMs + ); + + test( + "generates a .bicepparam file alongside main.bicep", + () => + withTestResult(async () => { + let workspacePath: string | undefined; + + await agent.run({ + setup: async (workspace: string) => { + workspacePath = workspace; + fs.writeFileSync( + path.join(workspace, "architecture.drawio"), + SAMPLE_DRAWIO_XML + ); + }, + prompt: + "Generate Bicep from architecture.drawio. Output to folder my-app.", + nonInteractive: true, + followUp: ["Continue until all files are written."], + }); + + expect(workspacePath).toBeDefined(); + // Assert the .bicepparam file is written under my-app/, not at the + // workspace root. + expectFiles( + workspacePath!, + [/\/my-app\/.+\.bicepparam$/], + [/^(?!.*\/my-app\/).*\.bicepparam$/] + ); + const hasBicepparam = doesWorkspaceFileIncludePattern( + workspacePath!, + /using\s+['"].*main\.bicep['"]/, + /\.bicepparam$/ + ); + expect(hasBicepparam).toBe(true); + }), + iacTestTimeoutMs + ); + }); +}); diff --git a/tests/azure-iac-generator/triggers.test.ts b/tests/azure-iac-generator/triggers.test.ts new file mode 100644 index 000000000..693d85fe8 --- /dev/null +++ b/tests/azure-iac-generator/triggers.test.ts @@ -0,0 +1,125 @@ +/** + * Trigger Tests for azure-iac-generator + * + * Tests that verify the skill triggers on appropriate prompts + * and does NOT trigger on unrelated prompts. + * + * Uses snapshot testing + parameterized tests for comprehensive coverage. + */ + +import { TriggerMatcher } from "../utils/trigger-matcher"; +import { loadSkill, LoadedSkill } from "../utils/skill-loader"; + +const SKILL_NAME = "azure-iac-generator"; + +describe(`${SKILL_NAME} - Trigger Tests`, () => { + let triggerMatcher: TriggerMatcher; + let skill: LoadedSkill; + + beforeAll(async () => { + skill = await loadSkill(SKILL_NAME); + triggerMatcher = new TriggerMatcher(skill); + }); + + describe("Should Trigger", () => { + // Prompts that SHOULD trigger this skill + const shouldTriggerPrompts: string[] = [ + // Live Azure reverse-engineering + "Generate Bicep from my Azure resource group", + "Generate bicep templates for my Azure resources", + "Reverse engineer my Azure infrastructure into Bicep", + "Export my Azure resource group as infrastructure as code", + "Generate IaC from my Azure subscription", + "Create Bicep templates from my existing Azure resources", + "Azure to Bicep conversion for my resource group", + "Convert my live Azure environment to Bicep", + "Export infrastructure as code from Azure", + "Reverse engineer my Azure resources into IaC", + + // Diagram-to-Bicep + "Generate Bicep from my Draw.io diagram", + "Create Bicep from my architecture diagram", + "Convert my .drawio file to Bicep templates", + "Bicep from my diagram", + "Diagram to Bicep for my Azure architecture", + ]; + + test.each(shouldTriggerPrompts)( + 'triggers on: "%s"', + (prompt) => { + const result = triggerMatcher.shouldTrigger(prompt); + expect(result.triggered).toBe(true); + } + ); + }); + + describe("Should NOT Trigger", () => { + // Prompts that should NOT trigger this skill + const shouldNotTriggerPrompts: string[] = [ + // Other cloud providers + "Describe my AWS S3 buckets", + "Check my AWS Lambda functions", + "Show my DigitalOcean droplets", + + // Cost optimization (use azure-cost) + "Optimize my cloud spending", + "Reduce my monthly bill", + + // Deployment (use azure-deploy) + "Deploy this app to production", + "Provision new infrastructure", + "Create a new web app", + + // Code generation + "Write a Python sorting algorithm", + "Fix the null pointer exception", + "Debug this JavaScript error", + + // Other Azure operations + "Generate a template", + "Configure RBAC permissions", + "Compare my dev and prod environments" + ]; + + test.each(shouldNotTriggerPrompts)( + 'does not trigger on: "%s"', + (prompt) => { + const result = triggerMatcher.shouldTrigger(prompt); + expect(result.triggered).toBe(false); + } + ); + }); + + describe("Trigger Keywords Snapshot", () => { + test("skill keywords match snapshot", () => { + expect(triggerMatcher.getKeywords()).toMatchSnapshot(); + }); + + test("skill description triggers match snapshot", () => { + expect({ + name: skill.metadata.name, + description: skill.metadata.description, + extractedKeywords: triggerMatcher.getKeywords() + }).toMatchSnapshot(); + }); + }); + + describe("Edge Cases", () => { + test("handles empty prompt", () => { + const result = triggerMatcher.shouldTrigger(""); + expect(result.triggered).toBe(false); + }); + + test("handles very long prompt", () => { + const longPrompt = "Generate Bicep from Azure ".repeat(1000); + const result = triggerMatcher.shouldTrigger(longPrompt); + expect(typeof result.triggered).toBe("boolean"); + }); + + test("is case insensitive", () => { + const result1 = triggerMatcher.shouldTrigger("generate bicep from azure"); + const result2 = triggerMatcher.shouldTrigger("GENERATE BICEP FROM AZURE"); + expect(result1.triggered).toBe(result2.triggered); + }); + }); +}); diff --git a/tests/azure-iac-generator/unit.test.ts b/tests/azure-iac-generator/unit.test.ts new file mode 100644 index 000000000..984572e81 --- /dev/null +++ b/tests/azure-iac-generator/unit.test.ts @@ -0,0 +1,177 @@ +/** + * Unit Tests for azure-iac-generator + * + * Test isolated skill logic and validation rules. + */ + +import { readFileSync } from "node:fs"; +import { loadSkill, LoadedSkill } from "../utils/skill-loader"; + +const SKILL_NAME = "azure-iac-generator"; + +describe(`${SKILL_NAME} - Unit Tests`, () => { + let skill: LoadedSkill; + + beforeAll(async () => { + skill = await loadSkill(SKILL_NAME); + }); + + describe("Skill Metadata", () => { + test("has valid SKILL.md with required fields", () => { + expect(skill.metadata).toBeDefined(); + expect(skill.metadata.name).toBe(SKILL_NAME); + expect(skill.metadata.description).toBeDefined(); + expect(skill.metadata.description.length).toBeGreaterThan(10); + }); + + test("description meets Medium-High compliance length", () => { + // Descriptions should be 150-1024 chars for Medium-High compliance + expect(skill.metadata.description.length).toBeGreaterThan(150); + expect(skill.metadata.description.length).toBeLessThanOrEqual(1024); + }); + + test("description contains WHEN trigger phrases", () => { + const description = skill.metadata.description; + expect(description).toContain("WHEN:"); + }); + + test("description contains DO NOT USE FOR boundary phrases", () => { + const description = skill.metadata.description; + expect(description).toContain("DO NOT USE FOR:"); + }); + }); + + describe("Frontmatter Formatting", () => { + test("frontmatter has no tabs", () => { + const raw = readFileSync(skill.filePath, "utf-8"); + const frontmatter = raw.split("---")[1]; + expect(frontmatter).not.toMatch(/\t/); + }); + + test("frontmatter keys are only supported attributes", () => { + const raw = readFileSync(skill.filePath, "utf-8"); + const frontmatter = raw.split("---")[1]; + const supported = [ + "name", "description", "compatibility", "license", "metadata", + "argument-hint", "disable-model-invocation", "user-invokable" + ]; + const keys = frontmatter.split("\n") + .filter((l: string) => /^[a-z][\w-]*\s*:/.test(l)) + .map((l: string) => l.split(":")[0].trim()); + for (const key of keys) { + expect(supported).toContain(key); + } + }); + }); + + describe("Skill Content", () => { + test("has substantive content", () => { + expect(skill.content).toBeDefined(); + expect(skill.content.length).toBeGreaterThan(100); + }); + + test("contains Prerequisites section", () => { + expect(skill.content).toContain("## Prerequisites"); + }); + + test("contains Quick Reference section with MCP tools", () => { + expect(skill.content).toContain("## Quick Reference"); + expect(skill.content).toContain("MCP tools"); + }); + + test("contains Error Handling section", () => { + expect(skill.content).toContain("## Error Handling"); + }); + }); + + describe("Output Structure", () => { + test("documents the expected output folder layout", () => { + expect(skill.content).toContain("main.bicep"); + expect(skill.content).toContain("main.bicepparam"); + expect(skill.content).toContain("modules/"); + expect(skill.content).toContain("dependencies/"); + expect(skill.content).toContain("README.md"); + }); + + test("lists all expected module files", () => { + expect(skill.content).toContain("networking.bicep"); + expect(skill.content).toContain("compute.bicep"); + expect(skill.content).toContain("data.bicep"); + expect(skill.content).toContain("identity.bicep"); + expect(skill.content).toContain("monitoring.bicep"); + }); + + test("warns against flat single-file output", () => { + // The skill must explicitly forbid a single flat main.bicep + expect(skill.content).toContain("NEVER generate a single flat"); + }); + }); + + describe("Routing", () => { + test("contains a routing section", () => { + expect(skill.content).toContain("## Routing"); + }); + + test("routes live-Azure requests to azure-to-bicep-workflow", () => { + expect(skill.content).toContain("azure-to-bicep-workflow.md"); + }); + + test("routes diagram requests to diagram-to-bicep-workflow", () => { + expect(skill.content).toContain("diagram-to-bicep-workflow.md"); + }); + + test("identifies live-Azure trigger keywords in routing", () => { + const content = skill.content.toLowerCase(); + // The routing section should mention resource group or subscription + expect(content).toMatch(/resource group|subscription/); + }); + + test("identifies diagram trigger keywords in routing", () => { + const content = skill.content.toLowerCase(); + expect(content).toMatch(/draw\.io|\.drawio|diagram/); + }); + }); + + describe("Mandatory References", () => { + test("references bicep-best-practices.md", () => { + expect(skill.content).toContain("bicep-best-practices.md"); + }); + + test("references azure-resource-configs.md", () => { + expect(skill.content).toContain("azure-resource-configs.md"); + }); + + test("references azure-deployment-verification.md", () => { + expect(skill.content).toContain("azure-deployment-verification.md"); + }); + + test("references version-currency.md", () => { + expect(skill.content).toContain("version-currency.md"); + }); + + test("references bicep-parsing.md", () => { + expect(skill.content).toContain("bicep-parsing.md"); + }); + }); + + describe("MCP Tool References", () => { + test("mentions Azure MCP tools for resource discovery", () => { + expect(skill.content).toContain("group_resource_list"); + }); + + test("mentions Bicep MCP tools for schema and best practices", () => { + expect(skill.content).toContain("get_bicep_best_practices"); + expect(skill.content).toContain("get_az_resource_type_schema"); + }); + }); + + describe("Security Requirements", () => { + test("addresses secrets handling with @secure()", () => { + expect(skill.content).toContain("@secure()"); + }); + + test("references readEnvironmentVariable for secrets in params", () => { + expect(skill.content).toContain("readEnvironmentVariable()"); + }); + }); +}); diff --git a/tests/skills.json b/tests/skills.json index 09fd5058f..fd504db44 100644 --- a/tests/skills.json +++ b/tests/skills.json @@ -12,6 +12,7 @@ "azure-diagnostics", "azure-enterprise-infra-planner", "azure-hosted-copilot-sdk", + "azure-iac-generator", "azure-kubernetes", "azure-kusto", "azure-messaging", @@ -29,6 +30,6 @@ "integrationTestSchedule": { "0 5 * * 2-6": "microsoft-foundry", "0 8 * * 2-6": "azure-deploy", - "0 12 * * 2-6": "airunway-aks-setup,appinsights-instrumentation,azure-ai,azure-aigateway,azure-cloud-migrate,azure-compliance,azure-compute,azure-cost,azure-diagnostics,azure-enterprise-infra-planner,azure-hosted-copilot-sdk,azure-kubernetes,azure-kusto,azure-messaging,azure-prepare,azure-quotas,azure-rbac,azure-resource-lookup,azure-resource-visualizer,azure-storage,azure-upgrade,azure-validate,entra-app-registration" + "0 12 * * 2-6": "airunway-aks-setup,appinsights-instrumentation,azure-ai,azure-aigateway,azure-cloud-migrate,azure-compliance,azure-compute,azure-cost,azure-diagnostics,azure-enterprise-infra-planner,azure-hosted-copilot-sdk,azure-iac-generator,azure-kubernetes,azure-kusto,azure-messaging,azure-prepare,azure-quotas,azure-rbac,azure-resource-lookup,azure-resource-visualizer,azure-storage,azure-upgrade,azure-validate,entra-app-registration" } }