From 4fcdc0924c80cfd6cf5edcf6b41133935cb55b58 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 12:45:51 +0800 Subject: [PATCH 01/21] Migrate Spring update workflows to azure-sdk-for-java --- .../workflows/test-spring-boot-rc-version.yml | 107 +++++++++ ...update-spring-cloud-azure-support-file.yml | 147 ++++++++++++ .../workflows/update-spring-dependencies.yml | 130 ++++++++++ ...enerate_spring_cloud_azure_support_file.py | 183 ++++++++++++++ ...rate_spring_versions_and_pr_description.py | 227 ++++++++++++++++++ 5 files changed, 794 insertions(+) create mode 100644 .github/workflows/test-spring-boot-rc-version.yml create mode 100644 .github/workflows/update-spring-cloud-azure-support-file.yml create mode 100644 .github/workflows/update-spring-dependencies.yml create mode 100644 sdk/spring/scripts/generate_spring_cloud_azure_support_file.py create mode 100644 sdk/spring/scripts/generate_spring_versions_and_pr_description.py diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml new file mode 100644 index 000000000000..2d4224a7bfaf --- /dev/null +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -0,0 +1,107 @@ +name: Test Spring Boot RC Version +on: + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Generate Version File + run: | + python ./sdk/spring/scripts/generate_spring_versions_and_pr_description.py + - name: Generate Spring Cloud Azure Support File + run: | + python ./sdk/spring/scripts/generate_spring_cloud_azure_support_file.py --include-rc + - name: Confirm Whether to Update + run: | + if [[ ! -f 'spring-versions.txt' ]]; then + echo "No new Spring Boot version, no updates!" + elif grep -q -- "-RC" spring-versions.txt; then + echo "Has RC version, create PR to test!" + mapfile -t versions < spring-versions.txt + { + echo "need_update_version=true" + echo "update_branch=update-spring-dependencies-$(date +%Y%m%d)-${GITHUB_RUN_ID}" + echo "spring_boot_version=${versions[0]}" + echo "spring_cloud_version=${versions[1]}" + echo "pr_descriptions=$(> $GITHUB_ENV + else + echo "No RC version, cancel update!" + fi + - name: Generate spring_boot_managed_external_dependencies.txt + if: ${{ env.need_update_version == 'true' }} + run: | + echo Updating Spring Boot Dependencies Version: ${{ env.spring_boot_version }} + echo Updating Spring Cloud Dependencies Version: ${{ env.spring_cloud_version }} + git checkout -b "${{ env.update_branch }}" + pip install termcolor + python ./sdk/spring/scripts/get_spring_boot_managed_external_dependencies.py -b ${{ env.spring_boot_version }} -c ${{ env.spring_cloud_version }} + - name: Update external_dependencies.txt + if: ${{ env.need_update_version == 'true' }} + run: | + pip install termcolor + pip install in_place + python ./sdk/spring/scripts/sync_external_dependencies.py -b ${{ env.spring_boot_version }} -sbmvn 4 + - name: Update Versions + if: ${{ env.need_update_version == 'true' }} + run: | + python ./eng/versioning/update_versions.py --sr + - name: Update ChangeLog + if: ${{ env.need_update_version == 'true' }} + run: | + python ./sdk/spring/scripts/update_changelog.py -b ${{ env.spring_boot_version }} -c ${{ env.spring_cloud_version }} + - name: Push Commit + if: ${{ env.need_update_version == 'true' }} + run: | + git config --global user.email github-actions@github.com + git config --global user.name github-actions + git add -A + git commit -m "Upgrade external dependencies to align with Spring Boot ${{ env.spring_boot_version }}" + sed -i "s/NONE_SUPPORTED_SPRING_CLOUD_VERSION/${spring_cloud_version}/g" ./sdk/spring/pipeline/spring-cloud-azure-supported-spring.json + git add ./sdk/spring/pipeline/spring-cloud-azure-supported-spring.json + git commit -m "Upgrade spring-cloud-azure-supported-spring" + git push origin "HEAD:${{ env.update_branch }}" + - name: Create Pull Request + id: create_pr + if: ${{ env.need_update_version == 'true' }} + uses: actions/github-script@v7 + with: + script: | + const body = `Test Spring Boot RC version [${process.env.spring_boot_version}](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/${process.env.spring_boot_version}/spring-boot-dependencies-${process.env.spring_boot_version}.pom) and Spring Cloud version [${process.env.spring_cloud_version}](https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/${process.env.spring_cloud_version}/spring-cloud-dependencies-${process.env.spring_cloud_version}.pom).\n${process.env.pr_descriptions}\n\nThis PR is created by GitHub Actions: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const pr = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: process.env.PR_TITLE, + head: process.env.update_branch, + base: 'main', + body, + draft: true + }); + core.setOutput('pull_request_number', String(pr.data.number)); + - name: Comment on Pull Request + if: ${{ env.need_update_version == 'true' }} + uses: actions/github-script@v7 + with: + script: | + const prNumber = Number('${{ steps.create_pr.outputs.pull_request_number }}'); + if (!prNumber) { + console.log('No pull request was created, nothing to comment on.'); + return; + } + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: '/azp run java - spring - tests' + }); diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml new file mode 100644 index 000000000000..da5d97ae9fc5 --- /dev/null +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -0,0 +1,147 @@ +name: Update Spring Cloud Azure Support File +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +env: + PR_TITLE: "Update Spring Boot and Spring Cloud versions for the Spring compatibility tests" + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + check-open-pr: + name: Check Open Pull Request + runs-on: ubuntu-latest + outputs: + has_open_pr: ${{ steps.check.outputs.has_open_pr }} + steps: + - uses: actions/checkout@v4 + - name: Check for Existing Open Pull Request + id: check + uses: actions/github-script@v7 + with: + script: | + const prTitle = process.env.PR_TITLE; + const { data: pullRequests } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open' + }); + + const openPRs = pullRequests.filter(pr => pr.title === prTitle); + core.setOutput('has_open_pr', openPRs.length > 0 ? 'true' : 'false'); + + update: + name: Update Support File and Create PR + needs: check-open-pr + if: ${{ needs.check-open-pr.outputs.has_open_pr == 'false' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Generate Spring Cloud Azure Support File + run: | + python ./sdk/spring/scripts/generate_spring_cloud_azure_support_file.py + - name: Set Branch Name with Timestamp + run: | + TIMESTAMP=$(date +%Y%m%d-%H%M%S) + GITHUB_ACTION_URL="https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}" + + echo "BRANCH_NAME=update-spring-cloud-azure-support-file-${TIMESTAMP}" >> $GITHUB_ENV + + echo "COMMIT_MESSAGE<> $GITHUB_ENV + echo "${PR_TITLE}." >> $GITHUB_ENV + echo "This commit is created by GitHub Action: ${GITHUB_ACTION_URL}" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + echo "PULL_REQUEST_BODY<> $GITHUB_ENV + echo "${PR_TITLE}." >> $GITHUB_ENV + echo "This commit is created by GitHub Action: ${GITHUB_ACTION_URL}" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + - name: Make Decision Based on Git Diff + run: | + git checkout -b "${{ env.BRANCH_NAME }}" + if [[ -n "$(git status -s)" ]]; then + echo "NEED_UPDATE_FILE=true" >> $GITHUB_ENV + else + echo "No file changes, no commits." + fi + - name: Update Spring Cloud Azure Timeline + if: ${{ env.NEED_UPDATE_FILE == 'true' }} + run: | + TODAY=$(date +%Y-%m-%d) + TIMELINE_FILE=docs/spring/Spring-Cloud-Azure-Timeline.md + SUPPORT_FILE=sdk/spring/pipeline/spring-cloud-azure-supported-spring.json + + OLD_4X=$(git show "HEAD:${SUPPORT_FILE}" | jq -Sc '[.[] | select(."spring-boot-version" | startswith("4."))] | sort_by([."spring-boot-version", ."spring-cloud-version", .supportStatus, .current])') + NEW_4X=$(jq -Sc '[.[] | select(."spring-boot-version" | startswith("4."))] | sort_by([."spring-boot-version", ."spring-cloud-version", .supportStatus, .current])' "${SUPPORT_FILE}") + + if [[ "${OLD_4X}" == "${NEW_4X}" ]]; then + echo "No Spring Boot 4.x changes detected, skip timeline update." + exit 0 + fi + + SUPPORTED_LINES=$(jq -r ' + .[] + | select(.supportStatus == "SUPPORTED") + | select(.["spring-boot-version"] | startswith("4.")) + | " - spring-boot-dependencies:\(.["spring-boot-version"]) and spring-cloud-dependencies:\(.["spring-cloud-version"])." + ' "${SUPPORT_FILE}") + + if [[ -z "${SUPPORTED_LINES}" ]]; then + echo "No supported Spring Boot 4.x entries found, skip timeline update." + exit 0 + fi + + NEW_ENTRY=$(printf ' - **%s**: In "java - spring - compatibility - tests" pipeline, run unit tests:\n%s' "$TODAY" "$SUPPORTED_LINES") + awk -v entry="$NEW_ENTRY" ' + { print } + /^## Timeline$/ && !inserted { print entry; inserted=1 } + ' "$TIMELINE_FILE" > "$TIMELINE_FILE.tmp" + mv "$TIMELINE_FILE.tmp" "$TIMELINE_FILE" + - name: Push Commit + if: ${{ env.NEED_UPDATE_FILE == 'true' }} + run: | + git config --global user.email github-actions@github.com + git config --global user.name github-actions + git add sdk/spring/pipeline/spring-cloud-azure-supported-spring.json + git add docs/spring/Spring-Cloud-Azure-Timeline.md + git commit -m "${{ env.COMMIT_MESSAGE }}" + git push origin "${{ env.BRANCH_NAME }}" + - name: Create Pull Request + id: create_pr + if: ${{ env.NEED_UPDATE_FILE == 'true' }} + uses: actions/github-script@v7 + with: + script: | + const pr = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: process.env.PR_TITLE, + head: process.env.BRANCH_NAME, + base: 'main', + body: process.env.PULL_REQUEST_BODY + }); + core.setOutput('pull_request_number', String(pr.data.number)); + - name: Comment on Pull Request + if: ${{ env.NEED_UPDATE_FILE == 'true' }} + uses: actions/github-script@v7 + with: + script: | + const prNumber = Number('${{ steps.create_pr.outputs.pull_request_number }}'); + if (!prNumber) { + console.log('No pull request was created, nothing to comment on.'); + return; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: '/azp run java - spring - tests' + }); diff --git a/.github/workflows/update-spring-dependencies.yml b/.github/workflows/update-spring-dependencies.yml new file mode 100644 index 000000000000..c043aff71b74 --- /dev/null +++ b/.github/workflows/update-spring-dependencies.yml @@ -0,0 +1,130 @@ +name: Update Spring Dependencies +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +env: + PR_TITLE_PREFIX: 'External dependencies upgrade - Spring Boot' + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + check: + name: Check for Open Spring Boot Upgrade PRs + runs-on: ubuntu-latest + outputs: + skip_pipeline: ${{ steps.check_prs.outputs.skip_pipeline }} + steps: + - uses: actions/checkout@v4 + - name: Check for Open Spring Boot Upgrade PRs + id: check_prs + uses: actions/github-script@v7 + with: + script: | + const prTitlePrefix = process.env.PR_TITLE_PREFIX; + const { data: pullRequests } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100 + }); + + const openPRs = pullRequests.filter(pr => pr.title.startsWith(prTitlePrefix)); + core.setOutput('skip_pipeline', openPRs.length > 0 ? 'true' : 'false'); + + update: + name: Update Dependencies and Create PR + runs-on: ubuntu-latest + needs: check + if: ${{ needs.check.outputs.skip_pipeline != 'true' }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Generate Version File + run: | + python ./sdk/spring/scripts/generate_spring_versions_and_pr_description.py + - name: Confirm Whether to Update + run: | + if [[ ! -f 'spring-versions.txt' ]]; then + echo "No new Spring Boot version, no updates." + elif grep -q -- '-' spring-versions.txt; then + echo "Has non-GA version, cancel update!" + else + echo "need_update_version=true" >> $GITHUB_ENV + echo "update_branch=update-spring-dependencies-$(date +%Y%m%d)-${GITHUB_RUN_ID}" >> $GITHUB_ENV + echo "spring_boot_version=$(sed -n '1p' spring-versions.txt)" >> $GITHUB_ENV + echo "spring_cloud_version=$(sed -n '2p' spring-versions.txt)" >> $GITHUB_ENV + echo "last_spring_boot_version=$(sed -n '3p' spring-versions.txt)" >> $GITHUB_ENV + echo "last_spring_cloud_version=$(sed -n '4p' spring-versions.txt)" >> $GITHUB_ENV + echo "pr_descriptions=$(cat pr-descriptions.txt)" >> $GITHUB_ENV + echo "PR_TITLE=${PR_TITLE_PREFIX} $(sed -n '1p' spring-versions.txt) and Spring Cloud $(sed -n '2p' spring-versions.txt)" >> $GITHUB_ENV + fi + - name: Generate spring_boot_managed_external_dependencies.txt + if: ${{ env.need_update_version == 'true' }} + run: | + echo Updating Spring Boot Dependencies Version: ${{ env.spring_boot_version }} + echo Updating Spring Cloud Dependencies Version: ${{ env.spring_cloud_version }} + git checkout -b "${{ env.update_branch }}" + pip install termcolor + python ./sdk/spring/scripts/get_spring_boot_managed_external_dependencies.py -b ${{ env.spring_boot_version }} -c ${{ env.spring_cloud_version }} + - name: Update external_dependencies.txt + if: ${{ env.need_update_version == 'true' }} + run: | + pip install termcolor + pip install in_place + python ./sdk/spring/scripts/sync_external_dependencies.py -b ${{ env.spring_boot_version }} -sbmvn 4 + - name: Update Versions + if: ${{ env.need_update_version == 'true' }} + run: | + python ./eng/versioning/update_versions.py --sr + - name: Update ChangeLog + if: ${{ env.need_update_version == 'true' }} + run: | + python ./sdk/spring/scripts/update_changelog.py -b ${{ env.spring_boot_version }} -c ${{ env.spring_cloud_version }} + - name: Push Commit + if: ${{ env.need_update_version == 'true' }} + run: | + git config --global user.email github-actions@github.com + git config --global user.name github-actions + git rm ./sdk/spring/scripts/spring_boot_${{ env.last_spring_boot_version }}_managed_external_dependencies.txt || true + git add -A + git commit -m "Upgrade external dependencies to align with Spring Boot ${{ env.spring_boot_version }}" + git push origin "HEAD:${{ env.update_branch }}" + - name: Create Pull Request + id: create_pr + if: ${{ env.need_update_version == 'true' }} + uses: actions/github-script@v7 + with: + script: | + const body = `Updates external dependencies to align with Spring Boot version [${process.env.spring_boot_version}](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/${process.env.spring_boot_version}/spring-boot-dependencies-${process.env.spring_boot_version}.pom) from [${process.env.last_spring_boot_version}](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/${process.env.last_spring_boot_version}/spring-boot-dependencies-${process.env.last_spring_boot_version}.pom) and Spring Cloud version [${process.env.spring_cloud_version}](https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/${process.env.spring_cloud_version}/spring-cloud-dependencies-${process.env.spring_cloud_version}.pom) from [${process.env.last_spring_cloud_version}](https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/${process.env.last_spring_cloud_version}/spring-cloud-dependencies-${process.env.last_spring_cloud_version}.pom).\n${process.env.pr_descriptions}\n\nThis PR is created by GitHub Actions: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const pr = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: process.env.PR_TITLE, + head: process.env.update_branch, + base: 'main', + body, + draft: false + }); + core.setOutput('pull_request_number', String(pr.data.number)); + - name: Comment on Pull Request + if: ${{ env.need_update_version == 'true' }} + uses: actions/github-script@v7 + with: + script: | + const prNumber = Number('${{ steps.create_pr.outputs.pull_request_number }}'); + if (!prNumber) { + console.log('No pull request was created, nothing to comment on.'); + return; + } + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: '/azp run java - spring - tests' + }); diff --git a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py new file mode 100644 index 000000000000..2837a6108754 --- /dev/null +++ b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +"""Generate sdk/spring/pipeline/spring-cloud-azure-supported-spring.json.""" + +import argparse +import json +import os +import re +import urllib.request + +SPRING_METADATA_URL = "https://spring.io/project_metadata/spring-boot" +SPRING_INITIALIZR_INFO_URL = "https://start.spring.io/actuator/info" +SUPPORT_FILE = "sdk/spring/pipeline/spring-cloud-azure-supported-spring.json" +NONE_SUPPORTED = "NONE_SUPPORTED_SPRING_CLOUD_VERSION" + + +def fetch_json(url): + req = urllib.request.Request(url, headers={"User-Agent": "spring-cloud-azure-tools-migration"}) + with urllib.request.urlopen(req, timeout=30) as resp: + return json.loads(resp.read().decode("utf-8")) + + +def tokenize_version(version): + tokens = [] + for part in re.split(r"[.\-]", version): + if part.isdigit(): + tokens.append((0, int(part))) + else: + upper = part.upper() + if upper.startswith("SNAPSHOT"): + tokens.append((1, -3)) + elif upper.startswith("M") and upper[1:].isdigit(): + tokens.append((1, -2, int(upper[1:]))) + elif upper.startswith("RC") and upper[2:].isdigit(): + tokens.append((1, -1, int(upper[2:]))) + else: + tokens.append((1, upper)) + return tokens + + +def compare_versions(a, b): + ta = tokenize_version(a) + tb = tokenize_version(b) + max_len = max(len(ta), len(tb)) + for i in range(max_len): + va = ta[i] if i < len(ta) else (0, 0) + vb = tb[i] if i < len(tb) else (0, 0) + if va == vb: + continue + return -1 if va < vb else 1 + return 0 + + +def parse_range_expression(expr): + rules = [] + for token in expr.split(): + m = re.match(r"^([><])(=*)(.*)$", token) + if not m: + continue + op = m.group(1) + inclusive = m.group(2) == "=" + version = m.group(3) + rules.append((op, inclusive, version)) + if not rules: + raise RuntimeError("Cannot parse Spring Initializr range: {}".format(expr)) + return rules + + +def in_range(version, rules): + for op, inclusive, bound in rules: + cmp_result = compare_versions(version, bound) + if op == ">": + if cmp_result < 0 or (cmp_result == 0 and not inclusive): + return False + elif op == "<": + if cmp_result > 0 or (cmp_result == 0 and not inclusive): + return False + return True + + +def find_compatible_spring_cloud_version(spring_boot_version, spring_cloud_ranges): + for cloud_version, expr in spring_cloud_ranges.items(): + if in_range(spring_boot_version, parse_range_expression(expr)): + return cloud_version + return NONE_SUPPORTED + + +def is_snapshot_milestone_or_rc(version, include_rc): + if version is None: + return False + has_snapshot_or_milestone = "SNAPSHOT" in version or "M" in version + has_rc = "RC" in version + return has_snapshot_or_milestone or (has_rc and not include_rc) + + +def is_version_supported(version): + return compare_versions(version, "3.5.0") >= 0 + + +def load_existing_support_map(): + with open(SUPPORT_FILE, "r", encoding="utf-8") as f: + existing = json.load(f) + return {item.get("spring-boot-version"): item for item in existing if item.get("spring-boot-version")} + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--include-rc", action="store_true", help="Include RC versions in generated output") + args = parser.parse_args() + + spring_metadata = fetch_json(SPRING_METADATA_URL) + releases = spring_metadata.get("projectReleases", []) + + initializr_info = fetch_json(SPRING_INITIALIZR_INFO_URL) + spring_cloud_ranges = ( + initializr_info.get("bom-ranges", {}).get("spring-cloud", {}) + or initializr_info.get("build", {}).get("bom-ranges", {}).get("spring-cloud", {}) + or initializr_info.get("build", {}).get("versions", {}).get("spring-cloud", {}) + or initializr_info.get("serviceCapabilities", {}).get("bom", {}).get("spring-cloud", {}) + or initializr_info.get("serviceBom", {}).get("spring-cloud", {}) + ) + if not spring_cloud_ranges: + raise RuntimeError("Cannot locate spring-cloud compatibility map in Spring Initializr response") + + existing_map = load_existing_support_map() + active_versions = set() + current_items = [] + + for release in releases: + version = release.get("version") + if not version: + continue + if not is_version_supported(version): + continue + if is_snapshot_milestone_or_rc(version, args.include_rc): + continue + + existing = existing_map.get(version, {}) + support_status = existing.get("supportStatus") + if support_status is None: + if release.get("releaseStatus") in ("GENERAL_AVAILABILITY", "PRERELEASE"): + support_status = "SUPPORTED" + else: + support_status = "TODO" + + cloud_version = existing.get("spring-cloud-version") + if existing.get("supportStatus") != "END_OF_LIFE": + cloud_version = find_compatible_spring_cloud_version(version, spring_cloud_ranges) + + current_items.append( + { + "current": bool(release.get("current", False)), + "releaseStatus": release.get("releaseStatus"), + "snapshot": bool(release.get("snapshot", False)), + "supportStatus": support_status, + "spring-boot-version": version, + "spring-cloud-version": cloud_version, + } + ) + active_versions.add(version) + + snapshot_items = [] + for version, metadata in existing_map.items(): + if version in active_versions: + continue + if is_snapshot_milestone_or_rc(version, args.include_rc): + continue + cloned = dict(metadata) + cloned["current"] = False + if version != "2.7.18": + cloned["supportStatus"] = "END_OF_LIFE" + snapshot_items.append(cloned) + + result = current_items + snapshot_items + result.sort(key=lambda item: tokenize_version(item.get("spring-boot-version", "0")), reverse=True) + + os.makedirs(os.path.dirname(SUPPORT_FILE), exist_ok=True) + with open(SUPPORT_FILE, "w", encoding="utf-8") as f: + json.dump(result, f, indent=2) + f.write("\n") + + +if __name__ == "__main__": + main() diff --git a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py new file mode 100644 index 000000000000..e87a4d36db18 --- /dev/null +++ b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +"""Generate spring-versions.txt and pr-descriptions.txt for Spring dependency update workflows.""" + +import argparse +import html +import json +import os +import re +import urllib.error +import urllib.request + +SPRING_METADATA_URL = "https://spring.io/project_metadata/spring-boot" +SPRING_INITIALIZR_INFO_URL = "https://start.spring.io/actuator/info" +SPRING_BOOT_RELEASE_TAG_URL = "https://github.com/spring-projects/spring-boot/releases/tag/v{}" +SPRING_BOOT_RELEASE_API_URL = "https://api.github.com/repos/spring-projects/spring-boot/releases/tags/v{}" +EXTERNAL_DEPENDENCIES_FILE = "eng/versioning/external_dependencies.txt" +SPRING_VERSIONS_OUTPUT = "spring-versions.txt" +PR_DESCRIPTIONS_OUTPUT = "pr-descriptions.txt" + + +def fetch_json(url): + req = urllib.request.Request(url, headers={"User-Agent": "spring-cloud-azure-tools-migration"}) + with urllib.request.urlopen(req, timeout=30) as resp: + return json.loads(resp.read().decode("utf-8")) + + +def tokenize_version(version): + tokens = [] + for part in re.split(r"[.\-]", version): + if part.isdigit(): + tokens.append((0, int(part))) + else: + upper = part.upper() + if upper.startswith("SNAPSHOT"): + tokens.append((1, -3)) + elif upper.startswith("M") and upper[1:].isdigit(): + tokens.append((1, -2, int(upper[1:]))) + elif upper.startswith("RC") and upper[2:].isdigit(): + tokens.append((1, -1, int(upper[2:]))) + else: + tokens.append((1, upper)) + return tokens + + +def compare_versions(a, b): + ta = tokenize_version(a) + tb = tokenize_version(b) + max_len = max(len(ta), len(tb)) + for i in range(max_len): + va = ta[i] if i < len(ta) else (0, 0) + vb = tb[i] if i < len(tb) else (0, 0) + if va == vb: + continue + return -1 if va < vb else 1 + return 0 + + +def sort_versions_desc(versions): + return sorted(versions, key=lambda v: tokenize_version(v), reverse=True) + + +def read_current_supported_versions(): + boot = None + cloud = None + with open(EXTERNAL_DEPENDENCIES_FILE, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if line.startswith("org.springframework.boot:spring-boot-dependencies;"): + boot = line.split(";", 1)[1] + elif line.startswith("org.springframework.cloud:spring-cloud-dependencies;"): + cloud = line.split(";", 1)[1] + if not boot or not cloud: + raise RuntimeError("Failed to read current Spring Boot/Cloud versions from external_dependencies.txt") + return boot, cloud + + +def parse_range_expression(expr): + rules = [] + for token in expr.split(): + m = re.match(r"^([><])(=*)(.*)$", token) + if not m: + continue + op = m.group(1) + inclusive = m.group(2) == "=" + version = m.group(3) + rules.append((op, inclusive, version)) + if not rules: + raise RuntimeError("Cannot parse Spring Initializr range: {}".format(expr)) + return rules + + +def in_range(version, rules): + for op, inclusive, bound in rules: + cmp_result = compare_versions(version, bound) + if op == ">": + if cmp_result < 0 or (cmp_result == 0 and not inclusive): + return False + elif op == "<": + if cmp_result > 0 or (cmp_result == 0 and not inclusive): + return False + return True + + +def find_compatible_spring_cloud_version(spring_boot_version, spring_cloud_ranges): + for cloud_version, expr in spring_cloud_ranges.items(): + if in_range(spring_boot_version, parse_range_expression(expr)): + return cloud_version + raise RuntimeError( + "No compatible spring-cloud version found for spring-boot {}".format(spring_boot_version) + ) + + +def release_notes_html(version): + release_url = SPRING_BOOT_RELEASE_TAG_URL.format(version) + try: + body = fetch_json(SPRING_BOOT_RELEASE_API_URL.format(version)).get("body", "") + except (urllib.error.HTTPError, urllib.error.URLError): + body = "" + + if body: + body = body.replace("\r\n", "\n") + body = re.split(r"\n##\s+.*Contributors.*", body, maxsplit=1, flags=re.IGNORECASE)[0] + body = html.escape(body).replace("\n", "
") + else: + body = "Release notes unavailable from GitHub API." + + return ( + "
Release notes" + "

Sourced from spring-boot releases.

" + "{}" + "
".format(release_url, body) + ) + + +def write_outputs(target_boot, target_cloud, current_boot, current_cloud, notes): + with open(SPRING_VERSIONS_OUTPUT, "w", encoding="utf-8") as f: + f.write(target_boot + "\n") + f.write(target_cloud + "\n") + f.write(current_boot + "\n") + f.write(current_cloud + "\n") + + with open(PR_DESCRIPTIONS_OUTPUT, "w", encoding="utf-8") as f: + f.write(notes) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--target-major", default="4", help="Target Spring Boot major version.") + args = parser.parse_args() + + spring_metadata = fetch_json(SPRING_METADATA_URL) + releases = spring_metadata.get("projectReleases", []) + ga_versions = [ + r.get("version") for r in releases + if r.get("releaseStatus") == "GENERAL_AVAILABILITY" and r.get("version", "").startswith(args.target_major) + ] + rc_versions = [ + r.get("version") for r in releases + if r.get("releaseStatus") == "PRERELEASE" and r.get("version", "").startswith(args.target_major) + ] + ga_versions = [v for v in ga_versions if v] + rc_versions = [v for v in rc_versions if v] + + if not ga_versions and not rc_versions: + return + + latest_ga = sort_versions_desc(ga_versions)[0] if ga_versions else None + latest_rc = sort_versions_desc(rc_versions)[0] if rc_versions else None + + current_boot, current_cloud = read_current_supported_versions() + is_new_ga = latest_ga is not None and compare_versions(latest_ga, current_boot) != 0 + is_new_rc = latest_rc is not None and compare_versions(latest_rc, current_boot) != 0 + + if not is_new_ga and not is_new_rc: + return + + initializr_info = fetch_json(SPRING_INITIALIZR_INFO_URL) + spring_cloud_ranges = ( + initializr_info + .get("bom-ranges", {}) + .get("spring-cloud", {}) + ) + if not spring_cloud_ranges: + spring_cloud_ranges = ( + initializr_info + .get("build", {}) + .get("bom-ranges", {}) + .get("spring-cloud", {}) + ) + if not spring_cloud_ranges: + spring_cloud_ranges = ( + initializr_info + .get("build", {}) + .get("versions", {}) + .get("spring-cloud", {}) + ) + if not spring_cloud_ranges: + spring_cloud_ranges = ( + initializr_info + .get("serviceCapabilities", {}) + .get("bom", {}) + .get("spring-cloud", {}) + ) + if not spring_cloud_ranges: + spring_cloud_ranges = ( + initializr_info + .get("serviceBom", {}) + .get("spring-cloud", {}) + ) + + if not spring_cloud_ranges: + raise RuntimeError("Cannot locate spring-cloud compatibility map in Spring Initializr response") + + target_boot = latest_ga if is_new_ga else latest_rc + if is_new_ga: + target_cloud = find_compatible_spring_cloud_version(target_boot, spring_cloud_ranges) + else: + try: + target_cloud = find_compatible_spring_cloud_version(target_boot, spring_cloud_ranges) + except RuntimeError: + target_cloud = find_compatible_spring_cloud_version(latest_ga, spring_cloud_ranges) + + write_outputs(target_boot, target_cloud, current_boot, current_cloud, release_notes_html(target_boot)) + + +if __name__ == "__main__": + main() From cd0a5a23b1914c1d31703cb002fe0d5560e574c8 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 12:53:02 +0800 Subject: [PATCH 02/21] Enable PR path-based triggers for migrated workflows --- .github/workflows/test-spring-boot-rc-version.yml | 8 ++++++++ .../workflows/update-spring-cloud-azure-support-file.yml | 6 ++++++ .github/workflows/update-spring-dependencies.yml | 8 ++++++++ 3 files changed, 22 insertions(+) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index 2d4224a7bfaf..430e61f6ddf6 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -1,5 +1,13 @@ name: Test Spring Boot RC Version on: + pull_request: + branches: + - main + paths: + - '.github/workflows/test-spring-boot-rc-version.yml' + - 'sdk/spring/scripts/generate_spring_versions_and_pr_description.py' + - 'sdk/spring/scripts/generate_spring_cloud_azure_support_file.py' + - 'sdk/spring/scripts/**' workflow_dispatch: permissions: diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml index da5d97ae9fc5..30a2fcdb83cd 100644 --- a/.github/workflows/update-spring-cloud-azure-support-file.yml +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -2,6 +2,12 @@ name: Update Spring Cloud Azure Support File on: schedule: - cron: '0 0 * * *' + pull_request: + branches: + - main + paths: + - '.github/workflows/update-spring-cloud-azure-support-file.yml' + - 'sdk/spring/scripts/generate_spring_cloud_azure_support_file.py' workflow_dispatch: env: diff --git a/.github/workflows/update-spring-dependencies.yml b/.github/workflows/update-spring-dependencies.yml index c043aff71b74..0c2eaf965a20 100644 --- a/.github/workflows/update-spring-dependencies.yml +++ b/.github/workflows/update-spring-dependencies.yml @@ -2,6 +2,14 @@ name: Update Spring Dependencies on: schedule: - cron: '0 0 * * *' + pull_request: + branches: + - main + paths: + - '.github/workflows/update-spring-dependencies.yml' + - 'sdk/spring/scripts/generate_spring_versions_and_pr_description.py' + - 'sdk/spring/scripts/generate_spring_cloud_azure_support_file.py' + - 'sdk/spring/scripts/**' workflow_dispatch: env: From 4debc5fc1bf2ccb4dd0bde82550cce00378af1e1 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 12:56:14 +0800 Subject: [PATCH 03/21] Address PR review feedback for Spring workflow migration --- .github/workflows/update-spring-cloud-azure-support-file.yml | 5 +++-- .../scripts/generate_spring_cloud_azure_support_file.py | 3 +++ .../scripts/generate_spring_versions_and_pr_description.py | 1 - 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml index 30a2fcdb83cd..36e14a6156a2 100644 --- a/.github/workflows/update-spring-cloud-azure-support-file.yml +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -32,10 +32,11 @@ jobs: with: script: | const prTitle = process.env.PR_TITLE; - const { data: pullRequests } = await github.rest.pulls.list({ + const pullRequests = await github.paginate(github.rest.pulls.list, { owner: context.repo.owner, repo: context.repo.repo, - state: 'open' + state: 'open', + per_page: 100 }); const openPRs = pullRequests.filter(pr => pr.title === prTitle); diff --git a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py index 2837a6108754..666dd231deff 100644 --- a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py +++ b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py @@ -145,6 +145,9 @@ def main(): cloud_version = existing.get("spring-cloud-version") if existing.get("supportStatus") != "END_OF_LIFE": cloud_version = find_compatible_spring_cloud_version(version, spring_cloud_ranges) + if cloud_version == NONE_SUPPORTED and support_status == "SUPPORTED": + # Keep matrix entries non-supported when Initializr does not provide a compatible Spring Cloud BOM. + support_status = "TODO" current_items.append( { diff --git a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py index e87a4d36db18..40b6c2646b73 100644 --- a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py +++ b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py @@ -4,7 +4,6 @@ import argparse import html import json -import os import re import urllib.error import urllib.request From 8cdc57ff6f4007ad7a16c2fc1b389ef3ad2a035b Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 12:58:30 +0800 Subject: [PATCH 04/21] Fix Spring version source detection in generator --- ...rate_spring_versions_and_pr_description.py | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py index 40b6c2646b73..c98ecba373b2 100644 --- a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py +++ b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py @@ -13,6 +13,7 @@ SPRING_BOOT_RELEASE_TAG_URL = "https://github.com/spring-projects/spring-boot/releases/tag/v{}" SPRING_BOOT_RELEASE_API_URL = "https://api.github.com/repos/spring-projects/spring-boot/releases/tags/v{}" EXTERNAL_DEPENDENCIES_FILE = "eng/versioning/external_dependencies.txt" +SUPPORT_MATRIX_FILE = "sdk/spring/pipeline/spring-cloud-azure-supported-spring.json" SPRING_VERSIONS_OUTPUT = "spring-versions.txt" PR_DESCRIPTIONS_OUTPUT = "pr-descriptions.txt" @@ -59,17 +60,47 @@ def sort_versions_desc(versions): def read_current_supported_versions(): + # Prefer the support matrix because it reflects the current Spring compatibility target. boot = None cloud = None + try: + with open(SUPPORT_MATRIX_FILE, "r", encoding="utf-8") as f: + entries = json.load(f) + current_entries = [ + e for e in entries + if e.get("current") and e.get("supportStatus") == "SUPPORTED" + ] + if current_entries: + current = current_entries[0] + boot = current.get("spring-boot-version") + cloud = current.get("spring-cloud-version") + except (FileNotFoundError, json.JSONDecodeError, TypeError): + pass + + if boot and cloud: + return boot, cloud + + # Fallback to external dependencies for compatibility with older layouts. + boot_candidates = { + "org.springframework.boot:spring-boot-dependencies", + "org.springframework.boot:spring-boot-starter-parent", + "org.springframework.boot:spring-boot-starter", + "org.springframework.boot:spring-boot-maven-plugin", + } with open(EXTERNAL_DEPENDENCIES_FILE, "r", encoding="utf-8") as f: for line in f: line = line.strip() - if line.startswith("org.springframework.boot:spring-boot-dependencies;"): - boot = line.split(";", 1)[1] - elif line.startswith("org.springframework.cloud:spring-cloud-dependencies;"): - cloud = line.split(";", 1)[1] + if not line or line.startswith("#") or ";" not in line: + continue + artifact, version = line.split(";", 1) + if artifact in boot_candidates and not boot: + boot = version + elif artifact == "org.springframework.cloud:spring-cloud-dependencies" and not cloud: + cloud = version if not boot or not cloud: - raise RuntimeError("Failed to read current Spring Boot/Cloud versions from external_dependencies.txt") + raise RuntimeError( + "Failed to read current Spring Boot/Cloud versions from support matrix and external_dependencies.txt" + ) return boot, cloud From 3ad40646806bc695caad38969b617a0902d0f925 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:01:04 +0800 Subject: [PATCH 05/21] Fix shellcheck quoting in Spring workflows --- .../workflows/test-spring-boot-rc-version.yml | 24 ++++++++-------- ...update-spring-cloud-azure-support-file.yml | 24 ++++++++-------- .../workflows/update-spring-dependencies.yml | 28 ++++++++++--------- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index 430e61f6ddf6..2d740b60ec8f 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -38,29 +38,29 @@ jobs: mapfile -t versions < spring-versions.txt { echo "need_update_version=true" - echo "update_branch=update-spring-dependencies-$(date +%Y%m%d)-${GITHUB_RUN_ID}" - echo "spring_boot_version=${versions[0]}" - echo "spring_cloud_version=${versions[1]}" - echo "pr_descriptions=$(> $GITHUB_ENV + printf 'update_branch=update-spring-dependencies-%s-%s\n' "$(date +%Y%m%d)" "${GITHUB_RUN_ID}" + printf 'spring_boot_version=%s\n' "${versions[0]}" + printf 'spring_cloud_version=%s\n' "${versions[1]}" + printf 'pr_descriptions=%s\n' "$(> "$GITHUB_ENV" else echo "No RC version, cancel update!" fi - name: Generate spring_boot_managed_external_dependencies.txt if: ${{ env.need_update_version == 'true' }} run: | - echo Updating Spring Boot Dependencies Version: ${{ env.spring_boot_version }} - echo Updating Spring Cloud Dependencies Version: ${{ env.spring_cloud_version }} + echo "Updating Spring Boot Dependencies Version: ${{ env.spring_boot_version }}" + echo "Updating Spring Cloud Dependencies Version: ${{ env.spring_cloud_version }}" git checkout -b "${{ env.update_branch }}" pip install termcolor - python ./sdk/spring/scripts/get_spring_boot_managed_external_dependencies.py -b ${{ env.spring_boot_version }} -c ${{ env.spring_cloud_version }} + python ./sdk/spring/scripts/get_spring_boot_managed_external_dependencies.py -b "${{ env.spring_boot_version }}" -c "${{ env.spring_cloud_version }}" - name: Update external_dependencies.txt if: ${{ env.need_update_version == 'true' }} run: | pip install termcolor pip install in_place - python ./sdk/spring/scripts/sync_external_dependencies.py -b ${{ env.spring_boot_version }} -sbmvn 4 + python ./sdk/spring/scripts/sync_external_dependencies.py -b "${{ env.spring_boot_version }}" -sbmvn 4 - name: Update Versions if: ${{ env.need_update_version == 'true' }} run: | @@ -68,7 +68,7 @@ jobs: - name: Update ChangeLog if: ${{ env.need_update_version == 'true' }} run: | - python ./sdk/spring/scripts/update_changelog.py -b ${{ env.spring_boot_version }} -c ${{ env.spring_cloud_version }} + python ./sdk/spring/scripts/update_changelog.py -b "${{ env.spring_boot_version }}" -c "${{ env.spring_cloud_version }}" - name: Push Commit if: ${{ env.need_update_version == 'true' }} run: | @@ -76,7 +76,7 @@ jobs: git config --global user.name github-actions git add -A git commit -m "Upgrade external dependencies to align with Spring Boot ${{ env.spring_boot_version }}" - sed -i "s/NONE_SUPPORTED_SPRING_CLOUD_VERSION/${spring_cloud_version}/g" ./sdk/spring/pipeline/spring-cloud-azure-supported-spring.json + sed -i "s/NONE_SUPPORTED_SPRING_CLOUD_VERSION/${spring_cloud_version}/g" "./sdk/spring/pipeline/spring-cloud-azure-supported-spring.json" git add ./sdk/spring/pipeline/spring-cloud-azure-supported-spring.json git commit -m "Upgrade spring-cloud-azure-supported-spring" git push origin "HEAD:${{ env.update_branch }}" diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml index 36e14a6156a2..0560b3cc6340 100644 --- a/.github/workflows/update-spring-cloud-azure-support-file.yml +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -59,22 +59,22 @@ jobs: TIMESTAMP=$(date +%Y%m%d-%H%M%S) GITHUB_ACTION_URL="https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}" - echo "BRANCH_NAME=update-spring-cloud-azure-support-file-${TIMESTAMP}" >> $GITHUB_ENV - - echo "COMMIT_MESSAGE<> $GITHUB_ENV - echo "${PR_TITLE}." >> $GITHUB_ENV - echo "This commit is created by GitHub Action: ${GITHUB_ACTION_URL}" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - echo "PULL_REQUEST_BODY<> $GITHUB_ENV - echo "${PR_TITLE}." >> $GITHUB_ENV - echo "This commit is created by GitHub Action: ${GITHUB_ACTION_URL}" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV + { + echo "BRANCH_NAME=update-spring-cloud-azure-support-file-${TIMESTAMP}" + echo "COMMIT_MESSAGE<> "$GITHUB_ENV" - name: Make Decision Based on Git Diff run: | git checkout -b "${{ env.BRANCH_NAME }}" if [[ -n "$(git status -s)" ]]; then - echo "NEED_UPDATE_FILE=true" >> $GITHUB_ENV + echo "NEED_UPDATE_FILE=true" >> "$GITHUB_ENV" else echo "No file changes, no commits." fi diff --git a/.github/workflows/update-spring-dependencies.yml b/.github/workflows/update-spring-dependencies.yml index 0c2eaf965a20..7bb430712b2c 100644 --- a/.github/workflows/update-spring-dependencies.yml +++ b/.github/workflows/update-spring-dependencies.yml @@ -63,29 +63,31 @@ jobs: elif grep -q -- '-' spring-versions.txt; then echo "Has non-GA version, cancel update!" else - echo "need_update_version=true" >> $GITHUB_ENV - echo "update_branch=update-spring-dependencies-$(date +%Y%m%d)-${GITHUB_RUN_ID}" >> $GITHUB_ENV - echo "spring_boot_version=$(sed -n '1p' spring-versions.txt)" >> $GITHUB_ENV - echo "spring_cloud_version=$(sed -n '2p' spring-versions.txt)" >> $GITHUB_ENV - echo "last_spring_boot_version=$(sed -n '3p' spring-versions.txt)" >> $GITHUB_ENV - echo "last_spring_cloud_version=$(sed -n '4p' spring-versions.txt)" >> $GITHUB_ENV - echo "pr_descriptions=$(cat pr-descriptions.txt)" >> $GITHUB_ENV - echo "PR_TITLE=${PR_TITLE_PREFIX} $(sed -n '1p' spring-versions.txt) and Spring Cloud $(sed -n '2p' spring-versions.txt)" >> $GITHUB_ENV + { + echo "need_update_version=true" + printf 'update_branch=update-spring-dependencies-%s-%s\n' "$(date +%Y%m%d)" "${GITHUB_RUN_ID}" + printf 'spring_boot_version=%s\n' "$(sed -n '1p' spring-versions.txt)" + printf 'spring_cloud_version=%s\n' "$(sed -n '2p' spring-versions.txt)" + printf 'last_spring_boot_version=%s\n' "$(sed -n '3p' spring-versions.txt)" + printf 'last_spring_cloud_version=%s\n' "$(sed -n '4p' spring-versions.txt)" + printf 'pr_descriptions=%s\n' "$(cat pr-descriptions.txt)" + printf 'PR_TITLE=%s %s and Spring Cloud %s\n' "${PR_TITLE_PREFIX}" "$(sed -n '1p' spring-versions.txt)" "$(sed -n '2p' spring-versions.txt)" + } >> "$GITHUB_ENV" fi - name: Generate spring_boot_managed_external_dependencies.txt if: ${{ env.need_update_version == 'true' }} run: | - echo Updating Spring Boot Dependencies Version: ${{ env.spring_boot_version }} - echo Updating Spring Cloud Dependencies Version: ${{ env.spring_cloud_version }} + echo "Updating Spring Boot Dependencies Version: ${{ env.spring_boot_version }}" + echo "Updating Spring Cloud Dependencies Version: ${{ env.spring_cloud_version }}" git checkout -b "${{ env.update_branch }}" pip install termcolor - python ./sdk/spring/scripts/get_spring_boot_managed_external_dependencies.py -b ${{ env.spring_boot_version }} -c ${{ env.spring_cloud_version }} + python ./sdk/spring/scripts/get_spring_boot_managed_external_dependencies.py -b "${{ env.spring_boot_version }}" -c "${{ env.spring_cloud_version }}" - name: Update external_dependencies.txt if: ${{ env.need_update_version == 'true' }} run: | pip install termcolor pip install in_place - python ./sdk/spring/scripts/sync_external_dependencies.py -b ${{ env.spring_boot_version }} -sbmvn 4 + python ./sdk/spring/scripts/sync_external_dependencies.py -b "${{ env.spring_boot_version }}" -sbmvn 4 - name: Update Versions if: ${{ env.need_update_version == 'true' }} run: | @@ -93,7 +95,7 @@ jobs: - name: Update ChangeLog if: ${{ env.need_update_version == 'true' }} run: | - python ./sdk/spring/scripts/update_changelog.py -b ${{ env.spring_boot_version }} -c ${{ env.spring_cloud_version }} + python ./sdk/spring/scripts/update_changelog.py -b "${{ env.spring_boot_version }}" -c "${{ env.spring_cloud_version }}" - name: Push Commit if: ${{ env.need_update_version == 'true' }} run: | From b28564b95aeefd3aa9a451317d0ecdac898c3392 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:03:18 +0800 Subject: [PATCH 06/21] Harden workflow triggers and version comparison --- .../workflows/test-spring-boot-rc-version.yml | 8 --- ...update-spring-cloud-azure-support-file.yml | 6 -- .../workflows/update-spring-dependencies.yml | 10 +-- ...enerate_spring_cloud_azure_support_file.py | 63 ++++++++++--------- ...rate_spring_versions_and_pr_description.py | 63 ++++++++++--------- 5 files changed, 69 insertions(+), 81 deletions(-) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index 2d740b60ec8f..688d8b21be50 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -1,13 +1,5 @@ name: Test Spring Boot RC Version on: - pull_request: - branches: - - main - paths: - - '.github/workflows/test-spring-boot-rc-version.yml' - - 'sdk/spring/scripts/generate_spring_versions_and_pr_description.py' - - 'sdk/spring/scripts/generate_spring_cloud_azure_support_file.py' - - 'sdk/spring/scripts/**' workflow_dispatch: permissions: diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml index 0560b3cc6340..97da7e694d4f 100644 --- a/.github/workflows/update-spring-cloud-azure-support-file.yml +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -2,12 +2,6 @@ name: Update Spring Cloud Azure Support File on: schedule: - cron: '0 0 * * *' - pull_request: - branches: - - main - paths: - - '.github/workflows/update-spring-cloud-azure-support-file.yml' - - 'sdk/spring/scripts/generate_spring_cloud_azure_support_file.py' workflow_dispatch: env: diff --git a/.github/workflows/update-spring-dependencies.yml b/.github/workflows/update-spring-dependencies.yml index 7bb430712b2c..ae775d56d199 100644 --- a/.github/workflows/update-spring-dependencies.yml +++ b/.github/workflows/update-spring-dependencies.yml @@ -2,14 +2,6 @@ name: Update Spring Dependencies on: schedule: - cron: '0 0 * * *' - pull_request: - branches: - - main - paths: - - '.github/workflows/update-spring-dependencies.yml' - - 'sdk/spring/scripts/generate_spring_versions_and_pr_description.py' - - 'sdk/spring/scripts/generate_spring_cloud_azure_support_file.py' - - 'sdk/spring/scripts/**' workflow_dispatch: env: @@ -34,7 +26,7 @@ jobs: with: script: | const prTitlePrefix = process.env.PR_TITLE_PREFIX; - const { data: pullRequests } = await github.rest.pulls.list({ + const pullRequests = await github.paginate(github.rest.pulls.list, { owner: context.repo.owner, repo: context.repo.repo, state: 'open', diff --git a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py index 666dd231deff..981363cc2918 100644 --- a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py +++ b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py @@ -13,41 +13,46 @@ NONE_SUPPORTED = "NONE_SUPPORTED_SPRING_CLOUD_VERSION" +def version_key(version): + match = re.match(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:[-.]?([A-Za-z]+)(\d*)?)?$", version) + if not match: + return (0, 0, 0, 0, 0, version) + + major = int(match.group(1) or 0) + minor = int(match.group(2) or 0) + patch = int(match.group(3) or 0) + qualifier = (match.group(4) or "").upper() + qualifier_num = int(match.group(5) or 0) + + # Higher rank means newer release status for the same base version. + if not qualifier: + qualifier_rank = 3 # GA + elif qualifier.startswith("RC"): + qualifier_rank = 2 + elif qualifier.startswith("M"): + qualifier_rank = 1 + elif qualifier.startswith("SNAPSHOT"): + qualifier_rank = 0 + else: + qualifier_rank = 0 + + return (major, minor, patch, qualifier_rank, qualifier_num, qualifier) + + def fetch_json(url): req = urllib.request.Request(url, headers={"User-Agent": "spring-cloud-azure-tools-migration"}) with urllib.request.urlopen(req, timeout=30) as resp: return json.loads(resp.read().decode("utf-8")) -def tokenize_version(version): - tokens = [] - for part in re.split(r"[.\-]", version): - if part.isdigit(): - tokens.append((0, int(part))) - else: - upper = part.upper() - if upper.startswith("SNAPSHOT"): - tokens.append((1, -3)) - elif upper.startswith("M") and upper[1:].isdigit(): - tokens.append((1, -2, int(upper[1:]))) - elif upper.startswith("RC") and upper[2:].isdigit(): - tokens.append((1, -1, int(upper[2:]))) - else: - tokens.append((1, upper)) - return tokens - - def compare_versions(a, b): - ta = tokenize_version(a) - tb = tokenize_version(b) - max_len = max(len(ta), len(tb)) - for i in range(max_len): - va = ta[i] if i < len(ta) else (0, 0) - vb = tb[i] if i < len(tb) else (0, 0) - if va == vb: - continue - return -1 if va < vb else 1 - return 0 + ka = version_key(a) + kb = version_key(b) + if ka == kb: + return 0 + if ka < kb: + return -1 + return 1 def parse_range_expression(expr): @@ -174,7 +179,7 @@ def main(): snapshot_items.append(cloned) result = current_items + snapshot_items - result.sort(key=lambda item: tokenize_version(item.get("spring-boot-version", "0")), reverse=True) + result.sort(key=lambda item: version_key(item.get("spring-boot-version", "0")), reverse=True) os.makedirs(os.path.dirname(SUPPORT_FILE), exist_ok=True) with open(SUPPORT_FILE, "w", encoding="utf-8") as f: diff --git a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py index c98ecba373b2..df248c2d0176 100644 --- a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py +++ b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py @@ -18,45 +18,50 @@ PR_DESCRIPTIONS_OUTPUT = "pr-descriptions.txt" +def version_key(version): + match = re.match(r"^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:[-.]?([A-Za-z]+)(\d*)?)?$", version) + if not match: + return (0, 0, 0, 0, 0, version) + + major = int(match.group(1) or 0) + minor = int(match.group(2) or 0) + patch = int(match.group(3) or 0) + qualifier = (match.group(4) or "").upper() + qualifier_num = int(match.group(5) or 0) + + # Higher rank means newer release status for the same base version. + if not qualifier: + qualifier_rank = 3 # GA + elif qualifier.startswith("RC"): + qualifier_rank = 2 + elif qualifier.startswith("M"): + qualifier_rank = 1 + elif qualifier.startswith("SNAPSHOT"): + qualifier_rank = 0 + else: + qualifier_rank = 0 + + return (major, minor, patch, qualifier_rank, qualifier_num, qualifier) + + def fetch_json(url): req = urllib.request.Request(url, headers={"User-Agent": "spring-cloud-azure-tools-migration"}) with urllib.request.urlopen(req, timeout=30) as resp: return json.loads(resp.read().decode("utf-8")) -def tokenize_version(version): - tokens = [] - for part in re.split(r"[.\-]", version): - if part.isdigit(): - tokens.append((0, int(part))) - else: - upper = part.upper() - if upper.startswith("SNAPSHOT"): - tokens.append((1, -3)) - elif upper.startswith("M") and upper[1:].isdigit(): - tokens.append((1, -2, int(upper[1:]))) - elif upper.startswith("RC") and upper[2:].isdigit(): - tokens.append((1, -1, int(upper[2:]))) - else: - tokens.append((1, upper)) - return tokens - - def compare_versions(a, b): - ta = tokenize_version(a) - tb = tokenize_version(b) - max_len = max(len(ta), len(tb)) - for i in range(max_len): - va = ta[i] if i < len(ta) else (0, 0) - vb = tb[i] if i < len(tb) else (0, 0) - if va == vb: - continue - return -1 if va < vb else 1 - return 0 + ka = version_key(a) + kb = version_key(b) + if ka == kb: + return 0 + if ka < kb: + return -1 + return 1 def sort_versions_desc(versions): - return sorted(versions, key=lambda v: tokenize_version(v), reverse=True) + return sorted(versions, key=version_key, reverse=True) def read_current_supported_versions(): From 7a63afbb3960be6a89257ea1987221c9b3d9962c Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:09:13 +0800 Subject: [PATCH 07/21] Run Spring update workflows from main ref --- .github/workflows/test-spring-boot-rc-version.yml | 1 + .github/workflows/update-spring-cloud-azure-support-file.yml | 3 +++ .github/workflows/update-spring-dependencies.yml | 3 +++ 3 files changed, 7 insertions(+) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index 688d8b21be50..89ceb9e54a9e 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -14,6 +14,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + ref: main fetch-depth: 0 - name: Generate Version File run: | diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml index 97da7e694d4f..313bb7cd721d 100644 --- a/.github/workflows/update-spring-cloud-azure-support-file.yml +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -20,6 +20,8 @@ jobs: has_open_pr: ${{ steps.check.outputs.has_open_pr }} steps: - uses: actions/checkout@v4 + with: + ref: main - name: Check for Existing Open Pull Request id: check uses: actions/github-script@v7 @@ -44,6 +46,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + ref: main fetch-depth: 0 - name: Generate Spring Cloud Azure Support File run: | diff --git a/.github/workflows/update-spring-dependencies.yml b/.github/workflows/update-spring-dependencies.yml index ae775d56d199..3a336b6c527e 100644 --- a/.github/workflows/update-spring-dependencies.yml +++ b/.github/workflows/update-spring-dependencies.yml @@ -20,6 +20,8 @@ jobs: skip_pipeline: ${{ steps.check_prs.outputs.skip_pipeline }} steps: - uses: actions/checkout@v4 + with: + ref: main - name: Check for Open Spring Boot Upgrade PRs id: check_prs uses: actions/github-script@v7 @@ -44,6 +46,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + ref: main fetch-depth: 0 - name: Generate Version File run: | From d6b71fb47f98b64da110485117b6e1ed25d47f13 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:11:10 +0800 Subject: [PATCH 08/21] Fix Copilot review issues in Spring update automation --- .../workflows/test-spring-boot-rc-version.yml | 33 ++++++++++++++++++- ...rate_spring_versions_and_pr_description.py | 6 ++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index 89ceb9e54a9e..34bfe52ad2c5 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -69,7 +69,38 @@ jobs: git config --global user.name github-actions git add -A git commit -m "Upgrade external dependencies to align with Spring Boot ${{ env.spring_boot_version }}" - sed -i "s/NONE_SUPPORTED_SPRING_CLOUD_VERSION/${spring_cloud_version}/g" "./sdk/spring/pipeline/spring-cloud-azure-supported-spring.json" + python - <<'PY' + import json + from pathlib import Path + + spring_boot_version = "${{ env.spring_boot_version }}" + spring_cloud_version = "${{ env.spring_cloud_version }}" + placeholder = "NONE_SUPPORTED_SPRING_CLOUD_VERSION" + matrix_path = Path("./sdk/spring/pipeline/spring-cloud-azure-supported-spring.json") + + with matrix_path.open("r", encoding="utf-8") as f: + entries = json.load(f) + + updated = False + for entry in entries: + if ( + entry.get("spring-boot-version") == spring_boot_version + and entry.get("spring-cloud-version") == placeholder + ): + entry["spring-cloud-version"] = spring_cloud_version + updated = True + break + + if not updated: + raise SystemExit( + "Did not find RC support-matrix entry for " + f"spring-boot-version={spring_boot_version} with placeholder cloud version" + ) + + with matrix_path.open("w", encoding="utf-8") as f: + json.dump(entries, f, indent=2) + f.write("\n") + PY git add ./sdk/spring/pipeline/spring-cloud-azure-supported-spring.json git commit -m "Upgrade spring-cloud-azure-supported-spring" git push origin "HEAD:${{ env.update_branch }}" diff --git a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py index df248c2d0176..3b2bc7486441 100644 --- a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py +++ b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py @@ -203,8 +203,8 @@ def main(): latest_rc = sort_versions_desc(rc_versions)[0] if rc_versions else None current_boot, current_cloud = read_current_supported_versions() - is_new_ga = latest_ga is not None and compare_versions(latest_ga, current_boot) != 0 - is_new_rc = latest_rc is not None and compare_versions(latest_rc, current_boot) != 0 + is_new_ga = latest_ga is not None and compare_versions(latest_ga, current_boot) > 0 + is_new_rc = latest_rc is not None and compare_versions(latest_rc, current_boot) > 0 if not is_new_ga and not is_new_rc: return @@ -253,6 +253,8 @@ def main(): try: target_cloud = find_compatible_spring_cloud_version(target_boot, spring_cloud_ranges) except RuntimeError: + if latest_ga is None: + raise target_cloud = find_compatible_spring_cloud_version(latest_ga, spring_cloud_ranges) write_outputs(target_boot, target_cloud, current_boot, current_cloud, release_notes_html(target_boot)) From 12d7d9e652554718d3c1fb978a4fd70513053c1c Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:18:15 +0800 Subject: [PATCH 09/21] Add Spring Initializr terms to cspell dictionary --- .vscode/cspell.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/cspell.json b/.vscode/cspell.json index acd03fb12e20..9b7f6959af3c 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -220,6 +220,9 @@ "adfs", "ADFS", "agentic", + "initializr", + "Initializr", + "INITIALIZR", "akhtabar", "alzimmer", "amqp", From b82f8ada051dc6e94177683d736d11e5cb126950 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:19:11 +0800 Subject: [PATCH 10/21] Fix RC workflow checkout for branch dispatch --- .github/workflows/test-spring-boot-rc-version.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index 34bfe52ad2c5..4693b21acc4e 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -14,7 +14,6 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: main fetch-depth: 0 - name: Generate Version File run: | @@ -45,7 +44,8 @@ jobs: run: | echo "Updating Spring Boot Dependencies Version: ${{ env.spring_boot_version }}" echo "Updating Spring Cloud Dependencies Version: ${{ env.spring_cloud_version }}" - git checkout -b "${{ env.update_branch }}" + git fetch origin main + git checkout -B "${{ env.update_branch }}" origin/main pip install termcolor python ./sdk/spring/scripts/get_spring_boot_managed_external_dependencies.py -b "${{ env.spring_boot_version }}" -c "${{ env.spring_cloud_version }}" - name: Update external_dependencies.txt From e392b764c1ad1674be931bae68d4d5541079dd00 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:20:35 +0800 Subject: [PATCH 11/21] Fix update workflows for branch dispatch scripts --- .../workflows/update-spring-cloud-azure-support-file.yml | 6 ++---- .github/workflows/update-spring-dependencies.yml | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml index 313bb7cd721d..008360800329 100644 --- a/.github/workflows/update-spring-cloud-azure-support-file.yml +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -20,8 +20,6 @@ jobs: has_open_pr: ${{ steps.check.outputs.has_open_pr }} steps: - uses: actions/checkout@v4 - with: - ref: main - name: Check for Existing Open Pull Request id: check uses: actions/github-script@v7 @@ -46,7 +44,6 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: main fetch-depth: 0 - name: Generate Spring Cloud Azure Support File run: | @@ -69,7 +66,8 @@ jobs: } >> "$GITHUB_ENV" - name: Make Decision Based on Git Diff run: | - git checkout -b "${{ env.BRANCH_NAME }}" + git fetch origin main + git checkout -B "${{ env.BRANCH_NAME }}" origin/main if [[ -n "$(git status -s)" ]]; then echo "NEED_UPDATE_FILE=true" >> "$GITHUB_ENV" else diff --git a/.github/workflows/update-spring-dependencies.yml b/.github/workflows/update-spring-dependencies.yml index 3a336b6c527e..18b4728d0a84 100644 --- a/.github/workflows/update-spring-dependencies.yml +++ b/.github/workflows/update-spring-dependencies.yml @@ -20,8 +20,6 @@ jobs: skip_pipeline: ${{ steps.check_prs.outputs.skip_pipeline }} steps: - uses: actions/checkout@v4 - with: - ref: main - name: Check for Open Spring Boot Upgrade PRs id: check_prs uses: actions/github-script@v7 @@ -46,7 +44,6 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: main fetch-depth: 0 - name: Generate Version File run: | @@ -74,7 +71,8 @@ jobs: run: | echo "Updating Spring Boot Dependencies Version: ${{ env.spring_boot_version }}" echo "Updating Spring Cloud Dependencies Version: ${{ env.spring_cloud_version }}" - git checkout -b "${{ env.update_branch }}" + git fetch origin main + git checkout -B "${{ env.update_branch }}" origin/main pip install termcolor python ./sdk/spring/scripts/get_spring_boot_managed_external_dependencies.py -b "${{ env.spring_boot_version }}" -c "${{ env.spring_cloud_version }}" - name: Update external_dependencies.txt From f81f713afc0fbf8fc0dd1662693c5e09969a2530 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:24:08 +0800 Subject: [PATCH 12/21] Add force-update test mode for RC workflow --- .../workflows/test-spring-boot-rc-version.yml | 63 +++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index 4693b21acc4e..29490100a7aa 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -1,6 +1,25 @@ name: Test Spring Boot RC Version on: workflow_dispatch: + inputs: + force_update: + description: 'Force the update flow for testing even when no RC is detected' + required: false + type: boolean + default: false + dry_run: + description: 'Run update steps but skip push/create PR/comment side effects' + required: false + type: boolean + default: true + spring_boot_version: + description: 'Optional override Spring Boot version when force_update is true' + required: false + type: string + spring_cloud_version: + description: 'Optional override Spring Cloud version when force_update is true' + required: false + type: string permissions: contents: write @@ -23,7 +42,39 @@ jobs: python ./sdk/spring/scripts/generate_spring_cloud_azure_support_file.py --include-rc - name: Confirm Whether to Update run: | - if [[ ! -f 'spring-versions.txt' ]]; then + FORCE_UPDATE="${{ github.event.inputs.force_update }}" + MANUAL_BOOT="${{ github.event.inputs.spring_boot_version }}" + MANUAL_CLOUD="${{ github.event.inputs.spring_cloud_version }}" + + if [[ "${FORCE_UPDATE}" == 'true' ]]; then + echo "Force update enabled for workflow testing." + + if [[ -n "${MANUAL_BOOT}" && -n "${MANUAL_CLOUD}" ]]; then + TARGET_BOOT="${MANUAL_BOOT}" + TARGET_CLOUD="${MANUAL_CLOUD}" + elif [[ -f 'spring-versions.txt' ]]; then + mapfile -t versions < spring-versions.txt + TARGET_BOOT="${versions[0]}" + TARGET_CLOUD="${versions[1]}" + else + echo "force_update=true requires spring-versions.txt or both manual version inputs." + exit 1 + fi + + PR_NOTES="" + if [[ -f 'pr-descriptions.txt' ]]; then + PR_NOTES="$(> "$GITHUB_ENV" + elif [[ ! -f 'spring-versions.txt' ]]; then echo "No new Spring Boot version, no updates!" elif grep -q -- "-RC" spring-versions.txt; then echo "Has RC version, create PR to test!" @@ -63,7 +114,7 @@ jobs: run: | python ./sdk/spring/scripts/update_changelog.py -b "${{ env.spring_boot_version }}" -c "${{ env.spring_cloud_version }}" - name: Push Commit - if: ${{ env.need_update_version == 'true' }} + if: ${{ env.need_update_version == 'true' && github.event.inputs.dry_run != 'true' }} run: | git config --global user.email github-actions@github.com git config --global user.name github-actions @@ -106,7 +157,7 @@ jobs: git push origin "HEAD:${{ env.update_branch }}" - name: Create Pull Request id: create_pr - if: ${{ env.need_update_version == 'true' }} + if: ${{ env.need_update_version == 'true' && github.event.inputs.dry_run != 'true' }} uses: actions/github-script@v7 with: script: | @@ -122,7 +173,7 @@ jobs: }); core.setOutput('pull_request_number', String(pr.data.number)); - name: Comment on Pull Request - if: ${{ env.need_update_version == 'true' }} + if: ${{ env.need_update_version == 'true' && github.event.inputs.dry_run != 'true' }} uses: actions/github-script@v7 with: script: | @@ -137,3 +188,7 @@ jobs: issue_number: prNumber, body: '/azp run java - spring - tests' }); + - name: Dry Run Summary + if: ${{ env.need_update_version == 'true' && github.event.inputs.dry_run == 'true' }} + run: | + echo "Dry run mode: update steps executed, push/PR/comment were intentionally skipped." From 52e00007607ef2daec7ff10ebea8790f5695db70 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:33:20 +0800 Subject: [PATCH 13/21] Harden PR body construction in Spring workflows --- .github/workflows/test-spring-boot-rc-version.yml | 7 ++++++- .github/workflows/update-spring-dependencies.yml | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index 29490100a7aa..aab40bab822b 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -161,7 +161,12 @@ jobs: uses: actions/github-script@v7 with: script: | - const body = `Test Spring Boot RC version [${process.env.spring_boot_version}](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/${process.env.spring_boot_version}/spring-boot-dependencies-${process.env.spring_boot_version}.pom) and Spring Cloud version [${process.env.spring_cloud_version}](https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/${process.env.spring_cloud_version}/spring-cloud-dependencies-${process.env.spring_cloud_version}.pom).\n${process.env.pr_descriptions}\n\nThis PR is created by GitHub Actions: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const body = [ + `Test Spring Boot RC version [${process.env.spring_boot_version}](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/${process.env.spring_boot_version}/spring-boot-dependencies-${process.env.spring_boot_version}.pom) and Spring Cloud version [${process.env.spring_cloud_version}](https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/${process.env.spring_cloud_version}/spring-cloud-dependencies-${process.env.spring_cloud_version}.pom).`, + process.env.pr_descriptions || '', + '', + `This PR is created by GitHub Actions: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}` + ].join('\n'); const pr = await github.rest.pulls.create({ owner: context.repo.owner, repo: context.repo.repo, diff --git a/.github/workflows/update-spring-dependencies.yml b/.github/workflows/update-spring-dependencies.yml index 18b4728d0a84..3b80e06636ec 100644 --- a/.github/workflows/update-spring-dependencies.yml +++ b/.github/workflows/update-spring-dependencies.yml @@ -104,7 +104,12 @@ jobs: uses: actions/github-script@v7 with: script: | - const body = `Updates external dependencies to align with Spring Boot version [${process.env.spring_boot_version}](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/${process.env.spring_boot_version}/spring-boot-dependencies-${process.env.spring_boot_version}.pom) from [${process.env.last_spring_boot_version}](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/${process.env.last_spring_boot_version}/spring-boot-dependencies-${process.env.last_spring_boot_version}.pom) and Spring Cloud version [${process.env.spring_cloud_version}](https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/${process.env.spring_cloud_version}/spring-cloud-dependencies-${process.env.spring_cloud_version}.pom) from [${process.env.last_spring_cloud_version}](https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/${process.env.last_spring_cloud_version}/spring-cloud-dependencies-${process.env.last_spring_cloud_version}.pom).\n${process.env.pr_descriptions}\n\nThis PR is created by GitHub Actions: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; + const body = [ + `Updates external dependencies to align with Spring Boot version [${process.env.spring_boot_version}](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/${process.env.spring_boot_version}/spring-boot-dependencies-${process.env.spring_boot_version}.pom) from [${process.env.last_spring_boot_version}](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/${process.env.last_spring_boot_version}/spring-boot-dependencies-${process.env.last_spring_boot_version}.pom) and Spring Cloud version [${process.env.spring_cloud_version}](https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/${process.env.spring_cloud_version}/spring-cloud-dependencies-${process.env.spring_cloud_version}.pom) from [${process.env.last_spring_cloud_version}](https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/${process.env.last_spring_cloud_version}/spring-cloud-dependencies-${process.env.last_spring_cloud_version}.pom).`, + process.env.pr_descriptions || '', + '', + `This PR is created by GitHub Actions: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}` + ].join('\n'); const pr = await github.rest.pulls.create({ owner: context.repo.owner, repo: context.repo.repo, From 78548108c082995c3bb17835a16c2088f52c3875 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:41:19 +0800 Subject: [PATCH 14/21] Remove RC workflow test inputs and adjust current spring baseline --- .../workflows/test-spring-boot-rc-version.yml | 63 ++----------------- .../spring-cloud-azure-supported-spring.json | 2 +- 2 files changed, 5 insertions(+), 60 deletions(-) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index aab40bab822b..e2c0bed0d3e1 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -1,25 +1,6 @@ name: Test Spring Boot RC Version on: workflow_dispatch: - inputs: - force_update: - description: 'Force the update flow for testing even when no RC is detected' - required: false - type: boolean - default: false - dry_run: - description: 'Run update steps but skip push/create PR/comment side effects' - required: false - type: boolean - default: true - spring_boot_version: - description: 'Optional override Spring Boot version when force_update is true' - required: false - type: string - spring_cloud_version: - description: 'Optional override Spring Cloud version when force_update is true' - required: false - type: string permissions: contents: write @@ -42,39 +23,7 @@ jobs: python ./sdk/spring/scripts/generate_spring_cloud_azure_support_file.py --include-rc - name: Confirm Whether to Update run: | - FORCE_UPDATE="${{ github.event.inputs.force_update }}" - MANUAL_BOOT="${{ github.event.inputs.spring_boot_version }}" - MANUAL_CLOUD="${{ github.event.inputs.spring_cloud_version }}" - - if [[ "${FORCE_UPDATE}" == 'true' ]]; then - echo "Force update enabled for workflow testing." - - if [[ -n "${MANUAL_BOOT}" && -n "${MANUAL_CLOUD}" ]]; then - TARGET_BOOT="${MANUAL_BOOT}" - TARGET_CLOUD="${MANUAL_CLOUD}" - elif [[ -f 'spring-versions.txt' ]]; then - mapfile -t versions < spring-versions.txt - TARGET_BOOT="${versions[0]}" - TARGET_CLOUD="${versions[1]}" - else - echo "force_update=true requires spring-versions.txt or both manual version inputs." - exit 1 - fi - - PR_NOTES="" - if [[ -f 'pr-descriptions.txt' ]]; then - PR_NOTES="$(> "$GITHUB_ENV" - elif [[ ! -f 'spring-versions.txt' ]]; then + if [[ ! -f 'spring-versions.txt' ]]; then echo "No new Spring Boot version, no updates!" elif grep -q -- "-RC" spring-versions.txt; then echo "Has RC version, create PR to test!" @@ -114,7 +63,7 @@ jobs: run: | python ./sdk/spring/scripts/update_changelog.py -b "${{ env.spring_boot_version }}" -c "${{ env.spring_cloud_version }}" - name: Push Commit - if: ${{ env.need_update_version == 'true' && github.event.inputs.dry_run != 'true' }} + if: ${{ env.need_update_version == 'true' }} run: | git config --global user.email github-actions@github.com git config --global user.name github-actions @@ -157,7 +106,7 @@ jobs: git push origin "HEAD:${{ env.update_branch }}" - name: Create Pull Request id: create_pr - if: ${{ env.need_update_version == 'true' && github.event.inputs.dry_run != 'true' }} + if: ${{ env.need_update_version == 'true' }} uses: actions/github-script@v7 with: script: | @@ -178,7 +127,7 @@ jobs: }); core.setOutput('pull_request_number', String(pr.data.number)); - name: Comment on Pull Request - if: ${{ env.need_update_version == 'true' && github.event.inputs.dry_run != 'true' }} + if: ${{ env.need_update_version == 'true' }} uses: actions/github-script@v7 with: script: | @@ -193,7 +142,3 @@ jobs: issue_number: prNumber, body: '/azp run java - spring - tests' }); - - name: Dry Run Summary - if: ${{ env.need_update_version == 'true' && github.event.inputs.dry_run == 'true' }} - run: | - echo "Dry run mode: update steps executed, push/PR/comment were intentionally skipped." diff --git a/sdk/spring/pipeline/spring-cloud-azure-supported-spring.json b/sdk/spring/pipeline/spring-cloud-azure-supported-spring.json index 7f615e1c68ea..bc2c5c09acf6 100644 --- a/sdk/spring/pipeline/spring-cloud-azure-supported-spring.json +++ b/sdk/spring/pipeline/spring-cloud-azure-supported-spring.json @@ -4,7 +4,7 @@ "releaseStatus" : "GENERAL_AVAILABILITY", "snapshot" : false, "supportStatus" : "SUPPORTED", - "spring-boot-version" : "4.1.0", + "spring-boot-version" : "4.0.0", "spring-cloud-version" : "2025.1.2" }, { From 4658b085fdad7fc981533b929a174f8e5a11f529 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:49:43 +0800 Subject: [PATCH 15/21] Revert temporary spring support matrix baseline --- sdk/spring/pipeline/spring-cloud-azure-supported-spring.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/spring/pipeline/spring-cloud-azure-supported-spring.json b/sdk/spring/pipeline/spring-cloud-azure-supported-spring.json index bc2c5c09acf6..7f615e1c68ea 100644 --- a/sdk/spring/pipeline/spring-cloud-azure-supported-spring.json +++ b/sdk/spring/pipeline/spring-cloud-azure-supported-spring.json @@ -4,7 +4,7 @@ "releaseStatus" : "GENERAL_AVAILABILITY", "snapshot" : false, "supportStatus" : "SUPPORTED", - "spring-boot-version" : "4.0.0", + "spring-boot-version" : "4.1.0", "spring-cloud-version" : "2025.1.2" }, { From 28cb2bffda6c0d811b7139dabcedd1a9e6d9efba Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:57:29 +0800 Subject: [PATCH 16/21] Support configurable base branch in Spring workflows --- .github/workflows/test-spring-boot-rc-version.yml | 14 +++++++++++--- .../update-spring-cloud-azure-support-file.yml | 12 +++++++++--- .github/workflows/update-spring-dependencies.yml | 12 +++++++++--- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index e2c0bed0d3e1..b800a279408a 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -1,6 +1,14 @@ name: Test Spring Boot RC Version on: workflow_dispatch: + inputs: + base_branch: + description: 'Base branch for update branch and PR target. Defaults to trigger branch.' + required: false + type: string + +env: + BASE_BRANCH: ${{ github.event.inputs.base_branch || github.ref_name }} permissions: contents: write @@ -44,8 +52,8 @@ jobs: run: | echo "Updating Spring Boot Dependencies Version: ${{ env.spring_boot_version }}" echo "Updating Spring Cloud Dependencies Version: ${{ env.spring_cloud_version }}" - git fetch origin main - git checkout -B "${{ env.update_branch }}" origin/main + git fetch origin "${{ env.BASE_BRANCH }}" + git checkout -B "${{ env.update_branch }}" "origin/${{ env.BASE_BRANCH }}" pip install termcolor python ./sdk/spring/scripts/get_spring_boot_managed_external_dependencies.py -b "${{ env.spring_boot_version }}" -c "${{ env.spring_cloud_version }}" - name: Update external_dependencies.txt @@ -121,7 +129,7 @@ jobs: repo: context.repo.repo, title: process.env.PR_TITLE, head: process.env.update_branch, - base: 'main', + base: process.env.BASE_BRANCH, body, draft: true }); diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml index 008360800329..070ad8372623 100644 --- a/.github/workflows/update-spring-cloud-azure-support-file.yml +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -3,8 +3,14 @@ on: schedule: - cron: '0 0 * * *' workflow_dispatch: + inputs: + base_branch: + description: 'Base branch for update branch and PR target. Defaults to trigger branch.' + required: false + type: string env: + BASE_BRANCH: ${{ github.event.inputs.base_branch || github.ref_name }} PR_TITLE: "Update Spring Boot and Spring Cloud versions for the Spring compatibility tests" permissions: @@ -66,8 +72,8 @@ jobs: } >> "$GITHUB_ENV" - name: Make Decision Based on Git Diff run: | - git fetch origin main - git checkout -B "${{ env.BRANCH_NAME }}" origin/main + git fetch origin "${{ env.BASE_BRANCH }}" + git checkout -B "${{ env.BRANCH_NAME }}" "origin/${{ env.BASE_BRANCH }}" if [[ -n "$(git status -s)" ]]; then echo "NEED_UPDATE_FILE=true" >> "$GITHUB_ENV" else @@ -126,7 +132,7 @@ jobs: repo: context.repo.repo, title: process.env.PR_TITLE, head: process.env.BRANCH_NAME, - base: 'main', + base: process.env.BASE_BRANCH, body: process.env.PULL_REQUEST_BODY }); core.setOutput('pull_request_number', String(pr.data.number)); diff --git a/.github/workflows/update-spring-dependencies.yml b/.github/workflows/update-spring-dependencies.yml index 3b80e06636ec..d9b79dcfbaf5 100644 --- a/.github/workflows/update-spring-dependencies.yml +++ b/.github/workflows/update-spring-dependencies.yml @@ -3,8 +3,14 @@ on: schedule: - cron: '0 0 * * *' workflow_dispatch: + inputs: + base_branch: + description: 'Base branch for update branch and PR target. Defaults to trigger branch.' + required: false + type: string env: + BASE_BRANCH: ${{ github.event.inputs.base_branch || github.ref_name }} PR_TITLE_PREFIX: 'External dependencies upgrade - Spring Boot' permissions: @@ -71,8 +77,8 @@ jobs: run: | echo "Updating Spring Boot Dependencies Version: ${{ env.spring_boot_version }}" echo "Updating Spring Cloud Dependencies Version: ${{ env.spring_cloud_version }}" - git fetch origin main - git checkout -B "${{ env.update_branch }}" origin/main + git fetch origin "${{ env.BASE_BRANCH }}" + git checkout -B "${{ env.update_branch }}" "origin/${{ env.BASE_BRANCH }}" pip install termcolor python ./sdk/spring/scripts/get_spring_boot_managed_external_dependencies.py -b "${{ env.spring_boot_version }}" -c "${{ env.spring_cloud_version }}" - name: Update external_dependencies.txt @@ -115,7 +121,7 @@ jobs: repo: context.repo.repo, title: process.env.PR_TITLE, head: process.env.update_branch, - base: 'main', + base: process.env.BASE_BRANCH, body, draft: false }); From d3fca62cf2556d04c1ba0593a28ef8e9088fa307 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 13:58:52 +0800 Subject: [PATCH 17/21] Run support-file generation after base checkout --- .github/workflows/update-spring-cloud-azure-support-file.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml index 070ad8372623..55a9edeb058c 100644 --- a/.github/workflows/update-spring-cloud-azure-support-file.yml +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -51,9 +51,6 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Generate Spring Cloud Azure Support File - run: | - python ./sdk/spring/scripts/generate_spring_cloud_azure_support_file.py - name: Set Branch Name with Timestamp run: | TIMESTAMP=$(date +%Y%m%d-%H%M%S) @@ -74,6 +71,7 @@ jobs: run: | git fetch origin "${{ env.BASE_BRANCH }}" git checkout -B "${{ env.BRANCH_NAME }}" "origin/${{ env.BASE_BRANCH }}" + python ./sdk/spring/scripts/generate_spring_cloud_azure_support_file.py if [[ -n "$(git status -s)" ]]; then echo "NEED_UPDATE_FILE=true" >> "$GITHUB_ENV" else From e25768d1566fc6c6fb29cf91c3e4b0f5030c518f Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 14:11:08 +0800 Subject: [PATCH 18/21] Harden Spring workflow env handling and BOM selection --- .../workflows/test-spring-boot-rc-version.yml | 17 ++++++++++++----- .../workflows/update-spring-dependencies.yml | 4 +++- .../generate_spring_cloud_azure_support_file.py | 3 ++- ...nerate_spring_versions_and_pr_description.py | 3 ++- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index b800a279408a..341915ebd5ca 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -41,8 +41,10 @@ jobs: printf 'update_branch=update-spring-dependencies-%s-%s\n' "$(date +%Y%m%d)" "${GITHUB_RUN_ID}" printf 'spring_boot_version=%s\n' "${versions[0]}" printf 'spring_cloud_version=%s\n' "${versions[1]}" - printf 'pr_descriptions=%s\n' "$(> "$GITHUB_ENV" else echo "No RC version, cancel update!" @@ -100,17 +102,22 @@ jobs: break if not updated: - raise SystemExit( - "Did not find RC support-matrix entry for " - f"spring-boot-version={spring_boot_version} with placeholder cloud version" + print( + "No RC support-matrix placeholder entry found; " + "skipping support-file patch as a no-op." ) + raise SystemExit(0) with matrix_path.open("w", encoding="utf-8") as f: json.dump(entries, f, indent=2) f.write("\n") PY git add ./sdk/spring/pipeline/spring-cloud-azure-supported-spring.json - git commit -m "Upgrade spring-cloud-azure-supported-spring" + if [[ -n "$(git diff --cached -- ./sdk/spring/pipeline/spring-cloud-azure-supported-spring.json)" ]]; then + git commit -m "Upgrade spring-cloud-azure-supported-spring" + else + echo "No support matrix changes detected, skip commit." + fi git push origin "HEAD:${{ env.update_branch }}" - name: Create Pull Request id: create_pr diff --git a/.github/workflows/update-spring-dependencies.yml b/.github/workflows/update-spring-dependencies.yml index d9b79dcfbaf5..a0e048f6a04a 100644 --- a/.github/workflows/update-spring-dependencies.yml +++ b/.github/workflows/update-spring-dependencies.yml @@ -68,8 +68,10 @@ jobs: printf 'spring_cloud_version=%s\n' "$(sed -n '2p' spring-versions.txt)" printf 'last_spring_boot_version=%s\n' "$(sed -n '3p' spring-versions.txt)" printf 'last_spring_cloud_version=%s\n' "$(sed -n '4p' spring-versions.txt)" - printf 'pr_descriptions=%s\n' "$(cat pr-descriptions.txt)" printf 'PR_TITLE=%s %s and Spring Cloud %s\n' "${PR_TITLE_PREFIX}" "$(sed -n '1p' spring-versions.txt)" "$(sed -n '2p' spring-versions.txt)" + echo 'pr_descriptions<> "$GITHUB_ENV" fi - name: Generate spring_boot_managed_external_dependencies.txt diff --git a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py index 981363cc2918..c31caf90c690 100644 --- a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py +++ b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py @@ -83,7 +83,8 @@ def in_range(version, rules): def find_compatible_spring_cloud_version(spring_boot_version, spring_cloud_ranges): - for cloud_version, expr in spring_cloud_ranges.items(): + for cloud_version in sorted(spring_cloud_ranges.keys(), key=version_key, reverse=True): + expr = spring_cloud_ranges[cloud_version] if in_range(spring_boot_version, parse_range_expression(expr)): return cloud_version return NONE_SUPPORTED diff --git a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py index 3b2bc7486441..d3bae9761f50 100644 --- a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py +++ b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py @@ -137,7 +137,8 @@ def in_range(version, rules): def find_compatible_spring_cloud_version(spring_boot_version, spring_cloud_ranges): - for cloud_version, expr in spring_cloud_ranges.items(): + for cloud_version in sort_versions_desc(list(spring_cloud_ranges.keys())): + expr = spring_cloud_ranges[cloud_version] if in_range(spring_boot_version, parse_range_expression(expr)): return cloud_version raise RuntimeError( From 023731666acb2b2ccb1578bafe882fa19ee0548b Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 14:24:04 +0800 Subject: [PATCH 19/21] Preserve support matrix JSON spacing style --- sdk/spring/scripts/generate_spring_cloud_azure_support_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py index c31caf90c690..3956dd899ec7 100644 --- a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py +++ b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py @@ -184,7 +184,7 @@ def main(): os.makedirs(os.path.dirname(SUPPORT_FILE), exist_ok=True) with open(SUPPORT_FILE, "w", encoding="utf-8") as f: - json.dump(result, f, indent=2) + json.dump(result, f, indent=2, separators=(",", " : ")) f.write("\n") From f76c1719d4e3d1463fd6e842050b39099ff75ff2 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 14:25:54 +0800 Subject: [PATCH 20/21] Fix RC target selection and support file robustness --- .github/workflows/test-spring-boot-rc-version.yml | 2 +- .../update-spring-cloud-azure-support-file.yml | 2 +- .../generate_spring_cloud_azure_support_file.py | 7 +++++-- ...generate_spring_versions_and_pr_description.py | 15 +++++++++++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-spring-boot-rc-version.yml b/.github/workflows/test-spring-boot-rc-version.yml index 341915ebd5ca..61387de80819 100644 --- a/.github/workflows/test-spring-boot-rc-version.yml +++ b/.github/workflows/test-spring-boot-rc-version.yml @@ -25,7 +25,7 @@ jobs: fetch-depth: 0 - name: Generate Version File run: | - python ./sdk/spring/scripts/generate_spring_versions_and_pr_description.py + python ./sdk/spring/scripts/generate_spring_versions_and_pr_description.py --prefer-prerelease - name: Generate Spring Cloud Azure Support File run: | python ./sdk/spring/scripts/generate_spring_cloud_azure_support_file.py --include-rc diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml index 55a9edeb058c..24ef62227ab5 100644 --- a/.github/workflows/update-spring-cloud-azure-support-file.yml +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -117,7 +117,7 @@ jobs: git config --global user.name github-actions git add sdk/spring/pipeline/spring-cloud-azure-supported-spring.json git add docs/spring/Spring-Cloud-Azure-Timeline.md - git commit -m "${{ env.COMMIT_MESSAGE }}" + git commit -m "$COMMIT_MESSAGE" git push origin "${{ env.BRANCH_NAME }}" - name: Create Pull Request id: create_pr diff --git a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py index 3956dd899ec7..7309a11f15d8 100644 --- a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py +++ b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py @@ -103,8 +103,11 @@ def is_version_supported(version): def load_existing_support_map(): - with open(SUPPORT_FILE, "r", encoding="utf-8") as f: - existing = json.load(f) + try: + with open(SUPPORT_FILE, "r", encoding="utf-8") as f: + existing = json.load(f) + except (FileNotFoundError, json.JSONDecodeError, TypeError): + return {} return {item.get("spring-boot-version"): item for item in existing if item.get("spring-boot-version")} diff --git a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py index d3bae9761f50..ee1b508d5050 100644 --- a/sdk/spring/scripts/generate_spring_versions_and_pr_description.py +++ b/sdk/spring/scripts/generate_spring_versions_and_pr_description.py @@ -182,6 +182,11 @@ def write_outputs(target_boot, target_cloud, current_boot, current_cloud, notes) def main(): parser = argparse.ArgumentParser() parser.add_argument("--target-major", default="4", help="Target Spring Boot major version.") + parser.add_argument( + "--prefer-prerelease", + action="store_true", + help="Prefer latest prerelease when both GA and prerelease updates are available.", + ) args = parser.parse_args() spring_metadata = fetch_json(SPRING_METADATA_URL) @@ -247,8 +252,14 @@ def main(): if not spring_cloud_ranges: raise RuntimeError("Cannot locate spring-cloud compatibility map in Spring Initializr response") - target_boot = latest_ga if is_new_ga else latest_rc - if is_new_ga: + if args.prefer_prerelease and is_new_rc: + target_boot = latest_rc + elif is_new_ga: + target_boot = latest_ga + else: + target_boot = latest_rc + + if target_boot == latest_ga: target_cloud = find_compatible_spring_cloud_version(target_boot, spring_cloud_ranges) else: try: From 28898811a60c715ac5fb3c591302dc46dba564c5 Mon Sep 17 00:00:00 2001 From: Rujun Chen Date: Wed, 1 Jul 2026 14:53:03 +0800 Subject: [PATCH 21/21] Tighten workflow permissions and document legacy support --- .../update-spring-cloud-azure-support-file.yml | 10 +++++++--- .github/workflows/update-spring-dependencies.yml | 10 +++++++--- .../generate_spring_cloud_azure_support_file.py | 5 ++++- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.github/workflows/update-spring-cloud-azure-support-file.yml b/.github/workflows/update-spring-cloud-azure-support-file.yml index 24ef62227ab5..488d72bc65d5 100644 --- a/.github/workflows/update-spring-cloud-azure-support-file.yml +++ b/.github/workflows/update-spring-cloud-azure-support-file.yml @@ -14,9 +14,9 @@ env: PR_TITLE: "Update Spring Boot and Spring Cloud versions for the Spring compatibility tests" permissions: - contents: write - pull-requests: write - issues: write + contents: read + pull-requests: read + issues: read jobs: check-open-pr: @@ -47,6 +47,10 @@ jobs: needs: check-open-pr if: ${{ needs.check-open-pr.outputs.has_open_pr == 'false' }} runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/update-spring-dependencies.yml b/.github/workflows/update-spring-dependencies.yml index a0e048f6a04a..86efccd61a7a 100644 --- a/.github/workflows/update-spring-dependencies.yml +++ b/.github/workflows/update-spring-dependencies.yml @@ -14,9 +14,9 @@ env: PR_TITLE_PREFIX: 'External dependencies upgrade - Spring Boot' permissions: - contents: write - pull-requests: write - issues: write + contents: read + pull-requests: read + issues: read jobs: check: @@ -47,6 +47,10 @@ jobs: runs-on: ubuntu-latest needs: check if: ${{ needs.check.outputs.skip_pipeline != 'true' }} + permissions: + contents: write + pull-requests: write + issues: write steps: - uses: actions/checkout@v4 with: diff --git a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py index 7309a11f15d8..3036fcd2ab43 100644 --- a/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py +++ b/sdk/spring/scripts/generate_spring_cloud_azure_support_file.py @@ -11,6 +11,9 @@ SPRING_INITIALIZR_INFO_URL = "https://start.spring.io/actuator/info" SUPPORT_FILE = "sdk/spring/pipeline/spring-cloud-azure-supported-spring.json" NONE_SUPPORTED = "NONE_SUPPORTED_SPRING_CLOUD_VERSION" +# Azure SDK for Java still keeps Spring Boot 2.7.18 as supported, even though other inactive +# versions are marked END_OF_LIFE when they drop out of the current Spring metadata feed. +LEGACY_SUPPORTED_VERSION = "2.7.18" def version_key(version): @@ -178,7 +181,7 @@ def main(): continue cloned = dict(metadata) cloned["current"] = False - if version != "2.7.18": + if version != LEGACY_SUPPORTED_VERSION: cloned["supportStatus"] = "END_OF_LIFE" snapshot_items.append(cloned)