Generated banner for Azure Upgrade
Generated logo for Azure Upgrade

Azure Upgrade

MicrosoftOfficialCreated by Microsoft

  • Skill
  • Workflow
  • Cloud and DevOps
  • MIT
  • Low risk
  • Has scripts

From the author

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).
— the description by Microsoft

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

Azure Upgrade provides a structured, automated approach to upgrading Azure resources and modernizing source code dependencies. It supports complex transitions such as moving Azure Functions from Consumption to Flex Consumption, migrating various Redis cache tiers to Azure Managed Redis, and refactoring legacy Java applications from the deprecated com.microsoft.azure SDKs to the modern com.azure ecosystem. The skill enforces safety through pre-migration assessments, explicit user confirmation for destructive actions, and idempotent automation scripts, ensuring that original workloads remain intact until the new configuration is fully validated.

What it does

  • Assesses Azure workloads for upgrade readiness across plans, tiers, and SKUs.
  • Automates the migration of Azure Functions from Consumption to Flex Consumption.
  • Modernizes legacy Java applications by replacing deprecated com.microsoft.azure SDKs with modern com.azure equivalents.
  • Facilitates the migration of Azure Cache for Redis (ACR/ACRE) to Azure Managed Redis (AMR).
  • Generates detailed migration status reports and maintains artifact isolation.
  • Provides a safe, gated workflow requiring user approval for all resource creation and configuration changes.

When to use it

  • When you need to upgrade your Azure Functions hosting plan to a more performant or cost-effective tier.
  • When your Java project relies on deprecated Azure SDKs and requires modernization to supported libraries.
  • When migrating existing Redis cache instances to the newer Azure Managed Redis service.
  • When you need a repeatable, documented process for changing Azure resource SKUs or hosting plans.

Advantages

  • Reduces manual effort and risk during complex infrastructure and code migrations.
  • Ensures safety by requiring explicit user approval for all destructive or state-changing operations.
  • Maintains high availability by keeping original resources running until the new ones are validated.
  • Promotes security best practices by encouraging the use of managed identities over connection strings.
  • Provides clear, step-by-step guidance for both infrastructure upgrades and source-code refactoring.

Usage examples

“Upgrade my function app from Consumption to Flex Consumption”
The agent will identify the current plan, assess compatibility, and guide you through the migration steps to the Flex Consumption plan.
“Migrate my Java project from com.microsoft.azure to com.azure”
The agent will inventory legacy dependencies, provide a migration plan, and assist in updating your build files and source code imports.
“Migrate my Redis cache to AMR”
The agent will assess your current Redis configuration and execute the migration path to Azure Managed Redis.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/microsoft/azure-upgrade/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip azure-upgrade.zip -d .agents/skills

Documentation by Microsoft

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 IntentExample 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

  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

SourceTargetReference
Azure Functions Consumption Plan (Linux)Azure Functions Flex Consumption Planworkflow.md
Azure Functions Consumption Plan (Windows)Azure Functions Flex Consumption Planworkflow.md
Legacy Azure Java SDK (com.microsoft.azure.*)Modern Azure Java SDK (com.azure.*)languages/java/README.md
Azure Cache for Redis (ACR/OSS) Basic/Standard/PremiumAzure Managed Redis (AMR)services/redis/redis-to-amr.md
Azure Cache for Redis Enterprise (ACRE) / Enterprise FlashAzure Managed Redis (AMR)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

ToolPurpose
mcp_azure_mcp_get_azure_bestpracticesGet Azure best practices for the target service
mcp_azure_mcp_documentationLook up Azure documentation for upgrade scenarios
mcp_azure_mcp_appserviceQuery App Service and Functions plan details
mcp_azure_mcp_applicationinsightsVerify 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)

  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 for path resolution.

References

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

Files

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())

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Python

The author says: python3.10+

Security

What our scanner noticed· Low risk

  • Instructions that use the network
  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Microsoft
Publisher
Microsoft (Official)
Source
microsoft/skills · .github/plugins/azure-skills/skills/azure-upgrade · commit 0696600
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from .github/plugins/azure-skills/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Also distributed in
microsoft/azure-skills

AutoGineer is not affiliated with or endorsed by Microsoft or Microsoft.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download azure-upgrade.zip