SKILL DETAIL
azure-upgrade
microsoft/azure-skills/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 to Flex Consumption), cross-service migrations (e.g., App Service to Container Apps), and SKU changes. It also covers Azure SDK for Java source-code modernization (e.g., legacy com.microsoft.azure.* to modern com.azure.*). This is not for cross-cloud migration; use azure-cloud-migrate for that. The skill follows a phased workflow: identify the source and target plans/SKUs, assess upgrade readiness, collect existing app settings, execute the upgrade steps, validate the app is reachable, and ask the user for next steps. It uses MCP tools to fetch Azure best practices and documentation, and follows scenario-specific references, such as upgrading Azure Functions from Consumption to Flex Consumption or migrating Azure Cache for Redis to Azure Managed Redis.
Installation
npx skills add https://github.com/microsoft/azure-skills --skill azure-upgrade
スキルファイル
SKILL.md
最終同期 · 2026/10/22
references/global-rules.md›
# 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_azure_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`
## Artifact Output Policy
> All migration artifacts (assessment reports, status files, downloaded packages, transient JSON used to transfer settings) must be written under a single, scoped directory — **never** loose in the workspace root.
### Placeholder
| Placeholder | Resolves to |
|---|---|
| `<UPGRADE_DIR>` | `<workspace-root>/.azure-upgrade/<scenario-id>/` (override: `$env:AZURE_UPGRADE_DIR` on PowerShell, `$AZURE_UPGRADE_DIR` on bash) |
`<scenario-id>` is defined by each scenario reference:
| Scenario | `<scenario-id>` |
|---|---|
| Functions Consumption → Flex (Linux or Windows) | source function-app name (e.g. `mathewc-test-isolated`) |
| Java SDK modernization | uses its own location: `.github/java-upgrade/{RUN_ID}/` *(historical — TODO: converge to `<UPGRADE_DIR>` in a future pass)* |
| Redis (ACR/ACRE) → AMR | n/a — routes to external skills, this policy does not apply |
### Rules
1. **Resolve `<UPGRADE_DIR>` once at session start.** Honor `$AZURE_UPGRADE_DIR` if set; otherwise use `.azure-upgrade/<scenario-id>/` under the current workspace root. Create the directory if missing.
2. **Announce the chosen directory to the user before the first write**, e.g. *"I'll save migration artifacts under `.azure-upgrade/<scenario-id>/`. Say so if you'd prefer a different location."* — non-blocking, same pattern as naming/storage choices in the scenario references.
3. **Add `.azure-upgrade/` to `.gitignore`** if the workspace is a git repo and the entry is missing. Append once; never modify other entries.
4. **Pass explicit paths to all download/write commands** (`--file <UPGRADE_DIR>/...`, `--dest <UPGRADE_DIR>`, `> <UPGRADE_DIR>/...`). Do not rely on the current working directory — terminal sessions reset cwd between calls.
5. **Clean up transient artifacts after the operation that produced them succeeds.** Generated files and downloaded packages may contain user code or interim state — delete them once they have been applied. If the operation fails, leave retryable non-secret artifacts in place so the user does not need to recreate or re-fetch them.
6. **Never persist raw secret values to disk — anywhere, including `<UPGRADE_DIR>`.** Connection strings, keys, passwords, and secret-bearing app settings must stay in scoped process memory only. Suppress command output and clear variables immediately after use. `<UPGRADE_DIR>` is for migration artifacts users already handle as part of normal app development — not raw secrets.
references/languages/java/bom-migration/bom-gradle-settings.md›
# BOM Migration — Gradle Programmatic Version Catalog (`settings.gradle`)
Some projects define version catalogs programmatically in `settings.gradle` / `settings.gradle.kts` instead of using a TOML file. OpenRewrite does not support this either ([openrewrite/rewrite#4852](https://github.com/openrewrite/rewrite/issues/4852)). Handle manually.
Use `TARGET_AZURE_SDK_BOM_VERSION` resolved in the workflow. Do not keep an existing programmatic catalog BOM version unless it exactly matches that value.
## Step 0 — Detect programmatic catalog usage
Look for a `dependencyResolutionManagement` block in `settings.gradle` or `settings.gradle.kts`:
```groovy
// settings.gradle (Groovy DSL)
dependencyResolutionManagement {
versionCatalogs {
libs {
version("azureSdk", "1.41.4")
version("azureStorage", "8.6.6")
library("azure", "com.microsoft.azure", "azure").versionRef("azureSdk")
library("azure-storage", "com.microsoft.azure", "azure-storage").versionRef("azureStorage")
}
}
}
```
```kotlin
// settings.gradle.kts (Kotlin DSL)
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
version("azureSdk", "1.41.4")
version("azureStorage", "8.6.6")
library("azure", "com.microsoft.azure", "azure").versionRef("azureSdk")
library("azure-storage", "com.microsoft.azure", "azure-storage").versionRef("azureStorage")
}
}
}
```
The `library()` call can also use the two-arg `module` form (the same syntax applies to Kotlin DSL):
```groovy
library("azure", "com.microsoft.azure:azure").versionRef("azureSdk")
library("azure-inline", "com.microsoft.azure:azure").version("1.41.4")
```
If the project has this pattern and contains Azure dependency entries, use this section. The same `libs.<alias>` accessor syntax is used in `build.gradle` as with TOML catalogs.
## Step 1 — Add or upgrade the BOM
Add a version and library entry for the BOM inside the `versionCatalogs` block:
```groovy
// Groovy DSL
dependencyResolutionManagement {
versionCatalogs {
libs {
version("azureSdkBom", "{bom_version}")
library("azure-sdk-bom", "com.azure", "azure-sdk-bom").versionRef("azureSdkBom")
}
}
}
```
```kotlin
// Kotlin DSL
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
version("azureSdkBom", "{bom_version}")
library("azure-sdk-bom", "com.azure", "azure-sdk-bom").versionRef("azureSdkBom")
}
}
}
```
If a BOM entry already exists, update the version string.
Add the platform dependency (if not already present).
Groovy DSL (`build.gradle`):
```groovy
dependencies {
implementation enforcedPlatform(libs.azure.sdk.bom)
}
```
Kotlin DSL (`build.gradle.kts`):
```kotlin
dependencies {
implementation(enforcedPlatform(libs.azure.sdk.bom))
}
```
## Step 2 — Remove explicit versions from BOM-managed Azure libraries
For each modern Azure library (`com.azure.*`) managed by the BOM, change its catalog entry to remove the version. Use the `withoutVersion()` call:
The snippets below show Groovy DSL (`settings.gradle`); the same code applies to Kotlin DSL (`settings.gradle.kts`).
Before:
```groovy
library("azure-identity", "com.azure", "azure-identity").versionRef("azureIdentity")
```
After:
```groovy
library("azure-identity", "com.azure", "azure-identity").withoutVersion()
```
For the two-arg module form (the same syntax applies to Kotlin DSL):
```groovy
// Before
library("azure-identity", "com.azure:azure-identity").versionRef("azureIdentity")
// After
library("azure-identity", "com.azure:azure-identity").withoutVersion()
```
Then remove any orphaned `version(...)` calls that are no longer referenced.
## Step 3 — Replace legacy Azure library entries with modern equivalents
For each legacy `com.microsoft.azure.*` library call:
1. Replace the group and artifact with the modern `com.azure.*` equivalent.
2. Change `.versionRef(...)` or `.version(...)` to `.withoutVersion()` if the new artifact is managed by the BOM.
3. Remove orphaned `version(...)` calls.
4. If you rename the alias (first argument), update **all** references in `build.gradle` / `build.gradle.kts` and any `bundle(...)` calls.
The snippets below show Groovy DSL (`settings.gradle`); the same code applies to Kotlin DSL (`settings.gradle.kts`).
Before:
```groovy
version("azureSdk", "1.41.4")
version("azureStorage", "8.6.6")
library("azure", "com.microsoft.azure", "azure").versionRef("azureSdk")
library("azure-storage", "com.microsoft.azure", "azure-storage").versionRef("azureStorage")
```
After:
```groovy
library("azure-resourcemanager", "com.azure.resourcemanager", "azure-resourcemanager").withoutVersion()
library("azure-storage-blob", "com.azure", "azure-storage-blob").withoutVersion()
```
Then update `build.gradle` (Groovy DSL):
```groovy
// Before
implementation libs.azure
implementation libs.azure.storage
// After
implementation libs.azure.resourcemanager
implementation libs.azure.storage.blob
```
Or `build.gradle.kts` (Kotlin DSL):
```kotlin
// Before
implementation(libs.azure)
implementation(libs.azure.storage)
// After
implementation(libs.azure.resourcemanager)
implementation(libs.azure.storage.blob)
```
## Step 4 — Handle bundles
If `bundle(...)` calls reference any renamed or removed aliases, update them:
Groovy DSL (`settings.gradle`):
```groovy
// Before
bundle("azureLibs", ["azure", "azure-storage"])
// After
bundle("azureLibs", ["azure-resourcemanager", "azure-storage-blob", "azure-identity"])
```
Kotlin DSL (`settings.gradle.kts`):
```kotlin
// Before
bundle("azureLibs", listOf("azure", "azure-storage"))
// After
bundle("azureLibs", listOf("azure-resourcemanager", "azure-storage-blob", "azure-identity"))
```
## Expected settings.gradle after migration
Groovy DSL (`settings.gradle`):
```groovy
dependencyResolutionManagement {
versionCatalogs {
libs {
version("azureSdkBom", "{bom_version}")
library("azure-sdk-bom", "com.azure", "azure-sdk-bom").versionRef("azureSdkBom")
library("azure-identity", "com.azure", "azure-identity").withoutVersion()
library("azure-resourcemanager", "com.azure.resourcemanager", "azure-resourcemanager").withoutVersion()
}
}
}
```
Kotlin DSL (`settings.gradle.kts`):
```kotlin
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
version("azureSdkBom", "{bom_version}")
library("azure-sdk-bom", "com.azure", "azure-sdk-bom").versionRef("azureSdkBom")
library("azure-identity", "com.azure", "azure-identity").withoutVersion()
library("azure-resourcemanager", "com.azure.resourcemanager", "azure-resourcemanager").withoutVersion()
}
}
}
```
references/languages/java/bom-migration/bom-gradle-toml.md›
# BOM Migration — Gradle TOML Version Catalog (`libs.versions.toml`)
OpenRewrite does not yet support TOML version catalogs ([openrewrite/rewrite#4400](https://github.com/openrewrite/rewrite/issues/4400)). Handle manually.
Check for `gradle/libs.versions.toml`. If it exists and contains Azure entries, apply these steps. Always update `build.gradle` / `build.gradle.kts` references in tandem.
Use `TARGET_AZURE_SDK_BOM_VERSION` resolved in the workflow. Do not keep an existing TOML BOM version unless it exactly matches that value.
> 💡 **Tip:** TOML alias `azure-sdk-bom` becomes accessor `libs.azure.sdk.bom` (hyphens→dots). CamelCase `azureSdkBom` becomes `libs.azureSdkBom`. Match the project's existing convention.
## Step 1 — Add or upgrade the BOM
In `gradle/libs.versions.toml`:
```toml
[versions]
azureSdkBom = "{bom_version}"
[libraries]
azure-sdk-bom = { group = "com.azure", name = "azure-sdk-bom", version.ref = "azureSdkBom" }
```
In `build.gradle` (Groovy DSL):
```groovy
implementation enforcedPlatform(libs.azure.sdk.bom)
```
In `build.gradle.kts` (Kotlin DSL):
```kotlin
implementation(enforcedPlatform(libs.azure.sdk.bom))
```
## Step 2 — Remove explicit versions from BOM-managed libraries
For each `com.azure.*` library in `[libraries]` that the BOM manages, drop `version.ref` / `version` and remove orphaned `[versions]` entries.
```toml
# Before
[versions]
azureIdentity = "1.15.0"
[libraries]
azureIdentity = { group = "com.azure", name = "azure-identity", version.ref = "azureIdentity" }
# After
[libraries]
azureIdentity = { group = "com.azure", name = "azure-identity" }
```
## Step 3 — Replace legacy entries with modern equivalents
For each `com.microsoft.azure.*` library: replace `group`/`name` with the modern `com.azure.*` equivalent, drop `version.ref`/`version` if BOM-managed, remove orphaned `[versions]` entries, and update alias references in `build.gradle` and `[bundles]`.
```toml
# Before
[versions]
azureSdk = "1.41.4"
azureStorage = "8.6.6"
[libraries]
azure = { group = "com.microsoft.azure", name = "azure", version.ref = "azureSdk" }
azureStorage = { group = "com.microsoft.azure", name = "azure-storage", version.ref = "azureStorage" }
# After (BOM-managed)
[libraries]
azureResourcemanager = { group = "com.azure.resourcemanager", name = "azure-resourcemanager" }
azureStorageBlob = { group = "com.azure", name = "azure-storage-blob" }
```
Update `build.gradle` and `[bundles]` to use the new aliases.
## TOML patterns to recognise
Libraries may use any of these forms — handle all of them:
```toml
lib = { group = "g", name = "a", version.ref = "v" }
lib = { module = "g:a", version.ref = "v" }
lib = { module = "g:a", version = "1.0" }
lib = { group = "g", name = "a", version = { strictly = "1.0" } }
```
references/languages/java/bom-migration/bom-gradle.md›
# BOM Migration — Gradle Projects (No Version Catalogs)
> **Python/script availability**: The script below requires Python 3.10+. If `python3 --version` (or `python --version`) fails, or if `upgrade_bom.py` exits unsuccessfully, skip the script path and follow [Manual Fallback](#manual-fallback-no-python-or-script-failure) instead.
## Automated (Python available)
Run the `upgrade_bom.py` script located at `references/languages/java/scripts/upgrade_bom.py` (relative to this skill). It resolves the latest stable BOM version, auto-detects Gradle, and performs:
1. **Set/upgrade the BOM** — adds `enforcedPlatform("com.azure:azure-sdk-bom:...")` if missing, or upgrades the version.
2. **Remove redundant explicit versions** — strips inline version strings from Azure dependencies managed by the BOM.
The following invocation works identically in **bash** and **PowerShell**:
```bash
# Path is relative to the skill directory (plugin/skills/azure-upgrade/)
python3 ./references/languages/java/scripts/upgrade_bom.py <project_dir>
```
Options:
- `--gradle <cmd>` — override the Gradle command (default: auto-detects `gradlew` or `gradle`).
If the script fails after starting, treat that as an automation failure only: keep the resolved `TARGET_AZURE_SDK_BOM_VERSION`, manually apply the fallback steps below, and continue validation.
Under the hood (OpenRewrite recipes):
- **Add BOM**: `AddPlatformDependency` ([docs](https://docs.openrewrite.org/recipes/gradle/addplatformdependency))
- **Upgrade BOM**: `UpgradeDependencyVersion` ([docs](https://docs.openrewrite.org/recipes/gradle/upgradedependencyversion))
- **Remove redundant versions**: `RemoveRedundantDependencyVersions` ([docs](https://docs.openrewrite.org/recipes/gradle/removeredundantdependencyversions))
> ⚠️ **Warning:** The script does **not** support Gradle version catalogs — neither TOML files nor programmatic `settings.gradle` catalogs. If the project uses either, follow [TOML catalog steps](./bom-gradle-toml.md) or [programmatic catalog steps](./bom-gradle-settings.md) instead.
## Expected build.gradle after migration
Groovy DSL (`build.gradle`):
```groovy
dependencies {
implementation enforcedPlatform("com.azure:azure-sdk-bom:{bom_version}")
implementation "com.azure:azure-identity"
implementation "com.azure.resourcemanager:azure-resourcemanager"
}
```
Kotlin DSL (`build.gradle.kts`):
```kotlin
dependencies {
implementation(enforcedPlatform("com.azure:azure-sdk-bom:{bom_version}"))
implementation("com.azure:azure-identity")
implementation("com.azure.resourcemanager:azure-resourcemanager")
}
```
## Manual Fallback (no Python or script failure)
When Python is unavailable or `upgrade_bom.py` fails, edit `build.gradle` (or `build.gradle.kts`) directly. Apply the same two steps as the script.
### Step 1 — Add or upgrade the BOM platform
Inside the `dependencies { }` block, add or update the `enforcedPlatform` line for `azure-sdk-bom`:
Groovy DSL:
```groovy
dependencies {
implementation enforcedPlatform("com.azure:azure-sdk-bom:{bom_version}")
// ...other dependencies...
}
```
Kotlin DSL:
```kotlin
dependencies {
implementation(enforcedPlatform("com.azure:azure-sdk-bom:{bom_version}"))
// ...other dependencies...
}
```
- **If the line exists**: update only the version to `{bom_version}`.
- **If the line is missing**: insert it at the top of the `dependencies` block.
- Use the same configuration (`implementation`, `api`, `compileOnly`, etc.) the project already uses for Azure deps. Repeat the platform line per configuration if needed.
- **Multi-project build**: add the platform line in every subproject that declares Azure dependencies, or apply it once via a shared `subprojects { }` / convention plugin.
### Step 2 — Remove redundant explicit versions
For every Azure dependency whose group starts with `com.azure` and is managed by the BOM (verify against `https://repo1.maven.org/maven2/com/azure/azure-sdk-bom/{bom_version}/azure-sdk-bom-{bom_version}.pom`), strip the version coordinate.
Groovy DSL — string notation:
```groovy
// Before
implementation "com.azure:azure-identity:1.13.0"
// After
implementation "com.azure:azure-identity"
```
Groovy DSL — map notation:
```groovy
// Before
implementation group: "com.azure", name: "azure-identity", version: "1.13.0"
// After
implementation group: "com.azure", name: "azure-identity"
```
Kotlin DSL:
```kotlin
// Before
implementation("com.azure:azure-identity:1.13.0")
// After
implementation("com.azure:azure-identity")
```
Do **not** strip versions from artifacts that are not managed by the BOM.
### Step 3 — Verify
Run the Gradle wrapper to inspect the resolved classpath. Use the form appropriate for your shell:
```bash
# bash / macOS / Linux
./gradlew dependencies --configuration runtimeClasspath
```
```powershell
# PowerShell on Windows
.\gradlew.bat dependencies --configuration runtimeClasspath
```
Then confirm:
- The platform `com.azure:azure-sdk-bom:{bom_version}` appears.
- `{bom_version}` equals `TARGET_AZURE_SDK_BOM_VERSION`.
- All BOM-managed Azure artifacts resolve to versions sourced from the BOM.
Then continue with the validation checklist in [bom-validation.md](./bom-validation.md).
references/languages/java/bom-migration/bom-maven.md›
# BOM Migration — Maven Projects
> **Python/script availability**: The script below requires Python 3.10+. If `python3 --version` (or `python --version`) fails, or if `upgrade_bom.py` exits unsuccessfully, skip the script path and follow [Manual Fallback](#manual-fallback-no-python-or-script-failure) instead.
## Automated (Python available)
Run the `upgrade_bom.py` script located at `references/languages/java/scripts/upgrade_bom.py` (relative to this skill). It resolves the latest stable BOM version, auto-detects Maven, and performs two steps:
1. **Set/upgrade the BOM** — adds `azure-sdk-bom` if missing, or upgrades the version if already present.
2. **Remove redundant explicit versions** — strips explicit `<version>` tags from individual Azure dependencies that are now managed by the BOM.
The following invocation works identically in **bash** and **PowerShell**:
```bash
# Path is relative to the skill directory (plugin/skills/azure-upgrade/)
python3 ./references/languages/java/scripts/upgrade_bom.py <project_dir>
```
Options:
- `--mvn <cmd>` — override the Maven command (default: auto-detects `mvnw` or `mvn`).
If the script fails after starting, treat that as an automation failure only: keep the resolved `TARGET_AZURE_SDK_BOM_VERSION`, manually apply the fallback steps below, and continue validation.
Under the hood (OpenRewrite recipes):
- **Add BOM**: `AddManagedDependency` ([docs](https://docs.openrewrite.org/recipes/maven/addmanageddependency))
- **Upgrade BOM**: `UpgradeDependencyVersion` ([docs](https://docs.openrewrite.org/recipes/maven/upgradedependencyversion))
- **Remove redundant versions**: `RemoveRedundantDependencyVersions` ([docs](https://docs.openrewrite.org/recipes/maven/removeredundantdependencyversions))
## Expected pom.xml after migration
```xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-sdk-bom</artifactId>
<version>{bom_version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
</dependency>
<dependency>
<groupId>com.azure.resourcemanager</groupId>
<artifactId>azure-resourcemanager</artifactId>
</dependency>
</dependencies>
```
## Manual Fallback (no Python or script failure)
When Python is unavailable or `upgrade_bom.py` fails, edit `pom.xml` directly. Apply the same two steps as the script:
### Step 1 — Add or upgrade `azure-sdk-bom`
Locate the `<dependencyManagement><dependencies>` block (create it inside `<project>` if absent). Add or update the BOM entry:
```xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-sdk-bom</artifactId>
<version>{bom_version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- keep any other managed dependencies here -->
</dependencies>
</dependencyManagement>
```
- **If the entry exists**: update only the `<version>` value to `{bom_version}`.
- **If the entry is missing**: insert the full `<dependency>` block above. Preserve any other existing managed dependencies.
- **Multi-module project**: add the BOM in the parent (aggregator) `pom.xml` only. Child modules inherit it.
### Step 2 — Remove redundant explicit versions
For every `<dependency>` whose `<groupId>` starts with `com.azure` (e.g. `com.azure`, `com.azure.resourcemanager`, `com.azure.spring`), check whether the BOM manages it (see the BOM POM at `https://repo1.maven.org/maven2/com/azure/azure-sdk-bom/{bom_version}/azure-sdk-bom-{bom_version}.pom`). If managed:
- Remove the `<version>` element entirely.
- Leave `<groupId>`, `<artifactId>`, `<scope>`, `<classifier>`, `<exclusions>`, etc. unchanged.
Before:
```xml
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.13.0</version>
</dependency>
```
After:
```xml
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
</dependency>
```
Do **not** strip versions from artifacts not managed by the BOM (verify each one against the BOM POM).
### Step 3 — Verify
Run `mvn -q -DskipTests dependency:tree` (the same command works in both **bash** and **PowerShell**) and confirm:
- `com.azure:azure-sdk-bom:pom:{bom_version}:import` appears in the managed dependencies and `{bom_version}` equals `TARGET_AZURE_SDK_BOM_VERSION`.
- All BOM-managed Azure artifacts resolve to versions from `{bom_version}`.
Then continue with the validation checklist in [bom-validation.md](./bom-validation.md).
references/languages/java/bom-migration/bom-migration.md›
# BOM Migration Guide
How to add or upgrade `azure-sdk-bom` and clean up redundant versions across all supported build configurations.
## Prerequisite — Python availability check
The Maven and plain-Gradle flows are automated by `scripts/upgrade_bom.py` (Python 3.10+). The script resolves the latest BOM internally; do not pass or pin a literal BOM version. Before picking a guide, verify Python is available:
The following check works in both **bash** and **PowerShell 7+** (the `||` operator is supported in both):
```bash
python3 --version || python --version
```
For Windows PowerShell 5.1, use:
```powershell
python3 --version; if ($LASTEXITCODE -ne 0) { python --version }
```
- **Python available** → use the script as documented in [bom-maven.md](./bom-maven.md) / [bom-gradle.md](./bom-gradle.md). If `upgrade_bom.py` fails for any reason, do not stop the migration; manually resolve the BOM version and follow the same guide's **Manual Fallback** section.
- **Python NOT available** → follow the **Manual Fallback** section in the same guide. Do not attempt to install Python; perform the edits by hand.
The TOML and programmatic-catalog guides ([bom-gradle-toml.md](./bom-gradle-toml.md), [bom-gradle-settings.md](./bom-gradle-settings.md)) are manual-only and unaffected by Python availability.
## Determine the latest BOM version
Resolve the target `azure-sdk-bom` version from the Azure SDK for Java source of truth before editing build files. This is mandatory: do not hardcode, guess, pin, or reuse an illustrative version from another example. Existing versions are accepted only when they exactly match the resolved latest stable version.
Always perform this resolution at the time the migration plan is generated. Never derive `TARGET_AZURE_SDK_BOM_VERSION` from the application being migrated, from a previously generated plan, from package-specific examples, or from model memory. If a resolved version differs from an existing `azure-sdk-bom` in the project, the existing BOM is stale and must be upgraded.
The following invocation works identically in **bash** and **PowerShell** (no shell-specific syntax):
```bash
# Path is relative to the skill directory (plugin/skills/azure-upgrade/)
python3 ./references/languages/java/scripts/upgrade_bom.py --get-latest-version
# or: python ./references/languages/java/scripts/upgrade_bom.py --get-latest-version
```
If Python is not available or the `--get-latest-version` command fails, fetch `https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/boms/azure-sdk-bom/pom.xml` directly and use the `<version>` value declared in that BOM `pom.xml`.
Do not continue until you have resolved that latest stable version explicitly. Add only that value to the plan's `Guidelines` section as `TARGET_AZURE_SDK_BOM_VERSION = <resolved-version>`.
## Decision Tree
```
Is the project Maven?
├─ YES → Maven projects (bom-maven.md)
└─ NO (Gradle)
├─ Does gradle/libs.versions.toml exist with Azure entries?
│ └─ YES → TOML catalog steps (bom-gradle-toml.md)
├─ Does settings.gradle define a programmatic versionCatalogs block with Azure entries?
│ └─ YES → Programmatic catalog steps (bom-gradle-settings.md)
└─ Neither (plain build.gradle dependencies)
└─ Plain Gradle projects (bom-gradle.md)
```
> 💡 **Tip:** To check which artifacts are managed by the BOM, fetch
> `https://repo1.maven.org/maven2/com/azure/azure-sdk-bom/{bom_version}/azure-sdk-bom-{bom_version}.pom`
> and look for `<dependency>` entries.
## Build-System Guides
| Build system | Guide |
| ----------------------------- | -------------------------------------------------- |
| Maven | [bom-maven.md](./bom-maven.md) |
| Gradle (no version catalog) | [bom-gradle.md](./bom-gradle.md) |
| Gradle + TOML version catalog | [bom-gradle-toml.md](./bom-gradle-toml.md) |
| Gradle + programmatic catalog | [bom-gradle-settings.md](./bom-gradle-settings.md) |
## Validation
See the [Validation Checklist](./bom-validation.md) — covers all build systems including TOML and programmatic `settings.gradle` catalogs.
references/languages/java/bom-migration/bom-validation.md›
# BOM Migration — Validation Checklist
After BOM migration, verify:
- [ ] Project compiles successfully.
- [ ] No legacy `com.microsoft.azure.*` dependencies remain anywhere (pom.xml, build.gradle, TOML, settings.gradle).
- [ ] BOM-managed Azure libraries have **no** explicit version (no `<version>` tag, no version string, no `version.ref`, no `.versionRef()`).
- [ ] Re-resolve the latest stable `azure-sdk-bom` from the Azure SDK for Java source of truth and confirm the plan guideline `TARGET_AZURE_SDK_BOM_VERSION` matches it exactly.
- [ ] Every BOM version in the migrated project equals `TARGET_AZURE_SDK_BOM_VERSION`; no BOM version was copied from the original project, examples, prior plans, or model memory.
- [ ] No stale `azure-sdk-bom` version remains.
## Additional checks for TOML version catalog projects
- [ ] No orphaned entries in `[versions]` (every version key must be referenced by at least one library or plugin).
- [ ] `[bundles]` aliases match current `[libraries]` aliases (no stale references).
- [ ] `build.gradle` uses `libs.<alias>` references — no raw `"group:artifact:version"` strings for Azure libraries.
## Additional checks for programmatic settings.gradle catalog projects
- [ ] No orphaned `version(...)` calls (every version must be referenced by at least one `library` or `plugin` entry).
- [ ] `bundle(...)` aliases match current `library(...)` aliases (no stale references).
- [ ] `build.gradle` uses `libs.<alias>` references — no raw `"group:artifact:version"` strings for Azure libraries.
references/languages/java/INSTRUCTION.md›
# Azure SDK Migration Guidelines
## Context
The application is identified using legacy Azure SDKs for Java (`com.microsoft.azure.*`). These libraries reached end of support in 2023. They are not recommended for use in production, should be migrated to the latest Azure SDKs with the latest security patches and new capabilities support.
Follow these steps:
- **Inventory legacy dependencies**: Use tools such as `mvn dependency:tree` or `gradlew dependencies` to find every `com.microsoft.azure.*` SDK and map each one to its modern counterpart under `com.azure.*`. Do **not** rely solely on the root reactor — also grep the entire repository for legacy coordinates so you catch build files that aren't reachable from the root project. Run from the repo root:
```bash
# Find every file referencing legacy groupIds/artifacts, including CI, samples, parent poms, buildSrc, version catalogs, Dockerfiles, and docs.
grep -RIn --exclude-dir={.git,target,build,node_modules,out} \
-E 'com\.microsoft\.azure(\.|:)|microsoft-azure-|azure-eventhubs-eph|azure-keyvault(:|["'\''])' .
```
PowerShell equivalent (run from repo root):
```powershell
Get-ChildItem -Path . -Recurse -File |
Where-Object { $_.FullName -notmatch '(\\|/)(\.git|target|build|node_modules|out)(\\|/)' } |
Select-String -Pattern 'com\.microsoft\.azure(\.|:)|microsoft-azure-|azure-eventhubs-eph|azure-keyvault(:|["''])'
```
Commonly overlooked locations:`.ci/**/pom.xml`, `ci/**`, parent/BOM poms, `buildSrc/`, `gradle/libs.versions.toml`, `settings.gradle(.kts)`, `archetype-resources/`, sample sub-modules, Dockerfiles, shell/PowerShell scripts, and README snippets. Every hit must end up on the migration file list.
- **Adopt supported SDKs**: Replace the legacy dependencies with their modern equivalents in your `pom.xml` or `build.gradle`, following the migration guide to align feature parity and new SDK names.
- **Update application code**: Refactor your code to the builder-based APIs, updated authentication flows (Azure Identity), and modern async or reactive patterns required by the latest SDKs. Add concise comments explaining non-obvious changes.
- **Test thoroughly**: Run unit, integration, and end-to-end tests to validate that the modern SDKs behave as expected, focusing on authentication, retry, and serialization differences.
## Migration Guide
### Assumption
- Project is Maven or Gradle.
- Java code is on JDK 8 or above.
### Migrate dependencies (Add them to plan guidelines when generating plan)
Immediately load and read [BOM Migration Guide](./bom-migration/bom-migration.md) before choosing `TARGET_AZURE_SDK_BOM_VERSION`; do not rely on this summary alone. The guide covers how to determine the latest BOM version, plus Maven, plain Gradle, TOML version catalogs (`libs.versions.toml`), and programmatic version catalogs (`settings.gradle`).
When writing the plan's `Guidelines` section, use only the latest stable `azure-sdk-bom` resolved by the BOM guide; do **not** infer it from the project, reuse an existing BOM version, copy a value from examples, or trust model memory. Record it as `TARGET_AZURE_SDK_BOM_VERSION = <resolved-version>` and use that exact value throughout the migration.
### Migrate Java Code
- Make a list of source code/maven/gradle files that contains legacy SDK packages. Migrate each of them.
- Determine legacy SDK artifacts according to previous files, find suitable migration guides in [Package-Specific Migration Guides](#package-specific-migration-guides) and follow the guides whenever possible. Record which migration guide URL you used for each legacy package (e.g., in your plan or commit messages), so you can validate against them later.
- **Do not change the Java `package ...;` declaration at the top of each source file, and do not rename or move the source file's directory path to match a new SDK package structure.** Keep every `.java` file in its original directory; only update `import` statements and type usages inside the file body. For example, if a file lives in `src/main/java/com/microsoft/azure/eventprocessorhosts/Consumer.java` with `package com.microsoft.azure.eventprocessorhosts;`, it must stay in that exact directory and keep that exact package declaration — even though the modern SDK uses `com.azure.messaging.eventhubs`.
- Do not upgrade JDK version, if it is already JDK 8 or above.
- If there is test in the project, Java code there also need to be updated.
## Package-Specific Source Code Guidelines (Add them to plan guidelines when generating plan)
Use these package-specific references:
- [com.microsoft.azure.management.\*\*](./package-specific/com.microsoft.azure.management.md)
- [com.microsoft.azure.eventprocessorhost](./package-specific/com.microsoft.azure.eventprocessorhost.md)
## Validation
**Make sure**
- Migrated project pass compilation.
- All tests pass. Don't silently skip tests.
- The plan's `Guidelines` section records the freshly resolved latest stable BOM target exactly as `TARGET_AZURE_SDK_BOM_VERSION = <resolved-version>`; this value must not remain a placeholder, must not be copied from the original project, and must match the current Azure SDK for Java BOM source of truth at validation time.
- No legacy SDK dependencies/references exist. This is a **hard gate**, not a self-assessment — you must prove it by running the commands below from the repo root and showing they return zero hits. Do not declare migration complete until all three return empty:
```bash
# 1. Legacy groupId / artifact references in ANY text file (pom.xml, *.gradle, *.gradle.kts, libs.versions.toml, Dockerfile, *.sh, *.md, etc.)
grep -RIn --exclude-dir={.git,target,build,node_modules,out} \
-E 'com\.microsoft\.azure(\.|:)|microsoft-azure-|azure-eventhubs-eph|azure-keyvault(:|["'\''])' .
# 2. Legacy imports still in Java sources
grep -RIn --include='*.java' -E '^\s*import\s+com\.microsoft\.azure\.' .
# 3. Every pom.xml and *.gradle(.kts) file in the repo (not just the root reactor) — eyeball each for legacy coordinates
find . -type d \( -name .git -o -name target -o -name build -o -name node_modules \) -prune -o \
-type f \( -name 'pom.xml' -o -name '*.gradle' -o -name '*.gradle.kts' -o -name 'libs.versions.toml' \) -print
```
PowerShell equivalent (run from repo root):
```powershell
# 1. Legacy groupId / artifact references in ANY text file
Get-ChildItem -Path . -Recurse -File |
Where-Object { $_.FullName -notmatch '(\\|/)(\.git|target|build|node_modules|out)(\\|/)' } |
Select-String -Pattern 'com\.microsoft\.azure(\.|:)|microsoft-azure-|azure-eventhubs-eph|azure-keyvault(:|["''])'
# 2. Legacy imports still in Java sources
Get-ChildItem -Path . -Recurse -File -Filter *.java |
Where-Object { $_.FullName -notmatch '(\\|/)(\.git|target|build|node_modules|out)(\\|/)' } |
Select-String -Pattern '^\s*import\s+com\.microsoft\.azure\.'
# 3. Every pom.xml and *.gradle(.kts) file in the repo — eyeball each for legacy coordinates
Get-ChildItem -Path . -Recurse -File -Include 'pom.xml','*.gradle','*.gradle.kts','libs.versions.toml' |
Where-Object { $_.FullName -notmatch '(\\|/)(\.git|target|build|node_modules)(\\|/)' } |
Select-Object -ExpandProperty FullName
```
Pay special attention to files outside the root Maven/Gradle reactor— e.g. `.ci/**/pom.xml`, `ci/**`, `buildSrc/`, sample sub-modules, archetype resources — these are frequently missed because `mvn dependency:tree` on the root project never visits them.
- If azure-sdk-bom is used, ensure the BOM version exactly matches the resolved latest stable version recorded in the plan's `Guidelines` section as `TARGET_AZURE_SDK_BOM_VERSION` and there are **NO** explicit version dependencies for Azure libraries that are in azure-sdk-bom.
E.g. Instead of `implementation 'com.azure.resourcemanager:azure-resourcemanager:2.60.0'`, we should use `implementation 'com.azure.resourcemanager:azure-resourcemanager'`.
For Azure libraries in azure-sdk-bom, check https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/boms/azure-sdk-bom/pom.xml.
If the BOM version is missing, or differs from the resolved latest stable version, or individual Azure packages still have explicit versions that should be managed by the BOM, follow the appropriate section in [BOM Migration Guide](./bom-migration/bom-migration.md) to fix it.
- **Version catalog projects**: Follow the [BOM Validation Checklist](./bom-migration/bom-validation.md) — it covers TOML, programmatic `settings.gradle` catalogs, and plain Gradle.
- For each migration guide you recorded during migration:
1. Fetch and read the full content of the guide URL.
2. Identify the migrated source files that correspond to that guide's package.
3. Verify the migrated code follows the guide's recommended API replacements, class mappings, authentication patterns, and async/sync conventions.
4. Fix any deviations — do not just report them.
## Package-Specific Migration Guides
- [Migrate to `com.azure.resourcemanager.**` from `com.microsoft.azure.management.**`](https://aka.ms/java-track2-migration-guide)
- [Migrate to com.azure:azure-messaging-servicebus from com.microsoft.azure:azure-servicebus](https://aka.ms/azsdk/java/migrate/sb)
- [Migrate to azure-messaging-eventhubs from azure-eventhubs and azure-eventhubs-eph](https://aka.ms/azsdk/java/migrate/eh)
- [Migrate to `azure-messaging-eventgrid` from `microsoft-azure-eventgrid`](https://aka.ms/azsdk/java/migrate/eg)
- [Storage Blob Service SDK Migration Guide from 8.x to 12.x](https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/storage/azure-storage-blob/migrationGuides/V8_V12.md)
- [Storage Blob Service SDK Migration Guide from 10.x/11.x to 12.x](https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/storage/azure-storage-blob/migrationGuides/V10_V12.md)
- [Storage Queue Service SDK Migration Guide from 8.x to 12.x](https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/storage/azure-storage-queue/migrationGuides/V8_V12.md)
- [Storage File Share Service SDK Migration Guide from 8.x to 12.x](https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/storage/azure-storage-file-share/migrationGuides/V8_V12.md)
- [Migrate to azure-security-keyvault-secrets from azure-keyvault](https://aka.ms/azsdk/java/migrate/kv-secrets)
- [Migrate to azure-security-keyvault-keys from azure-keyvault](https://aka.ms/azsdk/java/migrate/kv-keys)
- [Migrate to azure-security-keyvault-certificates from azure-keyvault](https://aka.ms/azsdk/java/migrate/kv-cert)
- [Migrate to `Azure-Compute-Batch` from `Microsoft-Azure-Batch`](https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/batch/azure-compute-batch/MigrationGuide.md)
- [Migrate to `azure-ai-documentintelligence` from `azure-ai-formrecognizer`](https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/documentintelligence/azure-ai-documentintelligence/MIGRATION_GUIDE.md)
- [Migrate to `azure-ai-formrecognizer 4.0.0-beta.1 - above` from `azure-ai-formrecognizer 3.1.x - lower`](https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/formrecognizer/azure-ai-formrecognizer/migration-guide.md)
- [Migration Guide from Azure OpenAI Java SDK to OpenAI Java SDK](https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/openai/azure-ai-openai-stainless/MIGRATION.md)
- [Migrate to azure-monitor-query from azure-loganalytics and azure-applicationinsights-query](https://aka.ms/azsdk/java/migrate/monitorquery)
references/languages/java/package-specific/com.microsoft.azure.eventprocessorhost.md›
# com.microsoft.azure.eventprocessorhost
## Hard Rules (MUST follow)
- **MUST** replace any legacy `InMemoryCheckpointManager` / `InMemoryLeaseManager` and any `EventProcessorHost` checkpoint/lease usage with `BlobCheckpointStore` backed by Azure Blob Storage. This is the **only** acceptable replacement.
- **MUST NOT** create a class named `InMemoryCheckpointStore`, copy/inline `SampleCheckpointStore`, or implement any custom in-memory `CheckpointStore`. The modern SDK intentionally omits an in-memory implementation due to production issues.
- **MUST NOT** introduce any other custom `CheckpointStore` implementation as part of this migration. If the legacy code stored checkpoints in memory, switch to `BlobCheckpointStore` — do not preserve the in-memory semantics.
- If the user explicitly asks for an in-memory implementation, refuse and direct them to the upstream `SampleCheckpointStore` reference instead of generating one.
> ⚠️ **Important:** The switch from `InMemoryCheckpointManager`/`InMemoryLeaseManager` to `BlobCheckpointStore` is an **expected and necessary behavioral change**. It is inherent to the modern SDK design and **must not block the migration from completing**. Do not treat this behavioral difference as a migration defect or attempt to preserve in-memory semantics — proceed with `BlobCheckpointStore` and complete the migration.
## Code Samples
### EventProcessorHost with InMemoryCheckpointManager / InMemoryLeaseManager
Replace usage of `InMemoryCheckpointManager` / `InMemoryLeaseManager` with [BlobCheckpointStore](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/eventhubs/azure-messaging-eventhubs-checkpointstore-blob/src/main/java/com/azure/messaging/eventhubs/checkpointstore/blob/BlobCheckpointStore.java). The modern EventProcessorClient does **not** ship an in-memory checkpoint store, and you must not create one.
Legacy code:
```java
InMemoryCheckpointManager checkpointManager = new InMemoryCheckpointManager();
InMemoryLeaseManager leaseManager = new InMemoryLeaseManager();
EventProcessorHost host = new EventProcessorHost(
hostName, eventHubName, consumerGroupName,
eventHubConnectionString, checkpointManager, leaseManager);
host.registerEventProcessor(MyEventProcessor.class).get();
```
Migrated code — replace with `BlobCheckpointStore` backed by Azure Blob Storage:
```java
BlobContainerAsyncClient blobContainerClient = new BlobContainerClientBuilder()
.connectionString(storageConnectionString)
.containerName(storageContainerName)
.buildAsyncClient();
// BlobCheckpointStore is the only supported replacement for InMemoryCheckpointManager/InMemoryLeaseManager.
// Do NOT create an InMemoryCheckpointStore or copy SampleCheckpointStore — there is no in-memory store in the modern SDK.
EventProcessorClient eventProcessorClient = new EventProcessorClientBuilder()
.connectionString(eventHubConnectionString, eventHubName)
.consumerGroup(consumerGroupName)
.checkpointStore(new BlobCheckpointStore(blobContainerClient))
.processEvent(eventContext -> {
// Process event and checkpoint
eventContext.updateCheckpoint();
})
.processError(errorContext -> {
System.err.printf("Error in partition %s: %s%n",
errorContext.getPartitionContext().getPartitionId(),
errorContext.getThrowable().getMessage());
})
.buildEventProcessorClient();
eventProcessorClient.start();
```
> ⚠️ **Warning:** Add dependency `com.azure:azure-messaging-eventhubs-checkpointstore-blob` to the project when using `BlobCheckpointStore`.
### EventProcessorHost with Azure Storage checkpoint/lease
Legacy code using the built-in storage-backed checkpoint/lease:
```java
EventProcessorHost host = EventProcessorHost.EventProcessorHostBuilder
.newBuilder(hostName, consumerGroupName)
.useAzureStorageCheckpointLeaseManager(storageConnectionString, storageContainerName, null)
.useEventHubConnectionString(eventHubConnectionString, eventHubName)
.build();
host.registerEventProcessor(MyEventProcessor.class).get();
```
Migrated code:
```java
BlobContainerAsyncClient blobContainerClient = new BlobContainerClientBuilder()
.connectionString(storageConnectionString)
.containerName(storageContainerName)
.buildAsyncClient();
EventProcessorClient eventProcessorClient = new EventProcessorClientBuilder()
.connectionString(eventHubConnectionString, eventHubName)
.consumerGroup(consumerGroupName)
.checkpointStore(new BlobCheckpointStore(blobContainerClient))
.processEvent(eventContext -> {
// Process event and checkpoint
eventContext.updateCheckpoint();
})
.processError(errorContext -> {
System.err.printf("Error in partition %s: %s%n",
errorContext.getPartitionContext().getPartitionId(),
errorContext.getThrowable().getMessage());
})
.buildEventProcessorClient();
eventProcessorClient.start();
```
### Required imports for migrated code
```java
import com.azure.messaging.eventhubs.EventProcessorClient;
import com.azure.messaging.eventhubs.EventProcessorClientBuilder;
import com.azure.messaging.eventhubs.checkpointstore.blob.BlobCheckpointStore;
import com.azure.storage.blob.BlobContainerAsyncClient;
import com.azure.storage.blob.BlobContainerClientBuilder;
```
### Required dependencies
Add these dependencies when migrating from `com.microsoft.azure:azure-eventhubs-eph`:
| Legacy Artifact | Modern Artifact |
|---|---|
| `com.microsoft.azure:azure-eventhubs-eph` | `com.azure:azure-messaging-eventhubs` |
| (included in above) | `com.azure:azure-messaging-eventhubs-checkpointstore-blob` |
references/languages/java/package-specific/com.microsoft.azure.management.md›
# com.microsoft.azure.management.\*\*
## Code Checklist
- Keep Azure resources, operations, and property values identical. The goal is functional equivalence, not feature expansion.
- Do not change the method sequence when creating or updating an Azure resource unless the new SDK requires it.
- Preserve the existing async pattern. For example, a delayed provisioning pattern that uses `Creatable<Resource>` should not be replaced by a direct `.create()` call. Similarly, when provisioning a resource, do not swap `.withNewDependencyResource` for `.withExistingDependencyResource` unless mandated by the new API surface.
- Keep the text emitted by logging and stdout/stderr unchanged to avoid breaking downstream consumers of those streams.
- Do not replace `resource.region()` with `resource.regionName()`; doing so changes the type from `Region` to `String` and can introduce subtle regressions.
## Code Samples
### Authentication with File
File-based authentication (e.g., `Azure.configure().authenticate(credentialFile)`) is **discouraged** under Azure's security-by-default posture: it relies on long-lived secrets stored on disk, which conflicts with the modern guidance to prefer managed identities, workload identity, or other credential types exposed by `DefaultAzureCredential`.
#### Detect: legacy file-based authentication patterns
Treat **any** of the following shapes in the legacy code as file-based authentication that must be replaced (not migrated). Triggers include `AZURE_AUTH_LOCATION`, `.authenticate(File)`, `ApplicationTokenCredentials.fromFile`, or any code path that reads `clientId` / `clientSecret` / `tenant` from disk and feeds them into a credential builder.
> **Important:** Reading `clientId`, `clientSecret`, or `tenantId` from **environment variables** (e.g., `System.getenv("AZURE_CLIENT_ID")`) does **not** constitute file-based authentication and should **not** be flagged by this rule. Only flag patterns that source credentials from files on disk.
```java
// Shape A: direct File overload
Azure azure = Azure.authenticate(new File(System.getenv("AZURE_AUTH_LOCATION")))
.withDefaultSubscription();
// Shape B: configure() chain with a File
File credFile = new File(authFilePath);
Azure azure = Azure.configure()
.withLogLevel(LogLevel.BASIC)
.authenticate(credFile)
.withDefaultSubscription();
// Shape C: ApplicationTokenCredentials.fromFile(...)
ApplicationTokenCredentials creds =
ApplicationTokenCredentials.fromFile(new File(authFile));
Azure azure = Azure.authenticate(creds).withSubscription(subscriptionId);
```
Do **not** emit a code sample that reproduces any of the above during the upgrade — including a "modernized" variant that parses the same file with Jackson and feeds it into `ClientSecretCredentialBuilder`. Instead, replace the authentication block in the migrated code with a `DefaultAzureCredential` (or another appropriate `TokenCredential`) and prepend a `TODO` comment that explains the change. For example:
```java
// TODO: The original code authenticated using a credential file (AZURE_AUTH_LOCATION),
// which is discouraged because it relies on long-lived secrets on disk and conflicts
// with Azure's security-by-default guidance. It has been replaced with
// DefaultAzureCredential. This change alters the authentication mechanism, so the
// resulting code path requires extra testing (local dev, CI, and target runtime
// identities) before it is considered production-ready.
TokenCredential credential = new DefaultAzureCredentialBuilder().build();
AzureProfile profile = new AzureProfile(AzureEnvironment.AZURE);
AzureResourceManager azure = AzureResourceManager.configure()
.authenticate(credential, profile)
.withDefaultSubscription();
```
Keep the `TODO` comment in the migrated source so reviewers and downstream maintainers are aware that the authentication mechanism changed and that the new code path has not been exercised by the original tests.
#### Rewrite obsolete authentication diagnostics
If legacy code mentions `AZURE_AUTH_LOCATION` only in a validation guard or exception message, do not carry that option into migrated runtime-visible text. Keep the guard, but rewrite the message around the credentials the migrated code actually supports, e.g. `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`, and `AZURE_SUBSCRIPTION_ID`. Do not scrub intentional `AZURE_AUTH_LOCATION` mentions from this guide's examples or TODO comments that explain why file-based auth was replaced.
### OKHttp Interceptors
Legacy OKHttp `Interceptor` implementation classes should be migrated to `HttpPipelinePolicy` implementation classes. This is a two-step migration — both steps are required:
**Step 1: Convert each `Interceptor` subclass to an `HttpPipelinePolicy` subclass.** Rename the class from `XxxInterceptor` to `XxxPolicy` and reimplement its logic against the `HttpPipelinePolicy` interface (i.e. `process(HttpPipelineCallContext, HttpPipelineNextPolicy)`) instead of the OKHttp `Interceptor.intercept(Chain)` API.
**Step 2: Register every converted policy on the new manager builder via `.withPolicy(new XxxPolicy())`.** Each `withNetworkInterceptor(new XxxInterceptor())` call on the legacy `RestClient.Builder` must have a corresponding `.withPolicy(new XxxPolicy())` call on `AzureResourceManager.configure()` (or the equivalent `XxxManager.configure()`). Preserve the original ordering of the interceptors when registering the policies.
Do not skip Step 2: converting the class without wiring it into the manager builder silently drops the behavior.
1. Legacy code:
```java
RestClient.Builder builder = new RestClient.Builder()
...
.withNetworkInterceptor(new ResourceGroupTaggingInterceptor())
...;
Azure.Authenticated azureAuthed = Azure.authenticate(builder.build(), subscriptionId, credentials.domain());
Azure azure = azureAuthed.withSubscription(subscriptionId);
```
2. Migrated code (note: `ResourceGroupTaggingPolicy` is the Step 1 conversion of `ResourceGroupTaggingInterceptor`, and it must be registered via `.withPolicy(...)` in Step 2):
```java
AzureResourceManager azureResourceManager = AzureResourceManager.configure()
.withPolicy(new ResourceGroupTaggingPolicy())
.authenticate(credential, profile)
.withDefaultSubscription();
```
### ProviderRegistrationInterceptor
If legacy client(XXManager) initializes with `ProviderRegistrationInterceptor`, check whether this client is one of the premium ones:
- Azure
- AuthorizationManager
- CdnManager
- ComputeManager
- ContainerInstanceManager
- ContainerRegistryManager
- ContainerServiceManager
- CosmosDBManager
- DnsZoneManager
- EventHubManager
- KeyVaultManager
- MonitorManager
- MSIManager
- NetworkManager
- RedisManager
- ResourceManager
- SearchServiceManager
- ServiceBusManager
- SqlServerManager
- StorageManager
- TrafficManager
If not, add `ProviderRegistrationPolicy` when initializing the client. Otherwise, don't.
For each legacy client, add along with whether to initialize with `ProviderRegistrationPolicy`, to the generated plan guideline, and migrate accordingly.
1. Legacy client(not premium client):
```java
BatchManager batchManager = BatchManager.configure()
.withLogLevel(LogLevel.BASIC)
.withInterceptor(new ProviderRegistrationInterceptor(credentials))
.authenticate(credentials, subscriptionId);
```
should be migrated to:
```java
BatchManager batchManager = BatchManager.configure()
.withPolicy(new ProviderRegistrationPolicy())
.withLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BASIC))
.authenticate(credential, profile);
```
2. Legacy client(premium clients):
```java
Azure azure = Azure.configure()
.withInterceptor(new ProviderRegistrationInterceptor(credentials))
.withLogLevel(LogLevel.BASIC)
.authenticate(credentials)
.withSubscription(subscriptionId);
```
should be migrated to:
```java
AzureResourceManager.configure()
.withLogLevel(HttpLogDetailLevel.BASIC)
.authenticate(credential, profile);
```
### BatchAccount
azure-resourcemanager-batch is no longer a premium/handwritten library. In BatchAccount, `withNewStorageAccount` should be replaced by `.withAutoStorage(new AutoStorageBaseProperties().withStorageAccountId(storageAccount.id()))`, while the `storageAccount` needs to be created separately.
Legacy code:
```java
BatchAccount batchAccount = azure.batchAccounts().define(batchAccountName)
.withRegion(region)
.withNewResourceGroup(rgName)
.defineNewApplication(applicationName)
.defineNewApplicationPackage(applicationPackageName)
.withAllowUpdates(true)
.withDisplayName(applicationDisplayName)
.attach()
.withNewStorageAccount(storageAccountName)
.create();
```
Migrated:
```java
StorageAccount storageAccount = storageManager.storageAccounts()
.define(storageAccountName)
.withRegion(REGION)
.withExistingResourceGroup(resourceGroup)
.create();
BatchAccount account = batchManager.batchAccounts()
.define(batchAccountName)
.withRegion(REGION)
.withExistingResourceGroup(resourceGroup)
.withAutoStorage(new AutoStorageBaseProperties().withStorageAccountId(storageAccount.id()))
.create();
// create application with batch account
application = batchManager.applications()
.define(applicationName)
.withExistingBatchAccount(resourceGroup, account.name())
.withDisplayName(applicationDisplayName)
.withAllowUpdates(true)
.create();
applicationPackage = batchManager.applicationPackages()
.define(applicationPackageName)
.withExistingApplication(resourceGroup, batchAccountName, applicationName)
.create();
```
references/languages/java/README.md›
# Java Legacy Azure SDK → Modern Azure SDK
> **Scenario scope**: Upgrade a Maven/Gradle project's Azure SDK dependencies from `com.microsoft.azure.*` (legacy, end-of-support 2023) to `com.azure.*` (modern) — source code, build files, tests.
>
> This is a **source-code modernization flow**, not an Azure service/plan/SKU upgrade. Follow the workflow below instead of the top-level `azure-upgrade` Steps. Do **NOT** use this for .NET, Python, JavaScript, or Go Azure SDK upgrades.
Upgrade all `com.microsoft.azure.*` to `com.azure.*` equivalents in one autonomous session.
You are an expert Azure SDK migration agent. Generate a unique run identifier at the start (format: `azure-sdk-upgrade-YYYYMMDD-HHMMSS`) and use it throughout all phases.
> ⚠️ **Lazy loading**: Do NOT pre-fetch the reference files listed below. Load each one **only when its workflow step is reached** or its trigger condition fires. Loading them upfront wastes context and causes premature decisions.
## Workflow (load references on demand)
Full procedure: per-phase files under `./workflow/` (load each one when entering that phase). Global rules apply to every step: [rules/execution-guidelines.md](./rules/execution-guidelines.md), [rules/efficiency.md](./rules/efficiency.md).
1. **Precheck** ([workflow/phase-1-precheck.md](./workflow/phase-1-precheck.md)) — Verify Maven/Gradle project, detect JDK/build tools. If git available, create branch `java-upgrade/{RUN_ID}`. → load `./templates/PLAN_TEMPLATE.md` to create `plan.md`.
- Step-wise rules: [rules/execution-guidelines.md](./rules/execution-guidelines.md) (Output directory, Git, Wrapper preference).
2. **Plan** ([workflow/phase-2-plan.md](./workflow/phase-2-plan.md)) — Inventory deps and populate `plan.md`. → load `./INSTRUCTION.md` for migration guide.
- Step-wise rules: [rules/upgrade-strategy.md](./rules/upgrade-strategy.md) (Incremental, Risk-first, Successor preference, Necessary/Meaningful steps).
3. **Execute** ([workflow/phase-3-execute.md](./workflow/phase-3-execute.md)) — Migrate build config then source, build/test/fix, commit per step. → load `./templates/PROGRESS_TEMPLATE.md` to create `progress.md`; load `./rules/` before running builds/tests.
- Step-wise rules: [rules/review-code-changes.md](./rules/review-code-changes.md), [rules/upgrade-strategy.md](./rules/upgrade-strategy.md) (Automation tools, Temporary errors OK), [rules/execution-guidelines.md](./rules/execution-guidelines.md) (Template compliance, Git).
4. **Validate** ([workflow/phase-4-summarize.md](./workflow/phase-4-summarize.md)) — Apply validation checklist. → load `./templates/SUMMARY_TEMPLATE.md` to create `summary.md`; load `./INSTRUCTION.md#validation`.
- Step-wise rules: [rules/upgrade-success-criteria.md](./rules/upgrade-success-criteria.md).
## Constraints
- 100% test pass · no premature termination · incremental changes · review each step
- Prefer wrappers (`mvnw`/`gradlew`)
## Examples
```
"upgrade legacy azure sdk" → precheck → plan → execute → validate
```
references/languages/java/rules/efficiency.md›
# Efficiency
- **Targeted reads**: Use `grep` over full file reads; read sections, not entire files.
- **Quiet commands**: Use `-q`, `--quiet` for build/test when appropriate.
- **Progressive writes**: Update `plan.md` and `progress.md` incrementally, not at end.
references/languages/java/rules/execution-guidelines.md›
# Execution Guidelines
- **Wrapper preference**: Use Maven Wrapper (`mvnw`/`mvnw.cmd`) or Gradle Wrapper (`gradlew`/`gradlew.bat`) when present in the project root, unless user explicitly specifies otherwise.
- **Template compliance**: Follow the HTML-comment instructions in the template reference files when creating and populating `.github/java-upgrade/{RUN_ID}/plan.md`, `progress.md`, `summary.md`. You may remove the HTML comments after populating each section.
- **Output directory**: All plan/progress/summary files are created under `.github/java-upgrade/{RUN_ID}/` in the project being migrated. Create this directory at the start of the run.
- **Uninterrupted run**: Complete each phase fully without pausing for user input.
- **Git**: If git is available, create a new branch `java-upgrade/{RUN_ID}` before starting the migration. Commit changes per step on this branch. If git is not available, log a warning and proceed — files remain uncommitted in the working directory. Use `N/A` for `<current_branch>` and `<current_commit_id>` placeholders.
references/languages/java/rules/review-code-changes.md›
# Review Code Changes (MANDATORY for each step)
After completing changes in each step, review code changes per the rules in the Progress Template BEFORE verification. Key areas:
- **Sufficiency**: All required upgrade changes are present — no missing modifications that would leave the upgrade incomplete.
- **Necessity**: No critical unnecessary changes. Unnecessary changes that do not affect behavior may be retained; however, it is essential to ensure that functional behavior remains consistent and security controls are preserved.
references/languages/java/rules/troubleshooting.md›
# Troubleshooting & Anti-Excuse Rules
Load this file when encountering build failures, test failures, or any temptation to stop mid-migration.
## Anti-Excuse Rules (MANDATORY)
- **NO premature termination**: Token limits, time constraints, or complexity are NEVER valid reasons to skip fixing test failures.
- **NO "close enough" acceptance**: 95% is NOT 100%. Every failing test requires a fix attempt with documented root cause.
- **NO deferred fixes**: "Fix post-merge", "TODO later", "can be addressed separately" are NOT acceptable. Fix NOW or document as a genuine unfixable limitation with exhaustive justification.
- **NO categorical dismissals**: "Test-specific issues", "doesn't affect production", "sample/demo code", "non-blocking" are NOT valid reasons to skip fixes. ALL tests must pass.
- **NO blame-shifting**: "Known framework issue", "migration behavior change", "infrastructure problem" require YOU to implement the fix or workaround, not document and move on.
- **Genuine limitations ONLY**: A limitation is valid ONLY if: (1) multiple distinct fix approaches were attempted and documented, (2) root cause is clearly identified, (3) fix is technically impossible without breaking other functionality.
## Critical: Do Not Stop Mid-Migration
You are expected to carry the migration to completion — either fully succeed or encounter an unrecoverable error. The following behaviors are **strictly prohibited**:
- **Do NOT stop to summarize progress.** Never output a message listing "what was done" and "what remains" as your final response.
- **Do NOT treat partial migration as acceptable.** Migrating some files but not others is not a valid stopping point. You must attempt every file and every dependency.
- **Do NOT hand off work to the user.** Never suggest the user "continue" or "complete the remaining items." You are responsible for finishing everything.
- **If you hit an error on one file, move on to the next.** A failure in one source file should not prevent you from migrating the rest. Come back to fix it after attempting all files.
- **If a build fails after migration, debug and fix it.** Do not stop at "build failed." Investigate the errors, fix them, and rebuild. Repeat until the build passes or you have exhausted all reasonable approaches.
**The only acceptable stopping conditions are:**
1. The migration is fully complete and the build passes.
2. You have attempted everything and an unrecoverable error prevents further progress (e.g., a fundamental API incompatibility with no workaround). In this case, clearly state the blocker.
references/languages/java/rules/upgrade-strategy.md›
# Upgrade Strategy
- **Incremental upgrades**: Stepwise dependency upgrades to avoid large jumps breaking builds.
- **Minimal changes**: Only upgrade dependencies essential for compatibility with the modern Azure SDKs.
- **Risk-first**: Handle EOL/challenging deps early in isolated steps.
- **Necessary/Meaningful steps only**: Each step MUST change code/config. NO steps for pure analysis/validation. Merge small related changes. **Test**: "Does this step modify project files?"
- **Automation tools**: Use automation tools like OpenRewrite for efficiency; always verify output. For BOM upgrades, run the [`scripts/upgrade_bom.py`](../scripts/upgrade_bom.py) script in the parent folder when Python 3.10+ is available; if Python is not available or the script fails, manually resolve the BOM version and follow the **Manual Fallback** sections in [bom-maven.md](../bom-migration/bom-maven.md) / [bom-gradle.md](../bom-migration/bom-gradle.md) instead (see [Migration Guidelines](../INSTRUCTION.md#maven-use-the-upgrade_bom-script)).
- **Successor preference**: Compatible successor > Adapter pattern > Code rewrite.
- **Build tool compatibility**: Check Maven/Gradle version compatibility with the project's JDK. Upgrade the build tool (including wrapper) if the current version does not support the JDK.
- **Temporary errors OK**: Steps may pass with known errors if resolved later or pre-existing.
references/languages/java/rules/upgrade-success-criteria.md›
# Upgrade Success Criteria (ALL must be met)
- **Goal**: All legacy Azure SDK dependencies (`com.microsoft.azure.*`) replaced with modern equivalents (`com.azure.*`).
- **Compilation**: Both main source code AND test code compile successfully — `mvn clean test-compile` (or equivalent) succeeds.
- **Test**: **100% test pass rate** — `mvn clean test` succeeds. Minimum acceptable: test pass rate ≥ baseline (pre-upgrade pass rate). Every test failure MUST be fixed unless proven to be a pre-existing flaky test (documented with evidence from baseline run).
If any criterion is not met, load [`./troubleshooting.md`](./troubleshooting.md) at that point — do NOT stop or defer.
references/languages/java/scripts/upgrade_bom.py›
#!/usr/bin/env python3
"""Upgrade or add azure-sdk-bom in a Maven or Gradle project using OpenRewrite.
Detects the build system automatically and applies the appropriate OpenRewrite
recipes. All operations use OpenRewrite — no manual XML/text manipulation.
Step 1 – Add or upgrade BOM:
- Maven (add): org.openrewrite.maven.AddManagedDependency
- Maven (upgrade): org.openrewrite.maven.UpgradeDependencyVersion
- Gradle (add): org.openrewrite.gradle.AddPlatformDependency
- Gradle (upgrade):org.openrewrite.gradle.UpgradeDependencyVersion
Step 2 – Remove redundant explicit versions:
- Maven: org.openrewrite.maven.RemoveRedundantDependencyVersions
- Gradle: org.openrewrite.gradle.RemoveRedundantDependencyVersions
Usage:
python3 upgrade_bom.py <project_dir> [latest] [options]
python3 upgrade_bom.py --get-latest-version
Arguments:
project_dir Path to the project root (must contain pom.xml or build.gradle).
latest Optional compatibility token. Explicit version pins are rejected.
Options:
--mvn <cmd> Maven command override.
--gradle <cmd> Gradle command override.
"""
from __future__ import annotations
import argparse
import os
import re
import stat
import subprocess
import sys
import textwrap
import urllib.error
import urllib.request
import xml.etree.ElementTree as ET
GROUP_ID = "com.azure"
ARTIFACT_ID = "azure-sdk-bom"
BOM_POM_URL = "https://raw.githubusercontent.com/Azure/azure-sdk-for-java/main/sdk/boms/azure-sdk-bom/pom.xml"
POM_NAMESPACE = {"m": "http://maven.apache.org/POM/4.0.0"}
MIN_BOM_VERSION = "1.3.0"
LATEST_VERSION_SENTINEL = "latest"
HTTP_TIMEOUT_SECONDS = 30
STABLE_SEMVER_RE = re.compile(r"^(\d+)\.(\d+)\.(\d+)$")
# Maven constants
MVN_REWRITE_PLUGIN = "org.openrewrite.maven:rewrite-maven-plugin"
MVN_REWRITE_ARTIFACT_COORDS = "org.openrewrite:rewrite-maven"
MVN_UPGRADE_RECIPE = "org.openrewrite.maven.UpgradeDependencyVersion"
MVN_ADD_MANAGED_RECIPE = "org.openrewrite.maven.AddManagedDependency"
MVN_REMOVE_REDUNDANT_RECIPE = "org.openrewrite.maven.RemoveRedundantDependencyVersions"
# Gradle constants
GRADLE_UPGRADE_RECIPE = "org.openrewrite.gradle.UpgradeDependencyVersion"
GRADLE_ADD_PLATFORM_RECIPE = "org.openrewrite.gradle.AddPlatformDependency"
GRADLE_REMOVE_REDUNDANT_RECIPE = "org.openrewrite.gradle.RemoveRedundantDependencyVersions"
REWRITE_YML_NAME = "rewrite.yml"
GRADLE_PLUGIN_MARKER = "// --- openrewrite-upgrade-bom-plugin (auto-added, safe to remove) ---"
# ---------------------------------------------------------------------------
# Build-system detection
# ---------------------------------------------------------------------------
def _detect_build_system(project_dir: str) -> str:
"""Return 'maven' or 'gradle' depending on which build file is present."""
if os.path.isfile(os.path.join(project_dir, "pom.xml")):
return "maven"
for name in ("build.gradle", "build.gradle.kts"):
if os.path.isfile(os.path.join(project_dir, name)):
return "gradle"
return "unknown"
def _get_latest_bom_version() -> str:
try:
with urllib.request.urlopen(BOM_POM_URL, timeout=HTTP_TIMEOUT_SECONDS) as response:
pom_xml = response.read()
except urllib.error.URLError as exc:
raise SystemExit(f"Failed to download {BOM_POM_URL}: {exc}") from exc
try:
root = ET.fromstring(pom_xml)
except ET.ParseError as exc:
raise SystemExit(f"Failed to parse BOM pom.xml: {exc}") from exc
version = root.findtext("m:version", namespaces=POM_NAMESPACE)
if not version:
raise SystemExit("Failed to find the azure-sdk-bom <version> in pom.xml")
return version.strip()
def _parse_stable_semver(version: str) -> tuple[int, int, int]:
match = STABLE_SEMVER_RE.fullmatch(version.strip())
if not match:
raise ValueError(
f"Invalid azure-sdk-bom version '{version}'. Expected stable MAJOR.MINOR.PATCH."
)
return tuple(int(part) for part in match.groups())
def _validate_minimum_bom_version(version: str) -> None:
parsed = _parse_stable_semver(version)
minimum = _parse_stable_semver(MIN_BOM_VERSION)
if parsed < minimum:
raise ValueError(
f"azure-sdk-bom {version} is below the minimum supported version "
f"{MIN_BOM_VERSION}; resolve the latest stable version from {BOM_POM_URL}."
)
def _resolve_latest_bom_version(requested_version: str | None) -> str:
if requested_version and requested_version.strip().lower() != LATEST_VERSION_SENTINEL:
raise ValueError(
"Explicit azure-sdk-bom version pins are not allowed in this migration flow. "
f"Use no version argument, or use '{LATEST_VERSION_SENTINEL}' for compatibility."
)
latest = _get_latest_bom_version()
_validate_minimum_bom_version(latest)
print(f"[upgrade_bom] Resolved latest azure-sdk-bom version: {latest}")
print(f"[upgrade_bom] Source: {BOM_POM_URL}")
return latest
# ---------------------------------------------------------------------------
# Maven helpers
# ---------------------------------------------------------------------------
def _detect_maven(project_dir: str) -> str:
if sys.platform == "win32":
wrapper = os.path.join(project_dir, "mvnw.cmd")
# .cmd files on Windows are invoked by the shell; no executable bit needed.
if os.path.isfile(wrapper):
return wrapper
else:
wrapper = os.path.join(project_dir, "mvnw")
if os.path.isfile(wrapper):
if not os.access(wrapper, os.X_OK):
# Wrapper exists but isn't executable (common after fresh clones
# on filesystems that don't preserve the +x bit). Try to fix it.
try:
mode = os.stat(wrapper).st_mode
os.chmod(wrapper, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
print(f"[upgrade_bom] Added executable bit to {wrapper}.")
except OSError as exc:
print(
f"[upgrade_bom] WARNING: mvnw exists at {wrapper} but is not "
f"executable and chmod failed ({exc}); falling back to 'mvn'.",
file=sys.stderr,
)
return "mvn"
if os.access(wrapper, os.X_OK):
return wrapper
return "mvn"
def _has_maven_bom_entry(pom_path: str) -> bool:
try:
tree = ET.parse(pom_path)
except ET.ParseError:
return False
ns = {"m": "http://maven.apache.org/POM/4.0.0"}
for dep in tree.findall(".//m:dependencyManagement/m:dependencies/m:dependency", ns):
gid = dep.find("m:groupId", ns)
aid = dep.find("m:artifactId", ns)
if gid is not None and aid is not None:
if gid.text == GROUP_ID and aid.text == ARTIFACT_ID:
return True
for dep in tree.findall(".//dependencyManagement/dependencies/dependency"):
gid = dep.find("groupId")
aid = dep.find("artifactId")
if gid is not None and aid is not None:
if gid.text == GROUP_ID and aid.text == ARTIFACT_ID:
return True
return False
def _run_maven_recipe(mvn_cmd: str, project_dir: str, recipe: str, options: str) -> int:
"""Run an OpenRewrite recipe via the rewrite-maven-plugin."""
cmd = [
mvn_cmd, "-U",
f"{MVN_REWRITE_PLUGIN}:run",
f"-Drewrite.recipeArtifactCoordinates={MVN_REWRITE_ARTIFACT_COORDS}",
f"-Drewrite.activeRecipes={recipe}",
f"-Drewrite.options={options}",
]
print(f"[upgrade_bom] Running: {' '.join(cmd)}")
return subprocess.run(cmd, cwd=project_dir).returncode
def _handle_maven(project_dir: str, bom_version: str, mvn_cmd: str | None) -> int:
pom_path = os.path.join(project_dir, "pom.xml")
mvn = mvn_cmd or _detect_maven(project_dir)
# Step 1: Add or upgrade the BOM
if not _has_maven_bom_entry(pom_path):
print("[upgrade_bom] No existing azure-sdk-bom entry found — adding via AddManagedDependency.")
options = ",".join([
f"groupId={GROUP_ID}",
f"artifactId={ARTIFACT_ID}",
f"version={bom_version}",
"type=pom",
"scope=import",
])
rc = _run_maven_recipe(mvn, project_dir, MVN_ADD_MANAGED_RECIPE, options)
if rc != 0:
print(f"[upgrade_bom] ERROR: AddManagedDependency exited with code {rc}", file=sys.stderr)
return rc
print(f"[upgrade_bom] azure-sdk-bom {bom_version} added successfully.")
else:
print(f"[upgrade_bom] Existing azure-sdk-bom entry found — upgrading to {bom_version}.")
options = ",".join([
f"groupId={GROUP_ID}",
f"artifactId={ARTIFACT_ID}",
f"newVersion={bom_version}",
"overrideManagedVersion=true",
])
rc = _run_maven_recipe(mvn, project_dir, MVN_UPGRADE_RECIPE, options)
if rc != 0:
print(f"[upgrade_bom] ERROR: UpgradeDependencyVersion exited with code {rc}", file=sys.stderr)
return rc
print(f"[upgrade_bom] azure-sdk-bom upgraded to {bom_version} successfully.")
# Step 2: Remove explicit versions from Azure deps managed by the BOM
print("[upgrade_bom] Removing redundant explicit versions for Azure dependencies...")
options = f"groupPattern={GROUP_ID}*,onlyIfManagedVersionIs=GTE"
rc = _run_maven_recipe(mvn, project_dir, MVN_REMOVE_REDUNDANT_RECIPE, options)
if rc != 0:
print(f"[upgrade_bom] WARNING: RemoveRedundantDependencyVersions exited with code {rc}", file=sys.stderr)
else:
print("[upgrade_bom] Redundant explicit versions removed successfully.")
return rc
# ---------------------------------------------------------------------------
# Gradle helpers
# ---------------------------------------------------------------------------
def _detect_gradle(project_dir: str) -> str:
if sys.platform == "win32":
wrapper = os.path.join(project_dir, "gradlew.bat")
if os.path.isfile(wrapper):
return wrapper
else:
wrapper = os.path.join(project_dir, "gradlew")
if os.path.isfile(wrapper):
if not os.access(wrapper, os.X_OK):
try:
mode = os.stat(wrapper).st_mode
os.chmod(wrapper, mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
print(f"[upgrade_bom] Added executable bit to {wrapper}.")
except OSError as exc:
print(
f"[upgrade_bom] WARNING: gradlew exists at {wrapper} but is not "
f"executable and chmod failed ({exc}); falling back to 'gradle'.",
file=sys.stderr,
)
return "gradle"
if os.access(wrapper, os.X_OK):
return wrapper
return "gradle"
def _find_gradle_build_file(project_dir: str) -> str | None:
for name in ("build.gradle", "build.gradle.kts"):
path = os.path.join(project_dir, name)
if os.path.isfile(path):
return path
return None
def _is_kotlin_dsl(build_file: str) -> bool:
return build_file.endswith(".kts")
def _has_gradle_bom_entry(build_file: str) -> bool:
"""Check whether build.gradle already references azure-sdk-bom."""
try:
with open(build_file, "r", encoding="utf-8") as f:
content = f.read()
except OSError:
return False
return f"{GROUP_ID}:{ARTIFACT_ID}" in content
def _create_rewrite_yml(project_dir: str, bom_version: str, has_bom: bool) -> str:
"""Create a temporary rewrite.yml with the appropriate OpenRewrite recipes.
When has_bom is True, uses UpgradeDependencyVersion to upgrade the existing BOM.
When has_bom is False, uses AddPlatformDependency to add a new enforcedPlatform BOM.
Always includes RemoveRedundantDependencyVersions as a final step.
"""
yml_path = os.path.join(project_dir, REWRITE_YML_NAME)
recipes: list[str] = []
if has_bom:
recipes.append(textwrap.dedent(f"""\
- org.openrewrite.gradle.UpgradeDependencyVersion:
groupId: {GROUP_ID}
artifactId: {ARTIFACT_ID}
newVersion: {bom_version}"""))
else:
recipes.append(textwrap.dedent(f"""\
- org.openrewrite.gradle.AddPlatformDependency:
groupId: {GROUP_ID}
artifactId: {ARTIFACT_ID}
version: {bom_version}
configuration: implementation
enforced: true"""))
recipes.append(textwrap.dedent(f"""\
- org.openrewrite.gradle.RemoveRedundantDependencyVersions:
groupPattern: {GROUP_ID}*
onlyIfManagedVersionIs: GTE"""))
yml_content = textwrap.dedent("""\
---
type: specs.openrewrite.org/v1beta/recipe
name: com.azure.UpgradeBom
displayName: Upgrade azure-sdk-bom and remove redundant versions
recipeList:
""") + "\n".join(recipes) + "\n"
with open(yml_path, "w", encoding="utf-8") as f:
f.write(yml_content)
print(f"[upgrade_bom] Created {yml_path}")
return yml_path
def _inject_gradle_rewrite_plugin(build_file: str) -> bool:
"""Temporarily add the OpenRewrite plugin to build.gradle if not present.
Returns True if the plugin block was injected (and should be cleaned up).
"""
with open(build_file, "r", encoding="utf-8") as f:
content = f.read()
if "org.openrewrite.rewrite" in content:
return False
kotlin = _is_kotlin_dsl(build_file)
if kotlin:
plugin_line = ' id("org.openrewrite.rewrite") version "latest.release"'
else:
plugin_line = ' id "org.openrewrite.rewrite" version "latest.release"'
rewrite_block_kt = textwrap.dedent("""\
rewrite {
activeRecipe("com.azure.UpgradeBom")
}
repositories {
mavenCentral()
}
""")
rewrite_block_groovy = rewrite_block_kt # same syntax for both DSLs here
plugins_pattern = re.compile(r"(plugins\s*\{)", re.MULTILINE)
match = plugins_pattern.search(content)
if match:
insert_pos = match.end()
# content[insert_pos:] already starts with the newline that follows
# `plugins {`, so don't add another one before the marker.
content = (
content[:insert_pos]
+ "\n"
+ GRADLE_PLUGIN_MARKER
+ "\n"
+ plugin_line
+ content[insert_pos:]
)
else:
# No plugins block — prepend one
content = (
"plugins {\n"
+ GRADLE_PLUGIN_MARKER
+ "\n"
+ plugin_line
+ "\n}\n\n"
+ content
)
content += GRADLE_PLUGIN_MARKER + "\n"
content += rewrite_block_kt if kotlin else rewrite_block_groovy
with open(build_file, "w", encoding="utf-8") as f:
f.write(content)
print(f"[upgrade_bom] Injected OpenRewrite plugin into {build_file}")
return True
def _remove_gradle_rewrite_plugin(build_file: str) -> None:
"""Remove the temporarily injected OpenRewrite plugin and config blocks."""
with open(build_file, "r", encoding="utf-8") as f:
lines = f.readlines()
cleaned: list[str] = []
marker_count = 0
i = 0
while i < len(lines):
line = lines[i]
if GRADLE_PLUGIN_MARKER in line:
marker_count += 1
if marker_count == 1:
# First marker (inside plugins {}): skip the marker line and
# the following injected plugin id line.
i += 2
continue
else:
# Second marker (at end of file): skip the marker and every
# remaining line — they're the injected rewrite {} and
# repositories {} blocks.
break
cleaned.append(line)
i += 1
with open(build_file, "w", encoding="utf-8") as f:
f.writelines(cleaned)
print(f"[upgrade_bom] Cleaned up OpenRewrite plugin from {build_file}")
def _run_gradle_openrewrite(gradle_cmd: str, project_dir: str) -> int:
cmd = [gradle_cmd, "rewriteRun"]
print(f"[upgrade_bom] Running: {' '.join(cmd)}")
return subprocess.run(cmd, cwd=project_dir).returncode
def _handle_gradle(project_dir: str, bom_version: str, gradle_cmd: str | None) -> int:
build_file = _find_gradle_build_file(project_dir)
if build_file is None:
print("[upgrade_bom] ERROR: no build.gradle or build.gradle.kts found", file=sys.stderr)
return 1
gradle = gradle_cmd or _detect_gradle(project_dir)
has_bom = _has_gradle_bom_entry(build_file)
if has_bom:
print(f"[upgrade_bom] Existing azure-sdk-bom entry found — upgrading to {bom_version}.")
else:
print("[upgrade_bom] No existing azure-sdk-bom entry found — adding via AddPlatformDependency.")
yml_path = None
injected = False
try:
# Set up OpenRewrite: create rewrite.yml + inject plugin temporarily
yml_path = _create_rewrite_yml(project_dir, bom_version, has_bom=has_bom)
injected = _inject_gradle_rewrite_plugin(build_file)
rc = _run_gradle_openrewrite(gradle, project_dir)
finally:
if yml_path and os.path.isfile(yml_path):
os.remove(yml_path)
print(f"[upgrade_bom] Removed {yml_path}")
if injected:
_remove_gradle_rewrite_plugin(build_file)
if rc != 0:
print(f"[upgrade_bom] ERROR: OpenRewrite exited with code {rc}", file=sys.stderr)
else:
print(f"[upgrade_bom] BOM set to {bom_version} and redundant versions removed successfully.")
return rc
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Upgrade azure-sdk-bom version in a Maven or Gradle project using OpenRewrite."
)
parser.add_argument("project_dir", nargs="?", help="Path to the project root.")
parser.add_argument(
"bom_version",
nargs="?",
help="Optional compatibility token. Only 'latest' is accepted; explicit versions are rejected.",
)
parser.add_argument("--mvn", default=None, help="Maven command override.")
parser.add_argument("--gradle", default=None, help="Gradle command override.")
parser.add_argument(
"--get-latest-version",
action="store_true",
help="Print the latest azure-sdk-bom version from the Azure SDK for Java BOM pom.xml.",
)
args = parser.parse_args(argv)
if args.get_latest_version:
if args.project_dir or args.bom_version:
parser.error("--get-latest-version does not accept project_dir or bom_version.")
print(_get_latest_bom_version())
return 0
if not args.project_dir:
parser.error("project_dir is required unless --get-latest-version is used.")
try:
bom_version = _resolve_latest_bom_version(args.bom_version)
except ValueError as exc:
raise SystemExit(f"[upgrade_bom] ERROR: {exc}") from exc
project_dir = os.path.abspath(args.project_dir)
build_system = _detect_build_system(project_dir)
if build_system == "maven":
print("[upgrade_bom] Detected Maven project.")
return _handle_maven(project_dir, bom_version, args.mvn)
elif build_system == "gradle":
print("[upgrade_bom] Detected Gradle project.")
return _handle_gradle(project_dir, bom_version, args.gradle)
else:
print(
f"[upgrade_bom] ERROR: No pom.xml or build.gradle found in {project_dir}",
file=sys.stderr,
)
return 1
if __name__ == "__main__":
sys.exit(main())
references/languages/java/templates/PLAN_TEMPLATE.md›
<!--
This is the upgrade plan template for Azure SDK migration.
RUN_ID should be replaced with the actual run identifier.
## PLANNING RULES
!!! DON'T REMOVE THIS COMMENT BLOCK BEFORE FINAL PLAN IS GENERATED AS IT CONTAINS IMPORTANT INSTRUCTIONS.
### Strategy
- **Incremental upgrades**: Stepwise dependency upgrades to avoid large jumps breaking builds
- **Minimal changes**: Only upgrade dependencies essential for compatibility
- **Risk-first**: Handle EOL/challenging deps early in isolated steps
- **Necessary/Meaningful steps only**: Each step MUST change code/config. NO steps for pure analysis/validation. Merge small related changes.
### Mandatory Steps
- **Step 1 (MANDATORY)**: Setup Baseline - Run compile/test with current JDK, document results.
- **Steps 2-N**: Upgrade steps - dependency order, high-risk early, isolated breaking changes
- **Final step (MANDATORY)**: Final Validation - verify all goals met, all TODOs resolved, 100% tests pass
### Verification Expectations
- **Steps 1-N (Setup/Upgrade)**: Focus on COMPILATION SUCCESS. Tests may fail during intermediate steps.
- **Final Validation**: COMPILATION SUCCESS + 100% TEST PASS
### Efficiency (IMPORTANT)
- **Targeted reads**: Use `grep` over full file reads; read specific sections, not entire files. Template files are large - only read the sections you need.
- **Quiet commands**: Use `-q`, `--quiet` for build/test commands when appropriate
- **Progressive writes**: Update plan.md incrementally, not at end
-->
# Upgrade Plan: <PROJECT_NAME> (<RUN_ID>)
- **Generated**: <datetime> <!-- replace with actual date and time when generating -->
- **HEAD Branch**: <current_branch> <!-- replace with actual head branch when generating -->
- **HEAD Commit ID**: <current_commit_id> <!-- replace with actual head commit id when generating -->
## Available Tools
<!--
List ONLY the JDKs and build tools that are required/used during the upgrade (not all discovered ones).
Use the environment detection results from Precheck to check availability.
Mark build tools that need upgrading for JDK compatibility as "**<TO_BE_UPGRADED>**".
If a wrapper (mvnw/gradlew) is present, also check the wrapper-defined version in
`.mvn/wrapper/maven-wrapper.properties` or `gradle/wrapper/gradle-wrapper.properties`.
NOTE: This section is finalized during Upgrade Path Design (after step sequence is known), not during Environment Analysis.
SAMPLE:
**JDKs**
- JDK 1.8.0: /path/to/jdk-8 (current project JDK, used by step 1)
**Build Tools**
- Maven 3.9.6: /path/to/maven
- Maven Wrapper: 3.8.1 → **<TO_BE_UPGRADED>** to 3.9.6+ (current version incompatible with project requirements)
-->
## Guidelines
<!--
User-specified guidelines or constraints in bullet points for this upgrade.
Extract these from the user's prompt if provided, or leave empty if none specified.
These guidelines take precedence over default upgrade strategies.
-->
> Note: You can add any specific guidelines or constraints for the upgrade process here if needed, bullet points are preferred. <!-- this note is for users, NEVER remove it -->
## Upgrade Goals
<!--
The primary goal is to replace all legacy Azure SDK dependencies (com.microsoft.azure.*) with their
modern equivalents (com.azure.*). List any additional user-specified goals below.
SAMPLE:
- Replace all `com.microsoft.azure.*` dependencies with `com.azure.*` equivalents
- Migrate source code to use modern Azure SDK APIs (builder pattern, Azure Identity)
-->
### Technology Stack
<!--
Table of core dependencies and their compatibility with upgrade goals.
IMPORTANT: Analyze ALL modules in multi-module projects, not just the root module.
Only include: direct dependencies + those critical for upgrade compatibility.
Columns:
- Technology/Dependency: Name of the dependency
- Current: Version currently in use
- Modern Equivalent: The com.azure.* replacement (or N/A if not an Azure SDK dep)
- Migration Notes: Notes on migration approach
IMPORTANT: Include build tools (Maven/Gradle), wrappers, and key build plugins in this table.
SAMPLE:
| Technology/Dependency | Current | Modern Equivalent | Migration Notes |
| ---------------------------------------- | ------- | ------------------------------------------ | -------------------------------------------- |
| com.microsoft.azure:azure-mgmt-resources | 1.41.4 | com.azure.resourcemanager:azure-resourcemanager | Use azure-sdk-bom for version management |
| com.microsoft.azure:azure-mgmt-storage | 1.41.4 | com.azure.resourcemanager:azure-resourcemanager | Included in azure-resourcemanager |
| com.microsoft.azure:azure-client-authentication | 1.7.14 | com.azure:azure-identity | Use ClientSecretCredential or DefaultAzureCredential |
| Maven (wrapper) | 3.6.3 | - | Check compatibility with project JDK |
-->
| Technology/Dependency | Current | Modern Equivalent | Migration Notes |
| --------------------- | ------- | ----------------- | --------------- |
### Derived Upgrades
<!--
Required upgrades inferred from the Azure SDK migration.
Each derived upgrade must have a justification explaining WHY it's required.
Common derivations:
- Legacy auth (azure-client-authentication) → azure-identity
- Legacy management (azure-mgmt-*) → azure-resourcemanager or specific azure-resourcemanager-* modules
- Legacy data plane SDKs → modern com.azure equivalents
- Add azure-sdk-bom for version management
SAMPLE:
- Add azure-sdk-bom for centralized version management of com.azure.* dependencies
- Replace azure-client-authentication with azure-identity (modern authentication library)
- Add jackson-databind if file-based authentication is used and Jackson is not already present
-->
## Upgrade Steps
<!--
Step-by-step upgrade plan. Each step should follow this format:
- **Step N: <Descriptive Title>**
- **Rationale**: Why this step is needed and why at this position
- **Changes to Make**: ≤5 bullet points (concise)
- **Verification**: Command, JDK, Expected Result
VERIFICATION EXPECTATIONS:
- Steps 1-N (Setup and Upgrade steps): Focus on COMPILATION SUCCESS. Tests may fail during intermediate steps.
- Final step: COMPILATION SUCCESS + TEST PASS through iterative fix loop.
MANDATORY FIRST STEP:
The first step MUST always be Setup Baseline.
MANDATORY SETUP BASELINE STEP SAMPLE:
- Step 1: Setup Baseline
- **Rationale**: Establish pre-upgrade compile and test results to measure upgrade success against.
- **Changes to Make**:
- [ ] Run baseline compilation with current JDK
- [ ] Run baseline tests with current JDK
- **Verification**:
- Command: `mvn clean compile test-compile -q && mvn clean test -q`
- JDK: <current project JDK path>
- Expected: Document SUCCESS/FAILURE, test pass rate (forms acceptance criteria)
---
SAMPLE STEP (dependency migration):
- Step N: Migrate Azure Management Dependencies
- **Rationale**: Replace legacy com.microsoft.azure management dependencies with modern com.azure.resourcemanager equivalents.
- **Changes to Make**:
- [ ] Add azure-sdk-bom to dependencyManagement
- [ ] Replace com.microsoft.azure:azure-mgmt-* with com.azure.resourcemanager:azure-resourcemanager
- [ ] Remove com.microsoft.azure:azure-client-authentication, add com.azure:azure-identity
- [ ] Fix compilation errors from API changes
- **Verification**:
- Command: `mvn clean test-compile -q`
- JDK: <JDK path>
- Expected: Compilation SUCCESS (tests may fail - will be fixed in Final Validation)
---
MANDATORY FINAL STEP (must always be the last step):
- Step N: Final Validation
- **Rationale**: Verify all upgrade goals met, project compiles successfully, all tests pass.
- **Changes to Make**:
- [ ] Verify no legacy com.microsoft.azure.* dependencies remain
- [ ] Resolve ALL TODOs and temporary workarounds from previous steps
- [ ] Clean rebuild with current JDK
- [ ] Fix any remaining compilation errors
- [ ] Run full test suite and fix ALL test failures (iterative fix loop until 100% pass)
- **Verification**:
- Command: `mvn clean test -q`
- JDK: <JDK path>
- Expected: Compilation SUCCESS + 100% tests pass
-->
## Key Challenges
<!--
Document high-risk areas that require special attention during upgrade.
Each challenge should have a mitigation strategy. Be concise.
Common challenges for Azure SDK migration:
- Authentication pattern changes (file-based auth, service principal)
- API surface changes (fluent builder patterns differ between legacy and modern)
- Async/reactive pattern differences
- Package structure changes (com.microsoft.azure.management.* → com.azure.resourcemanager.*)
SAMPLE:
- **Authentication Migration**
- **Challenge**: Legacy code uses file-based authentication via Azure.authenticate(credentialFile). Modern SDK requires explicit credential construction.
- **Strategy**: Read credential file with Jackson ObjectMapper, construct ClientSecretCredential, use AzureProfile for subscription context.
- **Fluent API Changes**
- **Challenge**: Method names and builder patterns differ between legacy and modern Azure Resource Manager.
- **Strategy**: Follow the migration guide at https://aka.ms/java-track2-migration-guide for method-level mappings.
-->
references/languages/java/templates/PROGRESS_TEMPLATE.md›
<!--
This is the upgrade progress tracker for Azure SDK migration.
Each step from plan.md should be tracked here with status, changes, verification results, and TODOs.
## EXECUTION RULES
!!! DON'T REMOVE THIS COMMENT BLOCK BEFORE UPGRADE IS COMPLETE AS IT CONTAINS IMPORTANT INSTRUCTIONS.
### Success Criteria
- **Goal**: All legacy Azure SDK dependencies (com.microsoft.azure.*) replaced with modern equivalents (com.azure.*)
- **Compilation**: Both main source code AND test code compile = `mvn clean test-compile` succeeds
- **Test**: 100% test pass rate = `mvn clean test` succeeds (or ≥ baseline with documented pre-existing flaky tests)
### Strategy
- **Uninterrupted run**: Complete execution without pausing for user input
- **NO premature termination**: Token limits, time constraints, or complexity are NEVER valid reasons to skip fixing.
- **Automation tools**: Use OpenRewrite etc. for efficiency; always verify output
### Verification Expectations
- **Steps 1-N (Setup/Upgrade)**: Focus on COMPILATION SUCCESS (both main and test code).
- On compilation success: Commit and proceed (even if tests fail - document count)
- On compilation error: Fix IMMEDIATELY and re-verify until both main and test code compile
- **NO deferred fixes** (for compilation): "Fix post-merge", "TODO later", "can be addressed separately" are NOT acceptable. Fix NOW or document as genuine unfixable limitation.
- **Final Validation Step**: Achieve COMPILATION SUCCESS + 100% TEST PASS.
- On test failure: Enter iterative test & fix loop until 100% pass or rollback to last-good-commit after exhaustive fix attempts
- **NO deferring test fixes** - this is the final gate
- **NO categorical dismissals**: "Test-specific issues", "doesn't affect production", "sample/demo code" are NOT valid reasons to skip. ALL tests must pass.
- **NO "close enough" acceptance**: 95% is NOT 100%. Every failing test requires a fix attempt with documented root cause.
- **NO blame-shifting**: "Known framework issue", "migration behavior change" require YOU to implement the fix or workaround.
### Review Code Changes (MANDATORY for each step)
After completing changes in each step, review code changes BEFORE verification to ensure:
1. **Sufficiency**: All changes required for the upgrade goal are present — no missing modifications that would leave the upgrade incomplete.
- All dependencies/plugins listed in the plan for this step are updated
- All required code changes (API migrations, import updates, config changes) are made
- All compilation and compatibility issues introduced by the upgrade are addressed
2. **Necessity**: All changes are strictly necessary for the upgrade — no unnecessary modifications, refactoring, or "improvements" beyond what's required. This includes:
- **Functional Behavior Consistency**: Original code behavior and functionality are maintained:
- Business logic unchanged
- API contracts preserved (inputs, outputs, error handling)
- Expected outputs and side effects maintained
- **Security Controls Preservation** (critical subset of behavior):
- **Authentication**: Login mechanisms, session management, token validation, MFA configurations
- **Authorization**: Role-based access control, permission checks, access policies, security annotations (@PreAuthorize, @Secured, etc.)
- **Password handling**: Password encoding/hashing algorithms, password policies, credential storage
- **Security configurations**: CORS policies, CSRF protection, security headers, SSL/TLS settings, OAuth/OIDC configurations
- **Audit logging**: Security event logging, access logging
**Review Code Changes Actions**:
- Review each changed file for missing upgrade changes, unintended behavior or security modifications
- If behavior must change due to framework requirements, document the change, the reason, and confirm equivalent functionality/protection is maintained
- Add missing changes that are required for the upgrade step to be complete
- Revert unnecessary changes that don't affect behavior or security controls
- Document review results in progress.md and commit message
### Commit Message Format
- First line: `Step <x>: <title> - Compile: <result>` or `Step <x>: <title> - Compile: <result>, Tests: <pass>/<total> passed`
- Body: Changes summary + concise known issues/limitations (≤5 lines)
- **Security note**: If any security-related changes were made, include "Security: <change description and justification>"
### Efficiency (IMPORTANT)
- **Targeted reads**: Use `grep` over full file reads; read specific sections, not entire files. Template files are large - only read the section you need.
- **Quiet commands**: Use `-q`, `--quiet` for build/test commands when appropriate
- **Progressive writes**: Update progress.md incrementally after each step, not at end
-->
# Upgrade Progress: <PROJECT_NAME> (<RUN_ID>)
- **Started**: <timestamp> <!-- replace with actual start timestamp -->
- **Plan Location**: `.github/java-upgrade/<RUN_ID>/plan.md`
- **Total Steps**: <number of steps from plan.md>
## Step Details
<!--
For each step in plan.md, track progress using this bullet list format:
- **Step N: <Step Title>**
- **Status**: <status emoji>
- 🔘 Not Started - Step has not been started yet
- ⏳ In Progress - Currently working on this step
- ✅ Completed - Step completed successfully
- ❗ Failed - Step failed after exhaustive attempts
- **Changes Made**: (≤5 bullets, keep each ≤20 words)
- Focus on what changed, not how
- **Review Code Changes**:
- Sufficiency: ✅ All required changes present / ⚠️ <list missing changes added, short and concise>
- Necessity: ✅ All changes necessary / ⚠️ <list unnecessary changes reverted, short and concise>
- Functional Behavior: ✅ Preserved / ⚠️ <list unavoidable changes with justification, short and concise>
- Security Controls: ✅ Preserved / ⚠️ <list unavoidable changes with justification and equivalent protection, short and concise>
- **Verification**:
- Command: <actual command executed>
- JDK: <JDK path used>
- Build tool: <Path of build tool used>
- Result: <SUCCESS/FAILURE with details>
- Notes: <any skipped checks, excluded modules, known issues>
- **Deferred Work**: List any deferred work, temporary workarounds (or "None")
- **Commit**: <commit hash> - <commit message first line>
---
SAMPLE UPGRADE STEP:
- **Step X: Migrate Azure Management Dependencies**
- **Status**: ✅ Completed
- **Changes Made**:
- Added azure-sdk-bom to dependencyManagement
- Replaced com.microsoft.azure:azure-mgmt-* with com.azure.resourcemanager
- Replaced azure-client-authentication with azure-identity
- **Review Code Changes**:
- Sufficiency: ✅ All required changes present
- Necessity: ✅ All changes necessary
- Functional Behavior: ✅ Preserved - API contracts and business logic unchanged
- Security Controls: ✅ Preserved - authentication pattern updated with equivalent protection
- **Verification**:
- Command: `mvn clean test-compile -q`
- JDK: /usr/lib/jvm/java-8-openjdk
- Build tool: /usr/local/maven/bin/mvn
- Result: ✅ Compilation SUCCESS | ⚠️ Tests: 10/12 passed (2 failures deferred to Final Validation)
- Notes: 2 test failures related to auth mock setup
- **Deferred Work**: Fix 2 test failures in Final Validation step (TestAuthHelper, TestResourceCreation)
- **Commit**: abc1234 - Step X: Migrate Azure Management Dependencies - Compile: SUCCESS, Tests: 10/12 passed
---
SAMPLE FINAL VALIDATION STEP:
- **Step X: Final Validation**
- **Status**: ✅ Completed
- **Changes Made**:
- Verified no legacy com.microsoft.azure.* dependencies remain
- Resolved 2 TODOs from Step 3
- Fixed 2 test failures (auth mock setup, resource assertion)
- **Review Code Changes**:
- Sufficiency: ✅ All required changes present
- Necessity: ✅ All changes necessary
- Functional Behavior: ✅ Preserved - all business logic and API contracts maintained
- Security Controls: ✅ Preserved - all authentication, authorization unchanged
- **Verification**:
- Command: `mvn clean test -q`
- JDK: /usr/lib/jvm/java-8-openjdk
- Build tool: /usr/local/maven/bin/mvn
- Result: ✅ Compilation SUCCESS | ✅ Tests: 12/12 passed (100% pass rate achieved)
- **Deferred Work**: None - all TODOs resolved
- **Commit**: xyz3456 - Step X: Final Validation - Compile: SUCCESS, Tests: 12/12 passed
-->
---
## Notes
<!--
Additional context, observations, or lessons learned during execution.
Use this section for:
- Unexpected challenges encountered
- Deviation from original plan
- Performance observations
- Recommendations for future upgrades
SAMPLE:
- azure-sdk-bom simplified version management significantly
- Authentication migration required adding jackson-databind for file-based auth
- Modern SDK fluent API method names differ from legacy — migration guide was essential
-->
references/languages/java/templates/SUMMARY_TEMPLATE.md›
<!--
This is the upgrade summary generated after successful completion of the Azure SDK migration.
It documents the final results, changes made, and lessons learned.
## SUMMARY RULES
!!! DON'T REMOVE THIS COMMENT BLOCK BEFORE UPGRADE IS COMPLETE AS IT CONTAINS IMPORTANT INSTRUCTIONS.
### Prerequisites (must be met before generating summary)
- All steps in plan.md have ✅ in progress.md
- Final Validation step completed successfully
### Success Criteria Verification
- **Goal**: All legacy Azure SDK dependencies (com.microsoft.azure.*) replaced with modern equivalents (com.azure.*)
- **Compilation**: Both main AND test code compile = `mvn clean test-compile` succeeds
- **Test**: 100% pass rate = `mvn clean test` succeeds (or ≥ baseline with documented pre-existing flaky tests)
### Content Guidelines
- **Upgrade Result**: MUST show 100% pass rate or justify EACH failure with exhaustive documentation
- **Tech Stack Changes**: Table with Dependency | Before | After | Reason
- **Commits**: List with IDs and messages from each step
- **Challenges**: Key issues and resolutions encountered
- **Limitations**: Only genuinely unfixable items where: (1) multiple fix approaches attempted, (2) root cause identified, (3) technically impossible to fix
- **Next Steps**: Recommendations for post-upgrade actions
### Efficiency (IMPORTANT)
- **Targeted reads**: Use `grep` over full file reads; read specific sections from progress.md, not entire files.
-->
# Upgrade Summary: <PROJECT_NAME> (<RUN_ID>)
- **Completed**: <timestamp> <!-- replace with actual completion timestamp -->
- **Plan Location**: `plan.md`
- **Progress Location**: `progress.md`
## Upgrade Result
<!--
Compare final compile/test results against baseline.
MUST show 100% pass rate or justify EACH failure with exhaustive documentation.
SAMPLE:
| Metric | Baseline | Final | Status |
| ---------- | ------------------ | ------------------ | ------ |
| Compile | ✅ SUCCESS | ✅ SUCCESS | ✅ |
| Tests | 12/12 passed | 12/12 passed | ✅ |
| JDK | JDK 8 | JDK 8 | ✅ |
| Build Tool | Maven 3.9.6 | Maven 3.9.6 | ✅ |
**Upgrade Goals Achieved**:
- ✅ All com.microsoft.azure.* dependencies replaced with com.azure.* equivalents
- ✅ Source code migrated to modern Azure SDK APIs
-->
| Metric | Baseline | Final | Status |
| ---------- | -------- | ----- | ------ |
| Compile | | | |
| Tests | | | |
| JDK | | | |
| Build Tool | | | |
**Upgrade Goals Achieved**:
## Tech Stack Changes
<!--
Table documenting all dependency changes made during the upgrade.
Only include dependencies that were actually changed.
SAMPLE:
| Dependency | Before | After | Reason |
| ----------------------------------------------- | ------- | ------------------------------------------------ | ----------------------------------- |
| com.microsoft.azure:azure-mgmt-resources | 1.41.4 | Removed | Replaced by azure-resourcemanager |
| com.azure.resourcemanager:azure-resourcemanager | N/A | (managed by azure-sdk-bom) | Modern replacement |
| com.microsoft.azure:azure-client-authentication | 1.7.14 | Removed | Replaced by azure-identity |
| com.azure:azure-identity | N/A | (managed by azure-sdk-bom) | Modern auth library |
| com.azure:azure-sdk-bom | N/A | <TARGET_AZURE_SDK_BOM_VERSION> | Centralized version management |
-->
| Dependency | Before | After | Reason |
| ---------- | ------ | ----- | ------ |
## Commits
<!--
List all commits made during the upgrade with their short IDs and messages.
SAMPLE:
| Commit | Message |
| ------- | ---------------------------------------------------------------------- |
| abc1234 | Step 1: Setup Baseline - Compile: SUCCESS \| Tests: 12/12 passed |
| def5678 | Step 2: Migrate Azure Dependencies - Compile: SUCCESS |
| ghi9012 | Step 3: Migrate Source Code - Compile: SUCCESS |
| jkl3456 | Step 4: Final Validation - Compile: SUCCESS \| Tests: 12/12 passed |
-->
| Commit | Message |
| ------ | ------- |
## Challenges
<!--
Document key challenges encountered during the upgrade and how they were resolved.
SAMPLE:
- **Authentication Migration**
- **Issue**: Legacy code uses Azure.authenticate(credentialFile) which has no direct equivalent in modern SDK.
- **Resolution**: Read credential file with Jackson ObjectMapper, construct ClientSecretCredential, use AzureProfile.
- **Files Changed**: AzureHelper.java, AppConfig.java
- **Fluent API Changes**
- **Issue**: Method names differ between legacy Azure.management and modern azure-resourcemanager.
- **Resolution**: Followed migration guide at https://aka.ms/java-track2-migration-guide
- **Files Changed**: ResourceProvisioner.java, StorageSetup.java
-->
## Limitations
<!--
Document any genuinely unfixable limitations that remain after the upgrade.
This section should be empty if all issues were resolved.
Only include items where: (1) multiple fix approaches were attempted, (2) root cause is identified,
(3) fix is technically impossible without breaking other functionality.
-->
## Review Code Changes Summary
<!--
Document review code changes results from the upgrade.
This section ensures the upgrade is both sufficient (complete) and necessary (no extraneous changes),
with original functionality and security controls preserved.
VERIFICATION AREAS:
1. Sufficiency: All required upgrade changes are present — no missing modifications
2. Necessity: All changes are strictly necessary — no unnecessary modifications, including:
- Functional Behavior Consistency: Business logic, API contracts, expected outputs
- Security Controls Preservation (critical subset of behavior):
- Authentication: Login mechanisms, session management, token validation, MFA configurations
- Authorization: Role-based access control, permission checks, access policies, security annotations (@PreAuthorize, @Secured, etc.)
- Password handling: Password encoding/hashing algorithms, password policies, credential storage
- Security configurations: CORS policies, CSRF protection, security headers, SSL/TLS settings, OAuth/OIDC configurations
- Audit logging: Security event logging, access logging
SAMPLE (no issues):
**Review Status**: ✅ All Passed
**Sufficiency**: ✅ All required upgrade changes are present
**Necessity**: ✅ All changes are strictly necessary
- Functional Behavior: ✅ Preserved — business logic, API contracts unchanged
- Security Controls: ✅ Preserved — authentication, authorization, password handling, security configs, audit logging unchanged
SAMPLE (with behavior changes):
**Review Status**: ⚠️ Changes Documented Below
**Sufficiency**: ✅ All required upgrade changes are present
**Necessity**: ⚠️ Behavior changes required by SDK migration (documented below)
- Functional Behavior: ✅ Preserved
- Security Controls: ⚠️ Changes made with equivalent protection
| Area | Change Made | Reason | Equivalent Behavior |
| ------------------ | ------------------------------------------------ | ---------------------------------------------- | --------------------- |
| Authentication | Azure.authenticate() → ClientSecretCredential | Legacy auth API removed in modern SDK | ✅ Same credentials |
-->
## Next Steps
<!--
Recommendations for post-upgrade actions.
SAMPLE:
- [ ] Run full integration test suite in staging environment
- [ ] Performance testing to validate no regression
- [ ] Update CI/CD pipelines if dependency versions changed
- [ ] Update documentation to reflect new Azure SDK versions
- [ ] Review azure-sdk-bom version periodically for updates
-->
## Artifacts
- **Plan**: `.github/java-upgrade/<RUN_ID>/plan.md`
- **Progress**: `.github/java-upgrade/<RUN_ID>/progress.md`
- **Summary**: `.github/java-upgrade/<RUN_ID>/summary.md` (this file)
- **Branch**: `java-upgrade/<RUN_ID>`
references/languages/java/workflow/phase-1-precheck.md›
# Phase 1: Precheck
Load this file when executing Phase 1. Refer back to [`upgrade-success-criteria`](../rules/upgrade-success-criteria.md) and [`upgrade-strategy`](../rules/upgrade-strategy.md) for success criteria and strategy, and [`../rules/troubleshooting.md`](../rules/troubleshooting.md) when failures occur.
| Category | Scenario | Action |
| ------------------- | -------------------------------- | ------------------------------------------------------ |
| Unsupported Project | Not a Maven/Gradle project | STOP with error |
| Unsupported Project | Git not installed or not managed | Log warning, continue without git |
| Invalid Goal | No legacy Azure SDK deps found | STOP — nothing to migrate |
| Java Version | Below JDK 8 | Include Java upgrade as part of the migration plan |
**Prerequisites**: JDK 8+ and Maven or Gradle must be pre-installed.
**Environment detection**:
Detect available JDKs:
1. Check `JAVA_HOME` and `JDK_HOME` environment variables
2. Run `java -version` and `javac -version` to detect the default JDK
3. Search common JDK installation paths (platform-specific: Program Files on Windows, /usr/lib/jvm on Linux, /Library/Java on macOS)
4. Check for version manager installations (SDKMAN, ASDF, jenv, Jabba)
5. For each found JDK, read the `release` file to determine the version
Report all found JDKs with their path, version, and discovery source.
Detect build tools:
1. Check for Maven Wrapper (`mvnw`/`mvnw.cmd`) or Gradle Wrapper (`gradlew`/`gradlew.bat`) in the project root — prefer wrappers when present
2. If a wrapper exists, read `.mvn/wrapper/maven-wrapper.properties` or `gradle/wrapper/gradle-wrapper.properties` to determine the wrapper-defined version
3. Run `mvn --version` or `gradle --version` to detect system installations
4. Check `MAVEN_HOME`/`M2_HOME` environment variables
Report all found installations with their path, version, and source.
**On success**: Create `.github/java-upgrade/{RUN_ID}/plan.md` from the Plan Template — replace placeholders (`<RUN_ID>`, `<PROJECT_NAME>`, `<current_branch>`, `<current_commit_id>`, datetime) and follow the HTML-comment instructions to populate each section.
references/languages/java/workflow/phase-2-plan.md›
# Phase 2: Generate Upgrade Plan
Load this file when executing Phase 2. Refer back to [`upgrade-success-criteria`](../rules/upgrade-success-criteria.md) and [`upgrade-strategy`](../rules/upgrade-strategy.md) for success criteria and strategy, and [`../rules/troubleshooting.md`](../rules/troubleshooting.md) when failures occur.
## 1. Initialize
1. Update `plan.md`: replace all remaining placeholders
2. Extract user-specified guidelines from prompt into "Guidelines" section (bulleted list; leave empty if none)
## 2. Environment Analysis
1. Read HTML comments in "Available Tools" section of `plan.md` to understand rules and expected format
2. Record discovered JDK versions and paths
3. Detect wrapper presence; if wrapper exists, read wrapper properties to determine build tool version
4. Check build tool version compatibility with JDK — flag incompatible versions for upgrade
## 3. Dependency Analysis
1. Read HTML comments in "Technology Stack" and "Derived Upgrades" sections of `plan.md` to understand rules and expected format
2. Identify core tech stack across **ALL modules** (direct deps + upgrade-critical deps)
3. Include build tool (Maven/Gradle) and build plugins (`maven-compiler-plugin`, `maven-surefire-plugin`, etc.) in the technology stack analysis
4. Flag EOL dependencies (high priority for upgrade)
5. Consult the Migration Guidelines for package mappings and migration guides
6. Populate "Technology Stack" and "Derived Upgrades"
## 4. Upgrade Path Design
1. Read HTML comments in "Key Challenges" and "Upgrade Steps" sections of `plan.md` to understand rules and expected format
2. For incompatible deps, prefer: Replacement > Adaptation > Rewrite
3. Finalize "Available Tools" section based on the planned step sequence
4. Design step sequence:
- **Step 1 (MANDATORY)**: Setup Baseline — run compile/test with current JDK, document results
- **Steps 2-N**: Upgrade steps — dependency order, high-risk early, isolated breaking changes. Compilation must pass (both main and test code); test failures documented for Final Validation.
- **Final step (MANDATORY)**: Final Validation — verify all goals met, all TODOs resolved, achieve **Upgrade Success Criteria** through iterative test & fix loop.
5. Identify high-risk areas for "Key Challenges" section
6. Write steps following format in `plan.md`
## 5. Plan Review
1. Verify all placeholders filled in `plan.md`, check for missing coverage/infeasibility/limitations
2. Revise plan as needed for completeness and feasibility; document unfixable limitations in "Plan Review" section
3. Ensure all sections of `plan.md` are fully populated (per **Template compliance** rule) and all HTML comments removed
After plan generation, proceed directly to execution — create `.github/java-upgrade/{RUN_ID}/progress.md` from the Progress Template, replace placeholders, and begin execution. Log the migration plan, then proceed without pausing for confirmation.
references/languages/java/workflow/phase-3-execute.md›
# Phase 3: Execute Upgrade Plan
Load this file when executing Phase 3. Refer back to [`upgrade-success-criteria`](../rules/upgrade-success-criteria.md) and [`upgrade-strategy`](../rules/upgrade-strategy.md) for success criteria and strategy, and [`../rules/troubleshooting.md`](../rules/troubleshooting.md) when failures occur.
## 1. Initialize
1. Read `plan.md` for step details
2. Update `progress.md`:
- Replace `<RUN_ID>`, `<PROJECT_NAME>` and timestamp placeholders
- Create step entries for each step in `plan.md` (per **Template compliance** rule)
## 2. Execute
For each step:
1. Read `plan.md` for step details and guidelines
2. Mark ⏳ in `progress.md`
3. Make changes as planned (use OpenRewrite if helpful, verify results)
- Add TODOs for any deferred work, e.g., temporary workarounds
4. **Review Code Changes** (per rules in Progress Template): Verify sufficiency (all required changes present) and necessity (no unnecessary changes, functional behavior preserved, security controls maintained).
- Add missing changes and revert unnecessary changes. Document any unavoidable behavior changes with justification.
5. Verify with specified command/JDK:
- **Steps 1-N (Setup/Upgrade)**: Compilation must pass (including both main and test code, fix immediately if not). Test failures acceptable — document count.
- **Final Validation Step**: Achieve **Upgrade Success Criteria** — iterative test & fix loop until 100% pass (or ≥ baseline). NO deferring. See [`../rules/troubleshooting.md`](../rules/troubleshooting.md) when stuck.
- Build: `mvn clean test-compile` (or `./gradlew compileTestJava` for Gradle)
- Test: `mvn clean test` (or `./gradlew test` for Gradle)
6. Commit on the `java-upgrade/{RUN_ID}` branch with message format (if git available; otherwise, log details in `progress.md`):
- First line: `Step <x>: <title> - Compile: <result>` or `Step <x>: <title> - Compile: <result>, Tests: <pass>/<total> passed` (if tests run)
- Body: Changes summary + concise known issues/limitations (≤5 lines)
- **Security note**: If any security-related changes were made, include "Security: <change description and justification>"
7. Update `progress.md` with step details and mark ✅ or ❗
## 3. Complete
1. Validate all steps in `plan.md` have ✅ in `progress.md`
2. Validate all **Upgrade Success Criteria** are met, or otherwise go back to Final Validation step to fix
references/languages/java/workflow/phase-4-summarize.md›
# Phase 4: Summarize & Validate
Load this file when executing Phase 4. Refer back to [`upgrade-success-criteria`](../rules/upgrade-success-criteria.md) and [`upgrade-strategy`](../rules/upgrade-strategy.md) for success criteria and strategy, and [`../rules/troubleshooting.md`](../rules/troubleshooting.md) when failures occur.
1. Create `.github/java-upgrade/{RUN_ID}/summary.md` from the Summary Template — replace placeholders and follow HTML-comment instructions to populate final results.
2. Apply the validation checklist from the Migration Guidelines:
- Migrated project passes compilation
- All tests pass — don't silently skip tests
- No legacy SDK dependencies/references exist
- If `azure-sdk-bom` is used, ensure no explicit version dependencies for Azure libraries in the BOM
- For each migration guide recorded during migration, fetch and verify the migrated code follows the guide's recommendations. Fix any deviations.
3. Populate `summary.md` (Upgrade Result, Tech Stack Changes, Commits, Challenges, Limitations, Next Steps)
4. Clean up temp files; remove HTML comments from all `.md` files
5. Verify all goals met
references/services/functions/consumption-to-flex/linux/assessment.md›
# 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 whether TLS/SSL certificates are in use. Flex Consumption uses the preview site-scoped certificate model, so plan to re-add certificates after migration and verify the documented limits and Linux certificate-loading behavior.
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_DIR>/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_DIR>/upgrade-assessment-report.md`. See [Artifact Output Policy](../../../../global-rules.md#artifact-output-policy) for how `<UPGRADE_DIR>` resolves (default: `.azure-upgrade/<source-app-name>/`).
```markdown
# Upgrade Assessment Report
## 1. Executive Summary
| Property | Value |
|----------|-------|
| **App Name** | <app-name> |
| **Resource Group** | <resource-group> |
| **Current Plan** | <current-plan (e.g., Consumption / Y1 Dynamic)> |
| **Target Plan** | <target-plan (e.g., Flex Consumption / FC1)> |
| **Region** | <region> |
| **Runtime** | <runtime and version> |
| **OS** | <Linux / Windows> |
| **Upgrade Readiness** | <Ready / Needs Attention / Blocked> |
| **Assessment Date** | <date> |
## 2. Compatibility Checks
| Check | Status | Details |
|-------|--------|---------|
| Region supported | ✅ / ❌ | |
| Language stack supported | ✅ / ❌ | |
| Stack version supported | ✅ / ❌ | |
| No deployment slots | ✅ / ⚠️ | |
| Certificate requirements assessed | ✅ / ⚠️ / N/A | |
| Blob triggers use EventGrid | ✅ / ⚠️ / N/A | |
| .NET isolated (not in-process) | ✅ / ❌ / N/A | |
## 3. App Settings Inventory
> Record setting names only. Never include app-setting values in the assessment.
| Setting | 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)
- [ ] <any blocking issues>
### Warnings (should address but not blocking)
- [ ] <any non-blocking concerns>
## 9. Recommendations
1. **Plan**: <recommended target plan>
2. **Auth**: <switch to Managed Identity if using connection strings>
3. **Monitoring**: <Application Insights configuration>
4. **Scaling**: <recommended instance count and concurrency settings>
## 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_azure_bestpractices` to get the latest recommendations for the target hosting plan.
references/services/functions/consumption-to-flex/linux/automation.md›
# 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
# Verify required tools
az version --query '"azure-cli"' -o tsv
jq --version
# Install resource-graph extension
az extension add --name resource-graph
# Login and set subscription
az login
az account set --subscription <SUBSCRIPTION_ID>
```
If `az` or `jq` is unavailable, stop and provide the relevant installation guidance. If the Azure CLI version is lower than `2.77.0`, stop and require an upgrade before running migration commands.
---
## 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=<APP_NAME>
rgName=<RESOURCE_GROUP>
```
### 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 <REGION> --runtime <LANGUAGE_STACK> \
--query '[].{version:version}' -o tsv
```
Replace `<REGION>` with the app's region and `<LANGUAGE_STACK>` 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, determine whether the source app actually uses those certificates. Plan to re-add used certificates after migration with the documented [site-scoped certificate process](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to#configure-site-scoped-certificates), verify the per-app limits, and update certificate-loading code to use the documented Linux file paths.
### 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=<APP_NAME>
rgName=<RESOURCE_GROUP>
# Inventory setting names only; never display or persist general setting values
az functionapp config appsettings list --name $appName --resource-group $rgName \
--query "[].name" -o tsv
```
### 3b. Collect Application Configurations
```bash
appName=<APP_NAME>
rgName=<RESOURCE_GROUP>
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=<APP_NAME>
rgName=<RESOURCE_GROUP>
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
UPGRADE_DIR=<UPGRADE_DIR>
(
set -e
trap 'unset runFromPackage' EXIT
echo "Checking run-from-package configuration..."
runFromPackage=$(az functionapp config appsettings list --name $appName --resource-group $rgName \
--query "[?name=='WEBSITE_RUN_FROM_PACKAGE'].value | [0]" -o tsv)
if [[ "$runFromPackage" =~ ^https:// ]]; then
echo "Downloading the remote deployment package..."
curl --fail --location --silent --show-error --output "$UPGRADE_DIR/source-package.zip" "$runFromPackage"
echo "Downloaded the remote deployment package without displaying its URL."
elif [[ -n "$runFromPackage" ]]; then
echo "Linux Consumption requires WEBSITE_RUN_FROM_PACKAGE to be a remote URL; found an unsupported non-URL value." >&2
exit 1
else
echo "No run-from-package URL was detected; use the scm-releases fallback below or redeploy from source control."
fi
)
```
If the remote package was downloaded, skip the storage download below. If no URL was configured, continue with the `scm-releases` fallback.
#### Download from scm-releases blob container
Linux Consumption apps store deployment packages in the `scm-releases` blob container (in `squashfs` format).
> 📁 The download below writes to `<UPGRADE_DIR>` (default: `.azure-upgrade/<source-app-name>/`). See [Artifact Output Policy](../../../../global-rules.md#artifact-output-policy). Resolve `UPGRADE_DIR` once at session start and create the directory before running.
```bash
appName=<APP_NAME>
rgName=<RESOURCE_GROUP>
UPGRADE_DIR=<UPGRADE_DIR> # e.g. .azure-upgrade/$appName — see global-rules
(
set -e
trap 'unset AZURE_STORAGE_CONNECTION_STRING' EXIT
echo "Getting the storage account connection string..."
export AZURE_STORAGE_CONNECTION_STRING=$(az functionapp config appsettings list --name $appName --resource-group $rgName \
--query "[?name=='AzureWebJobsStorage'].value | [0]" -o tsv)
if [[ -z "$AZURE_STORAGE_CONNECTION_STRING" ]]; then
echo "AzureWebJobsStorage was not found." >&2
exit 1
fi
echo "Getting the package name..."
packageName=$(az storage blob list --container-name scm-releases --query "[0].name" -o tsv)
if [[ -z "$packageName" ]]; then
echo "No deployment package was found in the scm-releases container." >&2
exit 1
fi
echo "Downloading package: $packageName -> $UPGRADE_DIR/$packageName"
az storage blob download --container-name scm-releases --name "$packageName" \
--file "$UPGRADE_DIR/$packageName" --no-progress --output none
echo "Downloaded package: $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_APP_NAME> \
--source-resource-group <SOURCE_RESOURCE_GROUP> \
--name <NEW_APP_NAME> \
--resource-group <RESOURCE_GROUP>
```
**Optional flags**:
- `--storage-account <ACCOUNT>` — use a different storage account
- `--maximum-instance-count <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 <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> \
--query "{name:name, kind:kind, sku:properties.sku}" --output table
# Review migrated app-setting names without displaying values
az functionapp config appsettings list --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> \
--query "[].name" -o tsv
# Check managed identity
az functionapp identity show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP>
# Check custom domains
az functionapp config hostname list --webapp-name <NEW_APP_NAME> --resource-group <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 <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> \
--enabled true --action <AUTH_ACTION>
```
#### Scale and Concurrency (if custom values needed)
```bash
# Set maximum scale-out (default: 100, range: 1-1000)
az functionapp scale config set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> \
--maximum-instance-count <MAX_SCALE_SETTING>
```
> ⚠️ 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 `<NEW_APP_NAME>` 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 <NEW_APP_NAME>` 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)
Verify that Azure Functions Core Tools v4 is available:
```bash
func --version
```
If `func` is unavailable or the reported major version is not `4`, stop and provide the [Core Tools installation guidance](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools). Do not install or upgrade Core Tools without user approval; offer the pipeline or existing-package deployment options instead.
```bash
# From your project directory
func azure functionapp publish <NEW_APP_NAME>
```
### Option C: Deploy Existing Package (if user selects option 3)
```bash
# Deploy the zip package downloaded in Step 3 (resolves to <UPGRADE_DIR>/<PACKAGE_NAME>
# per the Artifact Output Policy in references/global-rules.md)
az functionapp deployment source config-zip --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> \
--src <UPGRADE_DIR>/<PACKAGE_NAME>
```
### 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 <NEW_APP_NAME> --resource-group <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 <NEW_APP_NAME> --resource-group <RESOURCE_GROUP>
```
### Verify Plan
```bash
az functionapp show --name <NEW_APP_NAME> --resource-group <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/<FUNCTION_NAME>"
```
### 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 == "<NEW_APP_NAME>"
| where timestamp > ago(1d)
| project timestamp, message, operation_Name, customDimensions
| order by timestamp desc
```
---
## Step 7: Cleanup (Optional)
Do not offer source deletion until all of these conditions are verified:
1. all applicable production HTTP traffic, clients, DNS, and custom domains use the target;
2. every applicable production trigger has completed source-first cutover and target validation;
3. no production-readiness work remains deferred;
4. the source no longer processes production workloads.
If any condition is incomplete, retain the source for rollback and record cleanup as skipped or deferred.
Only after every condition passes, use `ask_user` immediately before deletion:
> Production cutover is complete and source app `<SOURCE_APP_NAME>` is no longer processing production workloads. May I permanently delete it? This is irreversible and removes the rollback app.
Run only after that explicit approval:
```bash
az functionapp delete --name <SOURCE_APP_NAME> --resource-group <SOURCE_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
If the source app was previously stopped, use `ask_user` immediately before restarting it:
> May I restart source app `<SOURCE_APP_NAME>` in resource group `<SOURCE_RESOURCE_GROUP>` to restore processing?
Run only after that explicit approval:
```bash
az functionapp start --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP>
```
Deleting the new Flex app requires a separate confirmation:
```bash
az functionapp delete --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP>
```
references/services/functions/consumption-to-flex/linux/workflow.md›
# 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 → Linux. For a Windows Consumption source, use the [Windows workflow](../windows/workflow.md).
> **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 | ⚠️ Site-scoped certificates in preview | Re-add certificates after migration, verify the per-app limits, and update code that loads certificates to use Linux file paths |
| 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 `<NEW_APP_NAME>` 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 <NEW_APP_NAME>` 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
> - Do not delete the original app until production traffic and every production trigger have completed cutover and target validation
### 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://<DEFAULT_HOST>/api/<FUNCTION_NAME>`)
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
- Offer deletion only after all production clients, DNS/custom domains, and triggers use the target; target execution is verified; no production-readiness work remains deferred; and the source no longer processes production workloads
- If any condition is incomplete, retain the source and record cleanup as skipped or deferred
- After every condition passes, use the confirmation and 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. Obtain immediate confirmation, then restart the source app if it was stopped: `az functionapp start --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP>`
2. Redirect clients back to original resources (queues/topics/containers)
3. Revert DNS or custom domain changes
4. Obtain separate confirmation before deleting 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)
references/services/functions/consumption-to-flex/windows/assessment.md›
# 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 running on Windows
- 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 whether TLS/SSL certificates are used and plan for the documented site-scoped certificate model, limits, and Linux certificate-loading paths
7. **Check Blob Triggers** — Verify blob triggers use EventGrid source (container polling not supported in Flex Consumption)
8. **Scan Source for Windows Dependencies** — Recommended but skippable. Static-analyze source code or deployment package for Win32/COM/WMI/registry/cert-store usage, Windows-only packages, Windows-RID targeting, and shell-outs to Windows tools. See [dependency-scan.md](dependency-scan.md) for the procedure and severity rubric. Findings populate §11 of the report; `Blocker` findings advisorily flip §1 `Upgrade Readiness` to `Needs Attention` (never hard-blocks).
9. **Assess Dependencies** — Review upstream and downstream service dependencies and plan mitigation strategies
10. **Generate Report** — Create `<UPGRADE_DIR>/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_DIR>/upgrade-assessment-report.md`. See [Artifact Output Policy](../../../../global-rules.md#artifact-output-policy) for how `<UPGRADE_DIR>` resolves (default: `.azure-upgrade/<source-app-name>/`).
```markdown
# Upgrade Assessment Report
## 1. Executive Summary
| Property | Value |
|----------|-------|
| **App Name** | <app-name> |
| **Resource Group** | <resource-group> |
| **Current Plan** | <current-plan (e.g., Consumption / Y1 Dynamic)> |
| **Target Plan** | <target-plan (e.g., Flex Consumption / FC1)> |
| **Region** | <region> |
| **Runtime** | <runtime and version> |
| **OS** | Windows |
| **Upgrade Readiness** | <Ready / Needs Attention / Blocked> |
| **Assessment Date** | <date> |
## 2. Compatibility Checks
| Check | Status | Details |
|-------|--------|---------|
| Region supported | ✅ / ❌ | |
| Language stack supported | ✅ / ❌ | |
| Stack version supported | ✅ / ❌ | |
| No deployment slots | ✅ / ⚠️ | |
| TLS/SSL certificates assessed | ✅ / ⚠️ / N/A | |
| Blob triggers use EventGrid | ✅ / ⚠️ / N/A | |
| .NET isolated (not in-process) | ✅ / ❌ / N/A | |
| Source code Windows dependency scan | ✅ / ⚠️ / ⏭ Skipped | See §11 |
## 3. App Settings Inventory
> Record setting names only. Never include app-setting values in the assessment.
> For surviving names containing `:`, mark **Migrate?** as `Convert` and record the Linux target name with `:` replaced by `__`. Note that .NET configuration maps `__` back to `:`.
| Setting | 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 | | | |
| TLS/SSL certificates | | | Re-add used certificates with the site-scoped certificate process |
| Explicit Azure Files mounts | | No | Catalog custom ID, account, share, type, and mount path; Windows Consumption doesn't support these mounts |
| Access restrictions | | | |
| Built-in auth | | Conditional | Configure and validate in Step 4f.auth, or record as deferred |
| Function access keys | | Conditional | Choose target-generated keys or preserve source host and HTTP-function keys; system and master keys aren't copied |
## 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)
- [ ] <any blocking issues>
### Warnings (should address but not blocking)
- [ ] <any non-blocking concerns>
## 9. Recommendations
1. **Plan**: <recommended target plan>
2. **Auth**: <switch to Managed Identity if using connection strings>
3. **Monitoring**: <Application Insights configuration>
4. **Scaling**: <recommended instance count and concurrency settings>
## 10. Next Steps
- [ ] Review and approve this assessment
- [ ] Address any blockers listed above
- [ ] Proceed to automated upgrade (Phase 3-4)
## 11. Source Code Windows Dependency Scan
| Property | Value |
|----------|-------|
| **Scan ran?** | Yes / No (skipped by user) |
| **Source scanned** | <local path / git URL / package from Step 3e> |
| **Language pattern set** | <FUNCTIONS_WORKER_RUNTIME value> |
| **Blockers** | <count> |
| **Likely issues** | <count> |
| **Manual review** | <count> |
### Findings
| Severity | Category | File | Line | Match | Remediation |
|----------|----------|------|------|-------|-------------|
| 🛑 / ⚠️ / ℹ️ | 1-8 | | | | |
> 📝 **Heuristic aid, not a guarantee.** The findings table reflects pattern matches only — uncommon Windows APIs, dynamically-loaded native libraries, transitive package dependencies, reflection-based calls, and runtime-generated code may not appear. The app owner is responsible for verifying no Windows dependencies remain.
>
> If `Scan ran? = No`, write a single line in place of the findings table: *"User declined the source code dependency scan. Migration proceeded without static analysis of Windows dependencies."*
>
> See [dependency-scan.md](dependency-scan.md) for the categories, severity rubric, and per-language patterns.
```
> 💡 **Tip:** Use `mcp_azure_mcp_get_azure_bestpractices` to get the latest recommendations for the target hosting plan.
references/services/functions/consumption-to-flex/windows/automation.md›
# Automation: Consumption to Flex Consumption Upgrade
> Azure CLI scripts to automate the upgrade from Windows Consumption plan to Flex Consumption plan.
>
> **Shell compatibility**: Scripts use a universal subset that runs unchanged in:
>
> - bash 5.x (Cloud Shell, WSL, macOS, Linux)
> - PowerShell 7.x on any OS
>
> Secret-handling steps provide separate Bash and PowerShell 7 blocks when the shells require different cleanup or quoting syntax. Step 4c uses `jq` for the Bash in-memory JSON transform and `ConvertFrom-Json` / `ConvertTo-Json` for PowerShell.
>
> Replace `<PLACEHOLDERS>` (e.g. `<APP_NAME>`, `<RESOURCE_GROUP>`) with literal values before running each command. Each `az` command is a single line for cross-shell safety; conditional steps are described in prose between code blocks.
>
> **Source docs**: [Windows migration guide](https://learn.microsoft.com/en-us/azure/azure-functions/migration/migrate-plan-consumption-to-flex?pivots=platform-windows)
>
> `az functionapp flex-migration` does not support Windows. All migration steps are performed using raw Azure CLI commands.
>
> ⚠️ **Source-app safety invariant**: All discovery and replication steps treat the source app as read-only. Before any command that changes the source app's settings, triggers, state, deployment, bindings, or existence, use `ask_user` to describe the exact command and impact and obtain explicit approval. Approval applies only to that specific action.
>
> ⚠️ **Workflow continuity**: After each step succeeds, immediately perform the next read-only preflight. If the next action requires approval, invoke `ask_user` in the same turn. Never stop after only announcing the next step and wait for the user to re-engage.
>
> 📍 **Progress reporting**: Create and maintain `<UPGRADE_DIR>/upgrade-status.md` using [Migration Progress](progress-tracking.md). Derive the current and total phases and executable steps from the workflow headings, then display the same `Phase <current> of <total> · <step> · <description> · <state>` banner before and after every step. Do not use percentages.
## Workflow Files
Load only the file for the current workflow unit. Complete it before loading the next file unless that file explicitly directs otherwise.
| Order | Workflow unit | Instructions |
|-------|---------------|--------------|
| 1 | Prerequisites, discovery, and compatibility assessment | [Discovery and Assessment](automation/discovery-and-assessment.md) |
| 2 | Source configuration and dependency inventory | [Configuration Inventory](automation/configuration-inventory.md) |
| 3 | Deployment package retrieval when local source is unavailable | [Deployment Package](automation/deployment-package.md) |
| 4 | Target naming, storage selection, authentication preflight, and creation | [App Creation](automation/app-creation.md) |
| 5 | Production-setting copy or defer decision and secure transfer | [App Settings](automation/app-settings.md) |
| 6 | Site configuration and managed identities | [Site and Identity Configuration](automation/site-and-identity.md) |
| 7 | Built-in authentication configuration or migration deferral | [Built-in Authentication](automation/built-in-authentication.md) |
| 8 | CORS, domains, and access restrictions | [Networking and Storage](automation/networking-and-storage.md) |
| 9 | Pre-deployment trigger safety, monitoring, and configuration verification | [Trigger Safety and Monitoring](automation/trigger-safety-and-monitoring.md) |
| 10 | Access-key inventory and preservation decision | [Access Key Strategy](automation/access-key-strategy.md) |
| 11 | Code deployment | [Code Deployment](automation/code-deployment.md) |
| 12 | Post-deployment access-key copy and verification | [Access Key Copy](automation/access-key-copy.md) |
| 13 | Post-upgrade validation and production cutover | [Validation and Cutover](automation/validation-and-cutover.md) |
| 14 | Outcome-gated cleanup and rollback | [Cleanup and Rollback](automation/cleanup-and-rollback.md) |
The headings inside these files remain the source of truth for stable step IDs and progress tracking. The table defines execution order; filenames describe responsibilities and do not encode ordering.references/services/functions/consumption-to-flex/windows/automation/access-key-copy.md›
# Access Key Copy
### 5a. Apply the Access Key Strategy
Run only after Step 5 verifies that all target functions synchronized. If Step 4l selected target-generated keys, mark this step `skipped - target-generated keys selected` and record that key-dependent clients must be updated before HTTP cutover.
If preservation was selected, process only HTTP-triggered functions from the verified Step 4j trigger inventory. Match those source and target functions by exact name. For a keyed source HTTP function without an exact target match, ask whether it was removed or renamed. Skip removed functions with a recorded reason; require an explicit source-to-target name mapping for renamed functions.
Fetch key values only into process memory. For each source host key, run:
```bash
az functionapp keys set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --key-type functionKeys --key-name <KEY_NAME> --key-value <KEY_VALUE> --output none --only-show-errors
```
For each key returned by `az functionapp function keys list` for each mapped HTTP function, run:
```bash
az functionapp function keys set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --function-name <TARGET_FUNCTION_NAME> --key-name <KEY_NAME> --key-value <KEY_VALUE> --output none --only-show-errors
```
Use `jq` in Bash and `ConvertFrom-Json` in PowerShell to enumerate the in-memory `functionKeys` object and per-function key properties. Exclude the per-function metadata fields `id`, `kind`, `name`, `properties`, and `type`. Check `$LASTEXITCODE` after every native command in PowerShell. Clear every JSON object, key value, and derived row in `trap`/`finally`.
> ⚠️ `--key-value` briefly exposes the value in the Azure CLI child process arguments. Run only in a trusted terminal, never enable shell tracing or CLI debug output, and use `--output none --only-show-errors`. Never write keys to migration artifacts or temporary files.
Reruns are idempotent: setting the same name and value overwrites the matching target key. Preserve target-only keys; don't delete anything.
After all writes, re-list source and target host keys and HTTP-function keys into memory and compare every selected name and value. Report only counts and pass/fail results. On any mismatch or CLI failure, clear variables, mark Step 5a blocked, and don't begin HTTP validation or cutover.
List target system-key names after deployment and record which webhook integrations need their target-generated values. Never copy or display the `_master` key or system-key values.
references/services/functions/consumption-to-flex/windows/automation/access-key-strategy.md›
# Access Key Strategy
### 4l. Choose the Access Key Strategy
Inventory key **names only**. Never display or persist values:
```bash
az functionapp keys list --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query "{hostKeys: keys(functionKeys), systemKeys: keys(systemKeys)}" -o json
```
Use the trigger inventory from Step 4j and list per-function key names only for HTTP-triggered source functions:
```bash
az functionapp function keys list --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --function-name <FUNCTION_NAME> --query "keys(@)[?@ != 'id' && @ != 'kind' && @ != 'name' && @ != 'properties' && @ != 'type']" -o json
```
Use `ask_user`:
> The Flex app has new access-key values by default. Existing clients that send source host or function keys must be updated unless those values are copied. Which strategy should this migration use?
1. **Use target-generated keys (recommended)** - keep separate credentials and update clients or workflows before HTTP cutover.
2. **Preserve client keys** - copy source host keys and HTTP-function keys after deployment so existing credentials continue working against the target hostname.
Explain that preservation means the same credentials authorize both apps while they coexist. Record the choice and key names, but no values, in `<UPGRADE_DIR>/upgrade-status.md`.
Don't inventory or copy per-function keys for timer, queue, blob, Event Hubs, Service Bus, Cosmos DB, or other non-HTTP triggers. Those keys don't authorize their trigger invocations.
The `_master` key is target-specific and is never copied because it grants runtime administrative access. System keys are extension-managed and their values can't be explicitly set; inventory their names, let target extensions create new values, and update dependent webhooks such as Event Grid or Durable Functions to use the target system keys.
If key-name inventory fails because `Microsoft.Web/sites/host/listkeys/action` isn't authorized, report the missing permission. The target-generated option remains available; preservation is blocked until the source keys can be read.
references/services/functions/consumption-to-flex/windows/automation/app-creation.md›
# App Creation
## Step 4: Create the Flex Consumption App
Since `az functionapp flex-migration` does not support Windows, each migration step is performed explicitly.
> **Resume guard**: If the user supplies an existing target resource ID after a provisioning handoff, follow [Adopt a User-Provisioned Target](#adopt-a-user-provisioned-target) before naming or storage-selection defaults. Do not run resource creation or post-create storage conversion against that target.
### New app naming (read before running 4b)
The new app's name is bound to `<NEW_APP_NAME>` for use in 4b and every subsequent step (4b.opt, 4c–4k, Step 5 deploy, Step 6 cutover, Step 7 cleanup).
**Default**: `<source>-flex` — e.g. `test-app` → `test-app-flex`.
> ⚠️ **Skill instruction**: Before running 4b, look at the user's original migration request. If they said "as `<name>`" or "named `<name>`" — follow that. Otherwise, **default to `<source>-flex`** and **announce the choice to the user before executing**, e.g.:
>
> > I'll create the new Flex app as `<source>-flex` in resource group `<RESOURCE_GROUP>` (region `<REGION>`). Say so if you'd prefer a different name.
>
> Then bind `<NEW_APP_NAME>` to the chosen name and proceed.
> 💡 Constraints: 2–60 chars, alphanumerics + hyphens, must start with a letter, no consecutive hyphens, globally unique in Azure (becomes `<APP_NAME>.azurewebsites.net`). If the default would exceed 60 chars or collides with an existing app, fall back to a shortened suffix (e.g. `<source>-fc`) and announce the fallback. Verify global uniqueness with `az functionapp show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP>` (a `ResourceNotFound` error confirms the name is available in your RG; for cross-tenant collisions you'll only find out when 4b fails with a name-conflict error).
### Storage account binding (read before running 4a/4b)
The new Flex app needs a storage account bound at creation time. Track both `<STORAGE_NAME>` and `<STORAGE_RESOURCE_ID>` for the selected account:
| Mode | When to use | Storage metadata |
|------|-------------|------------------|
| **Reuse source** | Most migrations. Matches `az functionapp flex-migration` behavior on Linux. No extra resources. | Use the source runtime storage metadata from Step 3a |
| **Create new** | You want isolated storage for blast-radius, compliance, or test/prod separation | Use the metadata returned by 4a |
| **Use existing different** | You already have a target storage account provisioned (e.g. via IaC) | Resolve and verify its name and resource ID before proceeding |
For an existing different account supplied by name, resolve its metadata before continuing:
```bash
az graph query -q "resources | where type =~ 'microsoft.storage/storageaccounts' | where name =~ '<STORAGE_NAME>' | project name, resourceGroup, subscriptionId, id" --query "data[0]" -o json
```
If the query returns no account, stop and ask the user for the resource ID. Never guess the account's resource group.
> ⚠️ **Skill instruction**: Storage selection is always an explicit user decision. Before running 4a or 4b, use `ask_user` to choose **reuse source**, **create new**, or **use existing different**, even when the original request did not mention storage. Explain that reusing storage avoids another resource but shares runtime/deployment dependencies, while new storage provides isolation. Do not announce a default and proceed without a response.
>
> Bind `<STORAGE_NAME>` and `<STORAGE_RESOURCE_ID>` only after the user selects a mode. If the account is outside `<RESOURCE_GROUP>`, use its resource ID as the `--storage-account` argument in 4b.
> 💡 When reusing the source storage account, keep the deployment storage configuration created for the target Flex app. Do not copy the source app's Windows content-share or run-from-package settings.
### 4a. Create a Storage Account *(only if user chose "create new")*
Skip this entire sub-step if reusing the source account or pointing at an existing different one.
```bash
az storage account create --name <NEW_STORAGE_NAME> --resource-group <RESOURCE_GROUP> --location <REGION> --sku Standard_LRS --allow-blob-public-access false --query "{name:name,id:id}" -o json
```
Then bind `<STORAGE_NAME>` and `<STORAGE_RESOURCE_ID>` from the output for use in 4b.
### 4b. Create the Flex Consumption App
**Storage authentication preflight (before creating the app):**
Read the selected account's configuration without retrieving storage keys:
```bash
az storage account show --ids <STORAGE_RESOURCE_ID> --query "{id:id,allowSharedKeyAccess:allowSharedKeyAccess}" -o json
```
Require a successful command and the expected account ID. In PowerShell, check `$LASTEXITCODE` immediately. A failed read or missing account is a blocker, not evidence that Shared Key access is allowed. On a successful read, `false` prohibits Shared Key authentication; `true` or an unset (`null`) property permits it at the account level. Also confirm any policy requirements with the user. Repeat this check for deployment storage if it uses a different account.
If either account prohibits Shared Key access, or policy requires identity-based storage, **do not run the `az functionapp create` command below**. Its basic flow configures host storage with a connection string; deployment-storage identity flags alone don't make host storage identity-based. Record Step 4b as blocked in `<UPGRADE_DIR>/upgrade-status.md`, explain the limitation, and direct the user to identity-first ARM/Bicep provisioning using the [Flex Consumption IaC samples](https://github.com/Azure-Samples/azure-functions-flex-consumption-samples/tree/main/IaC). Do not enable Shared Key access, retrieve keys, or attempt connection-string creation followed by conversion.
**When blocked on keyless provisioning, provide this handoff:** Show the user the assessed target name, region, runtime/version, selected storage resource IDs, and progress-file path. Explain that they must provision an **empty target dedicated to this migration**, with managed identity authentication and roles for both storage connections and the agreed monitoring configuration. Do not deploy function code yet: built-in authentication and non-HTTP trigger safety still need migration checks. Leave the source unchanged. Ask them to return with the target's full Azure resource ID, not credentials, using:
```text
Resume the Flex Consumption migration for <SOURCE_APP_NAME> using progress at <UPGRADE_DIR>/upgrade-status.md.
I provisioned a keyless target at <TARGET_RESOURCE_ID>. Use that existing target; do not create another app.
```
#### Adopt a User-Provisioned Target
1. Load and reconcile the saved progress using [Migration Progress](../progress-tracking.md). Keep Step 4b blocked while checking the target. If the progress file is missing, stop and ask for its location rather than guessing prior work. If it already identifies a different adopted target, stop and ask; never carry completed target work across different apps.
2. Require a full `Microsoft.Web/sites` resource ID, not a slot, and reject the source app itself. Resolve the target read-only. Require the source and target to be in the active subscription because subsequent migration commands assume that scope; don't silently switch subscriptions. Verify its name, resource group, region, Flex plan, runtime/version, and storage accounts against the saved decisions. Report mismatches and ask the user to resolve them or approve a revised plan before adopting the target.
3. Verify managed identity configuration and effective role assignments for **both** [host storage](https://learn.microsoft.com/en-us/azure/azure-functions/manage-connections?pivots=functions-auth-identity&tabs=host) and [deployment storage](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to#configure-deployment-settings), and the monitoring configuration expected by Step 4k. Resolve the target's configured Application Insights component read-only; bind `<TARGET_AI_NAME>`, `<TARGET_AI_RESOURCE_ID>`, and `<TARGET_AI_RESOURCE_GROUP>` from the verified component and its resource ID. Do not assume it is in the function app's resource group. Missing or ambiguous component metadata is a blocker; ask the user to identify the configured component rather than guessing. Host storage needs `Storage Blob Data Owner` plus permissions required by its triggers, bindings, and diagnostics. Deployment storage needs blob read/write access, normally `Storage Blob Data Contributor`; an existing `Storage Blob Data Owner` assignment covers this for the same identity and scope. Confirm permission to create missing role assignments; Contributor alone doesn't grant it. Read failures, missing permissions, incomplete configuration, and failed access validation remain blockers. Don't display secrets or automatically repair the target.
4. Check the target's deployed-function inventory and use `ask_user` to confirm it is an empty app dedicated to this migration and approve adopting it for the remaining configuration and deployment steps. A failed inventory isn't proof the target is empty. If code is already deployed or the app serves another workload, keep the migration blocked and request an empty target.
5. After verification and approval, bind `<NEW_APP_NAME>` and `<RESOURCE_GROUP>` from the resolved target, and `<STORAGE_NAME>` and `<STORAGE_RESOURCE_ID>` from its verified host storage. Preserve all source identifiers. Record the target resource ID, deployment storage resource ID, storage identity IDs, and all three monitoring placeholders above in progress Notes; never record keys or connection strings. Mark Step 4b `complete` with note `externally provisioned and verified`; mark pending 4a `skipped — storage provisioned externally` and 4b.opt `skipped — target storage identities verified; no conversion required`. Preserve already completed work. Resume at the first unfinished step at or after 4c, retaining all app-setting, authentication, trigger-safety, deployment, and source-change approval gates.
After adoption, do not fall through to the basic creation flow below or rerun storage conversion.
Continue with the basic creation flow only when Shared Key authentication is permitted for both storage connections.
Use `<RUNTIME>` and `<TARGET_RUNTIME_VERSION>` resolved and recorded in Step 2c. Do not derive the target version from `FUNCTIONS_WORKER_RUNTIME_VERSION` at creation time; that setting is not guaranteed to be present or authoritative, and Windows Consumption leaves `siteConfig.windowsFxVersion` empty.
```bash
az functionapp list-flexconsumption-runtimes --location <REGION> --runtime <RUNTIME> --query "[?version=='<TARGET_RUNTIME_VERSION>'].version | [0]" -o tsv
```
Require the command to return `<TARGET_RUNTIME_VERSION>`. If it returns empty, stop and reassess the target version rather than creating the app with a guessed or unsupported value.
Create the new app. Set `<STORAGE_CREATE_REFERENCE>` to `<STORAGE_NAME>` when the account is in `<RESOURCE_GROUP>`; otherwise set it to `<STORAGE_RESOURCE_ID>`:
```bash
az functionapp create --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --storage-account <STORAGE_CREATE_REFERENCE> --flexconsumption-location <REGION> --runtime <RUNTIME> --runtime-version <TARGET_RUNTIME_VERSION>
```
> 💡 This command matches the basic Flex Consumption create flow documented in [Create a Flex Consumption app (MS Learn)](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to?tabs=azure-cli%2Cazure-cli-publish&pivots=programming-language-csharp#create-a-flex-consumption-app). The platform sets `AzureWebJobsStorage` to a fresh connection string for `<STORAGE_NAME>` and uses the same value for the One Deploy package container (`DEPLOYMENT_STORAGE_CONNECTION_STRING`). Identity-based runtime storage is **not** the default — see [4b.opt](#4bopt-identity-based-storage) for conversion only where Shared Key authentication is permitted during creation.
>
> 💡 Unless Application Insights creation is explicitly disabled or blocked by policy, `az functionapp create` also creates a target Application Insights component and sets `APPLICATIONINSIGHTS_CONNECTION_STRING`. Resolve the configured component read-only and bind `<TARGET_AI_NAME>`, `<TARGET_AI_RESOURCE_ID>`, and `<TARGET_AI_RESOURCE_GROUP>` from its verified metadata, not the function app's resource group. Record all three in progress Notes for [4k](trigger-safety-and-monitoring.md#4k-verify-application-insights-monitoring); missing, unreadable, or ambiguous component metadata remains a blocker.
### 4b.opt. Identity-Based Storage
For an app created through the permitted connection-string flow, run this sub-step if the user requested identity-based storage or Step 3a reported identity-based source storage; otherwise skip it. This post-create conversion isn't a workaround for the blocked preflight above. When Shared Key authentication is prohibited, identity-based host and deployment storage are functional requirements that must be satisfied through identity-first provisioning before resuming.
This is a post-create hardening pass that mirrors the optional pattern documented in [Configure deployment settings (MS Learn)](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to?tabs=azure-cli%2Cazure-cli-publish&pivots=programming-language-csharp#configure-deployment-settings) but extended to also cover the runtime `AzureWebJobsStorage` binding (not just the deployment container).
Ensure the system identity is enabled (4e covers this — run that first if you skipped):
```bash
az functionapp identity assign --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP>
```
Grant the system identity the three storage data-plane roles on `<STORAGE_RESOURCE_ID>`:
> 💡 **Why `--assignee-object-id ... --assignee-principal-type ServicePrincipal`?** The bare `--assignee <pid>` form makes the CLI do a Microsoft Graph lookup to determine the principal type, which (a) prints a deprecation warning on every call and (b) intermittently fails with `Cannot find user or service principal in graph database` for managed identities created seconds earlier (Graph propagation race). Specifying `--assignee-object-id` plus `--assignee-principal-type ServicePrincipal` skips the lookup entirely.
```bash
az role assignment create --assignee-object-id $(az functionapp identity show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query principalId -o tsv) --assignee-principal-type ServicePrincipal --role "Storage Blob Data Owner" --scope <STORAGE_RESOURCE_ID>
```
```bash
az role assignment create --assignee-object-id $(az functionapp identity show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query principalId -o tsv) --assignee-principal-type ServicePrincipal --role "Storage Queue Data Contributor" --scope <STORAGE_RESOURCE_ID>
```
```bash
az role assignment create --assignee-object-id $(az functionapp identity show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query principalId -o tsv) --assignee-principal-type ServicePrincipal --role "Storage Table Data Contributor" --scope <STORAGE_RESOURCE_ID>
```
Add the identity-based `AzureWebJobsStorage__*` settings pointing at `<STORAGE_NAME>`:
```bash
az functionapp config appsettings set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --settings "AzureWebJobsStorage__accountName=<STORAGE_NAME>" "AzureWebJobsStorage__credential=managedidentity" "AzureWebJobsStorage__blobServiceUri=https://<STORAGE_NAME>.blob.core.windows.net" "AzureWebJobsStorage__queueServiceUri=https://<STORAGE_NAME>.queue.core.windows.net" "AzureWebJobsStorage__tableServiceUri=https://<STORAGE_NAME>.table.core.windows.net"
```
Remove the platform-set connection-string binding so the runtime uses identity exclusively:
```bash
az functionapp config appsettings delete --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --setting-names AzureWebJobsStorage
```
> ⚠️ **User-assigned identity variant**: If you want a UMI instead of the system identity, replace the role-assignment `--assignee` with the UMI principal ID, attach the UMI via `az functionapp identity assign --identities <UMI_RESOURCE_ID>` in 4f, and add `AzureWebJobsStorage__clientId=<UMI_CLIENT_ID>` to the appsettings set call above.
> 💡 **Deployment storage is separate**: The commands above configure only runtime `AzureWebJobsStorage`. To also move the One Deploy package container off the connection string, follow [Configure deployment settings](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to?tabs=azure-cli%2Cazure-cli-publish&pivots=programming-language-csharp#configure-deployment-settings) with `--deployment-storage-auth-type SystemAssignedIdentity` (or `UserAssignedIdentity`) and verify the deployment identity's blob permissions. Do not report deployment storage as identity-based just because runtime storage was converted.
references/services/functions/consumption-to-flex/windows/automation/app-settings.md›
# App Settings
### 4c. Migrate App Settings
> 🔒 **Security**: App settings contain secrets (connection strings, storage keys, API keys). The skill first reviews setting **names only**. If the user chooses to copy settings, a local pipeline streams values directly from the source ARM endpoint to the new app's ARM endpoint via process memory only — no temp file is ever written to disk.
**Phase 1 — Skill reviews setting names (no values)**
```bash
az functionapp config appsettings list --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query "[].name" -o tsv
```
> ⚠️ **Skill instruction**: Confirm none of the names above look surprising before proceeding — the 4c filter already strips Flex-deprecated names. Cross-reference [Deprecated Settings](../workflow.md#deprecated-settings-do-not-migrate) if anything is unfamiliar. Do NOT request values.
>
> Linux app-setting names cannot contain `:`. For every surviving source name containing `:`, show the user the planned `:` → `__` mapping before the copy decision (for example, `FunctionsHostingConfig:CustomSetting` → `FunctionsHostingConfig__CustomSetting`). .NET configuration maps `__` back to `:` on Linux. For other runtimes, record the rename for application-specific validation.
>
> Before reading values, compare mapped source names case-insensitively. If two source names map to the same target name, stop and use `ask_user`; never silently choose between them. An existing target name is not a collision: after the user approves the mapping, the source value replaces it using the same idempotent merge behavior as an unchanged setting name.
>
> Surface this once as a **post-migration follow-up** (do not block 4c on it): connection strings or keys for **Azure services** (Storage, Service Bus, Event Hubs, Cosmos DB, Key Vault) can be replaced with [identity-based connections](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference#configure-an-identity-based-connection) for better security — see the [identities-instead-of-secrets tutorial](https://learn.microsoft.com/en-us/azure/azure-functions/functions-identity-based-connections-tutorial). The user owns the conversion (code + role assignments), and it only applies to Azure services that support Microsoft Entra ID auth — settings for third-party services stay as-is.
>
> This optional follow-up doesn't override storage authentication requirements. If host or deployment storage prohibits Shared Key authentication, Step 4b must remain blocked until identity-first provisioning is verified; don't defer that requirement until after migration.
**Decision — Copy or defer source app settings**
Before reading any setting values or changing the target, use `ask_user`:
> Should I copy the filtered source app settings to `<NEW_APP_NAME>` now?
>
> - **Copy filtered settings** — preserves application configuration, but may connect the target code to the same production dependencies after deployment. Target-owned storage and Application Insights settings remain unchanged.
> - **Defer source settings** — leaves only target-generated settings in place so you can configure isolated or target-specific dependencies before deployment.
Record the choice in `<UPGRADE_DIR>/upgrade-status.md`.
- If the user chooses **copy**, continue to Phase 2.
- If the user chooses **defer**, do not fetch source setting values and do not run either Phase 2 block. Before Step 5, require confirmation that all required application-specific target settings have been configured. Reviewing setting names is allowed; never display their values.
**Phase 2 — Copy locally (values never leave the terminal, never touch disk)**
The `az functionapp config appsettings set` CLI surface only accepts `KEY=VALUE` pairs or `@file.json`, so PATCH the underlying ARM endpoint directly via `az rest`. Fetch both apps' settings, filter the source settings, and merge them into the target's existing dictionary. This preserves target-owned settings created by Step 4b, including `AzureWebJobsStorage`, `DEPLOYMENT_STORAGE_CONNECTION_STRING`, and the target-specific `APPLICATIONINSIGHTS_CONNECTION_STRING`.
The deny list and storage prefix are normalized to lowercase at runtime, so source apps with non-canonical key casing (e.g. `azurewebjobsstorage`) are stripped correctly and future contributors can add new deny-list entries in whichever casing reads best. Surviving source names replace every `:` with `__` before merging.
**Bash:**
```bash
(
set -e
trap 'unset SOURCE_SETTINGS TARGET_SETTINGS BODY' EXIT
SUB=$(az account show --query id -o tsv)
API='2024-04-01'
SOURCE_SETTINGS=$(az rest --method post --uri "/subscriptions/$SUB/resourceGroups/<SOURCE_RESOURCE_GROUP>/providers/Microsoft.Web/sites/<SOURCE_APP_NAME>/config/appsettings/list?api-version=$API")
TARGET_SETTINGS=$(az rest --method post --uri "/subscriptions/$SUB/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.Web/sites/<NEW_APP_NAME>/config/appsettings/list?api-version=$API")
BODY=$(printf '%s\n%s\n' "$SOURCE_SETTINGS" "$TARGET_SETTINGS" | jq -s '
(["WEBSITE_CONTENTAZUREFILECONNECTIONSTRING","WEBSITE_CONTENTSHARE","WEBSITE_CONTENTOVERVNET","WEBSITE_SKIP_CONTENTSHARE_VALIDATION","WEBSITE_RUN_FROM_PACKAGE","WEBSITE_RUN_FROM_ZIP","WEBSITE_USE_ZIP","FUNCTIONS_EXTENSION_VERSION","FUNCTIONS_WORKER_RUNTIME","FUNCTIONS_WORKER_RUNTIME_VERSION","FUNCTIONS_MAX_HTTP_CONCURRENCY","FUNCTIONS_WORKER_PROCESS_COUNT","FUNCTIONS_WORKER_DYNAMIC_CONCURRENCY_ENABLED","WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT","WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED","WEBSITE_LOAD_CERTIFICATES","WEBSITE_MOUNT_ENABLED","ENABLE_ORYX_BUILD","SCM_DO_BUILD_DURING_DEPLOYMENT","WEBSITE_DNS_SERVER","WEBSITE_NODE_DEFAULT_VERSION","WEBSITE_VNET_ROUTE_ALL","AzureWebJobsDashboard","APPLICATIONINSIGHTS_CONNECTION_STRING"] | map(ascii_downcase)) as $deny |
(.[0].properties | to_entries | map(select((.key | ascii_downcase) as $k | ($deny | index($k) | not) and ($k | startswith("azurewebjobsstorage") | not))) | map(. + {targetKey: (.key | gsub(":"; "__"))})) as $mapped |
($mapped | group_by(.targetKey | ascii_downcase) | map(select(length > 1) | map(.key))) as $sourceCollisions |
if ($sourceCollisions | length) > 0
then error("Mapped app-setting name collision; review setting names before retrying.")
else {properties: (.[1].properties + ($mapped | map({key: .targetKey, value: .value}) | from_entries))}
end')
az rest --method patch --uri "/subscriptions/$SUB/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.Web/sites/<NEW_APP_NAME>/config/appsettings?api-version=$API" --headers 'Content-Type=application/json' --body "$BODY" --output none
)
```
> 💡 **App Insights**: `APPLICATIONINSIGHTS_CONNECTION_STRING` is intentionally **excluded** to preserve the target-specific value created by Step 4b. Step [4k](trigger-safety-and-monitoring.md#4k-verify-application-insights-monitoring) verifies monitoring and preserves AAD ingestion only when the source already used it.
> 💡 **Storage (`AzureWebJobsStorage*`)**: Every setting whose name starts with `AzureWebJobsStorage` is intentionally **excluded**, matching the published migration filter. Preserve the target storage configuration verified by [Step 4b](app-creation.md#4b-create-the-flex-consumption-app), whether connection-string-based or identity-based. When converting a permitted connection-string target, [4b.opt](app-creation.md#4bopt-identity-based-storage) re-derives identity settings against `<STORAGE_NAME>`; don't copy source storage settings or downgrade a user-provisioned identity-based target.
> 💡 **Casing is preserved on the wire**: The `ascii_downcase` step is comparison-only. Surviving settings keep the exact key casing the source app had — they're PATCHed back unchanged.
**PowerShell 7:**
```powershell
$sourceJson = $targetJson = $sourceSettings = $targetSettings = $body = $accessToken = $secureToken = $null
try {
$sub = az account show --query id -o tsv
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($sub)) { throw 'Failed to resolve the current subscription.' }
$api = '2024-04-01'
$base = "/subscriptions/$sub/resourceGroups"
$sourceJson = az rest --method post --uri "$base/<SOURCE_RESOURCE_GROUP>/providers/Microsoft.Web/sites/<SOURCE_APP_NAME>/config/appsettings/list?api-version=$api"
if ($LASTEXITCODE -ne 0) { throw 'Failed to read source app settings.' }
$sourceSettings = $sourceJson | ConvertFrom-Json
$sourceJson = $null
$targetJson = az rest --method post --uri "$base/<RESOURCE_GROUP>/providers/Microsoft.Web/sites/<NEW_APP_NAME>/config/appsettings/list?api-version=$api"
if ($LASTEXITCODE -ne 0) { throw 'Failed to read target app settings.' }
$targetSettings = $targetJson | ConvertFrom-Json
$targetJson = $null
$rawDeny = @('WEBSITE_CONTENTAZUREFILECONNECTIONSTRING','WEBSITE_CONTENTSHARE','WEBSITE_CONTENTOVERVNET','WEBSITE_SKIP_CONTENTSHARE_VALIDATION','WEBSITE_RUN_FROM_PACKAGE','WEBSITE_RUN_FROM_ZIP','WEBSITE_USE_ZIP','FUNCTIONS_EXTENSION_VERSION','FUNCTIONS_WORKER_RUNTIME','FUNCTIONS_WORKER_RUNTIME_VERSION','FUNCTIONS_MAX_HTTP_CONCURRENCY','FUNCTIONS_WORKER_PROCESS_COUNT','FUNCTIONS_WORKER_DYNAMIC_CONCURRENCY_ENABLED','WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT','WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED','WEBSITE_LOAD_CERTIFICATES','WEBSITE_MOUNT_ENABLED','ENABLE_ORYX_BUILD','SCM_DO_BUILD_DURING_DEPLOYMENT','WEBSITE_DNS_SERVER','WEBSITE_NODE_DEFAULT_VERSION','WEBSITE_VNET_ROUTE_ALL','AzureWebJobsDashboard','APPLICATIONINSIGHTS_CONNECTION_STRING')
$deny = $rawDeny | ForEach-Object { $_.ToLowerInvariant() }
$merged = [ordered]@{}
foreach ($property in $targetSettings.properties.PSObject.Properties) { $merged[$property.Name] = $property.Value }
$mappedSourceNames = @{}
foreach ($property in $sourceSettings.properties.PSObject.Properties) {
$lower = $property.Name.ToLowerInvariant()
if ($deny -notcontains $lower -and -not $lower.StartsWith('azurewebjobsstorage')) {
$targetName = $property.Name.Replace(':', '__')
$normalizedTargetName = $targetName.ToLowerInvariant()
if ($mappedSourceNames.ContainsKey($normalizedTargetName)) { throw "Source app-setting names '$($mappedSourceNames[$normalizedTargetName])' and '$($property.Name)' map to the same target name '$targetName'." }
$mappedSourceNames[$normalizedTargetName] = $property.Name
$merged[$targetName] = $property.Value
}
}
$body = @{ properties = $merged } | ConvertTo-Json -Compress -Depth 10
$accessToken = az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($accessToken)) { throw 'Failed to acquire an Azure Resource Manager access token.' }
$secureToken = ConvertTo-SecureString ($accessToken.Trim()) -AsPlainText -Force
$accessToken = $null
$uri = "https://management.azure.com$base/<RESOURCE_GROUP>/providers/Microsoft.Web/sites/<NEW_APP_NAME>/config/appsettings?api-version=$api"
Invoke-RestMethod -Method Patch -Uri $uri -Authentication Bearer -Token $secureToken -ContentType 'application/json; charset=utf-8' -Body $body -ErrorAction Stop | Out-Null
}
finally {
$sourceJson = $targetJson = $sourceSettings = $targetSettings = $body = $accessToken = $secureToken = $null
Remove-Variable merged,mappedSourceNames -ErrorAction SilentlyContinue
}
```
> 🔒 **No file or secret-bearing command argument**: Unlike a `--settings @file.json` flow, nothing here touches disk. PowerShell sends the JSON body with `Invoke-RestMethod`, avoiding native command-line JSON escaping and keeping app-setting values out of process arguments on Windows, Linux, and macOS. Secret-bearing variables and the ARM access token are cleared whether the PATCH succeeds or fails. If the PATCH fails, re-run the entire block; both GETs and the merged PATCH are idempotent.
>
> If Azure returns extended error `04072` (`AppSetting with name ... is not allowed`), inspect the rejected name for Linux-invalid characters. A `:` rejection does not prove that its prefix is reserved; map `:` to `__`, rerun collision checks, and retry the full block.
references/services/functions/consumption-to-flex/windows/automation/built-in-authentication.md›
# Built-in Authentication
### 4f.auth. Configure Built-in Authentication
Use the safe source summary captured in Step 3c. If `enabled` is `false`, mark this step `skipped — source Easy Auth disabled`.
If authentication is enabled, do not copy the source auth payload wholesale. Provider registrations, client secrets, Key Vault references, audiences, and redirect URIs can be app-specific. Never display or persist provider secrets.
Get the target hostname:
```bash
az functionapp show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query defaultHostName -o tsv
```
For each enabled provider, identify the target callback URI required by that provider. Easy Auth callback URIs generally use `https://<TARGET_HOSTNAME>/.auth/login/<PROVIDER>/callback`, but verify the exact URI against the provider-specific instructions in [Configure built-in authentication](https://learn.microsoft.com/en-us/azure/azure-functions/migration/migrate-plan-consumption-to-flex#configure-built-in-authentication).
Use `ask_user`:
> The source app uses built-in authentication with `<PROVIDERS>`. Authentication registrations and secrets aren't transferred automatically. How should we handle authentication for `<NEW_APP_NAME>`?
1. **Configure and validate now (recommended)** — guide provider-by-provider recreation and verify the target before deployment.
2. **Defer the migration** — leave application code undeployed until authentication is configured and verified.
If deferred, record the enabled providers and target callback URIs in `<UPGRADE_DIR>/upgrade-status.md` without secrets. Mark this step `deferred — built-in authentication not configured` and stop before Step 5. Do not deploy code or run target HTTP validation while the authentication boundary is absent.
If configuring now:
1. For each provider, ask whether to reuse its existing registration or create a target-specific registration. Obtain approval before changing an external identity-provider registration.
2. Guide the user through the provider-specific configuration. Don't request secrets in chat or place them in migration artifacts; have the user configure them directly through the portal, an existing secret-bearing app setting, or a Key Vault reference.
3. Ensure the target callback URI is registered with the provider.
4. Verify the target using the same safe projection used in Step 3c:
```bash
az webapp auth show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query "{enabled:enabled,unauthenticatedClientAction:unauthenticatedClientAction,defaultProvider:defaultProvider,tokenStoreEnabled:tokenStoreEnabled,allowedAudiences:allowedAudiences,allowedExternalRedirectUrls:allowedExternalRedirectUrls,isAuthFromFile:isAuthFromFile,providers:{aadClientId:clientId,githubClientId:gitHubClientId,googleClientId:googleClientId,facebookAppId:facebookAppId,twitterConsumerKey:twitterConsumerKey,microsoftAccountClientId:microsoftAccountClientId}}" -o json
```
Compare enabled state, unauthenticated behavior, default provider, allowed audiences and redirects, token-store state, and configured provider identifiers. Record intentional differences. Mark the step complete only after the target provider configuration and callback URI are verified.
Do not use an enable-only `az webapp auth update` command as a substitute for recreating the provider configuration.
references/services/functions/consumption-to-flex/windows/automation/cleanup-and-rollback.md›
# Cleanup and Rollback
## Step 7: Cleanup (Optional)
Read the outcome and deferred work from `<UPGRADE_DIR>/upgrade-status.md`. Do not offer source deletion unless the outcome is `production cutover complete`.
Before offering deletion, verify and record that:
1. all applicable production HTTP traffic, clients, key-dependent workflows, DNS, and custom domains use the target;
2. every applicable production trigger has completed the source-first cutover and target validation;
3. no production-readiness item remains deferred;
4. the source no longer processes production workloads.
If any condition is not met, do not present a deletion prompt. Record cleanup as `skipped — production cutover incomplete` or `deferred — source retained for later cleanup`, matching the user's intent.
Only after all conditions pass, use `ask_user` immediately before deletion:
> Production cutover is complete and source app `<SOURCE_APP_NAME>` is no longer processing production workloads. May I permanently delete it? This is irreversible and removes the rollback app.
Run only after that explicit approval:
```bash
az functionapp delete --name <SOURCE_APP_NAME> --resource-group <SOURCE_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
For a function-specific production cutover, use the rollback transaction already authorized in [Step 6](validation-and-cutover.md#path-b-production-cutover): disable and verify the target function first, then re-enable and verify the source function.
If the source app was previously stopped, use `ask_user` immediately before restarting it:
> May I restart source app `<SOURCE_APP_NAME>` in resource group `<SOURCE_RESOURCE_GROUP>` to restore processing?
Run the source command only after explicit approval:
```bash
az functionapp start --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP>
```
After the source app or affected source functions are running, reverse each routing change made during cutover:
1. If clients or producers were redirected to new queues, topics, containers, consumer groups, event subscriptions, or endpoints, describe the exact reversal and obtain approval before redirecting them to the original resources.
2. If HTTP clients were changed to use the Flex app URL, restore the original source-app URL.
3. If DNS or custom-domain mappings were changed, use `ask_user` immediately before reverting each mapping to the source app.
4. Verify that the source receives HTTP traffic and that each restored trigger completes a normal execution cycle. Keep the corresponding target functions disabled until rollback validation succeeds to avoid duplicate processing.
Do not delete target resources or target-specific dependencies merely because traffic has been rolled back. Retain them for diagnosis or a later retry unless the user separately approves their deletion.
Deleting the new Flex app does not modify the source app, but still requires confirmation under the global destructive-action policy:
```bash
az functionapp delete --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP>
```
references/services/functions/consumption-to-flex/windows/automation/code-deployment.md›
# Code Deployment
## Step 5: Deploy Code
> ⚠️ **Code is NOT automatically migrated.** The new app is created with config only — you must deploy code separately.
>
> ✅ **Trigger safety when all disable-capable functions were approved and verified disabled**: Only HTTP triggers and any hyphenated functions following their recorded Step 4j path will be live after the runtime starts; per-function cutover for disable-capable functions happens in Step 6.
>
> ⚠️ **If disablement was declined**: Do not deploy until the required Step 4j warning and explicit deployment approval have been completed. Non-HTTP functions may begin processing or running schedules immediately after deployment.
>
> ⛔ **Built-in authentication gate**: If the source used Easy Auth, Step 4f.auth must be complete before deployment. If it is deferred or blocked, stop before placing application code on the target; otherwise previously protected endpoints could become publicly accessible.
>
> ✅ **Access-key decision**: Step 4l must record either target-generated keys or source-key preservation before deployment. Key values are applied only after functions synchronize.
### Confirm Deferred App Settings Are Ready
If Step 4c deferred source app settings, list target setting names without values:
```bash
az functionapp config appsettings list --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query "[].name" -o tsv
```
Then use `ask_user`:
> Source app settings were deferred. Have all application-specific settings required by this deployment been configured on `<NEW_APP_NAME>` using target-specific or intentionally shared dependencies?
Do not deploy until the user confirms. Target-generated settings such as `AzureWebJobsStorage` and `APPLICATIONINSIGHTS_CONNECTION_STRING` do not replace application-specific connection settings required by bindings or code.
### ask_user: Choose Deployment Method
Present these options to the user:
> Your new Flex Consumption app `<NEW_APP_NAME>` 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 <NEW_APP_NAME>` from your project directory
> 3. **Deploy existing package** — I'll deploy the package we downloaded earlier from the original app
---
### Hyphenated-Function Source-First Deployment Cutover
Run this only when Step 4j recorded **Source-first deployment cutover** for production-connected non-HTTP functions whose names contain `-`. Prepare the selected deployment method first, including any pipeline changes. Immediately before executing the actual deployment, use `ask_user` for the complete transaction:
For CI/CD, first verify that preparing or committing the pipeline change cannot automatically deploy the target. Require a manually dispatched deployment or have the user pause the automatic trigger; if deployment timing cannot be controlled, use the local-project or existing-package method instead. Do not disable source functions until the pipeline is ready for an immediate, deliberate deployment.
> May I disable the listed hyphenated functions on Windows source app `<SOURCE_APP_NAME>`, verify they are disabled, and deploy the Linux Flex target? Processing may pause and events may backlog. If any disablement, deployment, or validation step fails, I will restore and verify any source functions already changed; after deployment starts, I will stop the entire target app before restoring them. This approval applies only to the listed functions and this deployment attempt.
After approval, repeat these commands for every affected source function:
```bash
az functionapp config appsettings set --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --settings "AzureWebJobs.<FUNCTION_NAME>.Disabled=true" --output none
az functionapp function list --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query "[?config.name=='<FUNCTION_NAME>'].{name:config.name,disabled:isDisabled}" -o table
```
Track each source function that successfully reports `disabled=true`. Do not deploy until every affected source function reports `disabled=true`. If setting or verifying any function fails before deployment starts, immediately re-enable and verify every function already disabled, then report the failure. Run the selected deployment method below only after all functions pass, then perform the ARM function-state verification under **After Successful Deployment**.
If deployment fails, an affected target function reports unexpectedly, or initial validation fails, use the rollback included in the approval:
```bash
az functionapp stop --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP>
az functionapp config appsettings set --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --settings "AzureWebJobs.<FUNCTION_NAME>.Disabled=false" --output none
az functionapp function list --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query "[?config.name=='<FUNCTION_NAME>'].{name:config.name,disabled:isDisabled}" -o table
```
Repeat the final two commands for every affected source function and verify each reports `disabled=false`. Report that stopping the target affects all its functions; obtain new approval before another deployment attempt.
### 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)
Verify that Azure Functions Core Tools v4 is available:
```bash
func --version
```
If `func` is unavailable or the reported major version is not `4`, stop and provide the [Core Tools installation guidance](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools). Do not install or upgrade Core Tools without user approval; offer the pipeline or existing-package deployment options instead.
```bash
# From your project directory
func azure functionapp publish <NEW_APP_NAME>
```
Functions Core Tools performs a host health request after publishing. If the deployment pipeline and trigger synchronization complete but Option B ends with `403 Ip Forbidden`, the copied access restrictions blocked only that health request. Continue with the ARM verification below; do not modify access restrictions automatically.
### Option C: Deploy Existing Package (if user selects option 3)
Deploy the zip package downloaded in Step 3 (resolves to `<UPGRADE_DIR>/<PACKAGE_NAME>` per the [Artifact Output Policy](../../../../../global-rules.md#artifact-output-policy)):
```bash
az functionapp deployment source config-zip --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --src <UPGRADE_DIR>/<PACKAGE_NAME>
```
### After Successful Deployment
For every deployment method, verify deployment through ARM:
```bash
az functionapp show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query "{name:name,state:state,runtimeAvailabilityState:runtimeAvailabilityState}" -o table
```
```bash
az functionapp function list --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query "[].{name:config.name,triggerType:(config.bindings[?ends_with(type, 'Trigger')] | [0].type),disabled:isDisabled}" -o table
```
For every non-hyphenated function that Step 4j intended to disable, require `disabled=true`. If any reports false or null, stop and treat trigger safety as failed. Hyphenated functions must match the Step 4j path: isolated dependencies, source-first cutover, or explicit active-deployment approval. Never infer effective disablement from app-setting presence.
If the app is running with normal runtime availability and all expected functions are synchronized, treat deployment as successful but record functional smoke testing as deferred. Do not modify access restrictions automatically.
Inform the user:
> Code deployed. Continue to **Step 5a** to apply or confirm the selected access-key strategy before HTTP validation. If Step 4j disablement was approved, only HTTP endpoints are live; per-function cutover for non-HTTP functions happens in Step 6. If disablement was declined, report that non-HTTP functions are already active.
>
> The source app is still processing all events as before. Keep it running until cutover is complete; the Consumption plan only bills for actual usage.
---
references/services/functions/consumption-to-flex/windows/automation/configuration-inventory.md›
# Configuration Inventory
## Step 3: Pre-Migration — Collect Settings
> 💡 App settings themselves are migrated in Step 4c using a safe two-phase pattern. This section collects the *other* configuration (site config, identities, auth, access restrictions, deployment package) that must be replicated on the new app.
### 3a. Collect Application Configurations
Common site settings:
```bash
az functionapp config show --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "{http20Enabled:http20Enabled, httpsOnly:httpsOnly, minTlsVersion:minTlsVersion, minTlsCipherSuite:minTlsCipherSuite, clientCertEnabled:clientCertEnabled, clientCertMode:clientCertMode, clientCertExclusionPaths:clientCertExclusionPaths}"
```
SCM basic publishing credentials policies:
```bash
az resource show --resource-group <RESOURCE_GROUP> --name scm --namespace Microsoft.Web --resource-type basicPublishingCredentialsPolicies --parent sites/<APP_NAME> --query properties
```
Maximum scale-out limit:
```bash
az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT'].value" -o tsv
```
Runtime storage account:
`AzureWebJobsStorage__accountName` is safe to read directly when the source uses identity-based storage. Otherwise, extract `AccountName` from the secret-bearing `AzureWebJobsStorage` connection string in scoped memory. Display and record only the resolved storage resource metadata.
**Bash:**
```bash
(
set -e
trap 'unset storageConnection' EXIT
storageName=$(az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='AzureWebJobsStorage__accountName'].value | [0]" -o tsv)
if [[ -z "$storageName" ]]; then
storageAuthMode=connection-string
storageConnection=$(az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='AzureWebJobsStorage'].value | [0]" -o tsv)
storageName=$(printf '%s' "$storageConnection" | awk -F';' '{for(i=1;i<=NF;i++){split($i,a,"="); if(tolower(a[1])=="accountname"){print a[2]; exit}}}')
else
storageAuthMode=identity-based
fi
if [[ ! "$storageName" =~ ^[a-z0-9]{3,24}$ ]]; then
echo "Could not resolve a valid runtime storage account name." >&2
exit 1
fi
storageJson=$(az graph query -q "resources | where type =~ 'microsoft.storage/storageaccounts' | where name =~ '$storageName' | project name, resourceGroup, subscriptionId, id" --query "data[0]" -o json)
if [[ -z "$storageJson" || "$storageJson" == "null" ]]; then
echo "Runtime storage account '$storageName' was not found in accessible subscriptions." >&2
exit 1
fi
printf '%s\n' "$storageJson"
printf 'Runtime storage authentication: %s\n' "$storageAuthMode"
)
```
**PowerShell 7:**
```powershell
$storageConnection = $null
try {
$storageName = az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='AzureWebJobsStorage__accountName'].value | [0]" -o tsv
if ([string]::IsNullOrWhiteSpace($storageName)) {
$storageAuthMode = 'connection-string'
$storageConnection = az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='AzureWebJobsStorage'].value | [0]" -o tsv
if ($storageConnection -match '(?:^|;)AccountName=([^;]+)') { $storageName = $Matches[1] }
}
else {
$storageAuthMode = 'identity-based'
$storageName = $storageName.Trim()
}
if ($storageName -notmatch '^[a-z0-9]{3,24}$') { throw 'Could not resolve a valid runtime storage account name.' }
$storage = az graph query -q "resources | where type =~ 'microsoft.storage/storageaccounts' | where name =~ '$storageName' | project name, resourceGroup, subscriptionId, id" --query "data[0]" -o json | ConvertFrom-Json
if ($null -eq $storage) { throw "Runtime storage account '$storageName' was not found in accessible subscriptions." }
$storage | ConvertTo-Json
Write-Output "Runtime storage authentication: $storageAuthMode"
}
finally {
$storageConnection = $null
}
```
Record the safe `name`, `resourceGroup`, `subscriptionId`, `id`, and authentication mode. Never record or display the connection string. If the account cannot be resolved uniquely, stop and ask the user for its resource ID.
File share mount metadata:
```bash
az webapp config storage-account list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[].{customId:name,accountName:value.accountName,shareName:value.shareName,type:value.type,mountPath:value.mountPath,state:value.state}" -o table
```
Record the custom ID, storage account name, share name, type, and mount path for every result. Windows Consumption doesn't support explicit storage mounts, even though the control plane can retain and return this metadata. Catalog it as **not migrated** and explain that it might be stale or never exercised by the source runtime. Don't retrieve storage keys or configure a target mount.
Custom domains (excluding the default `*.azurewebsites.net`):
```bash
az functionapp config hostname list --webapp-name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?!contains(name, 'azurewebsites.net')]" --output table
```
CORS settings:
```bash
az functionapp cors show --name <APP_NAME> --resource-group <RESOURCE_GROUP>
```
### 3b. Identify Managed Identities and Role Assignments
Get the system-assigned identity's principal ID:
```bash
az functionapp identity show --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query principalId -o tsv
```
> If the principal ID above is non-empty, list its role assignments (substitute `<PRINCIPAL_ID>`):
```bash
az role assignment list --assignee <PRINCIPAL_ID> --all
```
List user-assigned managed identities attached to the app:
```bash
az functionapp identity show --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query userAssignedIdentities -o json
```
> For each identity in the output, copy its `principalId` and list its role assignments using the same `az role assignment list --assignee <PRINCIPAL_ID> --all` command above.
### 3c. Check Built-in Authentication
```bash
az webapp auth show --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "{enabled:enabled,unauthenticatedClientAction:unauthenticatedClientAction,defaultProvider:defaultProvider,tokenStoreEnabled:tokenStoreEnabled,allowedAudiences:allowedAudiences,allowedExternalRedirectUrls:allowedExternalRedirectUrls,isAuthFromFile:isAuthFromFile,providers:{aadClientId:clientId,githubClientId:gitHubClientId,googleClientId:googleClientId,facebookAppId:facebookAppId,twitterConsumerKey:twitterConsumerKey,microsoftAccountClientId:microsoftAccountClientId}}" -o json
```
Record only this nonsecret summary. Never query, display, or persist provider secret fields. If `isAuthFromFile` is true or the source uses a provider not represented by this projection, record that provider discovery requires manual review without displaying the auth file. Built-in authentication is handled in [Step 4f.auth](built-in-authentication.md#4fauth-configure-built-in-authentication).
### 3d. Review Inbound Access Restrictions
```bash
az functionapp config access-restriction show --name <APP_NAME> --resource-group <RESOURCE_GROUP>
```
references/services/functions/consumption-to-flex/windows/automation/deployment-package.md›
# Deployment Package
### 3e. Get Deployment Package (if needed)
Ideally your project files are in source control and you can redeploy from there. If not, you can fetch the active package directly from the source app.
#### Check WEBSITE_RUN_FROM_PACKAGE
**Bash:**
```bash
UPGRADE_DIR=<UPGRADE_DIR>
runFromPackage=$(az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='WEBSITE_RUN_FROM_PACKAGE'].value | [0]" -o tsv)
if [[ "$runFromPackage" =~ ^https:// ]]; then
curl --fail --location --silent --show-error --output "$UPGRADE_DIR/source-package.zip" "$runFromPackage"
echo "Downloaded the remote deployment package without displaying its URL."
elif [[ "$runFromPackage" == "1" ]]; then
echo "The package is stored in the app content share."
else
echo "The app is not configured for run-from-package."
fi
unset runFromPackage
```
**PowerShell 7:**
```powershell
$runFromPackage = $null
try {
$upgradeDir = '<UPGRADE_DIR>'
$runFromPackage = az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='WEBSITE_RUN_FROM_PACKAGE'].value | [0]" -o tsv
if ($LASTEXITCODE -ne 0) { throw 'Failed to read WEBSITE_RUN_FROM_PACKAGE.' }
$runFromPackage = ([string]$runFromPackage).Trim()
if ($runFromPackage -match '^https://') {
Invoke-WebRequest -Uri $runFromPackage -OutFile (Join-Path $upgradeDir 'source-package.zip') -ErrorAction Stop
Write-Output 'Downloaded the remote deployment package without displaying its URL.'
}
elseif ($runFromPackage -eq '1') {
Write-Output 'The package is stored in the app content share.'
}
else {
Write-Output 'The app is not configured for run-from-package.'
}
}
finally {
$runFromPackage = $null
}
```
Interpret the classification:
- **Remote package downloaded** → use `<UPGRADE_DIR>/source-package.zip` in Step 5. Skip the rest of this section.
- **`1`** → the package lives in the app's content file share. Continue below.
- **Empty** → not run-from-package. Either redeploy from source or capture the project files via Kudu (`https://<APP_NAME>.scm.azurewebsites.net`); package retrieval steps below do not apply.
#### Download the package from the content file share
> 🔒 **Security**: `AzureWebJobsStorage` contains the storage account key. Keep it out of command arguments and output by using the Azure CLI's `AZURE_STORAGE_CONNECTION_STRING` environment variable in a scoped block. The cleanup below runs on success or failure.
>
> ⚠️ **Skill instruction**: Do NOT request the value of `AzureWebJobsStorage`, `WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`, or any other connection string from the user. The commands below get them automatically without exposing them.
Get the content share name (not a secret — safe to display):
```bash
az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='WEBSITE_CONTENTSHARE'].value" -o tsv
```
Save the output as `<CONTENT_SHARE>` for the commands below.
> 📁 The download commands below write to `<UPGRADE_DIR>` (default: `.azure-upgrade/<source-app-name>/`). See [Artifact Output Policy](../../../../../global-rules.md#artifact-output-policy). Resolve `<UPGRADE_DIR>` once at session start and create the directory before running these commands.
Download the pointer file and active package in one scoped block. The package filename is not secret and is printed so it can be reused as `<PACKAGE_NAME>` in Step 5.
**Bash:**
```bash
(
set -e
trap 'unset AZURE_STORAGE_CONNECTION_STRING' EXIT
export AZURE_STORAGE_CONNECTION_STRING=$(az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='AzureWebJobsStorage'].value" -o tsv)
az storage file download --share-name <CONTENT_SHARE> --path data/SitePackages/packagename.txt --dest <UPGRADE_DIR> --no-progress --output none
PACKAGE_NAME=$(cat <UPGRADE_DIR>/packagename.txt)
az storage file download --share-name <CONTENT_SHARE> --path "data/SitePackages/$PACKAGE_NAME" --dest <UPGRADE_DIR> --no-progress --output none
rm <UPGRADE_DIR>/packagename.txt
printf 'Downloaded package: %s\n' "$PACKAGE_NAME"
)
```
**PowerShell 7:**
```powershell
$previousConnectionString = $env:AZURE_STORAGE_CONNECTION_STRING
try {
$env:AZURE_STORAGE_CONNECTION_STRING = az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='AzureWebJobsStorage'].value" -o tsv
if ([string]::IsNullOrWhiteSpace($env:AZURE_STORAGE_CONNECTION_STRING)) { throw 'AzureWebJobsStorage was not found.' }
az storage file download --share-name <CONTENT_SHARE> --path data/SitePackages/packagename.txt --dest <UPGRADE_DIR> --no-progress --output none
if ($LASTEXITCODE -ne 0) { throw 'Failed to download packagename.txt.' }
$packageName = (Get-Content <UPGRADE_DIR>/packagename.txt -Raw).Trim()
az storage file download --share-name <CONTENT_SHARE> --path "data/SitePackages/$packageName" --dest <UPGRADE_DIR> --no-progress --output none
if ($LASTEXITCODE -ne 0) { throw 'Failed to download the deployment package.' }
Remove-Item <UPGRADE_DIR>/packagename.txt
Write-Output "Downloaded package: $packageName"
}
finally {
$env:AZURE_STORAGE_CONNECTION_STRING = $previousConnectionString
$previousConnectionString = $null
}
```
> 💡 The package is a regular `.zip` file — deploy it to the new Flex Consumption app with `az functionapp deployment source config-zip --src <PACKAGE_NAME>` in Step 5. (Linux Consumption uses a different format — `squashfs` in the `scm-releases` blob container — that pattern does NOT apply here.)
>
> 💡 If your storage account is configured for Entra-ID-only auth and the connection string lookup fails, retrieve the account name with `az functionapp show --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "storageAccount" -o tsv` and re-run the file commands with `--account-name <NAME> --auth-mode login` (requires `Storage File Data Privileged Reader` on the share).
---
references/services/functions/consumption-to-flex/windows/automation/discovery-and-assessment.md›
# Discovery and Assessment
## Prerequisites
```bash
# Verify the Azure CLI version
az version --query '"azure-cli"' -o tsv
# Install required CLI extensions
az extension add --name resource-graph
az extension add --name application-insights
# Login and set subscription
az login
az account set --subscription <SUBSCRIPTION_ID>
```
If `az` is unavailable, stop and provide Azure CLI installation guidance. If the reported version is lower than `2.77.0`, stop and require an upgrade before running migration commands. When using the Bash path, also run `jq --version` before the first Bash block that uses `jq`; stop with installation guidance if it is unavailable. PowerShell 7 uses `ConvertFrom-Json` and does not require `jq`.
---
## Step 1: Identify Candidate Apps
Use a single Azure Resource Graph query to list all Windows function apps running on a Consumption (Y1/Dynamic) plan in the current subscription:
```bash
az graph query -q "resources | where subscriptionId == '$(az account show --query id -o tsv)' | where type == 'microsoft.web/sites' | where ['kind'] == 'functionapp' | where properties.sku == 'Dynamic' | project name, location, resourceGroup" --query data --output table
```
This returns app name, location, and resource group for every Windows Consumption (Y1) function app. The `kind == 'functionapp'` filter excludes Linux apps (`functionapp,linux`) and the `properties.sku == 'Dynamic'` filter excludes Premium / Dedicated / Isolated / Flex Consumption.
> 💡 The `$(az account show --query id -o tsv)` sub-expression works in both bash and PowerShell. The `--subscriptions` flag also accepts an explicit subscription ID if you want to target a different sub.
---
## Step 2: Assessment Checks
Run these checks against each candidate app from Step 1.
### 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
Read the runtime and Functions host generation from app settings:
```bash
az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='FUNCTIONS_WORKER_RUNTIME' || name=='FUNCTIONS_EXTENSION_VERSION'].{N:name,V:value}" -o table
```
Pass criteria:
- `FUNCTIONS_WORKER_RUNTIME` is one of: `dotnet-isolated`, `node`, `java`, `python`, `powershell`, `go`, `custom`.
- `FUNCTIONS_EXTENSION_VERSION` is `~4`.
**Not supported**: `dotnet` (in-process) — must migrate to isolated first. Any `FUNCTIONS_EXTENSION_VERSION` other than `~4` is a blocker.
The CLI only returns the stored values; the supported-list comparison is yours to make. Capture the runtime as `<RUNTIME>` for Step 2c. `FUNCTIONS_EXTENSION_VERSION` is the Functions host generation, not the language runtime version used by `az functionapp create --runtime-version`.
### 2c. Verify Stack Version Compatibility
First collect source-version signals. These values can be empty or stale and are evidence only; `FUNCTIONS_WORKER_RUNTIME_VERSION` is not guaranteed to be present and must not be treated as authoritative:
```bash
az functionapp config appsettings list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='FUNCTIONS_WORKER_RUNTIME_VERSION' || name=='WEBSITE_NODE_DEFAULT_VERSION'].{name:name,value:value}" -o table
az functionapp config show --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "{netFrameworkVersion:netFrameworkVersion,javaVersion:javaVersion,nodeVersion:nodeVersion,powerShellVersion:powerShellVersion}" -o table
```
Check the versions supported for `<RUNTIME>` in the source app's region:
```bash
az functionapp list-flexconsumption-runtimes --location <REGION> --runtime <RUNTIME> --query "[].version" -o tsv
```
Resolve `<TARGET_RUNTIME_VERSION>` from the strongest available evidence: source project metadata, then deployed-package metadata, then the Azure configuration signals above. Examples include `.csproj` `TargetFramework`, `package.json` `engines.node`, `pom.xml` or Gradle Java toolchain, `pyproject.toml`/`runtime.txt`, and `go.mod`.
Require the resolved value to exactly match one of the supported versions returned by the command. If the evidence is missing, conflicting, or maps to multiple supported versions, use `ask_user` to show the evidence and supported values and have the user select the intended target version. Explain that selecting a different language version can require code or dependency changes. Record both `<RUNTIME>` and `<TARGET_RUNTIME_VERSION>` in the assessment report and upgrade status; do not guess or default to the newest version.
### 2d. Check Deployment Slots
```bash
az functionapp deployment slot list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --output table
```
If this returns entries, the app has slots. Flex Consumption does NOT support slots — plan accordingly.
### 2e. Check TLS/SSL Certificates
List certificates available in the source app's resource group:
```bash
az webapp config ssl list --resource-group <RESOURCE_GROUP> --output table
```
If this returns output, determine which certificates the source app actually uses. Record whether each used certificate is private or public and confirm the target can fit within the Flex Consumption limit of three private and three public certificates per app. Plan to re-add them using the documented [site-scoped certificate process](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to#configure-site-scoped-certificates).
`WEBSITE_LOAD_CERTIFICATES` is not used by Flex Consumption. Code that accesses the Windows certificate store must instead load accessible certificates from `/var/ssl/certs` for public certificates or `/var/ssl/private` for private certificates.
### 2f. Check Blob Storage Triggers
Find blob triggers NOT using EventGrid source:
```bash
az functionapp function list --name <APP_NAME> --resource-group <RESOURCE_GROUP> --query "[].{Function:name, BlobTrigger:(config.bindings[?type=='blobTrigger'] | [0])} | [?BlobTrigger && BlobTrigger.source!='EventGrid'].{Function:Function, TriggerType:BlobTrigger.type, Source:BlobTrigger.source}" --output table
```
If this returns rows, those blob triggers must change from `LogsAndContainerScan` to `EventGrid` before upgrading, subject to the delivery check below.
For all Blob triggers, inspect the planned target inbound configuration (or the existing target's public network access, private endpoints, and main-site access restrictions). Event Grid push delivery [can't deliver through private endpoints](https://learn.microsoft.com/en-us/azure/event-grid/consume-private-endpoints); outbound VNet integration with storage does not provide inbound event delivery.
Before recommending conversion, use `ask_user` to agree on a supported Event Grid delivery path. A private-only target without one, unreadable target networking configuration, or an unresolved delivery requirement is a blocker; record it in the assessment and progress status. Do not enable public access or relax restrictions automatically. Check the agreed configuration when applying [target access restrictions](networking-and-storage.md#4i-migrate-access-restrictions) and report any unverified runtime delivery during [cutover](validation-and-cutover.md#per-function-cutover-event-driven-triggers).
### 2g. Source Code Windows Dependency Scan
Recommended for every Windows → Flex migration. Prefer the local source directory or a checked-out repository because project manifests and API usage are directly inspectable. Use the deployed package retrieved in [Step 3e](deployment-package.md#3e-get-deployment-package-if-needed) only when source is unavailable; it confirms what is running but compiled artifacts may reduce scan coverage. The scan checks for Windows-only APIs, packages, registry/cert-store/COM/WMI usage, Windows-RID targeting, Windows-only PowerShell modules, and shell-outs to Windows tools.
Before concluding that source is unavailable, use `ask_user` to request a local source directory or checked-out repository path. Do not assume the current workspace contains the function app. If the user cannot provide source, offer to download the deployed package or skip the scan.
The agent dispatches per-language scanning automatically from the `FUNCTIONS_WORKER_RUNTIME` value captured in [Step 2b](#2b-verify-language-stack-compatibility) — no separate language input needed.
Follow the full procedure (inputs, pattern sets for all seven worker runtimes, severity rubric, output model, §11 report section) in [dependency-scan.md](../dependency-scan.md).
> ⚠️ **Skill instruction**: Announce the scan to the user before running it, **and tell them the scan is a heuristic aid only — it pattern-matches the most common Windows-only APIs, packages, and shell-outs but is not exhaustive. The user remains responsible for verifying their code has no Windows dependencies.** If they decline, write *"User declined the source code dependency scan"* to §11 of the assessment report and proceed — this scan is **never** a hard gate. Any `Blocker` findings advisorily flip §1 Upgrade Readiness to `Needs Attention`; the user always decides whether to proceed.
---
references/services/functions/consumption-to-flex/windows/automation/networking-and-storage.md›
# Networking and Storage
### 4g. Migrate CORS Settings
Get CORS origins from the source app:
```bash
az functionapp cors show --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query allowedOrigins -o tsv
```
Add each origin to the new app (repeat per origin):
```bash
az functionapp cors add --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --allowed-origins <ORIGIN>
```
### 4h. Migrate Custom Domains
List custom domains on the source app (excluding the default `*.azurewebsites.net`):
```bash
az functionapp config hostname list --webapp-name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query "[?!contains(name, 'azurewebsites.net')].name" -o tsv
```
Adding a hostname binding does not update its DNS record or move traffic to the target. Verify domain ownership as described in [Migrate an active DNS name](https://learn.microsoft.com/en-us/azure/app-service/manage-custom-dns-migrate-domain), then attempt to add each domain to the new app (repeat per domain):
```bash
az functionapp config hostname add --webapp-name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --hostname <CUSTOM_DOMAIN>
```
If Azure rejects the binding because the hostname cannot be assigned to both apps, stop and record custom-domain migration as deferred. Do not remove the source binding, change DNS, or alter its certificate during this replication phase.
### 4i. Migrate Access Restrictions
Get access restrictions from the source app:
```bash
az functionapp config access-restriction show --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> -o json
```
For Blob/Event Grid triggers, first check the target's public network access and the complete proposed main-site rule set, including fallback actions, against the Event Grid delivery path agreed in [Step 2f](discovery-and-assessment.md#2f-check-blob-storage-triggers). Event Grid push delivery cannot reach a private-only endpoint; outbound VNet integration does not resolve this. If the configuration cannot be read or the proposed restrictions would block delivery, stop and record Step 4i as blocked. Use `ask_user` to resolve the delivery design; do not automatically enable public access or relax restrictions.
Recreate every explicit main-site rule, preserving its name, action, priority, description, and selector. Skip platform-generated fallback rows with priority `2147483647` (`Allow all` or `Deny all`); default actions are applied separately below.
```bash
az functionapp config access-restriction add --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --rule-name <RULE_NAME> --action <Allow|Deny> --priority <PRIORITY> --description "<DESCRIPTION>" --ip-address <IP_CIDR>
```
Use exactly one selector matching the source rule:
- IP/CIDR: `--ip-address <IP_CIDR>`
- Service tag: `--service-tag <SERVICE_TAG>`
- Virtual-network subnet: `--subnet <SUBNET_RESOURCE_ID>`
If the source has explicit SCM rules and `scmIpSecurityRestrictionsUseMain` is `false`, recreate them with the same command plus `--scm-site true`.
After all explicit rules are present, apply the captured fallback behavior. Set default actions last so the target remains reachable while rules are being added:
```bash
az functionapp config access-restriction set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --default-action <MAIN_DEFAULT_ACTION> --scm-default-action <SCM_DEFAULT_ACTION> --use-same-restrictions-for-scm-site <true|false>
```
> ⚠️ **Skill instruction**: Before setting a default action to `Deny`, verify that an explicit allow rule covers the user's smoke-test client and that required deployment traffic can still reach SCM. Use `ask_user` to list the rules and fallback actions being applied. Do not migrate platform-generated fallback rows as ordinary rules.
Verify actions, priorities, selectors, descriptions, default actions, and SCM behavior:
```bash
az functionapp config access-restriction show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> -o json
```
references/services/functions/consumption-to-flex/windows/automation/site-and-identity.md›
# Site and Identity Configuration
### 4d. Apply Site Configurations
Apply the site-level settings collected in Step 3a. Each command below corresponds to one field from the `az functionapp config show` output (and one from the SCM basic publishing query). **Run only the commands whose source value was non-null** — skipping a command leaves the new app at its Flex default for that field.
> 💡 Source-of-truth mapping: every value below comes directly from Step 3a. Do not re-query the source app — use the values you already captured.
HTTP/2 (`http20Enabled`):
```bash
az functionapp config set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --http20-enabled <true|false>
```
Minimum TLS version (`minTlsVersion`, e.g. `1.2` or `1.3`):
```bash
az functionapp config set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --min-tls-version <MIN_TLS_VERSION>
```
Minimum TLS cipher suite (`minTlsCipherSuite`):
```bash
az functionapp update --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --set siteConfig.minTlsCipherSuite=<MIN_TLS_CIPHER_SUITE>
```
HTTPS-only (`httpsOnly`):
```bash
az functionapp update --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --set httpsOnly=<true|false>
```
Client certificate enabled (`clientCertEnabled`):
```bash
az functionapp update --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --set clientCertEnabled=<true|false>
```
Client certificate mode (`clientCertMode`, one of `Required`, `Optional`, `OptionalInteractiveUser`):
```bash
az functionapp update --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --set clientCertMode=<CLIENT_CERT_MODE>
```
Client certificate exclusion paths (`clientCertExclusionPaths`):
```bash
az functionapp update --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --set clientCertExclusionPaths=<CLIENT_CERT_EXCLUSION_PATHS>
```
SCM basic publishing credentials policy (the `allow` value from Step 3a's `basicPublishingCredentialsPolicies` query):
```bash
az resource update --resource-group <RESOURCE_GROUP> --name scm --namespace Microsoft.Web --resource-type basicPublishingCredentialsPolicies --parent sites/<NEW_APP_NAME> --set properties.allow=<true|false>
```
> ⚠️ **Skill instruction**: For each command above, look at the corresponding field from Step 3a's output. If the value was `null` or the field was absent, **skip that command** — do not pass `null` or an empty string. If all Step 3a site-config values were already at Flex defaults (typically `httpsOnly=true`, `minTlsVersion=1.2`, all others null/false), this entire sub-step is a no-op and may be skipped.
### 4e. Migrate System-Assigned Managed Identity
Check if the source app has a system-assigned identity:
```bash
az functionapp identity show --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query principalId -o tsv
```
> Only proceed with the rest of this step if the principal ID above is non-empty. Save it as `<SOURCE_PRINCIPAL_ID>`.
Enable system identity on the new app:
```bash
az functionapp identity assign --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP>
```
Get the new app's system identity principal ID (save it as `<NEW_PRINCIPAL_ID>`):
```bash
az functionapp identity show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query principalId -o tsv
```
List the source app's role assignments — project just the fields needed to recreate them:
```bash
az role assignment list --assignee <SOURCE_PRINCIPAL_ID> --all --query "[].{role:roleDefinitionName, scope:scope}" -o tsv
```
> ⚠️ **Skill instruction**: For **each row** above, run the command below — substitute the row's first column into `<ROLE_NAME>` and the row's second column into `<RESOURCE_ID>`. The new SMI is a different principal than the source, so every assignment must be recreated explicitly; nothing carries over automatically. If 4b.opt already ran, the three storage data-plane roles on `<STORAGE_NAME>` are already assigned — skip those rows to avoid duplicates.
>
> Before recreating a role, determine whether its scope is a retained dependency or a resource replaced by the migration. Recreate retained dependency scopes directly. Do **not** grant the new identity access to the source Application Insights component; if the source uses AAD ingestion, Step [4k](trigger-safety-and-monitoring.md#4k-verify-application-insights-monitoring) grants `Monitoring Metrics Publisher` on the target component instead.
>
> ```bash
> az role assignment create --assignee-object-id <NEW_PRINCIPAL_ID> --assignee-principal-type ServicePrincipal --role "<ROLE_NAME>" --scope "<RESOURCE_ID>"
> ```
### 4f. Migrate User-Assigned Managed Identities
List user-assigned identities on the source app:
```bash
az functionapp identity show --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query userAssignedIdentities -o json
```
Assign each identity to the new app (repeat per identity resource ID):
```bash
az functionapp identity assign --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --identities <IDENTITY_RESOURCE_ID>
```
references/services/functions/consumption-to-flex/windows/automation/trigger-safety-and-monitoring.md›
# Trigger Safety and Monitoring
### 4j. Choose the Pre-Deployment State of Non-HTTP Functions
> ⚠️ **Why this decision exists**: When Step 5 deploys code, enabled non-HTTP functions can immediately consume events or run schedules. The risk is highest when 4c copied production settings, 4b reused the source storage account (including implicit `AzureWebJobsStorage` bindings), or timer triggers are present. Disabling the target functions before deployment makes cutover controllable in Step 6.
>
> HTTP triggers are intentionally left enabled — they don't pull from a queue, so the new app cannot "steal" HTTP traffic. Clients must be redirected to the new hostname explicitly, which gives you the same safety as disabling.
>
> Reference: [Disable functions in Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/disable-function).
>
> ⚠️ On Linux, a function whose name contains a hyphen (`-`) cannot be disabled through `AzureWebJobs.<FUNCTION_NAME>.Disabled`. Never claim that this setting protects a hyphenated function on the Flex target.
Read each deployed function's trigger binding from the ARM function metadata:
```bash
az functionapp function list --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query "[].{name:config.name,triggerType:(config.bindings[?ends_with(type, 'Trigger')] | [0].type)}" -o table
```
> 💡 Use the alias `triggerType`, not `type`. Azure CLI's table formatter suppresses common resource metadata fields named `type`, which can make a valid projection appear blank.
>
> ⚠️ **Skill instruction**:
>
> 1. Reconcile the ARM results with the trigger inventory recorded in the assessment report.
> 2. If ARM genuinely returns a blank, missing, or ambiguous trigger, use source attributes or `function.json` as the fallback.
> 3. If a function remains unresolved, stop — never assume it is event-driven or HTTP.
> 4. Confirm that every deployed function has exactly one classified trigger before presenting the disablement decision.
> 5. Split non-HTTP functions into names that contain `-` and names that do not. Record both lists in `<UPGRADE_DIR>/upgrade-status.md`.
Evaluate these risk factors:
- whether source app settings were copied in 4c;
- whether the source storage account was reused in 4b;
- whether timers or unresolved binding sources are present.
If there are no non-HTTP functions, mark Step 4j `skipped — no non-HTTP functions`. If settings were deferred, runtime storage and every trigger dependency are isolated from production, no timer or other autonomous function can affect production, and every binding source is resolved, mark Step 4j `skipped — target fully isolated`. In either case, do not present a disablement prompt or add per-function disable settings; record any hyphenated names and the Linux limitation, then continue to 4k.
If any production-connected non-HTTP function name contains `-`, do not attempt to disable it on the target. Use `ask_user` to list the affected functions, explain that they will be active when the Linux target starts, and offer these choices:
1. **Isolate target dependencies (recommended)** — configure target-specific queues, topics, containers, consumer groups, event subscriptions, schedules, or other bindings, then reassess Step 4j.
2. **Rename and repackage** — change the function names so setting-based disablement is supported, redeploy the source if required, and reassess.
3. **Source-first deployment cutover** — immediately before Step 5, disable and verify the affected functions on the Windows source, deploy the target, and validate it. This can pause processing and backlog events. If deployment or validation fails, stop the entire target app before restoring the source functions.
4. **Deploy active** — explicitly accept that the affected target functions can process production events or schedules as soon as deployment completes.
Record the selected path and affected function names in `<UPGRADE_DIR>/upgrade-status.md`. Choices 1 and 2 block deployment until completed. Choice 3 is executed as the transaction in [Step 5](code-deployment.md#hyphenated-function-source-first-deployment-cutover). Choice 4 requires the same immediate pre-deployment warning and approval as declined disablement.
For the remaining non-HTTP functions whose names do not contain `-`, use `ask_user` to list them and summarize the observed risk factors. Strongly recommend disabling all listed functions when any risk factor is present, but leave the decision to the user. Record the choice in `<UPGRADE_DIR>/upgrade-status.md`.
If the user approves, set a disable flag on the new app for every listed non-HTTP function whose name does not contain `-` (repeat per function name). Leave HTTP and hyphenated functions unchanged:
```bash
az functionapp config appsettings set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --settings "AzureWebJobs.<FUNCTION_NAME>.Disabled=true" --output none
```
The runtime reads these settings at startup, so they are intended to take effect when Step 5 deploys code. Do not call the functions effectively disabled until their runtime state is verified after deployment.
If disablement was approved, verify that the settings were written before moving on:
```bash
az functionapp config appsettings list --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?starts_with(name, 'AzureWebJobs.') && ends_with(name, '.Disabled')].{name:name, value:value}" -o table
```
> ⚠️ **Skill instruction**: If the user approves, apply one `--settings` call per function. Each call triggers a soft-restart but the underlying `Microsoft.Web/sites` PATCH has a payload limit, so batching dozens into one call can fail. If the source app has more than ~50 functions, write the disable settings to `<UPGRADE_DIR>/disabled_functions.json` (e.g. `[{"name": "AzureWebJobs.FuncA.Disabled", "value": "true"}, ...]`) and apply via `--settings @<UPGRADE_DIR>/disabled_functions.json` in a single call. Delete the file immediately after the apply succeeds (it does not contain secrets, but follow the [Artifact Output Policy](../../../../../global-rules.md#artifact-output-policy) for cleanup hygiene).
>
> If the user declines, or selects **Deploy active** for a hyphenated function, do not write a disable setting for the affected functions. Immediately before Step 5 deployment, use `ask_user` again to warn that the listed functions may consume shared events or run duplicate schedules as soon as deployment completes, then obtain explicit approval to deploy with them enabled.
### 4k. Verify Application Insights Monitoring
Microsoft Learn requires monitoring to be enabled before deployment but does not require changing workspaces or authentication models during migration. Preserve the monitoring configuration created or verified by Step 4b unless the user explicitly requests customization.
Restore `<TARGET_AI_NAME>`, `<TARGET_AI_RESOURCE_ID>`, and `<TARGET_AI_RESOURCE_GROUP>` from the verified Step 4b metadata in progress Notes. Require the name and resource group to agree with the component resource ID. Missing or conflicting metadata is a blocker; do not infer the component's resource group from the function app. Address the component by its full resource ID below so lookups use its own subscription and resource group.
This step verifies that Application Insights is configured and authorized. It does not query telemetry or prove that logs are being ingested; validate runtime behavior and telemetry after code deployment.
Verify the target has an Application Insights connection setting without displaying its value:
```bash
az functionapp config appsettings list --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?name=='APPLICATIONINSIGHTS_CONNECTION_STRING'].name" -o tsv
```
Verify the Application Insights component recorded from Step 4b:
```bash
az monitor app-insights component show --ids <TARGET_AI_RESOURCE_ID> --query "{name:name,id:id,workspaceResourceId:workspaceResourceId}" -o table
```
Require the component lookup to succeed and its returned name and resource ID to match the recorded metadata. If either the setting or component is absent, the read fails, or metadata conflicts, stop and investigate. Use `ask_user` before creating or wiring a replacement monitoring resource. Do not change the selected Log Analytics workspace unless the user explicitly requests monitoring customization.
Check whether the source used AAD-authenticated ingestion:
```bash
az functionapp config appsettings list --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query "[?name=='APPLICATIONINSIGHTS_AUTHENTICATION_STRING'].value | [0]" -o tsv
```
- If the value is not `Authorization=AAD`, keep the target's platform-created monitoring configuration unchanged. Offer AAD ingestion only as optional post-migration hardening.
- If the value is `Authorization=AAD`, preserve that posture on the target:
```bash
az functionapp config appsettings set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --settings "APPLICATIONINSIGHTS_AUTHENTICATION_STRING=Authorization=AAD" --output none
```
Use `<NEW_PRINCIPAL_ID>` from 4e and grant it `Monitoring Metrics Publisher` on the **target** Application Insights component:
```bash
az role assignment create --assignee-object-id <NEW_PRINCIPAL_ID> --assignee-principal-type ServicePrincipal --role "Monitoring Metrics Publisher" --scope <TARGET_AI_RESOURCE_ID>
```
Verify setting names and the target-scoped role without displaying connection-string values:
```bash
az functionapp config appsettings list --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query "[?starts_with(name, 'APPLICATIONINSIGHTS_')].name" -o tsv
```
```bash
az role assignment list --assignee-object-id <NEW_PRINCIPAL_ID> --scope <TARGET_AI_RESOURCE_ID> --query "[?roleDefinitionName=='Monitoring Metrics Publisher'].{role:roleDefinitionName,scope:scope}" -o table
```
### Verify Migration Results
Verify the new app exists and is configured:
```bash
az functionapp show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query "{name:name, kind:kind, sku:properties.sku}" --output table
```
Review target app-setting names:
```bash
az functionapp config appsettings list --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query "[].name" -o tsv
```
Check managed identity:
```bash
az functionapp identity show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP>
```
Check custom domains:
```bash
az functionapp config hostname list --webapp-name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --output table
```
### Configure Items Requiring Manual Setup
These are not handled by Steps 4a–4i and require manual configuration:
#### TLS/SSL Certificates
If Step 2e identified certificates used by the source app, re-add only those certificates using the documented [site-scoped certificate process](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to#configure-site-scoped-certificates). Confirm the target remains within the limit of three private and three public certificates.
`WEBSITE_LOAD_CERTIFICATES` is not used by Flex Consumption. Make each required certificate accessible to app code through the documented process, and update code that used the Windows certificate store to load from `/var/ssl/certs` for public certificates or `/var/ssl/private` for private certificates.
#### Scale and Concurrency (if custom values needed)
Use the `WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT` value collected in [Step 3a](configuration-inventory.md#3a-collect-application-configurations) as `<MAX_SCALE_SETTING>`. If the source setting was absent, keep the target default and skip this command. If it was set, confirm the value is within the Flex Consumption range of 1–1000 before applying it:
```bash
az functionapp scale config set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --maximum-instance-count <MAX_SCALE_SETTING>
```
> ⚠️ Reducing below 40 for HTTP apps can cause frequent request failures.
---
references/services/functions/consumption-to-flex/windows/automation/validation-and-cutover.md›
# Validation and Cutover
## Step 6: Post-Upgrade Validation
Enter Step 6 only after Step 5a is complete or skipped because target-generated keys were selected. If key preservation was selected but any host or HTTP-function key failed verification, stop.
### Confirm the source app is still healthy
Before touching the new app, verify the source is still serving traffic and processing events as expected. The cutover that follows assumes the source remains the system of record until each trigger is flipped over individually.
```bash
az functionapp show --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query "{name:name, state:state, hostNames:hostNames[0]}" -o table
```
Expected: `state` is `Running`. If anything looks off (state is `Stopped`, hostname is null, etc.), pause the cutover and investigate before proceeding — the cleanup steps later in this phase assume the source app is healthy.
### Smoke Test (run this first)
Get the new app's default hostname:
```bash
az functionapp show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query defaultHostName -o tsv
```
> ⚠️ **Flex propagation**: On a freshly-created Flex Consumption app, `defaultHostName` from `az functionapp show` can briefly return null while the platform finishes provisioning. If the command above returns empty, fall back to:
>
> ```bash
> az functionapp list --resource-group <RESOURCE_GROUP> --query "[?name=='<NEW_APP_NAME>'].defaultHostName | [0]" -o tsv
> ```
>
> The list endpoint populates the field as soon as the app exists.
Confirm the host is reachable (substitute `<HOSTNAME>` with the value above):
```bash
curl -s -o /dev/null -w "%{http_code}\n" "https://<HOSTNAME>"
```
Expected: a 2xx, 401, or 404 (the host is up). If the response is `403 Ip Forbidden` and the target has an intentional deny-by-default policy, record the HTTP smoke test as deferred until it can run from an already allowed client. Confirm the app and functions through the ARM commands from Step 5; do not add or relax access rules automatically.
If you get 503 or a connection refusal, the app failed to start — check logs:
```bash
az functionapp log tail --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP>
```
> 💡 On PowerShell 7+, `curl` resolves to the real `curl.exe` (the legacy `Invoke-WebRequest` alias was removed in 7.x). Confirm with `Get-Command curl` if unsure.
### Verify Plan
```bash
az functionapp show --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --query serverFarmId
```
### Test HTTP Endpoints
Test an individual HTTP trigger (substitute `<HOSTNAME>` and `<FUNCTION_NAME>`):
```bash
curl -s -o /dev/null -w "%{http_code}\n" "https://<HOSTNAME>/api/<FUNCTION_NAME>"
```
For a function that requires a function or host key, use the selected target key in the `x-functions-key` header without printing or persisting it. If target-generated keys were selected, don't treat HTTP cutover as complete until every key-dependent client or workflow has been updated and validated.
### Validate Built-in Authentication
Step 4f.auth must be complete or skipped because source Easy Auth was disabled before entering Step 6. If it is deferred or blocked, do not deploy code or run target HTTP checks.
If Step 4f.auth was completed, test an endpoint that requires authentication and confirm its anonymous behavior matches the verified target configuration: `401`/`403` for rejection or the expected provider redirect. Then complete one authenticated request using a test identity and confirm the expected authorization result without recording tokens.
### Per-Function Cutover (event-driven triggers)
For non-hyphenated functions effectively disabled and verified after Step 5, handle them one at a time. Never enable a production-connected target function before approval to disable its source counterpart; otherwise both apps can process the same event source while approval is pending. If Step 4j disablement was declined, report that controlled cutover protection was not established and validate for concurrent processing before any source change.
Do not use the target app-setting disable/enable transaction below for a function whose name contains `-`; Linux does not support disabling that name through `AzureWebJobs.<FUNCTION_NAME>.Disabled`. Use isolated target dependencies or the source-first deployment cutover recorded in Step 4j. If a hyphenated function was deployed active with explicit approval, validate for concurrent processing and do not claim controlled per-function cutover.
> ⚠️ **Per-trigger mitigations apply first**: Even with this controlled per-function cutover, the [Trigger Migration Risks](../workflow.md#trigger-migration-risks) table lists per-trigger-type prerequisites — new consumer group for Event Hubs, dedicated lease container for Cosmos DB, new event subscription for blob/EventGrid, etc. Apply the relevant mitigation **before** flipping the disable flag for the corresponding function.
For Blob/Event Grid triggers, check that the target's current networking configuration still permits the agreed inbound delivery path before enabling the target. Do not generate test events, create test blobs, or provision test resources for these triggers. Configuration checks, a healthy HTTP endpoint, and a successfully created event subscription do not prove runtime delivery. Use existing execution telemetry or user-provided validation evidence; otherwise report runtime delivery as unverified and leave trigger validation incomplete. Do not relax network restrictions automatically.
For each event-driven function, choose one of these paths:
#### Path A: Isolated Target Validation
Point the target binding at an isolated test queue, consumer group, lease container, event subscription, or equivalent resource. For a non-hyphenated function, enable and validate the target, then disable it again until production cutover is approved. A hyphenated function is already active after deployment; validate it only against isolated dependencies and do not restore its production binding until the transaction below is approved.
#### Hyphenated-Function Production Cutover
For a hyphenated function already validated against isolated dependencies, use `ask_user` for a transaction that:
1. Stops the entire target app and verifies it is stopped.
2. Disables the corresponding function on the Windows source and verifies `isDisabled=true`.
3. Replaces the target's isolated binding configuration with the production configuration.
4. Starts the target app and validates one normal execution cycle.
5. On failure, stops the target, restores its isolated binding configuration, re-enables and verifies the source function, and reports the failure.
Explain that stopping the Flex app affects every target function and that events may backlog during this transaction. Approval applies only to the named function, binding changes, and this cutover attempt.
#### Path B: Production Cutover
Use this path only for target function names that do not contain `-`.
1. Use `ask_user` **before enabling the target or changing the source**. Request approval for the complete function-specific transaction, including conditional rollback:
> May I cut over `<FUNCTION_NAME>` by disabling it on source app `<SOURCE_APP_NAME>`, verifying the source is disabled, and then enabling it on target app `<NEW_APP_NAME>`? This may briefly pause processing and allow events to backlog. If target activation or validation fails, I will disable the target and restore the source function. This approval applies only to this function and this cutover attempt.
Do not continue unless the user approves. If approval is declined, leave the source enabled and target disabled. Approval for one function does not cover another function or a later retry.
2. Disable the source function and suppress command output because app-setting responses can contain secrets:
```bash
az functionapp config appsettings set --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --settings "AzureWebJobs.<FUNCTION_NAME>.Disabled=true" --output none
```
3. Verify the source reports the function as disabled before enabling the target:
```bash
az functionapp function list --name <SOURCE_APP_NAME> --resource-group <SOURCE_RESOURCE_GROUP> --query "[?config.name=='<FUNCTION_NAME>'].{name:config.name,disabled:isDisabled}" -o table
```
4. Enable the target:
```bash
az functionapp config appsettings set --name <NEW_APP_NAME> --resource-group <RESOURCE_GROUP> --settings "AzureWebJobs.<FUNCTION_NAME>.Disabled=false" --output none
```
5. Verify target processing after one full execution cycle or controlled test event:
```kql
requests
| where cloud_RoleName == "<NEW_APP_NAME>"
| where operation_Name == "<FUNCTION_NAME>"
| where timestamp > ago(15m)
| summarize count(), avg(duration), countif(success == false) by bin(timestamp, 1m)
```
6. If target activation or validation fails, use the rollback authorized in step 1: disable and verify the target first, then re-enable and verify the source. Report the failure and obtain new approval before another cutover attempt.
> ⚠️ **Pause versus duplication**: Source-first cutover can briefly delay processing or miss a timer occurrence, but queued/event-stream work normally remains available as backlog. This is safer than enabling both production-connected functions concurrently, which can cause duplicate or competing processing.
### 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 == "<NEW_APP_NAME>"
| where timestamp > ago(1d)
| project timestamp, message, operation_Name, customDimensions
| order by timestamp desc
```
---
references/services/functions/consumption-to-flex/windows/dependency-scan.md›
# Source Code Windows Dependency Scan
> **When**: Phase 1 Assessment, after [Step 2f](automation/discovery-and-assessment.md#2f-check-blob-storage-triggers), as **[Step 2g](automation/discovery-and-assessment.md#2g-source-code-windows-dependency-scan)**. **Always recommended, never required.** If the user skips, proceed — they own the runtime risk.
## What This Scan Does
Walks the source tree for code, config, and packaging patterns that depend on Windows. Prefer source code because project manifests and API usage are directly inspectable. Use the deployed package from [Step 3e](automation/deployment-package.md#3e-get-deployment-package-if-needed) only when source is unavailable; it confirms what is running but may contain compiled artifacts that reduce scan coverage. Findings populate **§11** of the assessment report; ≥1 `Blocker` finding advisorily flips **§1 Upgrade Readiness** to `Needs Attention` (never hard-blocks).
> ⚠️ **Heuristic aid, not a guarantee.** This scan pattern-matches the most common Windows-only APIs, packages, RID targeting, and shell-outs to Windows tools. It is **not exhaustive** — uncommon Windows APIs, dynamically-loaded DLLs, transitive package dependencies, reflection-based calls, and runtime-generated code can slip past. **The app owner is ultimately responsible for verifying their code has no Windows dependencies.** False negatives are dangerous; false positives create friction. The scan's job is to **inform**, not gate.
## Inputs
1. **Source location** — use `ask_user` to explicitly request a local source directory or checked-out repository path; do not infer that the current workspace contains the function app. Explain that source provides the best scan coverage. If the user cannot provide source, offer the deployed package from Step 3e as a fallback. If they decline both, write "scan skipped" to §11 and stop.
2. **Language** — auto-dispatched from `FUNCTIONS_WORKER_RUNTIME` (captured in [Step 2b](automation/discovery-and-assessment.md#2b-verify-language-stack-compatibility)). Always run the agnostic set + the matching per-language set.
3. **`custom` worker** — agnostic set only; ask once if the handler is C#, Node, Java, Python, or PowerShell source and layer in that set if so.
## Categories
| # | Category |
|---|----------|
| 1 | Win32 / WinRT / COM / WMI APIs |
| 2 | Path / case / line-ending / env-var assumptions |
| 3 | Windows certificate store |
| 4 | Windows registry |
| 5 | Windows-only packages |
| 6 | Windows-RID / `net4x` targeting |
| 7 | Windows-only PowerShell modules |
| 8 | Shelling out to Windows tools |
## Finding Severity
| Severity | Meaning | Effect on §1 |
|----------|---------|-------------|
| 🛑 **Blocker** | Will definitely fail on Linux (e.g. `[DllImport("kernel32.dll")]`, `Microsoft.Win32.Registry`, `System.Drawing.Common` on .NET 6+, `.exe` handler for `custom` worker) | `Needs Attention` (advisory) |
| ⚠️ **Likely** | Often breaks but may be guarded (backslash paths, drive letters, RID-specific package refs) | None |
| ℹ️ **Review** | Heuristic worth a human eye (any P/Invoke, any external `Process.Start`, package name containing `Windows`) | None |
## How to Read These Patterns
Each bullet below states a **principle** followed by anchor examples. Treat the examples as starting points, not as an exhaustive whitelist — when you encounter similar patterns that aren't enumerated (a different system DLL, a different `System.*` namespace prefix, a different Windows admin exe, a different Windows-only PowerShell module, a different `win32`-flavored npm package name), flag them under the same category. The category headers in **bold** are the rule; the examples after the em-dash are how that rule commonly shows up.
## Language-Agnostic Patterns
Run for every worker runtime. Cite the category number on each finding.
- **(2) Windows path / env-var literals** — path-like strings with a drive letter (`@"C:\..."`, `"D:\\..."`) or Windows env-var references in the `%FOO%` shape (`%TEMP%`, `%USERPROFILE%`, `%WINDIR%`, `%PROGRAMFILES%`, `%APPDATA%`, `%LOCALAPPDATA%`, …).
- **(2) CRLF-dependent parsing** — `Split("\r\n")`, byte-exact file reads, anything that hard-codes `\r\n` line endings.
- **(8) Spawning a Windows-only executable** — language-specific syntax varies (see per-language), but the executable is one of: `cmd`, `powershell`/`pwsh`, `cscript`, `wscript`, `reg`, `wmic`, `tasklist`, `net`, `sc`, `iisreset`, `netsh`, `wevtutil`, or any path ending `.exe`/`.bat`/`.cmd`/`.ps1`.
- **(8) Windows device / pipe paths** — paths starting with `\\.\` (devices, named pipes), `\\?\` (Win32 long-path namespace), or `\\.\pipe\` specifically. No Linux equivalent.
- **(6) Windows-RID in build/deploy config** — `.csproj`/`.fsproj`/`launchSettings.json`/`Dockerfile`/pipeline yaml referencing `win-x64`, `win-x86`, `win-arm64`, or `win10-*` outside a Windows-only condition.
- **(6) Windows-only `runtimes/win-*/` payload** — deployment package contains `runtimes/win-*/` without a matching `runtimes/linux-x64/`.
## Per-Language Patterns
### `dotnet-isolated`
- **(1) Win32 P/Invoke** — `[DllImport("<dll>")]` of any Windows system DLL: `kernel32`, `user32`, `advapi32`, `ole32`, `gdi32`, `shell32`, `ntdll`, `wininet`, `crypt32`, etc.
- **(1) Windows-only .NET namespaces** — `using` directives, fully-qualified type names, or `PackageReference` whose namespace prefix exists only to wrap Windows. Match the **prefix**, not the leaf type. Known prefixes: `System.Drawing.*` (GDI+), `System.DirectoryServices.*` (ADSI/LDAP), `System.Management.*` (WMI), `Microsoft.Win32.*` (registry, scheduler), `System.ServiceProcess.*` (Windows services), `Microsoft.Office.Interop.*`.
- **(1) COM activation** — `[ComImport]`, `Marshal.GetActiveObject`, `Type.GetTypeFromProgID` / `…FromCLSID`, `Activator.CreateInstance` of a COM type, or string-literal ProgIDs (`WScript.Shell`, `Excel.Application`, `Word.Application`, …).
- **(3) Windows certificate store** — `X509Store(StoreLocation.LocalMachine | CurrentUser)`. Linux uses an OpenSSL-backed store; behavior differs even where the API exists.
- **(4) Windows registry** — anything under `Microsoft.Win32.Registry*` (`Registry.LocalMachine`, `Registry.CurrentUser`, `Registry.ClassesRoot`, `RegistryKey.*`).
- **(5) Windows-only NuGet packages** — `<PackageReference>` id matching `*Windows*`, `*.Win32*`, or `*.Office.Interop.*`. Specific offenders: `System.Drawing.Common`, `System.Management`, `System.DirectoryServices.AccountManagement`, `Microsoft.Windows.Compatibility`, `WindowsAzure.Storage`.
- **(6) Windows-RID targeting** — `<RuntimeIdentifier>` / `<RuntimeIdentifiers>` containing any `win-*` (`win-x64`, `win-x86`, `win-arm64`, `win10-*`) without a matching `linux-*`.
- **(6) `net4x` target framework** — `<TargetFramework[s]>` containing `net4` (also in [Known Limitations](workflow.md#known-limitations-and-migration-blockers)).
- **(8) Shelling out to a Windows-only tool** — `Process.Start(...)` / `new ProcessStartInfo(...)` whose executable matches the agnostic Windows-tool list.
### `node`
- **(1) Windows-only npm packages** — `require`/`import` of packages whose purpose is wrapping Windows. Naming patterns: `node-windows-*`, `windows-*`, `win32-*`. Known: `node-windows`, `windows-shortcuts`, `windows-foreground-love`, `node-task-scheduler`, `edge`/`edge-js` (CLR bridge), `regedit`.
- **(5) Windows-only npm dependencies** — `package.json` deps matching the naming patterns above, or `os: ["win32"]` (refuses to install on Linux), or `cpu` constraints excluding Linux arches.
- **(6) Pre-built Windows native add-ons** — `node_modules/**/*.node` files that are PE-format. Any `.node` shipped without a Linux-built equivalent fails to load. Solution: rebuild on Linux at deploy time.
- **(8) Shelling out to a Windows-only tool** — `child_process.exec`/`spawn`/`execSync` whose command matches the agnostic Windows-tool list.
### `java`
- **(1) Windows-only Java APIs** — `import` of namespaces wrapping Windows: `com.sun.jna.platform.win32.*`, `com.jacob.*` (COM bridge), `org.eclipse.swt.win32.*`, anything ending in `.win32`. Also flag `System.loadLibrary` / `System.load` whose argument resolves to a `.dll`.
- **(5) Windows-only Maven/Gradle deps** — `pom.xml` / `build.gradle` entries whose `groupId:artifactId` contains `windows`, `win32`, or `jacob`. Anchors: `com.sun.jna:jna-platform`, `net.sf.jacob-project:jacob`, `org.eclipse.swt.win32.*`.
- **(6) Bundled Windows native binaries** — `.dll` files under `src/main/resources/`, `META-INF/native/`, or shaded into the jar — won't load on Linux even if the calling Java is portable.
- **(8) Shelling out to a Windows-only tool** — `Runtime.exec(...)` / `new ProcessBuilder(...)` whose command matches the agnostic Windows-tool list.
### `python`
- **(1) Windows-only Python modules** — `import` of any module in the `win32*` family (`win32api`, `win32com`, `win32con`, `win32event`, `win32file`, `win32process`, `win32security`, …), the registry interfaces (`winreg`, `_winreg`), or COM/WMI bridges (`pythoncom`, `wmi`, `pywinauto`, `comtypes`, `pythonnet`).
- **(1) Win32 via ctypes** — `ctypes.windll.*`, `ctypes.WinDLL(...)`, `ctypes.OleDLL(...)`. The `ctypes.windll` attribute itself only exists on Windows Python.
- **(5) Windows-only pip dependencies** — `requirements.txt` / `pyproject.toml` / `Pipfile` entries whose name signals Windows-only (`pywin32`, `pywinauto`, `wmi`, `winshell`, `pythonnet`, `comtypes`, `win32-*`) or pinned via `sys_platform == 'win32'` environment marker without an alternate branch.
- **(8) Shelling out to a Windows-only tool** — `subprocess.run`/`Popen`/`call` or `os.system` whose command matches the agnostic Windows-tool list.
### `powershell`
**Flex Consumption packaging check:** Inspect `host.json`, `requirements.psd1`, and the app-root `Modules` folder to determine whether the app relies on managed dependencies. Flex Consumption doesn't support managed dependencies, even for Linux-compatible modules. If required modules aren't bundled with the app, record that preparation is required in assessment report §11 and advise packaging them in `Modules` before deployment. Verify that the deployment package includes the required modules and their dependencies. Any source or packaging changes require user approval.
- **(1, 7) WMI / CIM cmdlets** — `Get-WmiObject`, `Get-CimInstance`, `Invoke-WmiMethod`, `Invoke-CimMethod`. PowerShell 7 on Linux silently returns `null` instead of erroring — failures are easy to miss.
- **(1) COM activation & Windows-only providers** — `New-Object -ComObject …`, `Get-EventLog`, Windows-service form of `Get-Service`/`*-Service`, anything in `Microsoft.PowerShell.LocalAccounts`.
- **(4, 7) Registry cmdlets / provider paths** — any access to the `Registry::` provider: `Get-ItemProperty Registry::*`, `Get-ChildItem HKLM:` / `HKCU:` / `HKCR:`, `Set-ItemProperty -Path HKLM:\…`, `New-PSDrive -PSProvider Registry`.
- **(7) Windows-only modules** — `Import-Module` of modules shipped only with Windows PowerShell, RSAT, or a Windows feature. Anchors: `ActiveDirectory`, `GroupPolicy`, `ServerManager`, `Hyper-V`, `BitsTransfer`, `ScheduledTasks`, `MSOnline`, `AzureAD`, `Dism`.
- **(3) Windows certificate store** — paths under `Cert:\LocalMachine\*` or `Cert:\CurrentUser\*`. Linux PowerShell has a limited read-only implementation.
- **(7) `requirements.psd1` `RequiredModules`** — pinning any module above.
- **(8) Shelling out to a Windows-only tool** — `& cmd.exe …`, `Start-Process` of a Windows binary, `Invoke-Expression` of any Windows admin command line.
### `go`
- **(1) Windows-only Go APIs** — imports of `golang.org/x/sys/windows` or calls to `syscall.NewLazyDLL`, `windows.NewLazySystemDLL`, or other Windows DLL-loading APIs.
- **(5, 6) Windows-only build constraints** — required source files named `*_windows.go`, `//go:build windows` constraints without a Linux implementation, or build/deployment configuration that sets `GOOS=windows`.
- **(6) Windows handler binaries** — deployed `.exe` or PE-format handler binaries without a Linux ELF equivalent.
- **(8) Shelling out to a Windows-only tool** — `os/exec.Command` or `CommandContext` whose executable matches the agnostic Windows-tool list.
### `custom`
Run the agnostic set, plus:
- **(6) Handler binary is a Windows PE** — `file <handler>` reports `PE32+ executable … for MS Windows` → 🛑 Blocker (must be rebuilt for Linux).
- **(6) Bundled Windows native deps** — `.dll` files in the package without matching `.so` / Linux ELF.
- **(8) Windows-script handler entry-point** — `host.json` `customHandler.description.defaultExecutablePath` ending in `.exe`, `.bat`, `.cmd`, or `.ps1`.
If the user identifies the handler's source language as one of the supported seven, also layer in that set.
## Output
Each finding:
```yaml
- category: 1-8
severity: blocker | likely | review
file: <relative path>
line: <line number, if applicable>
match: <the matched code or setting>
remediation: <one-line fix or "Migrate to Elastic Premium on Windows">
```
Findings populate **§11 Source Code Windows Dependency Scan** of the assessment report — see the template in [assessment.md](assessment.md#assessment-report-format). If the user declined the scan, write *"User declined the source code dependency scan. Migration proceeded without static analysis of Windows dependencies."* in place of the findings table.
references/services/functions/consumption-to-flex/windows/progress-tracking.md›
# Migration Progress
Maintain `<UPGRADE_DIR>/upgrade-status.md` and show the current status in chat at every transition. The workflow documents are the source of truth; do not maintain a second hard-coded step inventory here.
## Build the Checklist
1. Read the ordered `### Phase` headings in [workflow.md](workflow.md) to determine the phase names and total.
2. Read the ordered workflow-unit table in [automation.md](automation.md). For each phase, load the linked automation files that own its work and add their executable headings in document order. Include conditional and optional headings; mark them skipped only after evaluating their conditions.
3. Use a numbered step label such as `4g` as its stable ID. For unnumbered work, use the exact heading text.
4. On resume, rebuild the workflow snapshot from the current headings and reconcile it with saved state:
- preserve matching completed, skipped, deferred, blocked, and pending entries;
- add new headings as pending in document order;
- record renamed or removed saved entries under Notes instead of silently discarding them.
## Resume After External Provisioning
When Step 4b is blocked on keyless provisioning and the user returns with a target resource ID, load [Adopt a User-Provisioned Target](automation/app-creation.md#adopt-a-user-provisioned-target) before any target writes. Keep the block until read-only verification and user approval succeed. Do not reapply target-name defaults or rerun creation/conversion commands.
Record the adopted target resource ID in `Current Status` and its verified storage, identity, and monitoring resource IDs in Notes. Mark Step 4b complete only as externally provisioned and verified; reconcile applicable skipped steps as directed by the adoption procedure. Set `Next action` to the first unfinished step at or after 4c, not app creation. Preserve all outstanding approval gates.
## Reporting Rules
1. Before each action, update `Current Status` and display: `Phase <current> of <total> · <step> · <description> · <state>`.
2. After success, mark the item complete and immediately display the next item.
3. At an approval gate, use state `awaiting approval` and invoke `ask_user` in the same turn.
4. Mark work `skipped — <reason>` when it is not applicable or the user intentionally omits it and no later action is expected in this migration.
5. Mark work `deferred — <reason>` when it remains outstanding but the user chooses to continue, such as access-restricted HTTP validation or production trigger cutover. Preserve it in the workflow snapshot and summarize it in `Next action` or `Notes`; never represent it as complete.
6. Mark work `blocked` when an intended action cannot proceed because of an error or unresolved dependency. Record the error without secrets and do not advance unless the user explicitly chooses to defer or skip the work.
7. The workflow may advance past skipped or deferred items. A phase may close with those states, but its summary must identify deferred work.
8. Do not report a percentage; validation and cutover duration varies by function count.
## Migration Outcomes
Track an outcome separately from the current step state:
| Outcome | Meaning |
|---------|---------|
| `in progress` | The workflow is still executing or awaiting decisions |
| `validation-only success` | The target was created, configured, deployed, and verified at the agreed scope, but applicable production traffic or trigger cutover remains skipped or deferred |
| `production cutover complete` | All applicable production traffic and triggers use the target, target processing is validated, no production-readiness work remains deferred, and the source no longer processes production workloads |
| `rolled back` | The target migration was abandoned or reversed and the source remains the production app |
Never infer `production cutover complete` from successful resource creation, deployment, ARM health, or partial validation alone. A user may explicitly conclude a test migration as `validation-only success`.
State meanings:
| State | Meaning |
|-------|---------|
| `pending` | Not started |
| `in progress` | Currently executing |
| `awaiting approval` | Waiting at an explicit user decision gate |
| `blocked` | Intended work cannot proceed |
| `skipped` | Not applicable or intentionally omitted; no later action expected |
| `deferred` | Postponed and still outstanding |
| `complete` | Finished and verified |
## Status Template
```markdown
# Windows Consumption to Flex Migration Status
## Current Status
| Property | Value |
|----------|-------|
| Source app | <SOURCE_APP_NAME> |
| Target app | <NEW_APP_NAME or pending> |
| Target resource ID | <verified target resource ID or pending> |
| Outcome | in progress / validation-only success / production cutover complete / rolled back |
| Current phase | <current> of <total derived from scenario headings> |
| Current step | <step> |
| State | pending / in progress / awaiting approval / blocked / skipped / deferred / complete |
| Next action | <description> |
| Last updated | <UTC timestamp> |
## Workflow Snapshot
<Populate from the current scenario and automation headings. Nest each phase's executable steps beneath it.>
## Notes
<Decisions, skipped steps, failures, workflow reconciliation, and resumability details. Never record secrets.>
```
references/services/functions/consumption-to-flex/windows/workflow.md›
# Windows Consumption Plan to Flex Consumption Plan Upgrade
> **Source**: Azure Functions Consumption Plan (Y1/Dynamic) on Windows
> **Target**: Azure Functions Flex Consumption Plan (FC1/FlexConsumption)
> **Platform**: Windows → Linux (Flex Consumption is Linux-based)
> **Docs**: [Windows migration guide](https://learn.microsoft.com/en-us/azure/azure-functions/migration/migrate-plan-consumption-to-flex?pivots=platform-windows)
> ⚠️ **This is a Windows-to-Linux migration**, not just a plan change. Flex Consumption is Linux-based. Apps with Windows-specific dependencies, in-process .NET, .NET Framework targeting, or Windows-RID deployment packages must be prepared before migration. See [Known Limitations and Migration Blockers](#known-limitations-and-migration-blockers).
## Before You Begin
Before running any discovery or Azure command, read the ordered `### Phase` headings in this workflow and the ordered workflow-unit table in [automation.md](automation.md). Present a concise user-facing summary in that order, combining implementation units under their corresponding phases. Do not maintain or infer a separate hard-coded step list.
State that the source app remains read-only during assessment and replication. Every source-changing cutover, rollback, or cleanup action requires a separate, immediate approval. After presenting the plan, confirm the source app and Flex Consumption target plan with the user before continuing.
## 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 may need changes** — unlike the Linux Consumption → Flex migration, this is a Windows-to-Linux OS change. Apps with Windows-only NuGet packages, P/Invoke, COM interop, .NET Framework dependencies, Windows-RID deployment packages, or Windows-specific PowerShell modules require preparation.
2. **Create a new Flex app alongside the existing one** — test thoroughly on the new app before cutting over. This 2-app approach lets you discover Linux compatibility issues safely before committing.
3. The new app runs in the **same resource group** with access to the same dependencies.
4. **Deployment model changes** — Windows Consumption stores code on Azure Files (SMB); Flex uses immutable blob packages via One Deploy. Existing MSDeploy, FTP, and Kudu git push pipelines will not work after migration.
5. You **control the timing** — the original Windows app stays running until you are confident in the migrated app.
### Source-App Safety
The source app is read-only throughout assessment and replication. Before any cutover, rollback, or cleanup command that changes the source app's settings, triggers, state, deployment, bindings, or existence, the skill must use `ask_user` to describe the exact action and obtain explicit approval. Approval is specific to that action and cannot be reused for later source changes.
## Platform Notes
- **Recommended plan**: Flex Consumption is the recommended serverless hosting plan for Azure Functions. Do not assume or state a Windows Consumption retirement date unless current Microsoft documentation publishes one.
- **OS change**: Flex Consumption is **Linux-based**. Windows-compiled binaries, Windows-only NuGet packages, COM interop, P/Invoke to Windows APIs, and Windows PowerShell modules will not run without modification.
- **Deployment model**: Windows Consumption uses Azure Files (SMB share via `WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`/`WEBSITE_CONTENTSHARE`); Flex uses blob-based One Deploy (immutable zip packages in Azure Blob Storage).
- **`az functionapp flex-migration` does not support Windows** — all migration steps use raw CLI commands (see [automation.md](automation.md)).
- **Elastic Premium alternative**: Apps with hard Windows dependencies that cannot be removed can migrate to [Elastic Premium on Windows](https://learn.microsoft.com/en-us/azure/azure-functions/functions-premium-plan) instead of Flex Consumption. This is more expensive but changes the hosting plan without requiring an OS migration.
## 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 8+) | .NET isolated worker, .NET 8+ | ✅ Yes — if no Windows-only dependencies and not published with a Windows RID |
| `dotnet-isolated` (.NET Framework) | .NET isolated worker targeting `net4x` | ❌ No — .NET Framework runtime not present on Linux; must retarget to .NET 8+ |
| `dotnet` | .NET in-process model | ❌ No — not supported on Flex; must migrate to isolated worker model first |
| `node` | JavaScript/TypeScript | ✅ Yes |
| `java` | Java | ✅ Yes |
| `python` | Python | ✅ Yes |
| `powershell` | PowerShell | ⚠️ Review — Windows-only modules (WMI, registry, COM interop) will not work on Linux |
| `go` | Go (Preview) | ✅ Yes — if the handler and native dependencies are built for Linux |
| `custom` | Custom handlers | ⚠️ Review — handler binary must be Linux-compatible |
### Known Limitations and Migration Blockers
#### Flex Consumption Platform Limits (shared with Linux migration)
| Feature | Status | Action Required |
|---------|--------|-----------------|
| Deployment slots | ❌ Not supported | Consolidate to production slot or rearchitect to separate apps |
| TLS/SSL certificates | ⚠️ Site-scoped certificates in preview | Re-add used certificates after migration, verify the per-app limits, and update code to use Linux certificate file paths |
| Blob trigger (polling) | ❌ EventGrid source only | Convert `LogsAndContainerScan` → `EventGrid`; create event subscription |
| Disable setting for function names containing `-` | ❌ Not supported on Linux | Isolate the target dependency, rename/repackage the function, use a source-first deployment cutover, or explicitly accept active-trigger risk |
| Azure Government | ❌ Not available | Cannot migrate yet |
| MSDeploy / FTP / Kudu git deploy | ❌ Not supported | Switch to `az functionapp deploy` or `func azure functionapp publish` |
| PowerShell managed dependencies | ❌ Not supported | Package all required modules in the app-root `Modules` folder before deployment; verify Linux compatibility. `requirements.psd1` alone doesn't install modules on Flex Consumption. See [PowerShell dependency management](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-powershell#dependency-management). |
#### Windows-to-Linux Specific Blockers
| Issue | How to Detect | Action Required |
|-------|---------------|-----------------|
| .NET in-process model | `FUNCTIONS_WORKER_RUNTIME=dotnet` app setting | Migrate to isolated worker model — requires code changes (different NuGet packages, `Program.cs` startup) |
| .NET Framework targeting (`net4x`) | `.csproj` target framework | Retarget to .NET 8+ — may require replacing Framework-only APIs and NuGet packages |
| Windows-only NuGet packages (GDI+, COM, P/Invoke) | Assembly/package scan (heuristic) | Replace with cross-platform alternatives, or migrate to Elastic Premium on Windows |
| Windows RID deployment package | `runtimes/win-x64/` present without `linux-x64` in package | Republish without a RID (framework-dependent) or with `-r linux-x64` |
| Path separator / case sensitivity | Code review | Replace `\` path construction with `Path.Combine()`; fix case-sensitive file references |
| PowerShell Windows-only modules (WMI, registry, COM) | `requirements.psd1` module imports | Replace with cross-platform alternatives or remove |
| Windows certificate store access | Code review | Migrate to file-based certificate loading |
| Unsupported language runtime version | Site config | Upgrade to a [supported version](https://learn.microsoft.com/en-us/azure/azure-functions/supported-languages) |
| App region not supported by Flex | Region vs. Flex availability list | Wait for regional availability, or relocate the app |
> 💡 **Elastic Premium escape hatch**: Apps with hard Windows dependencies that cannot be removed can migrate to [Elastic Premium on Windows](https://learn.microsoft.com/en-us/azure/azure-functions/functions-premium-plan) as an alternative. This changes the hosting plan without requiring an OS migration.
## New App Name
The new Flex Consumption app needs a globally unique name. The skill defaults to **`<source>-flex`** — e.g. if your source app is `test-app`, the new app will be `test-app-flex`. This is short, self-documenting, and signals the SKU change.
Two modes are supported. State your preference in your initial migration request:
| Mode | How to invoke | Resulting name |
|------|---------------|----------------|
| **Default suffix** *(default)* | Just say "migrate my function app" | `<source>-flex` |
| **Custom name** | "migrate my function app **as `<name>`**" or "… **named `<name>`**" | The name you provide |
> ⚠️ **Skill instruction**: Before executing Step 4b in [automation.md](automation.md), **announce the proposed name to the user**, e.g. *"I'll create the new Flex app as `test-app-flex` in resource group `test-app_group` (region `westus2`). Say so if you'd prefer a different name."* This gives the user a chance to course-correct without a blocking prompt.
> 💡 Constraints (from the underlying [MS Learn create flow](https://learn.microsoft.com/en-us/azure/azure-functions/flex-consumption-how-to?tabs=azure-cli%2Cazure-cli-publish&pivots=programming-language-csharp#create-a-flex-consumption-app)): globally unique in Azure (becomes `<APP_NAME>.azurewebsites.net`), 2–60 characters, alphanumerics + hyphens, must start with a letter, no consecutive hyphens. If the default `<source>-flex` would exceed 60 chars or collides with an existing app, fall back to a shortened suffix (e.g. `<source>-fc`) and announce that to the user.
## Storage Account Options
The new Flex Consumption app needs a storage account bound at creation time. The skill defaults to **reusing the source app's existing storage account** — the same default as `az functionapp flex-migration` on Linux. This is the lowest-friction option because there is no new storage resource to manage. Flex configures deployment storage on the target rather than reusing the source app's Windows content-share settings.
Three modes are supported. State your preference in your initial migration request — the skill will detect it and set the binding accordingly. Think of these as CLI-style flags:
| Mode | How to invoke | When to use |
|------|---------------|-------------|
| **Reuse source storage** *(default)* | Just say "migrate my function app" | Most migrations. No isolation needed between source and target. |
| **Create new storage** | "migrate my function app **with a new storage account**" or "… **and create a new storage account**" | You want isolated storage for blast-radius, compliance, or test/prod separation. |
| **Use a specific existing account** | "migrate my function app **using storage account `<name>`**" | You already have a target account provisioned (e.g. via IaC or central platform team). |
> ⚠️ **Skill instruction**: Before executing Step 4a/4b in [automation.md](automation.md), **announce the storage choice to the user**, e.g. *"I'll reuse your existing storage account `foo` for the new Flex app. Say so if you'd prefer a new isolated account or a different existing one."* This gives the user a chance to course-correct without a blocking prompt.
## Upgrade Phases
Initialize and maintain `<UPGRADE_DIR>/upgrade-status.md` using [Migration Progress](progress-tracking.md). Derive its workflow snapshot from the current scenario and automation headings, and show the current phase, substep, description, and state in chat at every transition; do not use a percentage.
### Phase 1: Assessment
Run all checks from [assessment.md](assessment.md). Since `az functionapp flex-migration` does not support Windows, eligibility is determined manually — use Step 1 and Step 2 in [automation.md](automation.md) to identify candidate apps and run compatibility checks.
- **Source code dependency scan** ([Step 2g](automation/discovery-and-assessment.md#2g-source-code-windows-dependency-scan) / [dependency-scan.md](dependency-scan.md)) — recommended for every Windows → Flex migration. Prefer a local source directory or checked-out repository; use the deployed package only when source is unavailable. The scan checks for Win32/COM/WMI/registry/cert-store usage, Windows-only packages, Windows-RID targeting, and shell-outs to Windows tools. Skippable — when skipped, the user owns the risk. Findings populate §11 of the assessment report and advisorily affect §1 `Upgrade Readiness`.
### 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. Inventory TLS/SSL certificates that must be re-added with the site-scoped certificate process
4. Identify managed identities and RBAC role assignments
5. Identify built-in authentication settings
6. Review inbound access restrictions
7. Get the code deployment package (if source code not in version control)
### Phase 3: Create Flex Consumption App
Execute Steps 4a–4l from [automation.md](automation.md). Each sub-step handles a discrete part of the migration:
- **4a** — *(optional)* Create a new storage account — only run if the user explicitly asked for new or different storage; see [Storage Account Options](#storage-account-options)
- **4b** — Run the storage authentication preflight; use connection-string creation only when Shared Key access is permitted. Otherwise block for user-provisioned, verified identity-first creation — [App Creation](automation/app-creation.md#4b-create-the-flex-consumption-app)
- **4b.opt** — Convert a permitted connection-string target to identity-based storage if the source was identity-based or the user requested it; not a workaround for prohibited Shared Key access
- **4c** — Review app-setting names, then ask whether to copy filtered source settings or defer them until target-specific dependencies are configured
- **4d** — Apply site configurations (HTTP/2, TLS, HTTPS-only, client certs, SCM basic auth)
- **4e** — Migrate system-assigned managed identity and role assignments
- **4f** — Migrate user-assigned managed identities
- **4f.auth** — If the source uses built-in authentication, configure and validate each provider before code deployment or defer the migration
- **4g** — Migrate CORS settings
- **4h** — Migrate custom domains
- **4i** — Migrate access restrictions
- **4j** — Skip disablement when settings and every trigger dependency are proven isolated from production and no autonomous function can affect production; otherwise separate hyphenated non-HTTP function names, which Linux cannot disable through app settings, and obtain the applicable safety decisions — [decision guidance](automation/trigger-safety-and-monitoring.md#4j-choose-the-pre-deployment-state-of-non-http-functions)
- **4k** — Verify the Application Insights configuration created with the target app; preserve AAD ingestion only when the source used it
- **4l** — Inventory access-key names and choose target-generated keys or opt in to preserving source host and HTTP-function key values
> 💡 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**: [Step 4b](automation/app-creation.md#4b-create-the-flex-consumption-app) checks Shared Key access and policy before creation. Where keys are prohibited, managed identity and effective roles are required for both runtime and deployment storage; block until identity-first provisioning is verified. Runtime identity settings alone don't configure deployment identity. [4b.opt](automation/app-creation.md#4bopt-identity-based-storage) is a post-create conversion only where connection-string creation was permitted. Step 4c preserves target storage settings rather than copying source `AzureWebJobsStorage*` settings.
- **Application Insights**: Preserve `Authorization=AAD` when the source already uses it; otherwise treat conversion as optional post-migration hardening
- When using User Assigned Managed Identity, pass `managedIdentityClientId` explicitly
### Phase 4: Verify and Configure the New App
Steps 4a–4i migrate application configuration; Step 4f.auth handles built-in authentication; Steps 4j–4k establish trigger safety and verify monitoring; Step 4l records the access-key strategy. Verify the results and manually configure anything not covered by those steps (see [automation.md](automation.md)):
1. **Verify** the chosen app-setting mode, identities, and custom domains
2. **Re-add** used TLS/SSL certificates with the site-scoped certificate process and update code that used the Windows certificate store
3. **Configure** the source app's custom scale limit, if one was set
4. **Verify** 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.
>
> ⚠️ If Step 4b storage authentication preflight or identity-first provisioning remains blocked, do not deploy code. Required storage identities, role assignments, and access validation cannot be deferred.
>
> ⚠️ If source app settings were deferred in Step 4c, deployment is blocked until the user confirms all required application-specific target settings are configured. The skill may inspect names, but never setting values.
>
> ⚠️ If built-in authentication was deferred in Step 4f.auth, deployment is blocked. The source authentication boundary must be recreated and verified before application code is placed on the target.
**Use `ask_user` to present these options:**
> Your new Flex Consumption app `<NEW_APP_NAME>` 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 <NEW_APP_NAME>` 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 synchronizes all functions, execute Step 5a. When preservation was selected, copy and verify source host and HTTP-function keys before any HTTP validation. System and master keys remain target-generated.
> ✅ If Step 4j disablement was approved and no risky hyphenated non-HTTP function was approved for active deployment, only HTTP endpoints will be live after deployment and per-function cutover happens in Phase 6.
>
> ✅ If Step 4j was skipped because the target is fully isolated, deploy without per-function disable settings and validate only against those isolated dependencies.
>
> ⚠️ If the user declined disablement, warn again and obtain explicit approval immediately before deployment because non-HTTP functions may begin processing or running schedules as soon as deployment completes.
**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
> - If target-generated access keys were selected, update key-dependent clients and workflows before directing them to the new URL
> - Enable HTTPS-only and managed identity on the new app for better security
> - Source deletion is available only after all applicable production traffic and triggers are cut over and validated
### 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). If an intentional access restriction returns `403 Ip Forbidden`, verify deployment and function synchronization through ARM and record functional smoke testing as deferred until it can run from an already allowed client; do not relax access rules automatically.
2. Verify the app is running on Flex Consumption (`az functionapp show --query sku`)
3. Test HTTP trigger endpoints (e.g. `curl https://<DEFAULT_HOST>/api/<FUNCTION_NAME>`)
4. **Per-function validation and cutover for event-driven triggers** — either validate the target against isolated test resources without changing the source, or use a production cutover transaction. For production, obtain function-specific approval first, disable and verify the source, then enable the target; the same approval must explicitly authorize rollback if target validation fails. Never enable a production-connected target while source-disable approval is pending. See [Per-Function Cutover](automation/validation-and-cutover.md#per-function-cutover-event-driven-triggers) for the full procedure.
5. Capture and compare performance benchmarks
6. Set up monitoring dashboards
7. Refine scale/concurrency settings
8. 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
- A validation-only success is not eligible for source deletion
- The skill may offer deletion only when the recorded outcome is `production cutover complete`, no production-readiness work remains deferred, and the source no longer processes production workloads
- Immediately before deletion, use `ask_user` as specified in [automation.md](automation.md) — Step 7
## Trigger Migration Risks
> ✅ **Recommended safety net**: When the user approves [Step 4j](automation/trigger-safety-and-monitoring.md#4j-choose-the-pre-deployment-state-of-non-http-functions), non-HTTP functions are disabled before deployment. Phase 6 then uses isolated target resources or a source-first, pre-approved production transaction so the target is never enabled while source-disable approval is pending. If the user declines Step 4j, record that dual processing or duplicate schedules may begin immediately after deployment. Apply the relevant mitigations below before enabling or deploying each function.
| 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 | Windows | Linux |
| Configuration | App settings | `functionAppConfig` section |
| Storage | `WEBSITE_CONTENTSHARE` setting | `deployment.storage` in `functionAppConfig` |
If the source app is managed through Bicep, ARM, or Terraform:
1. Define the Flex app and plan as new resources; a Consumption app cannot be converted to Flex Consumption in place.
2. Update the deployment definitions to use the new resource names and the Flex configuration above.
3. Reconcile resources created during migration instead of allowing IaC to recreate or overwrite them. For Terraform, import the new resources when appropriate.
4. Review the proposed changes with `az deployment group what-if` or `terraform plan` before applying them.
5. Update CI/CD pipelines to deploy to the new Flex app.
Do not mix ongoing manual configuration with resource-based deployments. If the app is not managed through IaC, skip this section.
## Deprecated Settings (Do NOT Migrate)
These app settings must be filtered out before applying to the new Flex app (Step 4c). The skill handles this automatically — Step 4c streams source settings through `jq`, deleting these keys in memory before PATCHing the new app's ARM endpoint.
### Azure Files content settings (always present on Windows Consumption — never migrate)
Every Windows Consumption app has these because the platform requires them for the Azure Files SMB share. Flex uses blob-based deployment instead — these have no equivalent.
These platform content settings are distinct from explicit Azure Files mount metadata returned by `az webapp config storage-account list`. Windows Consumption doesn't support explicit storage mounts, so catalog any returned metadata but don't migrate it.
- `WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`
- `WEBSITE_CONTENTSHARE`
- `WEBSITE_CONTENTOVERVNET`
- `WEBSITE_SKIP_CONTENTSHARE_VALIDATION`
### Deployment settings (Flex uses `deployment.storage` in `functionAppConfig`, not app settings)
Windows Consumption has three historical aliases for run-from-package — all must be removed:
- `WEBSITE_RUN_FROM_PACKAGE`
- `WEBSITE_RUN_FROM_ZIP` (legacy alias — same semantics as `WEBSITE_RUN_FROM_PACKAGE`)
- `WEBSITE_USE_ZIP` (original name — same semantics)
### Runtime / scale settings (configured via `functionAppConfig` in Flex, not app settings)
- `FUNCTIONS_EXTENSION_VERSION`
- `FUNCTIONS_WORKER_RUNTIME`
- `FUNCTIONS_WORKER_RUNTIME_VERSION`
- `FUNCTIONS_MAX_HTTP_CONCURRENCY`
- `FUNCTIONS_WORKER_PROCESS_COUNT`
- `FUNCTIONS_WORKER_DYNAMIC_CONCURRENCY_ENABLED`
- `WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT`
### Other unsupported settings
- `WEBSITE_USE_PLACEHOLDER_DOTNETISOLATED`
- `WEBSITE_LOAD_CERTIFICATES` (unused in Flex; use the site-scoped certificate process and make certificates accessible to app code)
- `AzureWebJobsDashboard` (legacy v1 setting — not used by the v4 runtime)
- `WEBSITE_MOUNT_ENABLED`
- `ENABLE_ORYX_BUILD`
- `SCM_DO_BUILD_DURING_DEPLOYMENT`
- `WEBSITE_DNS_SERVER`
- `WEBSITE_NODE_DEFAULT_VERSION`
- `WEBSITE_VNET_ROUTE_ALL`
### Application Insights (preserved from Step 4b and verified by Step 4k)
- `APPLICATIONINSIGHTS_CONNECTION_STRING` — Step 4c preserves the target-specific value normally created by `az functionapp create`; Step 4k verifies it without displaying the value. `APPLICATIONINSIGHTS_AUTHENTICATION_STRING=Authorization=AAD` is preserved only when the source already used AAD ingestion.
### Storage (replaced by Step 4b / 4b.opt)
Every setting whose name starts with `AzureWebJobsStorage` is dropped by the 4c filter. Preserve the target binding verified by Step 4b or configured by [Step 4b.opt](automation/app-creation.md#4bopt-identity-based-storage):
- `AzureWebJobsStorage` — discard the source connection string. Step 4b creates a fresh target connection string only when Shared Key authentication is permitted; identity-first targets must not receive one.
- All `AzureWebJobsStorage__*` variants — discard source settings so they can't silently point the target runtime at the wrong account. Preserve verified target identity settings or re-derive them through 4b.opt where conversion is permitted.
references/services/redis/redis-to-amr.md›
# Azure Cache for Redis → Azure Managed Redis (AMR) Migration
> **Target for both paths**: Azure Managed Redis (AMR) — M, B, X (Flash), A series
> **Source determines which dedicated skill to install** — see decision table below.
There are **two distinct migration paths** to AMR, depending on the source SKU. Each path is owned by a dedicated, versioned skill maintained by the Azure Managed Redis team. The `azure-upgrade` skill does **not** ship the SKU specs, pricing scripts, ARM automation, or template-transformation logic needed for either migration — it routes the user to the correct dedicated skill.
## Decision Table — Which Skill?
| Source SKU | ARM Resource Type | CLI / PowerShell | Dedicated Skill | Repo |
|---|---|---|---|---|
| **ACR / OSS Redis** — Basic, Standard, Premium (C0–C6, P1–P5) | `Microsoft.Cache/redis` | `az redis`, `*-AzRedisCache` | `amr-migration-skill` | https://github.com/AzureManagedRedis/amr-migration-skill |
| **ACRE** — Enterprise (`Enterprise_E*`), Enterprise Flash (`EnterpriseFlash_F*`) | `Microsoft.Cache/redisEnterprise` | `az redisenterprise`, `*-AzRedisEnterprise*` | `acre-to-amr-migration-skill` | https://github.com/AzureManagedRedis/acre-to-amr-migration-skill |
> ACR and ACRE are **fundamentally different products** with different ARM resource types, different APIs, and different migration mechanics. Picking the wrong skill will give the user wrong guidance. Always disambiguate before pointing.
## Disambiguation — How to Tell ACR from ACRE
Ask the user, or inspect the resource. Any **one** of these signals identifies the source:
**ACR (OSS) indicators** → use `amr-migration-skill`:
- Resource type `Microsoft.Cache/redis` (no "Enterprise" suffix)
- SKU names `Basic`, `Standard`, `Premium`, or sizes `C0`–`C6`, `P1`–`P5`
- CLI: `az redis ...` commands
- PowerShell: `New-AzRedisCache`, `Get-AzRedisCache`, etc.
- DNS suffix `.redis.cache.windows.net`
- Default TLS port `6380`
**ACRE indicators** → use `acre-to-amr-migration-skill`:
- Resource type `Microsoft.Cache/redisEnterprise`
- SKU names starting with `Enterprise_` (e.g. `Enterprise_E10`, `Enterprise_E20`) or `EnterpriseFlash_` (e.g. `EnterpriseFlash_F300`)
- CLI: `az redisenterprise ...` commands
- PowerShell: `New-AzRedisEnterpriseCache`, `Get-AzRedisEnterpriseCache`, etc.
- Default TLS port `10000`
- May reference geo-replication groups (`databases create-replica-link`), Private Endpoints, or Private Link DNS zones
If the user has a **mix of ACR and ACRE** resources, both skills should be installed and run separately on the relevant resources. Most users (~97% of the Azure Cache for Redis fleet) are on ACR; ACRE is the smaller (~3%) but functionally distinct case.
## What Each Skill Provides
### `amr-migration-skill` (ACR/OSS → AMR)
- SKU mapping ACR Basic/Standard/Premium → AMR M/B/X/A with validated SKU specs
- Real-time pricing scripts (PowerShell + bash) with HA, clustering, and MRPP logic
- Cache metrics assessment (default 7-day window) for sizing
- Automated migration via ARM REST APIs with **DNS switching** (old hostname keeps working; port still changes)
- Validate-before-migrate workflow with explicit confirmation on destructive actions
- IaC template migration for ARM, Bicep, and Terraform with worked before/after examples
- Feature comparison and retirement FAQ
- **Retirement deadlines this skill addresses**: ACR Basic/Standard/Premium tiers are being retired — for the current retirement and creation-block dates, see the [official ACR retirement announcement](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-retired-tiers).
### `acre-to-amr-migration-skill` (ACRE → AMR)
- Three modes: automation-script update (ARM/Bicep/CLI/PowerShell/Terraform checklist), interactive cache migration with confirmation gates, generic step-by-step guide
- ACRE → AMR SKU resolution via `listSkusForScaling` with fallback mapping table
- In-place migration for standalone caches; create-new-and-swap for geo-replicated caches
- Geo-replication database config parity (eviction policy, modules, etc.)
- Private Endpoint / Private Link migration and DNS zone updates
- Breaking-changes reference (property changes, DNS, API versions)
- Reusable polling scripts (`Poll-MigrationStatus.ps1`, `poll-migration-status.sh`)
- Post-migration ACRE resource cleanup
- **Retirement deadline this skill addresses**: ACRE / Enterprise Flash are being retired — for the current retirement date, see the [official ACR retirement announcement](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-retired-tiers).
## Agent Behavior
When a user asks about migrating any flavor of Azure Cache for Redis to Azure Managed Redis:
1. **Do not attempt the migration from this skill.** Neither SKU specs nor migration automation are inlined here.
2. **Determine the source** using the disambiguation signals above. If unclear, ask the user (or inspect the script/resource).
3. **Point the user to the correct dedicated skill** with its repo URL. The repo READMEs include install instructions for GitHub Copilot, Claude Code, and other compatible hosts.
4. After installation, the user's agent will match the dedicated skill on its trigger phrases (e.g. *"migrate my P2 cache to AMR"*, *"convert my Bicep Redis template"*, *"migrate my Enterprise_E10 cache"*, *"update my ACRE ARM template for AMR"*).
5. If the user has **both** ACR and ACRE resources, recommend installing **both** skills and running them on the relevant resources separately.
## Key Migration Facts (so the agent can set expectations)
These are summarized for awareness. For anything operational, defer to the dedicated skills.
### ACR → AMR
- **Port**: 6380 → **10000**. Non-TLS 6379 not supported on AMR.
- **DNS suffix**: `.redis.cache.windows.net` → `<region>.redis.azure.net`
- **Redis version**: 6 → **7.4**
- **No "shards"** in AMR-facing terminology — sharding is internal. Use performance tier (Balanced, Memory Optimized) and size (e.g. B10, M20).
- **Auth**: Recommend adopting **Microsoft Entra ID** post-migration. Entra config is **not** auto-migrated.
- **DNS-switch automated migration** keeps old hostname working, but the port change still applies — apps must be updated.
### ACRE → AMR
- **ARM resource type is unchanged** between the ACRE source and AMR target — both use `Microsoft.Cache/redisEnterprise`. Property values do change (API version, SKU naming, etc.); see the breaking-changes reference in the dedicated skill.
- **Geo-replicated caches** require create-new-and-swap, not in-place migration.
- **Private Endpoints / Private Link** DNS zones must be updated.
- **Database config parity** (eviction policy, modules) must match across replicas before migration.
## Why this is not implemented inline
Both migrations require maintained SKU tables, live pricing or capability lookups, and ARM-level orchestration that is updated independently of the `azure-skills` release cycle. Keeping that logic in two places would drift. The dedicated skill repos are the single source of truth and are versioned independently.
## References
- AMR Migration Skill (ACR → AMR): https://github.com/AzureManagedRedis/amr-migration-skill
- ACRE → AMR Migration Skill: https://github.com/AzureManagedRedis/acre-to-amr-migration-skill
- Azure Managed Redis docs: https://learn.microsoft.com/en-us/azure/redis/managed-redis/
- ACR retirement announcement: https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-retired-tiers
- Entra ID auth for AMR: https://learn.microsoft.com/en-us/azure/redis/managed-redis/managed-redis-entra-for-access-control-configuration
references/workflow-details.md›
# 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_DIR>/upgrade-status.md` (see [Artifact Output Policy](global-rules.md#artifact-output-policy) for path resolution):
```markdown
# Upgrade Status
## Upgrade Details
| Property | Value |
|----------|-------|
| **Source App** | <app-name> |
| **Source Plan** | <current-plan> |
| **Target Plan** | <target-plan> |
| **Resource Group** | <resource-group> |
| **Region** | <region> |
| **Started** | <date> |
## 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
<any issues, decisions, or observations during upgrade>
```
## Error Handling
If any phase fails:
1. Log the error in `<UPGRADE_DIR>/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 about source cleanup only when the scenario confirms production cutover and all cleanup prerequisites are complete. Otherwise state that the source is being retained for rollback.
SKILL.md›
---
name: azure-upgrade
description: "Assess and upgrade Azure workloads between plans, tiers, or SKUs, or modernize Azure SDK dependencies in source code. WHEN: upgrade Consumption to Flex Consumption, upgrade Azure Functions plan, change hosting plan, function app SKU, migrate App Service to Container Apps, modernize legacy Azure Java SDKs (com.microsoft.azure to com.azure), migrate Azure Cache for Redis (ACR/ACRE) to Azure Managed Redis (AMR)."
license: MIT
compatibility: python3.10+
metadata:
author: Microsoft
version: "1.2.3"
---
# 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. It also covers **Azure SDK for Java source-code modernization** (e.g. legacy Java `com.microsoft.azure.*` → modern `com.azure.*`). 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" |
| Discover Functions upgrade candidates | "What Consumption Function Apps do I have that can be upgraded 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" |
| Modernize legacy Azure Java SDK | "Migrate legacy Azure SDKs for Java", "Upgrade legacy Azure Java SDK", "Migrate my Java project from com.microsoft.azure to com.azure" |
| Migrate Azure Cache for Redis (ACR/OSS) to Azure Managed Redis (AMR) | "Migrate my Redis cache to AMR", "ACR to AMR", "OSS to AMR", "Upgrade my Premium P2 cache to Managed Redis", "Pick an AMR SKU", "Convert my Redis IaC template to AMR" |
| Migrate Azure Cache for Redis Enterprise (ACRE) to Azure Managed Redis (AMR) | "Migrate my Enterprise_E10 cache to AMR", "ACRE to AMR", "Update my ACRE IaC template for AMR", "Migrate EnterpriseFlash to AMR", "Migrate my geo-replicated Enterprise Redis" |
## Rules
1. Follow phases in order. Skip or defer work only when the selected scenario explicitly permits it, and record the reason in the migration status.
2. Generate an assessment before any upgrade operations
3. Load the scenario reference and follow its rules
4. Use `mcp_azure_mcp_get_azure_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. **Propose a name for any new resource** (default: `<source>-<target-suffix>`, e.g. `<source>-flex` for Flex Consumption migrations) and **announce the proposed name to the user before creating it** so they can override without a blocking prompt. Same pattern applies to other newly-created resources (storage accounts, App Insights, etc.) — see the scenario reference for service-specific defaults.
8. **Windows Consumption → Flex source-change gate**: treat the source app as read-only by default. Immediately before every command that would change its settings, triggers, state, deployment, bindings, or existence, use `ask_user` to describe the exact change and obtain explicit approval. Approval is action-specific and cannot be reused for later source changes.
9. Never delete or stop the original app without explicit user confirmation
10. **Keep approval-gated workflows moving**: after each completed step, immediately perform the next read-only preflight. If the next action requires approval, invoke `ask_user` in the same turn — never stop after merely announcing the next step and wait for the user to re-engage.
11. All automation scripts must be idempotent and resumable
## Upgrade Scenarios
| Source | Target | Reference |
|--------|--------|-----------|
| Azure Functions Consumption Plan (Linux) | Azure Functions Flex Consumption Plan | [workflow.md](references/services/functions/consumption-to-flex/linux/workflow.md) |
| Azure Functions Consumption Plan (Windows) | Azure Functions Flex Consumption Plan | [workflow.md](references/services/functions/consumption-to-flex/windows/workflow.md) |
| Legacy Azure Java SDK (`com.microsoft.azure.*`) | Modern Azure Java SDK (`com.azure.*`) | [languages/java/README.md](references/languages/java/README.md) |
| Azure Cache for Redis (ACR/OSS) Basic/Standard/Premium | Azure Managed Redis (AMR) | [services/redis/redis-to-amr.md](references/services/redis/redis-to-amr.md) |
| Azure Cache for Redis Enterprise (ACRE) / Enterprise Flash | Azure Managed Redis (AMR) | [services/redis/redis-to-amr.md](references/services/redis/redis-to-amr.md) |
> **Functions Consumption-to-Flex routing:** For a named app, inspect its Azure platform and load only the matching Linux or Windows workflow. For subscription-wide discovery, evaluate both platforms: use `az functionapp flex-migration list` for Linux and the Windows workflow's manual discovery and compatibility checks for Windows. Label Windows results as candidates until per-app code compatibility is assessed.
> SDK upgrade scenarios (e.g. Java legacy → modern) run a **source-code modernization flow** that is distinct from Azure service/plan/SKU upgrades: follow the scenario reference, **not** the Steps below.
> No matching scenario? Use `mcp_azure_mcp_documentation` and `mcp_azure_mcp_get_azure_bestpractices` tools to research the upgrade path.
## MCP Tools
| Tool | Purpose |
|------|---------|
| `mcp_azure_mcp_get_azure_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., [workflow.md](references/services/functions/consumption-to-flex/linux/workflow.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** — Use the checks defined by the selected scenario: verify target resource state, applicable HTTP endpoints and non-HTTP triggers, and monitoring. If access restrictions or unavailable test resources prevent execution, record validation as deferred — never relax security automatically or report functional success.
6. **Ask User** — Offer performance validation and IaC updates. Offer source cleanup only when the selected scenario confirms production cutover and all cleanup prerequisites are complete.
7. **Hand off** to `azure-validate` for deep validation or `azure-deploy` for CI/CD setup
Track progress in `<UPGRADE_DIR>/upgrade-status.md` — see [Artifact Output Policy](references/global-rules.md#artifact-output-policy) for path resolution.
## References
- [Global Rules](references/global-rules.md)
- [Workflow Details](references/workflow-details.md)
- **Functions**
- Linux
- [Consumption to Flex Consumption](references/services/functions/consumption-to-flex/linux/workflow.md)
- [Assessment](references/services/functions/consumption-to-flex/linux/assessment.md)
- [Automation Scripts](references/services/functions/consumption-to-flex/linux/automation.md)
- Windows
- [Consumption to Flex Consumption](references/services/functions/consumption-to-flex/windows/workflow.md)
- [Assessment](references/services/functions/consumption-to-flex/windows/assessment.md)
- [Automation Scripts](references/services/functions/consumption-to-flex/windows/automation.md)
- **Redis**
- [Redis (ACR or ACRE) to AMR Migration](references/services/redis/redis-to-amr.md) — routes to dedicated [amr-migration-skill](https://github.com/AzureManagedRedis/amr-migration-skill) (ACR/OSS) or [acre-to-amr-migration-skill](https://github.com/AzureManagedRedis/acre-to-amr-migration-skill) (Enterprise)
- **Java SDK Migration Templates**
- [Plan Template](references/languages/java/templates/PLAN_TEMPLATE.md)
- [Progress Template](references/languages/java/templates/PROGRESS_TEMPLATE.md)
- [Summary Template](references/languages/java/templates/SUMMARY_TEMPLATE.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