From 69522361983971a61184d1aa96bc00de28f357bc Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Sun, 30 Aug 2026 08:56:34 -0400 Subject: [PATCH 1/2] Verify release train workflow files exist before creating a release branch. Fixes #17 Signed-off-by: Ryan Baxter --- .../create-commercial-release-branch.yml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.github/workflows/create-commercial-release-branch.yml b/.github/workflows/create-commercial-release-branch.yml index d7d4176..ba3dcff 100644 --- a/.github/workflows/create-commercial-release-branch.yml +++ b/.github/workflows/create-commercial-release-branch.yml @@ -139,6 +139,44 @@ jobs: echo "Check passed: ${tag} does not exist in any repo." + # The release branch inherits its workflows from the source branch, and the + # final step of this job dispatches release-train-join.yml on it. Verify the + # release train workflows are on the source branch now, before anything is + # created, so a missing file fails fast instead of leaving a half-created + # release branch and a projects.json entry to clean up by hand. + - name: Verify release train workflows exist on source branch + env: + GH_TOKEN: ${{ inputs.token || secrets.token || secrets.GH_ACTIONS_REPO_TOKEN }} + run: | + source_repo="spring-cloud/${{ inputs.project }}" + source_branch="${{ inputs.branch }}" + + missing=() + for workflow in "release-train-join.yml" "release-train-ready.yml"; do + if gh api "repos/${source_repo}/contents/.github/workflows/${workflow}?ref=${source_branch}" --silent 2>/dev/null; then + echo " ✓ ${workflow} present in ${source_repo} at ${source_branch}" + else + echo " ✗ ${workflow} missing from ${source_repo} at ${source_branch}" + missing+=("${workflow}") + fi + done + + if [[ ${#missing[@]} -gt 0 ]]; then + echo + echo "ERROR: required release train workflow(s) missing from ${source_repo} at ${source_branch}:" + for workflow in "${missing[@]}"; do + echo " - .github/workflows/${workflow}" + done + echo + echo "The release branch is cut from ${source_branch} and inherits its workflows, so" + echo "release-train-join.yml could not be dispatched on it." + echo "Run run-github-actions-workflow-generator.yml for ${source_repo} on ${source_branch}" + echo "to generate the missing workflow(s), then re-run this workflow." + exit 1 + fi + + echo "Check passed: release train workflows are present on ${source_branch}." + - name: Create milestone in source repo if missing uses: ./.github/actions/create-milestone with: From 7cc7b379e941142fdb24cc866c90135f7409ab4b Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Sun, 30 Aug 2026 09:27:03 -0400 Subject: [PATCH 2/2] Move the release train workflow file list into a reusable action All three branch-creation workflows checked for the same two files with their own copy of the loop. Extract check-release-train-workflows so the list has one definition; callers choose between generating the missing files and failing fast via fail-on-missing. Signed-off-by: Ryan Baxter --- .../check-release-train-workflows/action.yml | 81 +++++++++++++++++++ .../create-commercial-release-branch.yml | 37 ++------- .../create-hotfix-release-branch.yml | 44 +++++----- .../workflows/create-oss-release-branch.yml | 49 +++++------ 4 files changed, 123 insertions(+), 88 deletions(-) create mode 100644 .github/actions/check-release-train-workflows/action.yml diff --git a/.github/actions/check-release-train-workflows/action.yml b/.github/actions/check-release-train-workflows/action.yml new file mode 100644 index 0000000..17bd237 --- /dev/null +++ b/.github/actions/check-release-train-workflows/action.yml @@ -0,0 +1,81 @@ +name: 'Check Release Train Workflows' +description: > + Checks a branch for the workflow files a release branch needs in order to join + a Spring release train, and reports which are missing. This is the single + definition of that file list - callers that generate the missing files and + callers that fail on them both read it from here. + +inputs: + repo: + description: 'Full repository path to check (e.g. spring-cloud/spring-cloud-config-commercial)' + required: true + branch: + description: 'Branch to check the workflow files on' + required: true + token: + description: 'GitHub token with read access to the repository' + required: true + fail-on-missing: + description: > + Fail the step when a workflow file is missing, rather than only reporting + it through the outputs. Use this where the caller has no way to generate + the missing files and wants to stop before doing any other work. + required: false + default: 'false' + +outputs: + any-missing: + description: "'true' when at least one required workflow file is missing, otherwise 'false'" + value: ${{ steps.check.outputs.any-missing }} + missing: + description: 'Space-separated list of the missing workflow file names (empty when none are missing)' + value: ${{ steps.check.outputs.missing }} + +runs: + using: composite + steps: + - name: Check for required release train workflows + id: check + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + REPO: ${{ inputs.repo }} + BRANCH: ${{ inputs.branch }} + FAIL_ON_MISSING: ${{ inputs.fail-on-missing }} + run: | + # The workflows a release branch must carry to join a release train. + required_workflows=("release-train-join.yml" "release-train-ready.yml") + + missing=() + for workflow in "${required_workflows[@]}"; do + if gh api "repos/${REPO}/contents/.github/workflows/${workflow}?ref=${BRANCH}" --silent 2>/dev/null; then + echo " ✓ ${workflow} present in ${REPO} at ${BRANCH}" + else + echo " ✗ ${workflow} missing from ${REPO} at ${BRANCH}" + missing+=("${workflow}") + fi + done + + if [[ ${#missing[@]} -eq 0 ]]; then + echo "any-missing=false" >> "$GITHUB_OUTPUT" + echo "missing=" >> "$GITHUB_OUTPUT" + echo "All required release train workflows are present on ${BRANCH}." + exit 0 + fi + + echo "any-missing=true" >> "$GITHUB_OUTPUT" + echo "missing=${missing[*]}" >> "$GITHUB_OUTPUT" + + if [[ "$FAIL_ON_MISSING" != "true" ]]; then + exit 0 + fi + + echo + echo "ERROR: required release train workflow(s) missing from ${REPO} at ${BRANCH}:" + for workflow in "${missing[@]}"; do + echo " - .github/workflows/${workflow}" + done + echo + echo "Run run-github-actions-workflow-generator.yml for ${REPO} on ${BRANCH}" + echo "to generate the missing workflow(s), then re-run this workflow." + exit 1 diff --git a/.github/workflows/create-commercial-release-branch.yml b/.github/workflows/create-commercial-release-branch.yml index ba3dcff..d04cfc3 100644 --- a/.github/workflows/create-commercial-release-branch.yml +++ b/.github/workflows/create-commercial-release-branch.yml @@ -145,37 +145,12 @@ jobs: # created, so a missing file fails fast instead of leaving a half-created # release branch and a projects.json entry to clean up by hand. - name: Verify release train workflows exist on source branch - env: - GH_TOKEN: ${{ inputs.token || secrets.token || secrets.GH_ACTIONS_REPO_TOKEN }} - run: | - source_repo="spring-cloud/${{ inputs.project }}" - source_branch="${{ inputs.branch }}" - - missing=() - for workflow in "release-train-join.yml" "release-train-ready.yml"; do - if gh api "repos/${source_repo}/contents/.github/workflows/${workflow}?ref=${source_branch}" --silent 2>/dev/null; then - echo " ✓ ${workflow} present in ${source_repo} at ${source_branch}" - else - echo " ✗ ${workflow} missing from ${source_repo} at ${source_branch}" - missing+=("${workflow}") - fi - done - - if [[ ${#missing[@]} -gt 0 ]]; then - echo - echo "ERROR: required release train workflow(s) missing from ${source_repo} at ${source_branch}:" - for workflow in "${missing[@]}"; do - echo " - .github/workflows/${workflow}" - done - echo - echo "The release branch is cut from ${source_branch} and inherits its workflows, so" - echo "release-train-join.yml could not be dispatched on it." - echo "Run run-github-actions-workflow-generator.yml for ${source_repo} on ${source_branch}" - echo "to generate the missing workflow(s), then re-run this workflow." - exit 1 - fi - - echo "Check passed: release train workflows are present on ${source_branch}." + uses: ./.github/actions/check-release-train-workflows + with: + repo: spring-cloud/${{ inputs.project }} + branch: ${{ inputs.branch }} + token: ${{ inputs.token || secrets.token || secrets.GH_ACTIONS_REPO_TOKEN }} + fail-on-missing: 'true' - name: Create milestone in source repo if missing uses: ./.github/actions/create-milestone diff --git a/.github/workflows/create-hotfix-release-branch.yml b/.github/workflows/create-hotfix-release-branch.yml index 82b07c5..81133f3 100644 --- a/.github/workflows/create-hotfix-release-branch.yml +++ b/.github/workflows/create-hotfix-release-branch.yml @@ -252,8 +252,22 @@ jobs: needs: [derive, update-versions] 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 + 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 }} @@ -261,17 +275,6 @@ jobs: commercial_project="${{ needs.derive.outputs.commercial_project }}" release_branch="${{ needs.derive.outputs.commercial_branch }}" - missing=false - for workflow in "release-train-join.yml" "release-train-ready.yml"; do - if gh api "repos/spring-cloud/${commercial_project}/contents/.github/workflows/${workflow}?ref=${release_branch}" --silent 2>/dev/null; then - echo " ✓ ${workflow} present" - else - echo " ✗ ${workflow} missing" - missing=true - fi - done - echo "needs-generation=${missing}" >> "$GITHUB_OUTPUT" - # 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']. @@ -312,26 +315,15 @@ jobs: JSEOF ) echo "primary-jdk=${primary_jdk}" >> "$GITHUB_OUTPUT" - - if [[ "$missing" == "false" ]]; then - echo "All required workflows are present — skipping generator." - else - echo "One or more required workflows are missing — generator will run (primary JDK: ${primary_jdk})." - fi - - - name: Checkout - if: steps.check.outputs.needs-generation == 'true' - uses: actions/checkout@v4 - with: - ref: ${{ inputs.sha || github.sha }} + 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.outputs.needs-generation == 'true' + 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.check.outputs.primary-jdk }} + primary-jdk: ${{ steps.jdk.outputs.primary-jdk }} token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} trigger-release-train-join: diff --git a/.github/workflows/create-oss-release-branch.yml b/.github/workflows/create-oss-release-branch.yml index a44fa32..07f0114 100644 --- a/.github/workflows/create-oss-release-branch.yml +++ b/.github/workflows/create-oss-release-branch.yml @@ -350,27 +350,25 @@ jobs: needs: [derive, update-release-branch] runs-on: ubuntu-latest steps: + - name: Checkout spring-cloud-github-actions + uses: actions/checkout@v4 + with: + ref: ${{ inputs.sha || github.sha }} + - name: Check for required release train workflows in OSS branch - id: check + id: check-workflows + uses: ./.github/actions/check-release-train-workflows + with: + repo: spring-cloud/${{ inputs.oss_repo }} + branch: ${{ inputs.oss_branch }} + token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} + + - name: Determine primary JDK for the generator + id: jdk + if: steps.check-workflows.outputs.any-missing == 'true' env: GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} run: | - oss_repo="spring-cloud/${{ inputs.oss_repo }}" - oss_branch="${{ inputs.oss_branch }}" - commercial_project="${{ needs.derive.outputs.commercial_project }}" - commercial_branch="${{ needs.derive.outputs.commercial_branch }}" - - missing=false - for workflow in "release-train-join.yml" "release-train-ready.yml"; do - if gh api "repos/${oss_repo}/contents/.github/workflows/${workflow}?ref=${oss_branch}" --silent 2>/dev/null; then - echo " ✓ ${workflow} present in OSS repo at ${oss_branch}" - else - echo " ✗ ${workflow} missing from OSS repo at ${oss_branch}" - missing=true - fi - done - echo "needs-generation=${missing}" >> "$GITHUB_OUTPUT" - # Look up the primary JDK for this OSS branch from projects.json. project_key="${{ inputs.oss_repo }}" oss_branch="${{ inputs.oss_branch }}" @@ -408,26 +406,15 @@ jobs: JSEOF ) echo "primary-jdk=${primary_jdk}" >> "$GITHUB_OUTPUT" - - if [[ "$missing" == "false" ]]; then - echo "All required workflows are present — skipping generator." - else - echo "One or more required workflows are missing — generator will run (primary JDK: ${primary_jdk})." - fi - - - name: Checkout spring-cloud-github-actions - if: steps.check.outputs.needs-generation == 'true' - uses: actions/checkout@v4 - with: - ref: ${{ inputs.sha || github.sha }} + echo "Generator will run with primary JDK ${primary_jdk} for the missing workflow(s): ${{ steps.check-workflows.outputs.missing }}" - name: Run workflow generator for commercial release branch - if: steps.check.outputs.needs-generation == 'true' + 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.check.outputs.primary-jdk }} + primary-jdk: ${{ steps.jdk.outputs.primary-jdk }} token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} # Dispatch release-train-join.yml in the commercial repo and wait for it to