Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/actions/dispatch-and-wait/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# dispatch-and-wait

Dispatches a `workflow_dispatch` workflow, waits for the run to complete, and fails the step unless the run's conclusion is `success`.

Used wherever a workflow starts another one and depends on its result: `release-train-join` from the three branch-creation workflows, `release-train-ready` from `spring-release-train-project-ready`, and each per-project run from `create-release-branches`.

## Inputs

| Name | Required | Default | Description |
|------|----------|---------|-------------|
| `repo` | yes | — | Full repository path of the workflow to run (e.g. `spring-cloud/spring-cloud-config-commercial`) |
| `workflow` | yes | — | Workflow file name (e.g. `release-train-join.yml`) |
| `ref` | yes | — | Branch or tag to run the workflow on |
| `inputs` | no | — | Workflow inputs, one `key=value` per line. Everything after the first `=` is the value, passed as a raw string, so values may contain spaces. Blank lines are ignored. |
| `summary-title` | no | — | When set, appends `<summary-title>: succeeded\|FAILED (<conclusion>) - <run url>` to the job's step summary |
| `interval` | no | `10` | Seconds between status checks |
| `token` | yes | — | GitHub token allowed to dispatch and read workflow runs in `repo` |

## Outputs

| Name | Description |
|------|-------------|
| `run-url` | URL of the dispatched run |
| `conclusion` | Conclusion of the run (`success`, `failure`, `cancelled`, ...) |

## Usage

```yaml
- name: Checkout
uses: actions/checkout@v4

- name: Trigger release-train-join workflow and wait
uses: ./.github/actions/dispatch-and-wait
with:
repo: spring-cloud/spring-cloud-config-commercial
workflow: release-train-join.yml
ref: release/5.0.4
inputs: |
deployment-destination=Spring Enterprise
release-train=2026.1
release-train-repository=spring-io/release-train
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
```

## Notes

- **The run's conclusion decides the result, not `gh run watch`'s exit status.** Right after dispatch, `gh run watch` can exit non-zero before the run has any jobs, even though the run goes on to succeed. The watch is only used to wait: if it returns before the run is `completed`, the action checks the status and watches again.
- The watch's error output is kept in the log rather than discarded, so an early exit is visible.
- The run id is read from the URL `gh workflow run` prints. If that output isn't a run URL, the step fails rather than waiting on nothing.
104 changes: 104 additions & 0 deletions .github/actions/dispatch-and-wait/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: 'Dispatch and Wait'
description: >
Dispatches a workflow_dispatch workflow, waits for the run to complete, and fails unless
the run's conclusion is success.

inputs:
repo:
description: 'Full repository path of the workflow to run (e.g. spring-cloud/spring-cloud-config-commercial)'
required: true
workflow:
description: 'Workflow file name (e.g. release-train-join.yml)'
required: true
ref:
description: 'Branch or tag to run the workflow on'
required: true
inputs:
description: >
Workflow inputs, one key=value per line. Everything after the first = is the value,
passed as a raw string, so values may contain spaces or further = signs. Blank lines
are ignored.
required: false
default: ''
summary-title:
description: >
When set, a line "<summary-title>: succeeded|FAILED (<conclusion>) - <run url>" is
appended to the job's step summary.
required: false
default: ''
interval:
description: 'Seconds between status checks'
required: false
default: '10'
token:
description: 'GitHub token allowed to dispatch and read workflow runs in repo'
required: true

outputs:
run-url:
description: 'URL of the dispatched run'
value: ${{ steps.run.outputs.run-url }}
conclusion:
description: 'Conclusion of the run (success, failure, cancelled, ...)'
value: ${{ steps.run.outputs.conclusion }}

runs:
using: composite
steps:
- name: Dispatch ${{ inputs.workflow }} and wait
id: run
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
REPO: ${{ inputs.repo }}
WORKFLOW: ${{ inputs.workflow }}
REF: ${{ inputs.ref }}
WORKFLOW_INPUTS: ${{ inputs.inputs }}
SUMMARY_TITLE: ${{ inputs.summary-title }}
INTERVAL: ${{ inputs.interval }}
run: |
set -euo pipefail

args=()
while IFS= read -r line; do
line="${line%$'\r'}"
[[ -z "${line//[[:space:]]/}" ]] && continue
if [[ "$line" != *=* ]]; then
echo "::error::Workflow input '${line}' is not key=value."
exit 1
fi
args+=(-f "$line")
done <<< "$WORKFLOW_INPUTS"

echo "Dispatching ${WORKFLOW} in ${REPO} on ${REF}..."
run_url=$(gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$REF" "${args[@]}")
run_id="${run_url##*/}"
if [[ ! "$run_id" =~ ^[0-9]+$ ]]; then
echo "::error::Could not read the run id from gh workflow run's output: '${run_url}'"
exit 1
fi
echo "run-url=${run_url}" >> "$GITHUB_OUTPUT"
echo "Dispatched. Waiting for ${run_url} to complete."

# gh run watch's exit status is not the run's result: right after dispatch it can
# exit non-zero before the run has any jobs, while the run goes on to succeed. So
# the watch is only used to wait, and the run's own conclusion decides the outcome,
# re-watching until the run has actually completed.
while :; do
gh run watch "$run_id" --repo "$REPO" --interval="$INTERVAL" > /dev/null \
|| echo "gh run watch exited early; checking the run's status."
status=$(gh run view "$run_id" --repo "$REPO" --json status --jq .status) || status=""
[[ "$status" == "completed" ]] && break
sleep "$INTERVAL"
done
conclusion=$(gh run view "$run_id" --repo "$REPO" --json conclusion --jq .conclusion)
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"

if [[ "$conclusion" == "success" ]]; then
echo "${WORKFLOW} succeeded: ${run_url}"
[[ -n "$SUMMARY_TITLE" ]] && echo "### ${SUMMARY_TITLE}: succeeded - ${run_url}" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
[[ -n "$SUMMARY_TITLE" ]] && echo "### ${SUMMARY_TITLE}: FAILED (${conclusion}) - ${run_url}" >> "$GITHUB_STEP_SUMMARY"
echo "::error::${WORKFLOW} in ${REPO} finished with conclusion '${conclusion}': ${run_url}"
exit 1
28 changes: 9 additions & 19 deletions .github/actions/spring-release-train-project-ready/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,25 +179,15 @@ runs:
fi

- name: Trigger release-train-ready workflow
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
run: |
run_url=$(gh workflow run release-train-ready.yml \
--repo spring-cloud/${{ inputs.project }} \
--ref release/${{ steps.version.outputs.version }} \
--field release-train=${{ inputs.spring-release-train-version }} \
--field release-train-repository=spring-io/release-train)
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo spring-cloud/${{ inputs.project }} --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
uses: ./.github/actions/dispatch-and-wait
with:
repo: spring-cloud/${{ inputs.project }}
workflow: release-train-ready.yml
ref: release/${{ steps.version.outputs.version }}
inputs: |
release-train=${{ inputs.spring-release-train-version }}
release-train-repository=spring-io/release-train
token: ${{ inputs.token }}

# A hotfix release/<version> branch is registered in config/projects.json for its own
# scheduled CI entry (create-hotfix-release-branch.yml); a GA release/<version> branch
Expand Down
32 changes: 10 additions & 22 deletions .github/workflows/create-commercial-release-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,25 +210,13 @@ jobs:
# evaluate falsy and skip the join rather than fail visibly.
- name: Trigger release-train-join workflow and wait
if: ${{ inputs['release-train'] != '' }}
env:
GH_TOKEN: ${{ inputs.token || secrets.token || secrets.GH_ACTIONS_REPO_TOKEN }}
run: |
commercial_project="${{ steps.setup.outputs.commercial-project }}"
release_branch="release/${{ steps.setup.outputs.release-version }}"

run_url=$(gh workflow run release-train-join.yml \
--repo "spring-cloud/${commercial_project}" \
--ref "${release_branch}" \
--field "deployment-destination=Spring Enterprise" \
--field "release-train=${{ inputs.release-train }}" \
--field "release-train-repository=spring-io/release-train")
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo "spring-cloud/${commercial_project}" --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
uses: ./.github/actions/dispatch-and-wait
with:
repo: spring-cloud/${{ steps.setup.outputs.commercial-project }}
workflow: release-train-join.yml
ref: release/${{ steps.setup.outputs.release-version }}
inputs: |
deployment-destination=Spring Enterprise
release-train=${{ inputs['release-train'] }}
release-train-repository=spring-io/release-train
token: ${{ inputs.token || secrets.token || secrets.GH_ACTIONS_REPO_TOKEN }}
39 changes: 16 additions & 23 deletions .github/workflows/create-hotfix-release-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -493,31 +493,24 @@ jobs:
needs: [derive, ensure-workflows, create-milestone]
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 != '' }}
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
shell: bash
run: |
commercial_project="${{ needs.derive.outputs.commercial_project }}"
release_branch="${{ needs.derive.outputs.commercial_branch }}"
echo "Triggering release-train-join.yml in spring-cloud/${commercial_project} on ${release_branch}..."
run_url=$(gh workflow run release-train-join.yml \
--repo "spring-cloud/${commercial_project}" \
--ref "${release_branch}" \
--field "deployment-destination=Spring Enterprise" \
--field "release-train=${{ inputs.spring_release_train }}" \
--field "release-train-repository=spring-io/release-train")
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo "spring-cloud/${commercial_project}" --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
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]
Expand Down
39 changes: 16 additions & 23 deletions .github/workflows/create-oss-release-branch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -621,31 +621,24 @@ jobs:
needs: [derive, ensure-workflows, create-milestone]
runs-on: ubuntu-latest
steps:
- name: Trigger release-train-join workflow and wait
- name: Checkout
if: ${{ inputs.spring_release_train != '' }}
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: |
commercial_project="${{ needs.derive.outputs.commercial_project }}"
commercial_branch="${{ needs.derive.outputs.commercial_branch }}"
uses: actions/checkout@v4
with:
ref: ${{ inputs.sha || github.sha }}

echo "Triggering release-train-join.yml in spring-cloud/${commercial_project} on ${commercial_branch}..."
run_url=$(gh workflow run release-train-join.yml \
--repo "spring-cloud/${commercial_project}" \
--ref "${commercial_branch}" \
--field "deployment-destination=Maven Central" \
--field "release-train=${{ inputs.spring_release_train }}" \
--field "release-train-repository=spring-io/release-train")
echo "Dispatched workflow run. Waiting for $run_url to complete."
run_id=${run_url##*/}
watch_exit_code=0
gh run watch $run_id --repo "spring-cloud/${commercial_project}" --exit-status --interval=3 > /dev/null 2>&1 || watch_exit_code=$?
if [[ $watch_exit_code -eq 0 ]]; then
echo "Workflow run succeeded."
else
echo "Workflow run failed."
fi
exit $watch_exit_code
- 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=Maven Central
release-train=${{ inputs.spring_release_train }}
release-train-repository=spring-io/release-train
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}

# Squash all [skip actions] initialization commits into a single commit and
# push it (without [skip actions]) to trigger the CI pipeline on the newly
Expand Down
Loading
Loading