diff --git a/plugin/skills/azure-diagnostics/SKILL.md b/plugin/skills/azure-diagnostics/SKILL.md index bdf3f308d..3b9508493 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 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." license: MIT metadata: author: Microsoft - version: "1.0.2" + version: "1.0.3" --- # Azure Diagnostics diff --git a/plugin/skills/azure-upgrade/SKILL.md b/plugin/skills/azure-upgrade/SKILL.md new file mode 100644 index 000000000..23ebfd631 --- /dev/null +++ b/plugin/skills/azure-upgrade/SKILL.md @@ -0,0 +1,76 @@ +--- +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 + +> 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 000000000..a386be528 --- /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 000000000..e04ec3e64 --- /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 000000000..ee8f2cffb --- /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 000000000..2d5f35b8f --- /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 000000000..054cd0a8d --- /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 diff --git a/tests/azure-diagnostics/__snapshots__/triggers.test.ts.snap b/tests/azure-diagnostics/__snapshots__/triggers.test.ts.snap index 2f16b1314..b9c3abd4f 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", 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 000000000..8b8f25e9f --- /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/integration.test.ts b/tests/azure-upgrade/integration.test.ts new file mode 100644 index 000000000..9c2db9cb4 --- /dev/null +++ b/tests/azure-upgrade/integration.test.ts @@ -0,0 +1,88 @@ +/** + * Integration Tests for azure-upgrade + * + * 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 + */ + +import { + shouldSkipIntegrationTests, + getIntegrationSkipReason, + useAgentRunner, +} from "../utils/agent-runner"; +import { softCheckSkill, shouldEarlyTerminateForSkillInvocation } from "../utils/evaluate"; + +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; + +describeIntegration(`${SKILL_NAME}_ - Integration Tests`, () => { + const agent = useAgentRunner(); + + 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) + }); + + 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; + } + }); + + 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) + }); + + 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; + } + }); + }); + +}); diff --git a/tests/azure-upgrade/triggers.test.ts b/tests/azure-upgrade/triggers.test.ts new file mode 100644 index 000000000..c051a765c --- /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 000000000..cbd38e76c --- /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 f74e02904..a6dd4ea0e 100644 --- a/tests/skills.json +++ b/tests/skills.json @@ -18,6 +18,7 @@ "azure-resource-lookup", "azure-resource-visualizer", "azure-storage", + "azure-upgrade", "azure-validate", "entra-app-registration", "microsoft-foundry" @@ -25,6 +26,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-quotas,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-quotas,azure-rbac,azure-resource-lookup,azure-resource-visualizer,azure-storage,azure-upgrade,azure-validate,entra-app-registration" } } \ No newline at end of file