Repository navigation
Create Hotfix Release Branch #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Create Hotfix Release Branch | |
| # Creates a commercial hotfix release branch. By default it is cut from the tip of the | |
| # commercial repo's <major>.<minor>.x-internal branch, preserving its full git history so | |
| # the hotfix branch can later be rebased onto an updated -internal branch. Setting use_tag | |
| # instead creates it from an OSS tag, as an orphan branch with no shared history (today's | |
| # original behaviour) — full commercial customisation, squashed to a single commit. | |
| # | |
| # The commercial repo is always the OSS repo with -commercial appended. The hotfix version | |
| # (e.g. 5.0.4.1) is looked up from this project's own entry in the release train's | |
| # jenkins-releaser-config properties file (releaser.fixed-versions[<project>]), keyed by | |
| # release_train_version. That release train is always still unfinalized when a hotfix | |
| # branch is cut, so the looked-up value always carries a -SNAPSHOT qualifier (e.g. | |
| # 5.0.4.1-SNAPSHOT) - derive strips it before validating and using the version. From that | |
| # version: | |
| # - the release branch is release/<version> (e.g. release/5.0.4.1) | |
| # - the internal branch is <major>.<minor>.x-internal (e.g. 5.0.x-internal) | |
| # - the internal branch's pom.xml is expected to already be at | |
| # <major>.<minor>.<patch>-INTERNAL-SNAPSHOT (e.g. 5.0.4-INTERNAL-SNAPSHOT) | |
| # - the OSS tag, used only when use_tag is set, is v<major>.<minor>.<patch> (e.g. v5.0.4) | |
| # | |
| # Before branching from -internal, the workflow validates that the -internal branch's | |
| # pom.xml version matches the expected -INTERNAL-SNAPSHOT version above. If it doesn't, the | |
| # -internal branch is out of date and the workflow fails before making any changes. | |
| # | |
| # After the branch is initialised, the project version is stamped to <version>-SNAPSHOT | |
| # (e.g. 5.0.4.1-SNAPSHOT), and dependency versions are updated from the release train. | |
| # Optionally, an explicit versions override can be supplied on top. | |
| # Finally, release-train-join.yml is triggered in the commercial repo, when a | |
| # spring_release_train is supplied — leaving that input empty prepares the branch | |
| # and stops short of joining. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| oss_repo: | |
| description: 'Open source repository name in the spring-cloud org (e.g. spring-cloud-stream)' | |
| required: true | |
| type: string | |
| release_train_version: | |
| description: "Release train version (e.g. 2025.1.2) whose jenkins-releaser-config properties file names this project's hotfix version and supplies dependency versions" | |
| required: true | |
| type: string | |
| use_tag: | |
| description: 'Create the branch from an OSS tag (v<major>.<minor>.<patch>) instead of the <major>.<minor>.x-internal branch' | |
| required: false | |
| type: boolean | |
| default: false | |
| spring_release_train: | |
| description: 'Spring release train this hotfix is part of (e.g. 2026.1). Supplying it joins that train once the branch is prepared; leave it empty to prepare the branch without joining.' | |
| required: false | |
| type: string | |
| default: '' | |
| versions: | |
| description: 'JSON map of dependency versions to apply (e.g. {"spring-boot":"3.3.0","spring-cloud-commons":"4.1.1"}), applied on top of the release train dependency versions' | |
| required: false | |
| type: string | |
| default: '' | |
| sha: | |
| description: 'Commit SHA of this repo to copy release-train action files from. Defaults to the commit that triggered this workflow.' | |
| required: false | |
| type: string | |
| default: '' | |
| workflow_call: | |
| inputs: | |
| oss_repo: | |
| description: 'Open source repository name in the spring-cloud org (e.g. spring-cloud-stream)' | |
| required: true | |
| type: string | |
| release_train_version: | |
| description: "Release train version (e.g. 2025.1.2) whose jenkins-releaser-config properties file names this project's hotfix version and supplies dependency versions" | |
| required: true | |
| type: string | |
| use_tag: | |
| description: 'Create the branch from an OSS tag (v<major>.<minor>.<patch>) instead of the <major>.<minor>.x-internal branch' | |
| required: false | |
| type: boolean | |
| default: false | |
| spring_release_train: | |
| description: 'Spring release train this hotfix is part of (e.g. 2026.1). Supplying it joins that train once the branch is prepared; leave it empty to prepare the branch without joining.' | |
| required: false | |
| type: string | |
| default: '' | |
| versions: | |
| description: 'JSON map of dependency versions to apply (e.g. {"spring-boot":"3.3.0"}), applied on top of the release train dependency versions' | |
| required: false | |
| type: string | |
| default: '' | |
| sha: | |
| description: 'Commit SHA of this repo to copy release-train action files from. Defaults to the commit that triggered this workflow.' | |
| required: false | |
| type: string | |
| default: '' | |
| secrets: | |
| token: | |
| description: 'GitHub token with contents: write on the commercial repo. Falls back to the GH_ACTIONS_REPO_TOKEN organization secret.' | |
| required: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Looks up this project's hotfix version from the release train's releaser config, and | |
| # derives every other branch/version name from it. GitHub Actions expressions have no | |
| # replace/substring/split function so this needs a shell step. | |
| derive: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| commercial_repo: ${{ steps.derive.outputs.commercial_repo }} | |
| commercial_branch: ${{ steps.derive.outputs.commercial_branch }} | |
| commercial_project: ${{ steps.derive.outputs.commercial_project }} | |
| commercial_version: ${{ steps.derive.outputs.commercial_version }} | |
| internal_branch: ${{ steps.derive.outputs.internal_branch }} | |
| expected_internal_version: ${{ steps.derive.outputs.expected_internal_version }} | |
| oss_tag: ${{ steps.derive.outputs.oss_tag }} | |
| auto_version: ${{ steps.derive.outputs.auto_version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Look up hotfix version and derive branch names | |
| id: derive | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| run: | | |
| OSS_REPO="spring-cloud/${{ inputs.oss_repo }}" | |
| COMMERCIAL_REPO="${OSS_REPO}-commercial" | |
| COMMERCIAL_PROJECT="${COMMERCIAL_REPO#spring-cloud/}" | |
| RELEASE_TRAIN_VERSION="${{ inputs.release_train_version }}" | |
| # Detect the project name from the OSS repo's root pom.xml artifactId, the same | |
| # way update-project-versions/src/index.js's detectProjectName does. | |
| ARTIFACT_ID=$(gh api "repos/${OSS_REPO}/contents/pom.xml" --jq '.content' | base64 -d | python3 -c " | |
| import xml.etree.ElementTree as ET, sys | |
| root = ET.parse('/dev/stdin').getroot() | |
| ns = (root.tag.split('}')[0] + '}') if '}' in root.tag else '' | |
| a = root.find(f'{ns}artifactId') | |
| print(a.text.strip() if a is not None else '', end='') | |
| ") | |
| if [[ -z "$ARTIFACT_ID" ]]; then | |
| echo "ERROR: could not read artifactId from ${OSS_REPO}'s root pom.xml." | |
| exit 1 | |
| fi | |
| PROJECT_NAME=$(echo "$ARTIFACT_ID" | sed -E 's/-dependencies$//; s/-parent$//') | |
| echo "Artifact ID: $ARTIFACT_ID" | |
| echo "Project name: $PROJECT_NAME" | |
| # Fetch this project's fixed version for the release train from | |
| # spring-cloud-release-commercial@jenkins-releaser-config. | |
| CONFIG_FILE=$(node .github/scripts/releaser-config-file.js "$RELEASE_TRAIN_VERSION") | |
| CONFIG_URL="https://raw.githubusercontent.com/spring-cloud/spring-cloud-release-commercial/jenkins-releaser-config/${CONFIG_FILE}" | |
| echo "Fetching releaser config from ${CONFIG_URL}" | |
| HTTP_STATUS=$(curl -s -o /tmp/releaser-config.properties -w '%{http_code}' \ | |
| -H "Authorization: Bearer ${GH_TOKEN}" "$CONFIG_URL") | |
| if [[ "$HTTP_STATUS" != "200" ]]; then | |
| echo "ERROR: failed to fetch releaser config from ${CONFIG_URL} (HTTP ${HTTP_STATUS})." | |
| exit 1 | |
| fi | |
| HOTFIX_VERSION=$(PROJECT_NAME="$PROJECT_NAME" python3 -c " | |
| import os, re | |
| project = os.environ['PROJECT_NAME'] | |
| with open('/tmp/releaser-config.properties') as f: | |
| content = f.read() | |
| for line in content.split('\n'): | |
| m = re.match(r'^releaser\.fixed-versions\[([^\]]+)\]=(.+)\$', line) | |
| if m and m.group(1).strip() == project: | |
| print(m.group(2).strip(), end='') | |
| break | |
| ") | |
| if [[ -z "$HOTFIX_VERSION" ]]; then | |
| echo "ERROR: project '${PROJECT_NAME}' was not found in the releaser config for release train ${RELEASE_TRAIN_VERSION}." | |
| echo "Ensure the root pom.xml artifactId matches a key in the properties file." | |
| exit 1 | |
| fi | |
| echo "Hotfix version: $HOTFIX_VERSION" | |
| # The releaser config is for a release train that hasn't been finalized yet, so | |
| # every fixed version in it - including this project's - still carries a | |
| # -SNAPSHOT qualifier (e.g. 5.0.4.1-SNAPSHOT). That is the expected shape for a | |
| # hotfix branch creation, not an error: strip it before validating the remaining | |
| # major.minor.patch.hotfix segments are numeric. | |
| HOTFIX_VERSION="${HOTFIX_VERSION%-SNAPSHOT}" | |
| # A hotfix version has exactly 4 numeric segments: major.minor.patch.hotfix | |
| IFS='.' read -r MAJOR MINOR PATCH HOTFIX_NUM EXTRA <<< "$HOTFIX_VERSION" | |
| if [[ -z "$MAJOR" || -z "$MINOR" || -z "$PATCH" || -z "$HOTFIX_NUM" || -n "$EXTRA" \ | |
| || ! "${MAJOR}${MINOR}${PATCH}${HOTFIX_NUM}" =~ ^[0-9]+$ ]]; then | |
| echo "ERROR: '${HOTFIX_VERSION}' is not a 4-segment hotfix version (major.minor.patch.hotfix)." | |
| exit 1 | |
| fi | |
| INTERNAL_BRANCH="${MAJOR}.${MINOR}.x-internal" | |
| EXPECTED_INTERNAL_VERSION="${MAJOR}.${MINOR}.${PATCH}-INTERNAL-SNAPSHOT" | |
| OSS_TAG="v${MAJOR}.${MINOR}.${PATCH}" | |
| COMMERCIAL_BRANCH="release/${HOTFIX_VERSION}" | |
| AUTO_VERSION="${HOTFIX_VERSION}-SNAPSHOT" | |
| echo "commercial_repo=${COMMERCIAL_REPO}" >> "$GITHUB_OUTPUT" | |
| echo "commercial_branch=${COMMERCIAL_BRANCH}" >> "$GITHUB_OUTPUT" | |
| echo "commercial_project=${COMMERCIAL_PROJECT}" >> "$GITHUB_OUTPUT" | |
| echo "commercial_version=${HOTFIX_VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "internal_branch=${INTERNAL_BRANCH}" >> "$GITHUB_OUTPUT" | |
| echo "expected_internal_version=${EXPECTED_INTERNAL_VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "oss_tag=${OSS_TAG}" >> "$GITHUB_OUTPUT" | |
| echo "auto_version=${AUTO_VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "OSS repo: $OSS_REPO" | |
| echo "Commercial repo: $COMMERCIAL_REPO" | |
| echo "Commercial project: $COMMERCIAL_PROJECT" | |
| echo "Commercial branch: $COMMERCIAL_BRANCH" | |
| echo "Internal branch: $INTERNAL_BRANCH" | |
| echo "Expected internal version: $EXPECTED_INTERNAL_VERSION" | |
| echo "OSS tag (use_tag only): $OSS_TAG" | |
| echo "Auto version: $AUTO_VERSION" | |
| # Fails before any branch is touched if the -internal branch's pom.xml version doesn't | |
| # match the base version this hotfix is built on — i.e. the -internal branch is out of | |
| # date. A no-op when use_tag is set. | |
| validate-internal-branch: | |
| needs: derive | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Validate -internal branch pom.xml version | |
| if: ${{ !inputs.use_tag }} | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| run: | | |
| COMMERCIAL_REPO="${{ needs.derive.outputs.commercial_repo }}" | |
| INTERNAL_BRANCH="${{ needs.derive.outputs.internal_branch }}" | |
| EXPECTED_VERSION="${{ needs.derive.outputs.expected_internal_version }}" | |
| CONTENT=$(gh api "repos/${COMMERCIAL_REPO}/contents/pom.xml" -X GET -f ref="$INTERNAL_BRANCH" --jq '.content') || { | |
| echo "ERROR: could not read pom.xml from '${INTERNAL_BRANCH}' in ${COMMERCIAL_REPO}. Does the branch exist?" | |
| exit 1 | |
| } | |
| ACTUAL_VERSION=$(echo "$CONTENT" | base64 -d | python3 -c " | |
| import xml.etree.ElementTree as ET, sys | |
| root = ET.parse('/dev/stdin').getroot() | |
| ns = (root.tag.split('}')[0] + '}') if '}' in root.tag else '' | |
| v = root.find(f'{ns}version') | |
| print(v.text.strip() if v is not None else '', end='') | |
| ") | |
| echo "Internal branch: $INTERNAL_BRANCH" | |
| echo "Expected version: $EXPECTED_VERSION" | |
| echo "Actual version: $ACTUAL_VERSION" | |
| if [[ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ]]; then | |
| echo "ERROR: '${INTERNAL_BRANCH}' in ${COMMERCIAL_REPO} has version '${ACTUAL_VERSION}' but expected '${EXPECTED_VERSION}'." | |
| echo "The -internal branch is out of date — update it before creating the hotfix branch." | |
| exit 1 | |
| fi | |
| echo "-internal branch version matches. OK to proceed." | |
| initialize: | |
| needs: [derive, validate-internal-branch] | |
| if: ${{ inputs.use_tag }} | |
| uses: ./.github/workflows/initialize-commercial-branch.yml | |
| with: | |
| oss_repo: ${{ format('spring-cloud/{0}', inputs.oss_repo) }} | |
| oss_tag: ${{ needs.derive.outputs.oss_tag }} | |
| commercial_repo: ${{ needs.derive.outputs.commercial_repo }} | |
| commercial_branch: ${{ needs.derive.outputs.commercial_branch }} | |
| set_default_branch: false | |
| secrets: inherit | |
| # Cuts release/<version> from the tip of the -internal branch, preserving its full git | |
| # history (so the hotfix branch can later be rebased onto an updated -internal branch), | |
| # then layers on only what -internal doesn't already have: a retargeted ci-release.yml | |
| # (mirrors create-oss-release-branch.yml's release/<version>-from-internal flow — | |
| # distribution-management/workflow-rewrite/settings.xml are redundant here, since | |
| # ci-release.yml + release-ci-settings.xml already point the build at the commercial | |
| # deploy target), Broadcom license headers (and checkstyle-header.txt in the same | |
| # commit), and a config/projects.json registration — a hotfix branch carries real changes | |
| # on top of -internal and can fail its own CI independently of it, unlike a GA | |
| # release/<version>, so it needs its own scheduled CI entry. The entry is removed again | |
| # by spring-release-train-project-ready once the hotfix ships. A no-op when use_tag is set. | |
| create-internal-release-branch: | |
| needs: [derive, validate-internal-branch] | |
| if: ${{ !inputs.use_tag }} | |
| runs-on: ubuntu-latest | |
| outputs: | |
| base_sha: ${{ steps.create.outputs.base_sha }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.sha || github.sha }} | |
| - name: Create release branch from internal branch | |
| id: create | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| run: | | |
| COMMERCIAL_REPO="${{ needs.derive.outputs.commercial_repo }}" | |
| COMMERCIAL_BRANCH="${{ needs.derive.outputs.commercial_branch }}" | |
| INTERNAL_BRANCH="${{ needs.derive.outputs.internal_branch }}" | |
| if gh api "repos/${COMMERCIAL_REPO}/git/ref/heads/${COMMERCIAL_BRANCH}" --silent 2>/dev/null; then | |
| echo "ERROR: ${COMMERCIAL_BRANCH} already exists in ${COMMERCIAL_REPO}." | |
| exit 1 | |
| fi | |
| BASE_SHA=$(gh api "repos/${COMMERCIAL_REPO}/git/ref/heads/${INTERNAL_BRANCH}" --jq '.object.sha') | |
| echo "Creating ${COMMERCIAL_BRANCH} in ${COMMERCIAL_REPO} from ${INTERNAL_BRANCH} at ${BASE_SHA}..." | |
| gh api "repos/${COMMERCIAL_REPO}/git/refs" \ | |
| --method POST \ | |
| --field ref="refs/heads/${COMMERCIAL_BRANCH}" \ | |
| --field sha="${BASE_SHA}" | |
| echo "base_sha=${BASE_SHA}" >> "$GITHUB_OUTPUT" | |
| echo "Branch ${COMMERCIAL_BRANCH} created." | |
| - name: Add commercial release CI files | |
| uses: ./.github/actions/add-commercial-release-files | |
| with: | |
| repository: ${{ needs.derive.outputs.commercial_repo }} | |
| branch: ${{ needs.derive.outputs.commercial_branch }} | |
| retarget-ci-release-from: ${{ needs.derive.outputs.internal_branch }} | |
| token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| - name: Update license headers | |
| uses: ./.github/actions/update-license-headers | |
| with: | |
| repository: ${{ needs.derive.outputs.commercial_repo }} | |
| branch: ${{ needs.derive.outputs.commercial_branch }} | |
| token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| - name: Update projects.json | |
| uses: ./.github/actions/update-projects-json | |
| with: | |
| oss-repo: ${{ format('spring-cloud/{0}', inputs.oss_repo) }} | |
| oss-branch: '' | |
| commercial-branch: ${{ needs.derive.outputs.commercial_branch }} | |
| set-default-branch: false | |
| token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| update-versions: | |
| needs: [derive, initialize, create-internal-release-branch] | |
| if: ${{ !failure() && !cancelled() }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Clone commercial branch | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| run: | | |
| COMMERCIAL_REPO="${{ needs.derive.outputs.commercial_repo }}" | |
| COMMERCIAL_BRANCH="${{ needs.derive.outputs.commercial_branch }}" | |
| git clone --single-branch --branch "$COMMERCIAL_BRANCH" \ | |
| "https://x-access-token:${GH_TOKEN}@github.com/${COMMERCIAL_REPO}.git" _version_repo | |
| git -C _version_repo config user.name "Spring Builds" | |
| git -C _version_repo config user.email "svc.spring-builds@broadcom.com" | |
| # Updates dependency version properties from the release train. This also stamps the | |
| # project version to the release train's value; the next step overwrites that with | |
| # the correct hotfix snapshot. | |
| - name: Update dependency versions from release train | |
| uses: ./.github/actions/update-project-versions | |
| with: | |
| release-train-version: ${{ inputs.release_train_version }} | |
| token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| directory: _version_repo | |
| commercial: 'true' # use release config from commercial repo | |
| # Always stamp the project version to the hotfix snapshot. When versions is supplied | |
| # it also updates dependency properties in the same pass; otherwise versions:'{}' | |
| # ensures only the project version is touched. | |
| - name: Stamp hotfix project version | |
| uses: ./.github/actions/update-project-versions | |
| with: | |
| project-version: ${{ needs.derive.outputs.auto_version }} | |
| versions: ${{ inputs.versions || '{}' }} | |
| directory: _version_repo | |
| - name: Commit and push version updates | |
| shell: bash | |
| run: | | |
| cd _version_repo | |
| git add . | |
| if git diff --cached --quiet; then | |
| echo "No version changes to commit." | |
| exit 0 | |
| fi | |
| git commit -m "Updating project version to ${{ needs.derive.outputs.auto_version }} [skip actions]" | |
| git push origin "${{ needs.derive.outputs.commercial_branch }}" | |
| echo "Version updates committed." | |
| create-milestone: | |
| needs: derive | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Create milestone in commercial repo if missing | |
| uses: ./.github/actions/create-milestone | |
| with: | |
| repo: ${{ needs.derive.outputs.commercial_repo }} | |
| version: ${{ needs.derive.outputs.commercial_version }} | |
| token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| ensure-workflows: | |
| needs: [derive, update-versions] | |
| if: ${{ !failure() && !cancelled() }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.sha || github.sha }} | |
| - name: Check for required release train workflows | |
| id: check-workflows | |
| uses: ./.github/actions/check-release-train-workflows | |
| with: | |
| repo: ${{ needs.derive.outputs.commercial_repo }} | |
| branch: ${{ needs.derive.outputs.commercial_branch }} | |
| token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| - name: Determine primary JDK for the generator | |
| id: jdk | |
| if: steps.check-workflows.outputs.any-missing == 'true' | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| run: | | |
| commercial_project="${{ needs.derive.outputs.commercial_project }}" | |
| release_branch="${{ needs.derive.outputs.commercial_branch }}" | |
| # Look up primary JDK from projects.json. | |
| # initialize-commercial-branch already ran update-projects-json, which added | |
| # commercial.jdkVersions[release_branch] sourced from oss.jdkVersions['X.Y.x']. | |
| # We look up by the commercial branch name directly, matching how the generator | |
| # queries scheduled branches. | |
| project_key="${commercial_project%-commercial}" | |
| echo "JDK lookup: commercial.jdkVersions['${release_branch}'] in project '${project_key}'" | |
| gh api repos/spring-cloud/spring-cloud-github-actions/contents/config/projects.json \ | |
| -X GET -f ref=main --jq '.content' | base64 -d > /tmp/projects.json | |
| export PROJECT_KEY="${project_key}" | |
| export COMMERCIAL_BRANCH="${release_branch}" | |
| primary_jdk=$(node - << 'JSEOF' | |
| const fs = require('fs'); | |
| const projectKey = process.env.PROJECT_KEY; | |
| const commercialBranch = process.env.COMMERCIAL_BRANCH; | |
| const projects = JSON.parse(fs.readFileSync('/tmp/projects.json', 'utf8')); | |
| const defaults = projects.defaults || {}; | |
| function getJdks(pk, type, branch) { | |
| const cfg = (projects[pk] || {})[type] || {}; | |
| const jdkmap = cfg.jdkVersions || {}; | |
| if (jdkmap[branch]) return jdkmap[branch]; | |
| if (jdkmap['default']) { | |
| process.stderr.write(`WARNING: '${branch}' not found in projects.json commercial.jdkVersions for '${pk}' — falling back to commercial default: ${JSON.stringify(jdkmap['default'])}\n`); | |
| return jdkmap['default']; | |
| } | |
| const defMap = (defaults[type] || {}).jdkVersions || {}; | |
| if (defMap[branch]) return defMap[branch]; | |
| const globalFallback = defMap['default'] || ['17', '21', '25']; | |
| process.stderr.write(`WARNING: '${branch}' not found in projects.json for '${pk}' — falling back to global default: ${JSON.stringify(globalFallback)}\n`); | |
| return globalFallback; | |
| } | |
| const jdks = getJdks(projectKey, 'commercial', commercialBranch); | |
| process.stdout.write(jdks.includes('8') ? '8' : '17'); | |
| JSEOF | |
| ) | |
| echo "primary-jdk=${primary_jdk}" >> "$GITHUB_OUTPUT" | |
| echo "Generator will run with primary JDK ${primary_jdk} for the missing workflow(s): ${{ steps.check-workflows.outputs.missing }}" | |
| - name: Run workflow generator for hotfix branch | |
| if: steps.check-workflows.outputs.any-missing == 'true' | |
| uses: ./.github/actions/generate-workflows-for-branch | |
| with: | |
| repo: ${{ needs.derive.outputs.commercial_repo }} | |
| branch: ${{ needs.derive.outputs.commercial_branch }} | |
| primary-jdk: ${{ steps.jdk.outputs.primary-jdk }} | |
| token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| trigger-release-train-join: | |
| needs: [derive, ensure-workflows, create-milestone] | |
| if: ${{ !failure() && !cancelled() }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| if: ${{ inputs.spring_release_train != '' }} | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.sha || github.sha }} | |
| - name: Trigger release-train-join workflow and wait | |
| if: ${{ inputs.spring_release_train != '' }} | |
| uses: ./.github/actions/dispatch-and-wait | |
| with: | |
| repo: spring-cloud/${{ needs.derive.outputs.commercial_project }} | |
| workflow: release-train-join.yml | |
| ref: ${{ needs.derive.outputs.commercial_branch }} | |
| inputs: | | |
| deployment-destination=Spring Enterprise | |
| release-train=${{ inputs.spring_release_train }} | |
| release-train-repository=spring-io/release-train | |
| token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| trigger-ci: | |
| needs: [derive, initialize, create-internal-release-branch, trigger-release-train-join] | |
| if: ${{ !failure() && !cancelled() }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| # The tag path is an orphan branch with no history worth keeping, so its | |
| # initialization commits are collapsed into a single root commit. | |
| - name: Squash initialization commits into an orphan root commit and push to trigger CI | |
| if: ${{ inputs.use_tag }} | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| run: | | |
| COMMERCIAL_REPO="${{ needs.derive.outputs.commercial_repo }}" | |
| BRANCH="${{ needs.derive.outputs.commercial_branch }}" | |
| git clone --single-branch --branch "$BRANCH" \ | |
| "https://x-access-token:${GH_TOKEN}@github.com/${COMMERCIAL_REPO}.git" _trigger_repo | |
| cd _trigger_repo | |
| git config user.name "Spring Builds" | |
| git config user.email "svc.spring-builds@broadcom.com" | |
| # Capture the current remote tip so the force push is guarded against a | |
| # concurrent update to the branch. | |
| REMOTE_SHA="$(git rev-parse HEAD)" | |
| # Create a fresh single-commit history from the current tree. The orphan | |
| # checkout keeps the working tree and index, so committing captures the | |
| # fully initialised branch content as one root commit. | |
| git checkout --orphan _squashed | |
| git commit -m "Initialize hotfix branch" | |
| git push --force-with-lease="${BRANCH}:${REMOTE_SHA}" \ | |
| origin "HEAD:refs/heads/${BRANCH}" | |
| echo "Squashed initialization commits into a single commit on ${COMMERCIAL_REPO}@${BRANCH}." | |
| # The -internal path preserves real history, so only the initialization commits | |
| # added on top of the fork point are squashed — everything at and before base_sha | |
| # (the -internal branch's history) is left untouched, so the branch can later be | |
| # rebased onto an updated -internal branch. | |
| - name: Squash initialization commits onto the internal branch fork point and push to trigger CI | |
| if: ${{ !inputs.use_tag }} | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} | |
| run: | | |
| COMMERCIAL_REPO="${{ needs.derive.outputs.commercial_repo }}" | |
| BRANCH="${{ needs.derive.outputs.commercial_branch }}" | |
| BASE_SHA="${{ needs.create-internal-release-branch.outputs.base_sha }}" | |
| git clone --single-branch --branch "$BRANCH" \ | |
| "https://x-access-token:${GH_TOKEN}@github.com/${COMMERCIAL_REPO}.git" _trigger_repo | |
| cd _trigger_repo | |
| git config user.name "Spring Builds" | |
| git config user.email "svc.spring-builds@broadcom.com" | |
| if [[ "$(git rev-parse HEAD)" != "$BASE_SHA" ]]; then | |
| git reset --soft "$BASE_SHA" | |
| git commit -m "Initialize hotfix release branch" | |
| git push --force-with-lease origin "$BRANCH" | |
| echo "Squashed initialization commits into a single commit on ${COMMERCIAL_REPO}@${BRANCH}." | |
| else | |
| git commit --allow-empty -m "Initialize hotfix release branch" | |
| git push origin "$BRANCH" | |
| echo "No initialization commits to squash; pushed empty commit to trigger CI on ${COMMERCIAL_REPO}@${BRANCH}." | |
| fi |