From aaa6fe24db4e40c7e140780df1b0c6489dc19c6d Mon Sep 17 00:00:00 2001 From: Madhura Bharadwaj Date: Tue, 10 Mar 2026 12:29:29 -0500 Subject: [PATCH 1/7] Add azure-upgrade skill for Azure Functions Consumption to Flex Consumption migration --- plugin/skills/azure-upgrade/SKILL.md | 72 +++ .../azure-upgrade/references/global-rules.md | 47 ++ .../services/functions/assessment.md | 119 +++++ .../services/functions/automation.md | 421 ++++++++++++++++++ .../services/functions/consumption-to-flex.md | 227 ++++++++++ .../references/workflow-details.md | 59 +++ 6 files changed, 945 insertions(+) create mode 100644 plugin/skills/azure-upgrade/SKILL.md create mode 100644 plugin/skills/azure-upgrade/references/global-rules.md create mode 100644 plugin/skills/azure-upgrade/references/services/functions/assessment.md create mode 100644 plugin/skills/azure-upgrade/references/services/functions/automation.md create mode 100644 plugin/skills/azure-upgrade/references/services/functions/consumption-to-flex.md create mode 100644 plugin/skills/azure-upgrade/references/workflow-details.md diff --git a/plugin/skills/azure-upgrade/SKILL.md b/plugin/skills/azure-upgrade/SKILL.md new file mode 100644 index 00000000..53399ba4 --- /dev/null +++ b/plugin/skills/azure-upgrade/SKILL.md @@ -0,0 +1,72 @@ +--- +name: azure-upgrade +description: "Assess and upgrade Azure workloads from one Azure service, hosting plan, or SKU to another within Azure. Covers plan/tier upgrades (e.g. Consumption to Flex Consumption), cross-service migrations (e.g. App Service to Container Apps), and SKU changes. Generates assessment reports and automates Azure CLI steps. Both source and destination are within Azure. WHEN: upgrade Consumption to Flex Consumption, upgrade Azure Functions plan, migrate hosting plan, upgrade Functions SKU, move to Flex Consumption, upgrade Azure service tier, Azure-to-Azure upgrade, change hosting plan, upgrade function app plan, migrate App Service to Container Apps, move between Azure services." +--- + +# Azure Upgrade + +> This skill handles **assessment and automated upgrades** of existing Azure workloads from one Azure service, hosting plan, or SKU to another — all within Azure. This includes plan/tier upgrades (e.g. Consumption → Flex Consumption), cross-service migrations (e.g. App Service → Container Apps), and SKU changes. This is NOT for cross-cloud migration — use `azure-cloud-migrate` for that. + +## Triggers + +| User Intent | Example Prompts | +|-------------|-----------------| +| Upgrade Azure Functions plan | "Upgrade my function app from Consumption to Flex Consumption" | +| Change hosting tier | "Move my function app to a better plan" | +| Assess upgrade readiness | "Is my function app ready for Flex Consumption?" | +| Automate plan migration | "Automate the steps to upgrade my Functions plan" | + +## Rules + +1. Follow phases sequentially — do not skip +2. Generate an assessment before any upgrade operations +3. Load the scenario reference and follow its rules +4. Use `mcp_azure_mcp_get_bestpractices` and `mcp_azure_mcp_documentation` MCP tools +5. Destructive actions require `ask_user` — [global-rules](references/global-rules.md) +6. Always confirm the target plan/SKU with the user before proceeding +7. Never delete or stop the original app without explicit user confirmation +8. All automation scripts must be idempotent and resumable + +## Upgrade Scenarios + +| Source | Target | Reference | +|--------|--------|-----------| +| Azure Functions Consumption Plan | Azure Functions Flex Consumption Plan | [consumption-to-flex.md](references/services/functions/consumption-to-flex.md) | + +> No matching scenario? Use `mcp_azure_mcp_documentation` and `mcp_azure_mcp_get_bestpractices` tools to research the upgrade path. + +## MCP Tools + +| Tool | Purpose | +|------|---------| +| `mcp_azure_mcp_get_bestpractices` | Get Azure best practices for the target service | +| `mcp_azure_mcp_documentation` | Look up Azure documentation for upgrade scenarios | +| `mcp_azure_mcp_appservice` | Query App Service and Functions plan details | +| `mcp_azure_mcp_applicationinsights` | Verify monitoring configuration | + +## Steps + +1. **Identify** — Determine the source and target Azure plans/SKUs. Ask user to confirm. +2. **Assess** — Analyze existing app for upgrade readiness → load scenario reference (e.g., [consumption-to-flex.md](references/services/functions/consumption-to-flex.md)) +3. **Pre-migrate** — Collect settings, identities, configs from the existing app +4. **Upgrade** — Execute the automated upgrade steps (create new resources, migrate settings, deploy code) +5. **Validate** — Hit the function app default URL to confirm the app is reachable, then verify endpoints and monitoring +6. **Ask User** — "Upgrade complete. Would you like to verify performance, clean up the old app, or update your IaC?" +7. **Hand off** to `azure-validate` for deep validation or `azure-deploy` for CI/CD setup + +Track progress in `upgrade-status.md` inside the workspace root. + +## References + +- [Global Rules](references/global-rules.md) +- [Workflow Details](references/workflow-details.md) +- **Functions** + - [Consumption to Flex Consumption](references/services/functions/consumption-to-flex.md) + - [Assessment](references/services/functions/assessment.md) + - [Automation Scripts](references/services/functions/automation.md) + +## Next + +After upgrade is validated, hand off to: +- `azure-validate` — for thorough post-upgrade validation +- `azure-deploy` — if the user wants to set up CI/CD for the new app diff --git a/plugin/skills/azure-upgrade/references/global-rules.md b/plugin/skills/azure-upgrade/references/global-rules.md new file mode 100644 index 00000000..a386be52 --- /dev/null +++ b/plugin/skills/azure-upgrade/references/global-rules.md @@ -0,0 +1,47 @@ +# Global Rules + +These rules apply to ALL phases of the azure-upgrade skill. + +## Destructive Action Policy + +⛔ **NEVER** perform destructive actions without explicit user confirmation via `ask_user`: +- Deleting apps, services, or resource groups +- Stopping or disabling the original app/service +- Overwriting app settings or configuration in the new app +- Removing the original hosting plan or service tier +- Modifying DNS or custom domain bindings + +## User Confirmation Required + +Always use `ask_user` before: +- Selecting target Azure subscription +- Selecting target Azure region/location +- Creating new Azure resources +- Stopping or deleting the original app/service +- Modifying custom domains or network restrictions +- Any irreversible configuration change + +## Best Practices + +- Always use `mcp_azure_mcp_get_bestpractices` tool before generating upgrade commands +- Prefer managed identity over connection strings — upgrades are a good time to improve security +- **Always target the latest supported runtime version** — check Azure docs for the newest GA version +- Keep the original app/service running until the upgraded one is fully validated +- Use the same resource group for the new resource to maintain access to existing dependencies +- Follow Azure naming conventions for all new resources + +## Identity-First Authentication (Zero API Keys) + +> Enterprise subscriptions commonly enforce policies that block local auth. Always design for identity-based access from the start. + +- Prefer managed identity connections over connection strings/keys +- Use `DefaultAzureCredential` in code — works locally and in Azure +- When using User Assigned Managed Identity, always pass `managedIdentityClientId` explicitly +- See service-specific identity configuration in the scenario reference files + +## Rollback Policy + +- Always document rollback steps before executing upgrade +- Keep the original app intact and running until upgrade is validated +- If upgrade fails, guide the user to restart the original app +- Never delete the original app automatically — always require `ask_user` diff --git a/plugin/skills/azure-upgrade/references/services/functions/assessment.md b/plugin/skills/azure-upgrade/references/services/functions/assessment.md new file mode 100644 index 00000000..e04ec3e6 --- /dev/null +++ b/plugin/skills/azure-upgrade/references/services/functions/assessment.md @@ -0,0 +1,119 @@ +# Assessment: Functions Plan Upgrade + +Generate an upgrade assessment report before any changes to Azure resources. + +## Prerequisites + +- User has an existing Azure Functions app on a Consumption or other plan +- User has Azure CLI v2.77.0+ installed +- User has Owner or Contributor role in the target resource group +- The `resource-graph` extension is installed (`az extension add --name resource-graph`) + +## Assessment Steps + +1. **Identify Source App** — Confirm the function app name, resource group, region, and current hosting plan +2. **Check Region Compatibility** — Verify the target plan is available in the app's region +3. **Verify Language Stack** — Confirm the app's runtime is supported on the target plan +4. **Verify Stack Version** — Confirm the runtime version is supported on the target plan in the region +5. **Check Deployment Slots** — Determine if slots are in use (Flex Consumption doesn't support slots) +6. **Check Certificates** — Determine if TLS/SSL certificates are in use (not yet supported in Flex Consumption) +7. **Check Blob Triggers** — Verify blob triggers use EventGrid source (container polling not supported in Flex Consumption) +8. **Assess Dependencies** — Review upstream and downstream service dependencies and plan mitigation strategies +9. **Generate Report** — Create `upgrade-assessment-report.md` + +## Assessment Report Format + +> ⚠️ **MANDATORY**: Use these exact section headings in every assessment report. Do NOT rename, reorder, or omit sections. + +The report MUST be saved as `upgrade-assessment-report.md` in the workspace root. + +```markdown +# Upgrade Assessment Report + +## 1. Executive Summary + +| Property | Value | +|----------|-------| +| **App Name** | | +| **Resource Group** | | +| **Current Plan** | | +| **Target Plan** | | +| **Region** | | +| **Runtime** | | +| **OS** | | +| **Upgrade Readiness** | | +| **Assessment Date** | | + +## 2. Compatibility Checks + +| Check | Status | Details | +|-------|--------|---------| +| Region supported | ✅ / ❌ | | +| Language stack supported | ✅ / ❌ | | +| Stack version supported | ✅ / ❌ | | +| No deployment slots | ✅ / ⚠️ | | +| No TLS/SSL certificates | ✅ / ⚠️ | | +| Blob triggers use EventGrid | ✅ / ⚠️ / N/A | | +| .NET isolated (not in-process) | ✅ / ❌ / N/A | | + +## 3. App Settings Inventory + +| Setting | Value | Migrate? | Notes | +|---------|-------|----------|-------| +| | | Yes / No / Convert | | + +## 4. Managed Identities + +| Type | Principal ID | Roles | Action | +|------|-------------|-------|--------| +| System-assigned | | | Recreate in new app | +| User-assigned | | | Reassign to new app | + +## 5. Application Configurations + +| Configuration | Current Value | Migrate? | Notes | +|---------------|---------------|----------|-------| +| CORS settings | | | | +| Custom domains | | | | +| HTTP version | | | | +| HTTPS only | | | | +| TLS version | | | | +| Client certificates | | | | +| Access restrictions | | | | +| Built-in auth | | | | + +## 6. Trigger & Binding Analysis + +| Function | Trigger Type | Source | Migration Risk | Mitigation | +|----------|-------------|--------|----------------|------------| +| | | | Low / Medium / High | | + +## 7. Dependent Services + +| Service | Dependency Type | Migration Risk | Mitigation Strategy | +|---------|----------------|----------------|---------------------| +| | Upstream / Downstream | | | + +## 8. Blockers & Warnings + +### Blockers (must fix before upgrade) +- [ ] + +### Warnings (should address but not blocking) +- [ ] + +## 9. Recommendations + +1. **Plan**: +2. **Auth**: +3. **Monitoring**: +4. **Scaling**: + +## 10. Next Steps + +- [ ] Review and approve this assessment +- [ ] Address any blockers listed above +- [ ] Proceed to automated upgrade (Phase 3-4) +``` + +> 💡 **Tip:** Use `mcp_azure_mcp_get_bestpractices` to get the latest recommendations for the target hosting plan. diff --git a/plugin/skills/azure-upgrade/references/services/functions/automation.md b/plugin/skills/azure-upgrade/references/services/functions/automation.md new file mode 100644 index 00000000..ee8f2cff --- /dev/null +++ b/plugin/skills/azure-upgrade/references/services/functions/automation.md @@ -0,0 +1,421 @@ +# Automation: Consumption to Flex Consumption Upgrade + +> These are the Azure CLI scripts to automate the upgrade from Linux Consumption plan to Flex Consumption plan. +> All scripts use `bash` syntax compatible with Azure Cloud Shell. For PowerShell, adapt accordingly. +> +> **Source docs**: [Linux migration guide](https://learn.microsoft.com/en-us/azure/azure-functions/migration/migrate-plan-consumption-to-flex?pivots=platform-linux) + +## Prerequisites + +```bash +# Ensure Azure CLI v2.77.0+ +az --version + +# Install resource-graph extension +az extension add --name resource-graph + +# Login and set subscription +az login +az account set --subscription +``` + +--- + +## Step 1: Identify Candidate Apps + +```bash +# List all Linux Consumption apps with eligibility status +az functionapp flex-migration list +``` + +This returns two arrays: +- `eligible_apps` — apps that can be migrated to Flex Consumption +- `ineligible_apps` — apps with specific reasons why not + +The output includes app name, resource group, location, and runtime stack for each app. + +--- + +## Step 2: Assessment Checks + +Set variables for your app: + +```bash +appName= +rgName= +``` + +### 2a. Confirm Region Compatibility + +```bash +# List all regions where Flex Consumption is available +az functionapp list-flexconsumption-locations --query "sort_by(@, &name)[].{Region:name}" -o table +``` + +### 2b. Verify Language Stack Compatibility + +Supported stacks: `dotnet-isolated`, `node`, `java`, `python`, `powershell`, `custom`. +**Not supported**: `dotnet` (in-process) — must migrate to isolated first. + +### 2c. Verify Stack Version Compatibility + +```bash +# Check supported versions for a specific runtime in a specific region +az functionapp list-flexconsumption-runtimes --location --runtime \ + --query '[].{version:version}' -o tsv +``` + +Replace `` with the app's region and `` with one of: `dotnet-isolated`, `java`, `node`, `powershell`, `python`. + +### 2d. Check Deployment Slots + +```bash +# List any deployment slots +az functionapp deployment slot list --name $appName --resource-group $rgName --output table +``` + +If this returns entries, the app has slots. Flex Consumption does NOT support slots — plan accordingly. + +### 2e. Check TLS/SSL Certificates + +```bash +# List certificates available to the app +az webapp config ssl list --resource-group $rgName +``` + +If this returns output, the app likely uses certificates. Flex Consumption does NOT support certs yet. + +### 2f. Check Blob Storage Triggers + +```bash +# Find blob triggers NOT using EventGrid source +az functionapp function list --name $appName --resource-group $rgName \ + --query "[?config.bindings[0].type=='blobTrigger' && config.bindings[0].source!='EventGrid'].{Function:name,TriggerType:config.bindings[0].type,Source:config.bindings[0].source}" \ + --output table +``` + +If this returns rows, convert those blob triggers from `LogsAndContainerScan` to `EventGrid` before upgrading. + +--- + +## Step 3: Pre-Migration — Collect Settings + +### 3a. Collect App Settings + +```bash +appName= +rgName= + +# Get all app settings as JSON +app_settings=$(az functionapp config appsettings list --name $appName --resource-group $rgName) +echo "$app_settings" +``` + +### 3b. Collect Application Configurations + +```bash +appName= +rgName= + +echo "Getting commonly used site settings..." +az functionapp config show --name $appName --resource-group $rgName \ + --query "{http20Enabled:http20Enabled, httpsOnly:httpsOnly, minTlsVersion:minTlsVersion, \ + minTlsCipherSuite:minTlsCipherSuite, clientCertEnabled:clientCertEnabled, \ + clientCertMode:clientCertMode, clientCertExclusionPaths:clientCertExclusionPaths}" + +echo "Checking for SCM basic publishing credentials policies..." +az resource show --resource-group $rgName --name scm --namespace Microsoft.Web \ + --resource-type basicPublishingCredentialsPolicies --parent sites/$appName --query properties + +echo "Checking for the maximum scale-out limit configuration..." +az functionapp config appsettings list --name $appName --resource-group $rgName \ + --query "[?name=='WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT'].value" -o tsv + +echo "Checking for any file share mount configurations..." +az webapp config storage-account list --name $appName --resource-group $rgName + +echo "Checking for any custom domains..." +az functionapp config hostname list --webapp-name $appName --resource-group $rgName \ + --query "[?contains(name, 'azurewebsites.net')==\`false\`]" --output table + +echo "Checking for any CORS settings..." +az functionapp cors show --name $appName --resource-group $rgName +``` + +### 3c. Identify Managed Identities and Role Assignments + +```bash +appName= +rgName= + +echo "Checking for a system-assigned managed identity..." +systemUserId=$(az functionapp identity show --name $appName --resource-group $rgName \ + --query "principalId" -o tsv) + +if [[ -n "$systemUserId" ]]; then + echo "System-assigned identity principal ID: $systemUserId" + echo "Checking for role assignments..." + az role assignment list --assignee $systemUserId --all +else + echo "No system-assigned identity found." +fi + +echo "Checking for user-assigned managed identities..." +userIdentities=$(az functionapp identity show --name $appName --resource-group $rgName \ + --query 'userAssignedIdentities' -o json) + +if [[ "$userIdentities" != "{}" && "$userIdentities" != "null" ]]; then + echo "$userIdentities" | jq -c 'to_entries[]' | while read -r identity; do + echo "User-assigned identity: $(echo "$identity" | jq -r '.key' | sed 's|.*/userAssignedIdentities/||')" + echo "Checking for role assignments..." + az role assignment list --assignee $(echo "$identity" | jq -r '.value.principalId') --all --output json + echo + done +else + echo "No user-assigned identities found." +fi +``` + +### 3d. Check Built-in Authentication + +```bash +az webapp auth show --name $appName --resource-group $rgName +``` + +### 3e. Review Inbound Access Restrictions + +```bash +az functionapp config access-restriction show --name $appName --resource-group $rgName +``` + +### 3f. Get Deployment Package (if needed) + +Ideally your project files are in source control and you can redeploy from there. If not: + +#### Check WEBSITE_RUN_FROM_PACKAGE + +```bash +az functionapp config appsettings list --name $appName --resource-group $rgName \ + --query "[?name=='WEBSITE_RUN_FROM_PACKAGE'].value" -o tsv +``` + +If this returns a URL, download the package from that remote location. + +#### Download from scm-releases blob container + +Linux Consumption apps store deployment packages in the `scm-releases` blob container (in `squashfs` format). + +```bash +appName= +rgName= + +echo "Getting the storage account connection string..." +storageConnection=$(az functionapp config appsettings list --name $appName --resource-group $rgName \ + --query "[?name=='AzureWebJobsStorage'].value" -o tsv) + +echo "Getting the package name..." +packageName=$(az storage blob list --connection-string $storageConnection --container-name scm-releases \ + --query "[0].name" -o tsv) + +echo "Downloading package: $packageName" +az storage blob download --connection-string $storageConnection --container-name scm-releases \ + --name $packageName --file $packageName +``` + +> 💡 If your storage account is restricted to managed identity access only, you may need to grant your Azure account the `Storage Blob Data Reader` role. + +--- + +## Step 4: Create the Flex Consumption App + +```bash +# Automated migration — creates new app and migrates most configurations +az functionapp flex-migration start \ + --source-name \ + --source-resource-group \ + --name \ + --resource-group +``` + +**Optional flags**: +- `--storage-account ` — use a different storage account +- `--maximum-instance-count ` — set max scale-out instances +- `--skip-access-restrictions` — skip migrating IP access restrictions +- `--skip-cors` — skip migrating CORS settings +- `--skip-hostnames` — skip migrating custom domains +- `--skip-managed-identities` — skip migrating managed identity configurations +- `--skip-storage-mount` — skip migrating storage mount configurations + +The command automatically: +- Assesses your source app for Flex Consumption compatibility +- Creates a new function app in the Flex Consumption plan +- Migrates app settings, identity assignments, storage mounts, CORS, custom domains, and access restrictions + +### Verify Migration Results + +```bash +# Verify new app exists and is configured +az functionapp show --name --resource-group \ + --query "{name:name, kind:kind, sku:properties.sku}" --output table + +# Review migrated app settings +az functionapp config appsettings list --name --resource-group \ + --output table + +# Check managed identity +az functionapp identity show --name --resource-group + +# Check custom domains +az functionapp config hostname list --webapp-name --resource-group \ + --output table +``` + +### Configure Items Not Auto-Migrated + +The `flex-migration start` command handles most settings, but these may need manual configuration: + +#### Built-in Authentication + +```bash +# Recreate auth settings if your original app used Easy Auth +az webapp auth update --name --resource-group \ + --enabled true --action +``` + +#### Scale and Concurrency (if custom values needed) + +```bash +# Set maximum scale-out (default: 100, range: 1-1000) +az functionapp scale config set --name --resource-group \ + --maximum-instance-count +``` + +> ⚠️ Reducing below 40 for HTTP apps can cause frequent request failures. + +--- + +## Step 5: Deploy Code + +> ⚠️ **Code is NOT automatically migrated.** The new app is created with config only — you must deploy code separately. + +### ask_user: Choose Deployment Method + +Present these options to the user: + +> Your new Flex Consumption app `` has been created and configured. Now we need to deploy your function code. How would you like to proceed? +> +> 1. **Update CI/CD pipeline** — I'll help you update your Azure Pipelines or GitHub Actions workflow to target the new app +> 2. **Deploy from local project** — I'll run `func azure functionapp publish ` from your project directory +> 3. **Deploy existing package** — I'll deploy the package we downloaded earlier from the original app + +--- + +### Option A: Update CI/CD Pipeline (if user selects option 1) + +Update your existing pipeline (Azure Pipelines or GitHub Actions) to target the new app name. + +- [Build and deploy with Azure Pipelines](https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-azure-devops) +- [Build and deploy with GitHub Actions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-github-actions) + +### Option B: Deploy from Local Project (if user selects option 2) + +```bash +# From your project directory +func azure functionapp publish +``` + +### Option C: Deploy Existing Package (if user selects option 3) + +```bash +# Deploy the zip package downloaded in Step 3 +az functionapp deployment source config-zip --name --resource-group \ + --src +``` + +### After Successful Deployment + +Inform the user: + +> Code deployed! Next steps to consider: +> +> - The original app is still running — keep it as rollback for a few days +> - Update any clients/pipelines to point to the new URL +> - Enable HTTPS-only and managed identity on the new app for better security +> - When confident, you can delete the original app + +--- + +## Step 6: Post-Upgrade Validation + +### Smoke Test (run this first) + +```bash +# Minimum viability check — confirm the app is reachable at all +DEFAULT_HOST=$(az functionapp show --name --resource-group \ + --query "defaultHostName" -o tsv) + +HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://$DEFAULT_HOST") +echo "App responded with HTTP $HTTP_STATUS" + +# Expected: 2xx or 401/404 (means the host is up). +# If 503 or connection refused → the app failed to start. Check logs: +# az functionapp log tail --name --resource-group +``` + +### Verify Plan + +```bash +az functionapp show --name --resource-group --query "serverFarmId" +``` + +### Test HTTP Endpoints + +```bash +# Test an HTTP trigger function +curl -s -o /dev/null -w "%{http_code}" "https://$DEFAULT_HOST/api/" +``` + +### Performance Benchmarks (Application Insights KQL) + +```kql +requests +| where timestamp > ago(1d) +| summarize percentiles(duration, 50, 95, 99) by bin(timestamp, 1h) +| render timechart +``` + +### Check for Errors + +```kql +traces +| where severityLevel == 3 +| where cloud_RoleName == "" +| where timestamp > ago(1d) +| project timestamp, message, operation_Name, customDimensions +| order by timestamp desc +``` + +--- + +## Step 7: Cleanup (Optional) + +```bash +# ⛔ REQUIRES ask_user confirmation before executing + +# Delete the original function app +az functionapp delete --name --resource-group +``` + +> 💡 No rush. The Consumption plan only charges for actual usage, so keeping the old app (with triggers disabled) costs very little. We recommend keeping it for a few days/weeks. + +--- + +## Rollback + +```bash +# Restart the original app if it was stopped +az functionapp start --name --resource-group + +# Optionally delete the new Flex Consumption app +az functionapp delete --name --resource-group +``` diff --git a/plugin/skills/azure-upgrade/references/services/functions/consumption-to-flex.md b/plugin/skills/azure-upgrade/references/services/functions/consumption-to-flex.md new file mode 100644 index 00000000..2d5f35b8 --- /dev/null +++ b/plugin/skills/azure-upgrade/references/services/functions/consumption-to-flex.md @@ -0,0 +1,227 @@ +# Consumption Plan to Flex Consumption Plan Upgrade + +> **Source**: Azure Functions Consumption Plan (Y1/Dynamic) on Linux +> **Target**: Azure Functions Flex Consumption Plan (FC1/FlexConsumption) +> **Platform**: Linux only (Windows support planned for future) +> **Docs**: [Linux migration guide](https://learn.microsoft.com/en-us/azure/azure-functions/migration/migrate-plan-consumption-to-flex?pivots=platform-linux) + +## Why Upgrade? + +- **Faster cold starts** — always-ready instances mean functions respond more quickly +- **Better scaling** — per-function scaling and concurrency controls +- **Virtual network support** — connect to private networks and use private endpoints +- **Active investment** — Flex Consumption is where new features land first + +## What to Expect + +1. Your **code stays the same** — no rewriting required if on a supported language version +2. You'll **create a new app** alongside the existing one +3. New app runs in the **same resource group** with access to the same dependencies +4. You **control the timing** — test thoroughly before switching over + +## Platform Notes + +- Linux Consumption is being **retired September 30, 2028** +- Azure CLI provides automated migration commands: `az functionapp flex-migration list` and `az functionapp flex-migration start` +- The `flex-migration` commands handle assessment, app creation, and most configuration migration automatically +- Deployment packages are in `squashfs` format stored in `scm-releases` blob container + +## Prerequisites + +- Azure CLI v2.77.0+ +- `resource-graph` extension: `az extension add --name resource-graph` +- `jq` tool for JSON processing +- Owner or Contributor role in the resource group +- Permissions to create and manage function apps, storage accounts, App Insights resources, and managed identity role assignments + +## Compatibility Requirements + +### Supported Language Stacks + +| Stack ID | Language | Supported? | +|----------|----------|------------| +| `dotnet-isolated` | .NET (isolated worker model) | ✅ Yes | +| `node` | JavaScript/TypeScript | ✅ Yes | +| `java` | Java | ✅ Yes | +| `python` | Python | ✅ Yes | +| `powershell` | PowerShell | ✅ Yes | +| `dotnet` | .NET (in-process model) | ❌ No — must migrate to isolated first | +| `custom` | Custom handlers | ✅ Yes | + +### Known Limitations (Flex Consumption) + +| Feature | Status | Impact | +|---------|--------|--------| +| Deployment slots | ❌ Not supported | Rearchitect to use separate apps | +| TLS/SSL certificates | ❌ Not supported | Wait for support or find alternative | +| Blob trigger (polling) | ❌ Only EventGrid source | Convert `LogsAndContainerScan` → `EventGrid` | +| Azure Government | ❌ Not available | Cannot migrate yet | + +## Upgrade Phases + +### Phase 1: Assessment + +Run all checks from [assessment.md](assessment.md). Use the automated eligibility check: + +```bash +# Automated eligibility check — scans all Linux Consumption apps +az functionapp flex-migration list +``` + +This returns `eligible_apps` and `ineligible_apps` arrays with specific reasons for any incompatibilities. For detailed manual checks, see [automation.md](automation.md). + +### Phase 2: Pre-migration (Collect Everything) + +Collect all settings and configurations from the existing app before creating the new one. See **Step-by-step scripts** in [automation.md](automation.md): + +1. Collect app settings +2. Collect application configurations (CORS, custom domains, HTTP version, TLS, client certs, etc.) +3. Identify managed identities and RBAC role assignments +4. Identify built-in authentication settings +5. Review inbound access restrictions +6. Get the code deployment package (if source code not in version control) + +### Phase 3: Create Flex Consumption App + +Run the automated migration command from [automation.md](automation.md) — Step 4. + +The `flex-migration start` command automatically: +- Assesses your source app for Flex Consumption compatibility +- Creates a new function app in the Flex Consumption plan +- Migrates app settings, identity assignments, storage mounts, CORS, custom domains, and access restrictions + +> 💡 Use Microsoft Entra ID + managed identities instead of connection strings when creating the new app. + +### Identity-First Configuration (Functions) + +Enterprise subscriptions commonly enforce policies blocking local auth. Configure identity-based access: + +- **Storage accounts**: Use `AzureWebJobsStorage__credential`, `__clientId`, and service-specific URIs (`__blobServiceUri`, `__queueServiceUri`, `__tableServiceUri`) +- **Application Insights**: Use `APPLICATIONINSIGHTS_AUTHENTICATION_STRING` with `Authorization=AAD` +- When using User Assigned Managed Identity, pass `managedIdentityClientId` explicitly + +### Phase 4: Verify and Configure the New App + +The `flex-migration start` command handles most configuration. Verify the results and manually configure anything it doesn't cover (see [automation.md](automation.md)): + +1. **Verify** migrated app settings, identities, custom domains +2. **Configure** built-in authentication (if used — not auto-migrated) +3. **Configure** scale and concurrency settings (if custom values needed) +4. **Enable** Application Insights monitoring + +### Phase 5: Deploy Code + +> ⚠️ **Code is NOT automatically migrated.** The new app is created with config only — you must deploy code separately. + +**Use `ask_user` to present these options:** + +> Your new Flex Consumption app `` has been created and configured. Now we need to deploy your function code. How would you like to proceed? +> +> 1. **Update CI/CD pipeline** — I'll help you update your Azure Pipelines or GitHub Actions workflow to target the new app +> 2. **Deploy from local project** — I'll run `func azure functionapp publish ` from your project directory +> 3. **Deploy existing package** — I'll deploy the package we downloaded earlier from the original app + +After user selects an option, execute the corresponding deployment method from [automation.md](automation.md) — Step 5. + +> ⚠️ After deployment, triggers immediately start processing. Review mitigation strategies for your trigger types. + +**After successful deployment, inform the user:** + +> Code deployed! Next steps to consider: +> +> - The original app is still running — keep it as rollback for a few days +> - Update any clients/pipelines to point to the new URL +> - Enable HTTPS-only and managed identity on the new app for better security +> - When confident, you can delete the original app + +### Phase 6: Post-Upgrade Validation + +1. **Smoke test** — Get the app’s default hostname via `az functionapp show --query defaultHostName -o tsv`, then hit that URL and confirm it returns a non-error response (HTTP 2xx/4xx, not connection refused or 503). This is the minimum bar before proceeding. +2. Verify the app is running on Flex Consumption (`az functionapp show --query sku`) +3. Test HTTP trigger endpoints (e.g. `curl https:///api/`) +4. Capture and compare performance benchmarks +5. Set up monitoring dashboards +6. Refine scale/concurrency settings +7. Update IaC (Bicep/Terraform) to reflect new plan + +### Phase 7: Cleanup (Optional) + +- Keep the original app for a few days/weeks as rollback +- Consumption plan charges only for actual usage — low cost to keep idle +- When confident, delete using the command in [automation.md](automation.md) — Step 7 + +## Trigger Migration Risks + +| Trigger Type | Risk | Mitigation | +|-------------|------|------------| +| Azure Blob storage | High | Create separate container for event-based trigger in new app | +| Azure Cosmos DB | High | Create dedicated lease container for new app; set `StartFromBeginning: false` | +| Azure Event Grid | Medium | Recreate event subscriptions; ensure idempotent functions | +| Azure Event Hubs | Medium | Create new consumer group for new app | +| Azure Service Bus | High | Create new topic/queue; update senders; drain original before shutdown | +| Azure Storage Queue | High | Create new queue; update senders; drain original before shutdown | +| HTTP | Low | Update clients to target new app URL | +| Timer | Low | Offset schedules during cutover to avoid simultaneous execution | + +## IaC Key Differences + +| Property | Consumption | Flex Consumption | +|----------|-------------|------------------| +| SKU | `Y1` (Dynamic) | `FC1` (FlexConsumption) | +| Plan required | Optional (auto-created) | Required (must be explicit) | +| OS | Linux | Linux only | +| Configuration | App settings | `functionAppConfig` section | +| Storage | `WEBSITE_CONTENTSHARE` setting | `deployment.storage` in `functionAppConfig` | + +## Deprecated Settings (Do NOT Migrate) + +These app settings are NOT supported in Flex Consumption and should be filtered out: + +- `WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED` +- `AzureWebJobsStorage*` (replaced by identity-based config) +- `WEBSITE_MOUNT_ENABLED` +- `ENABLE_ORYX_BUILD` +- `FUNCTIONS_EXTENSION_VERSION` (set via `functionAppConfig`) +- `FUNCTIONS_WORKER_RUNTIME` (set via `functionAppConfig`) +- `FUNCTIONS_WORKER_RUNTIME_VERSION` +- `FUNCTIONS_MAX_HTTP_CONCURRENCY` +- `FUNCTIONS_WORKER_PROCESS_COUNT` +- `FUNCTIONS_WORKER_DYNAMIC_CONCURRENCY_ENABLED` +- `SCM_DO_BUILD_DURING_DEPLOYMENT` +- `WEBSITE_CONTENTAZUREFILECONNECTIONSTRING` +- `WEBSITE_CONTENTOVERVNET` +- `WEBSITE_CONTENTSHARE` +- `WEBSITE_DNS_SERVER` +- `WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT` +- `WEBSITE_NODE_DEFAULT_VERSION` +- `WEBSITE_RUN_FROM_PACKAGE` +- `WEBSITE_SKIP_CONTENTSHARE_VALIDATION` +- `WEBSITE_VNET_ROUTE_ALL` +- `APPLICATIONINSIGHTS_CONNECTION_STRING` (already created in new app) + +## Troubleshooting + +| Issue | Remediation | +|-------|-------------| +| Cold start performance issues | Review concurrency settings; check for missing dependencies | +| Missing bindings | Verify extension bundles; update binding configurations | +| Permission errors | Check identity assignments and role permissions | +| Network connectivity | Validate access restrictions and networking settings | +| Missing App Insights | Recreate the Application Insights connection | +| App fails to start | Check portal Diagnose & Solve; review App Insights Failures blade | +| Triggers not processing | Verify binding configs, connection strings, consumer groups | + +## Rollback + +1. Restart the original app: `az functionapp start --name --resource-group ` +2. Redirect clients back to original resources (queues/topics/containers) +3. Revert DNS or custom domain changes +4. Delete the new Flex Consumption app if needed + +## References + +- [Flex Consumption plan overview](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-plan) +- [How to use the Flex Consumption plan](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to) +- [Azure CLI flex-migration commands](https://learn.microsoft.com/en-us/cli/azure/functionapp/flex-migration) (Linux only) +- [Flex Consumption IaC samples](https://github.com/Azure-Samples/azure-functions-flex-consumption-samples/tree/main/IaC) +- [Flex Consumption plan deprecations](https://learn.microsoft.com/en-us/azure/azure-functions/functions-app-settings#flex-consumption-plan-deprecations) diff --git a/plugin/skills/azure-upgrade/references/workflow-details.md b/plugin/skills/azure-upgrade/references/workflow-details.md new file mode 100644 index 00000000..054cd0a8 --- /dev/null +++ b/plugin/skills/azure-upgrade/references/workflow-details.md @@ -0,0 +1,59 @@ +# Workflow Details + +## Upgrade Workflow Phases + +The azure-upgrade skill follows a structured workflow to ensure safe, repeatable upgrades. + +## Phase Overview + +``` +┌──────────┐ ┌──────────┐ ┌─────────────┐ ┌─────────┐ ┌──────────┐ +│ Identify │───▶│ Assess │───▶│ Pre-migrate │───▶│ Upgrade │───▶│ Validate │ +└──────────┘ └──────────┘ └─────────────┘ └─────────┘ └──────────┘ +``` + +## Progress Tracking + +Create and maintain `upgrade-status.md` in the workspace root: + +```markdown +# Upgrade Status + +## Upgrade Details + +| Property | Value | +|----------|-------| +| **Source App** | | +| **Source Plan** | | +| **Target Plan** | | +| **Resource Group** | | +| **Region** | | +| **Started** | | + +## Phase Status + +- [ ] Phase 1: Identify — Determine source/target plans +- [ ] Phase 2: Assess — Check readiness and compatibility +- [ ] Phase 3: Pre-migrate — Collect settings and configurations +- [ ] Phase 4: Upgrade — Execute upgrade automation +- [ ] Phase 5: Validate — Verify new app functionality + +## Notes + + +``` + +## Error Handling + +If any phase fails: +1. Log the error in `upgrade-status.md` +2. Do NOT proceed to the next phase +3. Inform the user of the failure and suggest remediation +4. Offer to retry the failed phase or rollback + +## Hand-off + +After successful validation: +- Offer to hand off to `azure-validate` for deeper testing +- Offer to hand off to `azure-deploy` for CI/CD pipeline setup +- Ask if the user wants to clean up the original app From 7aa4e3bcbc1d69b76fbbf65ec5ba93a2b0df4925 Mon Sep 17 00:00:00 2001 From: Madhura Bharadwaj Date: Tue, 10 Mar 2026 16:27:06 -0500 Subject: [PATCH 2/7] feat: add azure-upgrade tests and improve SKILL.md frontmatter - Add unit, trigger, and integration tests for azure-upgrade skill - Add create-function-app-consumption.sh setup script - Condense SKILL.md description to meet 60-word limit - Register azure-upgrade in tests/skills.json --- plugin/skills/azure-upgrade/SKILL.md | 2 +- .../__snapshots__/triggers.test.ts.snap | 81 +++++++++++ .../create-function-app-consumption.sh | 35 +++++ tests/azure-upgrade/integration.test.ts | 79 +++++++++++ tests/azure-upgrade/triggers.test.ts | 100 ++++++++++++++ tests/azure-upgrade/unit.test.ts | 128 ++++++++++++++++++ tests/skills.json | 1 + 7 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 tests/azure-upgrade/__snapshots__/triggers.test.ts.snap create mode 100644 tests/azure-upgrade/create-function-app-consumption.sh create mode 100644 tests/azure-upgrade/integration.test.ts create mode 100644 tests/azure-upgrade/triggers.test.ts create mode 100644 tests/azure-upgrade/unit.test.ts diff --git a/plugin/skills/azure-upgrade/SKILL.md b/plugin/skills/azure-upgrade/SKILL.md index 53399ba4..e60f088f 100644 --- a/plugin/skills/azure-upgrade/SKILL.md +++ b/plugin/skills/azure-upgrade/SKILL.md @@ -1,6 +1,6 @@ --- name: azure-upgrade -description: "Assess and upgrade Azure workloads from one Azure service, hosting plan, or SKU to another within Azure. Covers plan/tier upgrades (e.g. Consumption to Flex Consumption), cross-service migrations (e.g. App Service to Container Apps), and SKU changes. Generates assessment reports and automates Azure CLI steps. Both source and destination are within Azure. WHEN: upgrade Consumption to Flex Consumption, upgrade Azure Functions plan, migrate hosting plan, upgrade Functions SKU, move to Flex Consumption, upgrade Azure service tier, Azure-to-Azure upgrade, change hosting plan, upgrade function app plan, migrate App Service to Container Apps, move between Azure services." +description: "Assess and upgrade Azure workloads between plans, tiers, or SKUs within Azure. Generates assessment reports and automates upgrade steps. WHEN: upgrade Consumption to Flex Consumption, upgrade Azure Functions plan, migrate hosting plan, upgrade Functions SKU, move to Flex Consumption, upgrade Azure service tier, change hosting plan, upgrade function app plan, migrate App Service to Container Apps." --- # Azure Upgrade diff --git a/tests/azure-upgrade/__snapshots__/triggers.test.ts.snap b/tests/azure-upgrade/__snapshots__/triggers.test.ts.snap new file mode 100644 index 00000000..8b8f25e9 --- /dev/null +++ b/tests/azure-upgrade/__snapshots__/triggers.test.ts.snap @@ -0,0 +1,81 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`azure-upgrade - Trigger Tests Trigger Keywords Snapshot skill description triggers match snapshot 1`] = ` +{ + "description": "Assess and upgrade Azure workloads between plans, tiers, or SKUs within Azure. Generates assessment reports and automates upgrade steps. WHEN: upgrade Consumption to Flex Consumption, upgrade Azure Functions plan, migrate hosting plan, upgrade Functions SKU, move to Flex Consumption, upgrade Azure service tier, change hosting plan, upgrade function app plan, migrate App Service to Container Apps.", + "extractedKeywords": [ + "app service", + "apps", + "assess", + "assessment", + "automates", + "azure", + "between", + "change", + "consumption", + "container", + "deploy", + "flex", + "function", + "functions", + "generates", + "hosting", + "mcp", + "migrate", + "monitor", + "move", + "plan", + "plans", + "reports", + "service", + "skus", + "steps", + "tier", + "tiers", + "upgrade", + "validation", + "when", + "within", + "workloads", + ], + "name": "azure-upgrade", +} +`; + +exports[`azure-upgrade - Trigger Tests Trigger Keywords Snapshot skill keywords match snapshot 1`] = ` +[ + "app service", + "apps", + "assess", + "assessment", + "automates", + "azure", + "between", + "change", + "consumption", + "container", + "deploy", + "flex", + "function", + "functions", + "generates", + "hosting", + "mcp", + "migrate", + "monitor", + "move", + "plan", + "plans", + "reports", + "service", + "skus", + "steps", + "tier", + "tiers", + "upgrade", + "validation", + "when", + "within", + "workloads", +] +`; diff --git a/tests/azure-upgrade/create-function-app-consumption.sh b/tests/azure-upgrade/create-function-app-consumption.sh new file mode 100644 index 00000000..a7767081 --- /dev/null +++ b/tests/azure-upgrade/create-function-app-consumption.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Passed validation in Cloud Shell on 2/28/2026 + +# For the recommended serverless plan, see create-function-app-flex-consumption. +# Function app and storage account names must be unique. + +# Variable block +let "randomIdentifier=$RANDOM*$RANDOM" +location="eastus" +resourceGroup="rg-upgrade-test-$randomIdentifier" +tag="azure-upgrade-integration-test" +storageAccount="stupgradetest$randomIdentifier" +functionAppName="func-upgrade-source-$randomIdentifier" +skuStorage="Standard_LRS" +functionsVersion="4" +runtime="dotnet-isolated" +runtimeVersion="8.0" + +# Create a resource group +echo "Creating resource group: $resourceGroup in $location..." +az group create --name $resourceGroup --location "$location" --tags $tag + +# Create an Azure storage account in the resource group. +echo "Creating storage account: $storageAccount" +az storage account create --name $storageAccount --location "$location" --resource-group $resourceGroup --sku $skuStorage + +# Create a Linux Consumption function app in the resource group. +echo "Creating consumption function app: $functionAppName" +az functionapp create --name $functionAppName --storage-account $storageAccount \ + --consumption-plan-location "$location" --resource-group $resourceGroup \ + --runtime $runtime --runtime-version $runtimeVersion \ + --functions-version $functionsVersion --os-type Linux + +# echo "Deleting all resources" +# az group delete --name $resourceGroup -y diff --git a/tests/azure-upgrade/integration.test.ts b/tests/azure-upgrade/integration.test.ts new file mode 100644 index 00000000..54290fe7 --- /dev/null +++ b/tests/azure-upgrade/integration.test.ts @@ -0,0 +1,79 @@ +/** + * Integration Tests for azure-upgrade + * + * Tests skill behavior with a real Copilot agent session. + * Uses two prompts via the Copilot CLI: + * 1. Create a Linux Consumption function app using the shell script and deploy hello-world code + * 2. Migrate the app to Flex Consumption + * + * Prerequisites: + * 1. npm install -g @github/copilot-cli + * 2. Run `copilot` and authenticate + * 3. Azure CLI authenticated with an active subscription + */ + +import { + shouldSkipIntegrationTests, + getIntegrationSkipReason, + useAgentRunner, +} from "../utils/agent-runner"; +import { isSkillInvoked, expectFiles, softCheckSkill, shouldEarlyTerminateForSkillInvocation } from "../utils/evaluate"; +import * as path from "path"; +import * as fs from "fs"; + +const SKILL_NAME = "azure-upgrade"; + +// Check if integration tests should be skipped at module level +const skipTests = shouldSkipIntegrationTests(); +const skipReason = getIntegrationSkipReason(); + +// Log skip reason if skipping +if (skipTests && skipReason) { + console.log(`⏭️ Skipping integration tests: ${skipReason}`); +} + +const describeIntegration = skipTests ? describe.skip : describe; +const upgradeTestTimeoutMs = 2700000; +const FOLLOW_UP_PROMPT = ["Go with recommended options."]; + +describeIntegration(`${SKILL_NAME}_ - Integration Tests`, () => { + const agent = useAgentRunner(); + + describe("upgrade-functions-consumption-to-flex", () => { + test("creates consumption app, deploys code, then upgrades to flex consumption", async () => { + let workspacePath: string | undefined; + + const agentMetadata = await agent.run({ + setup: async (workspace: string) => { + workspacePath = workspace; + + // Copy the shell script into the workspace so the agent can reference it + const scriptSource = path.join(__dirname, "create-function-app-consumption.sh"); + const scriptDest = path.join(workspace, "create-function-app-consumption.sh"); + fs.copyFileSync(scriptSource, scriptDest); + }, + prompt: + "Create a Linux consumption function app using the create-function-app-consumption.sh shell script " + + "in this workspace and then deploy a hello world code into it. " + + "Use my current subscription.", + nonInteractive: true, + followUp: [ + "Validate if the deployed function app is Linux before proceeding with the migration. " + + "Now migrate this app to flex consumption. " + + "The new app should have the same name as the source app but with a '-flex' suffix. " + + "Go with recommended options.", + ], + }); + + // Verify the azure-upgrade skill was invoked + const isSkillUsed = isSkillInvoked(agentMetadata, SKILL_NAME); + expect(isSkillUsed).toBe(true); + + // Verify upgrade assessment report was created in the workspace + expect(workspacePath).toBeDefined(); + expectFiles(workspacePath!, [ + /upgrade-assessment-report\.md$|upgrade-status\.md$|assessment.*\.md$/i, + ], []); + }, upgradeTestTimeoutMs); + }); +}); \ No newline at end of file diff --git a/tests/azure-upgrade/triggers.test.ts b/tests/azure-upgrade/triggers.test.ts new file mode 100644 index 00000000..c051a765 --- /dev/null +++ b/tests/azure-upgrade/triggers.test.ts @@ -0,0 +1,100 @@ +/** + * Trigger Tests for azure-upgrade + * + * 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-upgrade"; + +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 - Azure-to-Azure upgrade workflows + const shouldTriggerPrompts: string[] = [ + "Upgrade my function app from Consumption to Flex Consumption", + "Move my function app to a better plan", + "Is my function app ready for Flex Consumption?", + "Automate the steps to upgrade my Functions plan", + "Upgrade my Azure Functions SKU", + "Change my function app hosting plan", + "Migrate my Azure Functions from Consumption to Flex Consumption", + "Assess my function app for upgrade readiness", + ]; + + 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 (avoid upgrade/Azure keywords) + const shouldNotTriggerPrompts: string[] = [ + "What is the weather today?", + "Help me write a poem", + "Explain quantum computing", + "How do I use Google Cloud Platform?", + "Write a Python script to parse JSON", + "What is the capital of France?", + "Help me debug my React application", + "How do I optimize MySQL queries?", + ]; + + 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 = "upgrade Azure Functions plan ".repeat(1000); + const result = triggerMatcher.shouldTrigger(longPrompt); + expect(typeof result.triggered).toBe("boolean"); + }); + + test("is case insensitive", () => { + const lower = triggerMatcher.shouldTrigger("upgrade my function app to flex consumption"); + const upper = triggerMatcher.shouldTrigger("UPGRADE MY FUNCTION APP TO FLEX CONSUMPTION"); + expect(lower.triggered).toBe(upper.triggered); + }); + }); +}); diff --git a/tests/azure-upgrade/unit.test.ts b/tests/azure-upgrade/unit.test.ts new file mode 100644 index 00000000..cbd38e76 --- /dev/null +++ b/tests/azure-upgrade/unit.test.ts @@ -0,0 +1,128 @@ +/** + * Unit Tests for azure-upgrade + * + * Test isolated skill logic and validation rules. + */ + +import { loadSkill, LoadedSkill } from "../utils/skill-loader"; + +const SKILL_NAME = "azure-upgrade"; + +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.toLowerCase(); + expect(description).toContain("when:"); + }); + + test("description word count is within limit", () => { + const words = skill.metadata.description.split(/\s+/).length; + expect(words).toBeLessThanOrEqual(60); + }); + }); + + describe("Skill Content", () => { + test("has substantive content", () => { + expect(skill.content).toBeDefined(); + expect(skill.content.length).toBeGreaterThan(100); + }); + + test("contains expected sections", () => { + expect(skill.content).toContain("## Triggers"); + expect(skill.content).toContain("## Rules"); + expect(skill.content).toContain("## Steps"); + expect(skill.content).toContain("## Upgrade Scenarios"); + expect(skill.content).toContain("## MCP Tools"); + }); + + test("references azure-validate for post-upgrade validation", () => { + expect(skill.content).toContain("azure-validate"); + }); + + test("references azure-deploy for CI/CD hand-off", () => { + expect(skill.content).toContain("azure-deploy"); + }); + + test("distinguishes from azure-cloud-migrate", () => { + expect(skill.content).toContain("azure-cloud-migrate"); + }); + }); + + describe("Upgrade Workflow", () => { + test("mentions assessment phase", () => { + expect(skill.content.toLowerCase()).toContain("assess"); + }); + + test("includes identify phase", () => { + expect(skill.content.toLowerCase()).toContain("identify"); + }); + + test("includes pre-migrate phase", () => { + expect(skill.content.toLowerCase()).toContain("pre-migrate"); + }); + + test("includes validate phase", () => { + expect(skill.content.toLowerCase()).toContain("validate"); + }); + + test("tracks progress in upgrade-status.md", () => { + expect(skill.content).toContain("upgrade-status.md"); + }); + + test("references upgrade scenarios", () => { + const content = skill.content.toLowerCase(); + const hasConsumptionToFlex = content.includes("consumption") && content.includes("flex"); + const hasScenarios = content.includes("upgrade scenarios"); + expect(hasConsumptionToFlex || hasScenarios).toBe(true); + }); + + test("references consumption-to-flex scenario", () => { + expect(skill.content).toContain("consumption-to-flex.md"); + }); + + test("references workflow details", () => { + expect(skill.content).toContain("workflow-details.md"); + }); + + test("references global rules", () => { + expect(skill.content).toContain("global-rules.md"); + }); + }); + + describe("Safety and Confirmation", () => { + test("requires user confirmation for destructive actions", () => { + const content = skill.content.toLowerCase(); + expect(content).toContain("confirm"); + }); + + test("requires idempotent and resumable scripts", () => { + const content = skill.content.toLowerCase(); + expect(content).toContain("idempotent"); + }); + + test("prohibits deleting original app without confirmation", () => { + const content = skill.content.toLowerCase(); + const hasDeleteProtection = content.includes("never delete") || content.includes("explicit user confirmation"); + expect(hasDeleteProtection).toBe(true); + }); + }); +}); diff --git a/tests/skills.json b/tests/skills.json index aaaad0c2..cd9c909f 100644 --- a/tests/skills.json +++ b/tests/skills.json @@ -17,6 +17,7 @@ "azure-resource-lookup", "azure-resource-visualizer", "azure-storage", + "azure-upgrade", "azure-validate", "entra-app-registration", "microsoft-foundry" From f1b9de9473d0992eddb0249ae62308768857502c Mon Sep 17 00:00:00 2001 From: Koumudi Kaluvakolanu Date: Wed, 11 Mar 2026 14:32:37 -0700 Subject: [PATCH 3/7] fix integration tests --- tests/azure-upgrade/integration.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/azure-upgrade/integration.test.ts b/tests/azure-upgrade/integration.test.ts index 54290fe7..c54daa05 100644 --- a/tests/azure-upgrade/integration.test.ts +++ b/tests/azure-upgrade/integration.test.ts @@ -17,9 +17,13 @@ import { getIntegrationSkipReason, useAgentRunner, } from "../utils/agent-runner"; -import { isSkillInvoked, expectFiles, softCheckSkill, shouldEarlyTerminateForSkillInvocation } from "../utils/evaluate"; +import { isSkillInvoked, expectFiles } from "../utils/evaluate"; import * as path from "path"; import * as fs from "fs"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); const SKILL_NAME = "azure-upgrade"; @@ -34,7 +38,6 @@ if (skipTests && skipReason) { const describeIntegration = skipTests ? describe.skip : describe; const upgradeTestTimeoutMs = 2700000; -const FOLLOW_UP_PROMPT = ["Go with recommended options."]; describeIntegration(`${SKILL_NAME}_ - Integration Tests`, () => { const agent = useAgentRunner(); From 57c9456de5ce5f958e5523d7efc1cfb7fbf11e62 Mon Sep 17 00:00:00 2001 From: Koumudi Kaluvakolanu Date: Wed, 11 Mar 2026 16:04:57 -0700 Subject: [PATCH 4/7] added upgrade skills to skills.json --- tests/skills.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/skills.json b/tests/skills.json index cd9c909f..38e9dfaf 100644 --- a/tests/skills.json +++ b/tests/skills.json @@ -25,6 +25,6 @@ "integrationTestSchedule": { "0 5 * * 2-6": "microsoft-foundry", "0 8 * * 2-6": "azure-deploy", - "0 12 * * 2-6": "appinsights-instrumentation,azure-ai,azure-aigateway,azure-cloud-migrate,azure-compliance,azure-compute,azure-cost-optimization,azure-diagnostics,azure-hosted-copilot-sdk,azure-kusto,azure-messaging,azure-prepare,azure-rbac,azure-resource-lookup,azure-resource-visualizer,azure-storage,azure-validate,entra-app-registration" + "0 12 * * 2-6": "appinsights-instrumentation,azure-ai,azure-aigateway,azure-cloud-migrate,azure-compliance,azure-compute,azure-cost-optimization,azure-diagnostics,azure-hosted-copilot-sdk,azure-kusto,azure-messaging,azure-prepare,azure-rbac,azure-resource-lookup,azure-resource-visualizer,azure-storage,azure-upgrade,azure-validate,entra-app-registration" } } \ No newline at end of file From 41bce9b90f79e12326650f4851528cc76a8c4ed5 Mon Sep 17 00:00:00 2001 From: Koumudi Kaluvakolanu Date: Thu, 12 Mar 2026 11:54:40 -0700 Subject: [PATCH 5/7] run sensei on azure-upgrade, refactor azure-diagnostics skill description, update integration test --- plugin/skills/azure-diagnostics/SKILL.md | 2 +- plugin/skills/azure-upgrade/SKILL.md | 4 ++++ tests/azure-upgrade/create-function-app-consumption.sh | 9 +++------ tests/azure-upgrade/integration.test.ts | 3 ++- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/plugin/skills/azure-diagnostics/SKILL.md b/plugin/skills/azure-diagnostics/SKILL.md index bdf3f308..d3fdce67 100644 --- a/plugin/skills/azure-diagnostics/SKILL.md +++ b/plugin/skills/azure-diagnostics/SKILL.md @@ -1,6 +1,6 @@ --- name: azure-diagnostics -description: "Debug and troubleshoot production issues on Azure. Covers Container Apps and Function Apps diagnostics, log analysis with KQL, health checks, and common issue resolution for image pulls, cold starts, health probes, and function invocation failures. WHEN: debug production issues, troubleshoot container apps, troubleshoot function apps, troubleshoot Azure Functions, analyze logs with KQL, fix image pull failures, resolve cold start issues, investigate health probe failures, check resource health, view application logs, find root cause of errors, function app not working, function invocation failures." +description: "Debug and troubleshoot Azure Container Apps and Function Apps production issues, log analysis with KQL, health checks, and common issue resolution for image pulls, cold starts, health probes, and function invocation failures. WHEN: debug production issues, troubleshoot container apps, troubleshoot function apps, troubleshoot Azure Functions, analyze logs with KQL, fix image pull failures, investigate health probe failures, check resource health, view application logs, find root cause of errors, function app not working." license: MIT metadata: author: Microsoft diff --git a/plugin/skills/azure-upgrade/SKILL.md b/plugin/skills/azure-upgrade/SKILL.md index e60f088f..23ebfd63 100644 --- a/plugin/skills/azure-upgrade/SKILL.md +++ b/plugin/skills/azure-upgrade/SKILL.md @@ -1,6 +1,10 @@ --- name: azure-upgrade description: "Assess and upgrade Azure workloads between plans, tiers, or SKUs within Azure. Generates assessment reports and automates upgrade steps. WHEN: upgrade Consumption to Flex Consumption, upgrade Azure Functions plan, migrate hosting plan, upgrade Functions SKU, move to Flex Consumption, upgrade Azure service tier, change hosting plan, upgrade function app plan, migrate App Service to Container Apps." +license: MIT +metadata: + author: Microsoft + version: "1.0.0" --- # Azure Upgrade diff --git a/tests/azure-upgrade/create-function-app-consumption.sh b/tests/azure-upgrade/create-function-app-consumption.sh index a7767081..2834eb7e 100644 --- a/tests/azure-upgrade/create-function-app-consumption.sh +++ b/tests/azure-upgrade/create-function-app-consumption.sh @@ -8,17 +8,16 @@ let "randomIdentifier=$RANDOM*$RANDOM" location="eastus" resourceGroup="rg-upgrade-test-$randomIdentifier" -tag="azure-upgrade-integration-test" storageAccount="stupgradetest$randomIdentifier" functionAppName="func-upgrade-source-$randomIdentifier" skuStorage="Standard_LRS" functionsVersion="4" -runtime="dotnet-isolated" -runtimeVersion="8.0" +runtime="node" +runtimeVersion="22" # Create a resource group echo "Creating resource group: $resourceGroup in $location..." -az group create --name $resourceGroup --location "$location" --tags $tag +az group create --name $resourceGroup --location "$location" # Create an Azure storage account in the resource group. echo "Creating storage account: $storageAccount" @@ -31,5 +30,3 @@ az functionapp create --name $functionAppName --storage-account $storageAccount --runtime $runtime --runtime-version $runtimeVersion \ --functions-version $functionsVersion --os-type Linux -# echo "Deleting all resources" -# az group delete --name $resourceGroup -y diff --git a/tests/azure-upgrade/integration.test.ts b/tests/azure-upgrade/integration.test.ts index c54daa05..282a77d3 100644 --- a/tests/azure-upgrade/integration.test.ts +++ b/tests/azure-upgrade/integration.test.ts @@ -75,7 +75,8 @@ describeIntegration(`${SKILL_NAME}_ - Integration Tests`, () => { // Verify upgrade assessment report was created in the workspace expect(workspacePath).toBeDefined(); expectFiles(workspacePath!, [ - /upgrade-assessment-report\.md$|upgrade-status\.md$|assessment.*\.md$/i, + /upgrade-status\.md$/i, + /upgrade-assessment-report\.md$/i, ], []); }, upgradeTestTimeoutMs); }); From 292009bba12ffe987714d59bd3654ffa29dd0c47 Mon Sep 17 00:00:00 2001 From: Koumudi Kaluvakolanu Date: Thu, 12 Mar 2026 15:47:29 -0700 Subject: [PATCH 6/7] added invocation tests and removed end to end tests --- .../create-function-app-consumption.sh | 32 ------ tests/azure-upgrade/integration.test.ts | 103 +++++++++--------- 2 files changed, 54 insertions(+), 81 deletions(-) delete mode 100644 tests/azure-upgrade/create-function-app-consumption.sh diff --git a/tests/azure-upgrade/create-function-app-consumption.sh b/tests/azure-upgrade/create-function-app-consumption.sh deleted file mode 100644 index 2834eb7e..00000000 --- a/tests/azure-upgrade/create-function-app-consumption.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# Passed validation in Cloud Shell on 2/28/2026 - -# For the recommended serverless plan, see create-function-app-flex-consumption. -# Function app and storage account names must be unique. - -# Variable block -let "randomIdentifier=$RANDOM*$RANDOM" -location="eastus" -resourceGroup="rg-upgrade-test-$randomIdentifier" -storageAccount="stupgradetest$randomIdentifier" -functionAppName="func-upgrade-source-$randomIdentifier" -skuStorage="Standard_LRS" -functionsVersion="4" -runtime="node" -runtimeVersion="22" - -# Create a resource group -echo "Creating resource group: $resourceGroup in $location..." -az group create --name $resourceGroup --location "$location" - -# Create an Azure storage account in the resource group. -echo "Creating storage account: $storageAccount" -az storage account create --name $storageAccount --location "$location" --resource-group $resourceGroup --sku $skuStorage - -# Create a Linux Consumption function app in the resource group. -echo "Creating consumption function app: $functionAppName" -az functionapp create --name $functionAppName --storage-account $storageAccount \ - --consumption-plan-location "$location" --resource-group $resourceGroup \ - --runtime $runtime --runtime-version $runtimeVersion \ - --functions-version $functionsVersion --os-type Linux - diff --git a/tests/azure-upgrade/integration.test.ts b/tests/azure-upgrade/integration.test.ts index 282a77d3..9c2db9cb 100644 --- a/tests/azure-upgrade/integration.test.ts +++ b/tests/azure-upgrade/integration.test.ts @@ -1,15 +1,25 @@ /** * Integration Tests for azure-upgrade * - * Tests skill behavior with a real Copilot agent session. - * Uses two prompts via the Copilot CLI: - * 1. Create a Linux Consumption function app using the shell script and deploy hello-world code - * 2. Migrate the app to Flex Consumption - * + * Tests skill invocation with migration-related prompts. + * + * NOTE: End-to-end migration test is NOT included due to test environment limitations. + * + * The azure-upgrade skill's core command requires an existing source Consumption function app: + * az functionapp flex-migration start \ + * --source-name \ + * --source-resource-group \ + * --name \ + * --resource-group + * + * Challenge: Creating a valid Consumption function app in test environments is blocked by: + * - Azure Policy requirements (no shared key access on storage accounts) + * - Complex identity-based storage configuration (RBAC, managed identity setup) + * - Deployment failures when using standard `az functionapp create` commands + * Prerequisites: * 1. npm install -g @github/copilot-cli * 2. Run `copilot` and authenticate - * 3. Azure CLI authenticated with an active subscription */ import { @@ -17,13 +27,7 @@ import { getIntegrationSkipReason, useAgentRunner, } from "../utils/agent-runner"; -import { isSkillInvoked, expectFiles } from "../utils/evaluate"; -import * as path from "path"; -import * as fs from "fs"; -import { fileURLToPath } from "url"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +import { softCheckSkill, shouldEarlyTerminateForSkillInvocation } from "../utils/evaluate"; const SKILL_NAME = "azure-upgrade"; @@ -37,47 +41,48 @@ if (skipTests && skipReason) { } const describeIntegration = skipTests ? describe.skip : describe; -const upgradeTestTimeoutMs = 2700000; describeIntegration(`${SKILL_NAME}_ - Integration Tests`, () => { const agent = useAgentRunner(); - describe("upgrade-functions-consumption-to-flex", () => { - test("creates consumption app, deploys code, then upgrades to flex consumption", async () => { - let workspacePath: string | undefined; + describe("skill-invocation", () => { + test("invokes azure-upgrade skill for Functions Consumption to Flex migration prompt", async () => { + try { + const agentMetadata = await agent.run({ + prompt: "Migrate my Azure Functions app from Consumption to Flex Consumption plan", + nonInteractive: true, + followUp: ["Go with recommended options."], + shouldEarlyTerminate: (agentMetadata) => shouldEarlyTerminateForSkillInvocation(agentMetadata, SKILL_NAME) + }); - const agentMetadata = await agent.run({ - setup: async (workspace: string) => { - workspacePath = workspace; + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + }); - // Copy the shell script into the workspace so the agent can reference it - const scriptSource = path.join(__dirname, "create-function-app-consumption.sh"); - const scriptDest = path.join(workspace, "create-function-app-consumption.sh"); - fs.copyFileSync(scriptSource, scriptDest); - }, - prompt: - "Create a Linux consumption function app using the create-function-app-consumption.sh shell script " + - "in this workspace and then deploy a hello world code into it. " + - "Use my current subscription.", - nonInteractive: true, - followUp: [ - "Validate if the deployed function app is Linux before proceeding with the migration. " + - "Now migrate this app to flex consumption. " + - "The new app should have the same name as the source app but with a '-flex' suffix. " + - "Go with recommended options.", - ], - }); + test("invokes azure-upgrade skill for upgrading Functions plan prompt", async () => { + try { + const agentMetadata = await agent.run({ + prompt: "Upgrade my Azure Functions hosting plan to Flex Consumption", + nonInteractive: true, + followUp: ["Go with recommended options."], + shouldEarlyTerminate: (agentMetadata) => shouldEarlyTerminateForSkillInvocation(agentMetadata, SKILL_NAME) + }); - // Verify the azure-upgrade skill was invoked - const isSkillUsed = isSkillInvoked(agentMetadata, SKILL_NAME); - expect(isSkillUsed).toBe(true); - - // Verify upgrade assessment report was created in the workspace - expect(workspacePath).toBeDefined(); - expectFiles(workspacePath!, [ - /upgrade-status\.md$/i, - /upgrade-assessment-report\.md$/i, - ], []); - }, upgradeTestTimeoutMs); + softCheckSkill(agentMetadata, SKILL_NAME); + } catch (e: unknown) { + if (e instanceof Error && e.message?.includes("Failed to load @github/copilot-sdk")) { + console.log("⏭️ SDK not loadable, skipping test"); + return; + } + throw e; + } + }); }); -}); \ No newline at end of file + +}); From 249ea61eaa340c70b58bd1abf8259e329a1f2297 Mon Sep 17 00:00:00 2001 From: Koumudi Kaluvakolanu Date: Thu, 12 Mar 2026 16:07:32 -0700 Subject: [PATCH 7/7] bump version and trigger snapshot for azure-diagnostics, sensei: improve azure-diagnostics frontmatter --- plugin/skills/azure-diagnostics/SKILL.md | 4 +-- .../__snapshots__/triggers.test.ts.snap | 28 ++++--------------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/plugin/skills/azure-diagnostics/SKILL.md b/plugin/skills/azure-diagnostics/SKILL.md index d3fdce67..3b950849 100644 --- a/plugin/skills/azure-diagnostics/SKILL.md +++ b/plugin/skills/azure-diagnostics/SKILL.md @@ -1,10 +1,10 @@ --- name: azure-diagnostics -description: "Debug and troubleshoot Azure Container Apps and Function Apps production issues, log analysis with KQL, health checks, and common issue resolution for image pulls, cold starts, health probes, and function invocation failures. WHEN: debug production issues, troubleshoot container apps, troubleshoot function apps, troubleshoot Azure Functions, analyze logs with KQL, fix image pull failures, investigate health probe failures, check resource health, view application logs, find root cause of errors, function app not working." +description: "Debug and troubleshoot Azure Container Apps and Function Apps production issues using log analysis, health checks, and KQL. WHEN: debug production issues, troubleshoot containerized apps, troubleshoot Azure Functions, analyze logs with KQL, fix image pull failures, investigate health probe failures, troubleshoot cold starts, check resource health, find root cause of errors, function not working." license: MIT metadata: author: Microsoft - version: "1.0.2" + version: "1.0.3" --- # Azure Diagnostics diff --git a/tests/azure-diagnostics/__snapshots__/triggers.test.ts.snap b/tests/azure-diagnostics/__snapshots__/triggers.test.ts.snap index 2f16b131..b9c3abd4 100644 --- a/tests/azure-diagnostics/__snapshots__/triggers.test.ts.snap +++ b/tests/azure-diagnostics/__snapshots__/triggers.test.ts.snap @@ -2,11 +2,10 @@ exports[`azure-diagnostics - Trigger Tests Trigger Keywords Snapshot skill description triggers match snapshot 1`] = ` { - "description": "Debug and troubleshoot production issues on Azure. Covers Container Apps and Function Apps diagnostics, log analysis with KQL, health checks, and common issue resolution for image pulls, cold starts, health probes, and function invocation failures. WHEN: debug production issues, troubleshoot container apps, troubleshoot function apps, troubleshoot Azure Functions, analyze logs with KQL, fix image pull failures, resolve cold start issues, investigate health probe failures, check resource health, view application logs, find root cause of errors, function app not working, function invocation failures.", + "description": "Debug and troubleshoot Azure Container Apps and Function Apps production issues using log analysis, health checks, and KQL. WHEN: debug production issues, troubleshoot containerized apps, troubleshoot Azure Functions, analyze logs with KQL, fix image pull failures, investigate health probe failures, troubleshoot cold starts, check resource health, find root cause of errors, function not working.", "extractedKeywords": [ "analysis", "analyze", - "application", "apps", "azure", "cause", @@ -14,9 +13,8 @@ exports[`azure-diagnostics - Trigger Tests Trigger Keywords Snapshot skill descr "checks", "cli", "cold", - "common", "container", - "covers", + "containerized", "debug", "diagnostic", "diagnostics", @@ -28,25 +26,18 @@ exports[`azure-diagnostics - Trigger Tests Trigger Keywords Snapshot skill descr "health", "image", "investigate", - "invocation", - "issue", "issues", "logs", "mcp", "monitor", "probe", - "probes", "production", "pull", - "pulls", - "resolution", - "resolve", "resource", "root", - "start", "starts", "troubleshoot", - "view", + "using", "when", "with", "working", @@ -59,7 +50,6 @@ exports[`azure-diagnostics - Trigger Tests Trigger Keywords Snapshot skill keywo [ "analysis", "analyze", - "application", "apps", "azure", "cause", @@ -67,9 +57,8 @@ exports[`azure-diagnostics - Trigger Tests Trigger Keywords Snapshot skill keywo "checks", "cli", "cold", - "common", "container", - "covers", + "containerized", "debug", "diagnostic", "diagnostics", @@ -81,25 +70,18 @@ exports[`azure-diagnostics - Trigger Tests Trigger Keywords Snapshot skill keywo "health", "image", "investigate", - "invocation", - "issue", "issues", "logs", "mcp", "monitor", "probe", - "probes", "production", "pull", - "pulls", - "resolution", - "resolve", "resource", "root", - "start", "starts", "troubleshoot", - "view", + "using", "when", "with", "working",