Skip to content

Commit 416a7cb

Browse files
cdoernfranciscojavierarceoleseb
authored
ci: add release automation workflows to 0.4.x (#5278)
Backport release automation workflows to the 0.4.x branch to enable easy releases. ## What does this PR do? Backports the release automation infrastructure to 0.4.x: - **prepare-release.yml**: Workflow to prepare a release by updating version files - **post-release.yml**: Workflow to automate post-release tasks (dev tag, version bumps) - **pypi.yml updates**: Add compute-version job for consistent version computation + Docker polling fix ## Backported commits 1. 8b37a85 - feat(ci): automate post-release and pre-release version management 2. 9b6365c - chore: fix post-release workflow and remove broken docker image 3. ab8fded - fix(ci): use RELEASE_PAT and PRs in post-release workflow 4. 2157c09 - fix(ci): add uv lock to post-release workflow to update stale lockfile 5. acec7ca - fix: poll test PyPI before building Docker images to avoid race condition ## Changes - ✅ Added `.github/workflows/prepare-release.yml` - ✅ Added `.github/workflows/post-release.yml` - ✅ Updated `.github/workflows/pypi.yml` with compute-version job and Docker polling - ✅ Updated `.github/workflows/README.md` to document new workflows ## Testing The workflows can be tested via workflow_dispatch: - `prepare-release.yml` can be manually triggered with a version and branch - `post-release.yml` can be manually triggered with a release tag ## Related This is part of the work to bring the 0.4.x branch up to date for easy releases, along with #5277 which adds 0.4.x to scheduled CI. --------- Signed-off-by: Charlie Doern <cdoern@redhat.com> Co-authored-by: Francisco Javier Arceo <arceofrancisco@gmail.com> Co-authored-by: Sébastien Han <seb@redhat.com>
1 parent d622fdc commit 416a7cb

4 files changed

Lines changed: 595 additions & 61 deletions

File tree

.github/workflows/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ Llama Stack uses GitHub Actions for Continuous Integration (CI). Below is a tabl
1212
| SqlStore Integration Tests | [integration-sql-store-tests.yml](integration-sql-store-tests.yml) | Run the integration test suite with SqlStore |
1313
| Integration Tests (Replay) | [integration-tests.yml](integration-tests.yml) | Run the integration test suites from tests/integration in replay mode |
1414
| Vector IO Integration Tests | [integration-vector-io-tests.yml](integration-vector-io-tests.yml) | Run the integration test suite with various VectorIO providers |
15+
| Post-release automation | [post-release.yml](post-release.yml) | Post-release automation |
1516
| Pre-commit | [pre-commit.yml](pre-commit.yml) | Run pre-commit checks |
17+
| Prepare release | [prepare-release.yml](prepare-release.yml) | Prepare release |
1618
| Test Llama Stack Build | [providers-build.yml](providers-build.yml) | Test llama stack build |
1719
| Test llama stack list-deps | [providers-list-deps.yml](providers-list-deps.yml) | Test llama stack list-deps |
1820
| Build, test, and publish packages | [pypi.yml](pypi.yml) | Build, test, and publish packages |

.github/workflows/post-release.yml

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
# =============================================================================
2+
# Post-Release Automation
3+
# =============================================================================
4+
#
5+
# Triggered after the pypi.yml workflow succeeds for a release event. Automates:
6+
# A) Tagging main with the next dev tag (e.g., v0.5.1-dev after v0.5.0)
7+
# B) Bumping fallback_version on main via PR (main is a protected branch)
8+
# C) Updating npm lockfile on the release branch via PR
9+
#
10+
# =============================================================================
11+
12+
name: Post-release automation
13+
14+
on:
15+
workflow_run:
16+
workflows: ["Build, test, and publish packages"]
17+
types:
18+
- completed
19+
workflow_dispatch:
20+
inputs:
21+
tag:
22+
description: 'Release tag (e.g., v0.6.0). Use to re-run post-release steps after a failure.'
23+
required: true
24+
type: string
25+
26+
permissions:
27+
contents: write
28+
pull-requests: write
29+
30+
jobs:
31+
post-release:
32+
name: Post-release housekeeping
33+
# Only run when:
34+
# - workflow_dispatch (manual testing), OR
35+
# - the pypi.yml workflow completed successfully AND was triggered by a release
36+
if: |
37+
github.event_name == 'workflow_dispatch' || (
38+
github.event.workflow_run.conclusion == 'success' &&
39+
github.event.workflow_run.event == 'release'
40+
)
41+
runs-on: ubuntu-latest
42+
steps:
43+
- name: Checkout repository
44+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
45+
with:
46+
fetch-depth: 0
47+
token: ${{ secrets.RELEASE_PAT }}
48+
49+
- name: Parse release version
50+
id: parse
51+
env:
52+
GH_TOKEN: ${{ github.token }}
53+
run: |
54+
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
55+
TAG="${{ inputs.tag }}"
56+
else
57+
# The triggering workflow was a release event — find the tag from the
58+
# commit SHA that the workflow ran on, or fall back to the latest release.
59+
HEAD_SHA="${{ github.event.workflow_run.head_sha }}"
60+
TAG=$(git tag --points-at "$HEAD_SHA" 2>/dev/null | grep '^v' | head -1)
61+
if [ -z "$TAG" ]; then
62+
# Fallback: query the latest published release via GitHub API
63+
TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq '.tag_name')
64+
fi
65+
if [ -z "$TAG" ]; then
66+
echo "::error::Could not determine release tag"
67+
exit 1
68+
fi
69+
fi
70+
71+
# Strip 'v' prefix
72+
VERSION="${TAG#v}"
73+
74+
# Split into components
75+
MAJOR="${VERSION%%.*}"
76+
REST="${VERSION#*.}"
77+
MINOR="${REST%%.*}"
78+
PATCH="${REST#*.}"
79+
# Strip any pre-release suffix from patch (e.g., rc1)
80+
PATCH="${PATCH%%[a-zA-Z-]*}"
81+
82+
NEXT_PATCH=$((PATCH + 1))
83+
NEXT_DEV_TAG="v${MAJOR}.${MINOR}.${NEXT_PATCH}-dev"
84+
NEXT_FALLBACK="${MAJOR}.${MINOR}.${NEXT_PATCH}.dev0"
85+
RELEASE_BRANCH="release-${MAJOR}.${MINOR}.x"
86+
87+
echo "version=${VERSION}" >> $GITHUB_OUTPUT
88+
echo "major=${MAJOR}" >> $GITHUB_OUTPUT
89+
echo "minor=${MINOR}" >> $GITHUB_OUTPUT
90+
echo "patch=${PATCH}" >> $GITHUB_OUTPUT
91+
echo "next_patch=${NEXT_PATCH}" >> $GITHUB_OUTPUT
92+
echo "next_dev_tag=${NEXT_DEV_TAG}" >> $GITHUB_OUTPUT
93+
echo "next_fallback=${NEXT_FALLBACK}" >> $GITHUB_OUTPUT
94+
echo "release_branch=${RELEASE_BRANCH}" >> $GITHUB_OUTPUT
95+
96+
echo "Release version: ${VERSION}"
97+
echo "Next dev tag: ${NEXT_DEV_TAG}"
98+
echo "Next fallback: ${NEXT_FALLBACK}"
99+
echo "Release branch: ${RELEASE_BRANCH}"
100+
101+
# -----------------------------------------------------------------------
102+
# Step A: Tag main with the next dev tag
103+
# -----------------------------------------------------------------------
104+
- name: Push dev tag to main
105+
run: |
106+
DEV_TAG="${{ steps.parse.outputs.next_dev_tag }}"
107+
108+
# Check if tag already exists
109+
if git rev-parse "$DEV_TAG" >/dev/null 2>&1; then
110+
echo "Tag $DEV_TAG already exists, skipping"
111+
exit 0
112+
fi
113+
114+
# Tag the HEAD of main
115+
MAIN_SHA=$(git rev-parse origin/main)
116+
git tag "$DEV_TAG" "$MAIN_SHA"
117+
git push origin "$DEV_TAG"
118+
echo "Pushed tag $DEV_TAG to main ($MAIN_SHA)"
119+
120+
- name: Set up uv
121+
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1
122+
123+
# -----------------------------------------------------------------------
124+
# Step B: Bump fallback_version on main and open PR
125+
# -----------------------------------------------------------------------
126+
- name: Create fallback_version bump PR
127+
env:
128+
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
129+
run: |
130+
NEXT_FALLBACK="${{ steps.parse.outputs.next_fallback }}"
131+
VERSION="${{ steps.parse.outputs.version }}"
132+
BRANCH="post-release/bump-fallback-${VERSION}"
133+
134+
# Check if PR already exists
135+
EXISTING=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number')
136+
if [ -n "$EXISTING" ]; then
137+
echo "PR #${EXISTING} already exists for $BRANCH, skipping"
138+
exit 0
139+
fi
140+
141+
git checkout -b "$BRANCH" origin/main
142+
143+
# Update fallback_version in root pyproject.toml
144+
sed -i "s/^fallback_version = .*/fallback_version = \"${NEXT_FALLBACK}\"/" pyproject.toml
145+
146+
# Update fallback_version in src/llama_stack_api/pyproject.toml
147+
sed -i "s/^fallback_version = .*/fallback_version = \"${NEXT_FALLBACK}\"/" src/llama_stack_api/pyproject.toml
148+
149+
# Bump llama-stack-client minimum version to match the release
150+
sed -i "s/\"llama-stack-client>=.*\"/\"llama-stack-client>=${VERSION}\"/" pyproject.toml
151+
152+
# Regenerate lockfile to resolve the updated client version
153+
uv lock
154+
155+
# Check if there are changes
156+
if git diff --quiet; then
157+
echo "No changes to fallback_version, skipping PR"
158+
exit 0
159+
fi
160+
161+
git config --local user.name "github-actions[bot]"
162+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
163+
git add pyproject.toml src/llama_stack_api/pyproject.toml uv.lock
164+
git commit -s -m "chore: bump fallback_version to ${NEXT_FALLBACK} after ${VERSION} release"
165+
git push origin "$BRANCH"
166+
167+
gh pr create \
168+
--base main \
169+
--head "$BRANCH" \
170+
--title "chore: bump fallback_version to ${NEXT_FALLBACK}" \
171+
--body "$(cat <<EOF
172+
Automated post-release version bump after v${VERSION}.
173+
174+
Updates fallback_version in both pyproject.toml files to ${NEXT_FALLBACK}.
175+
176+
This PR was created automatically by the post-release workflow.
177+
EOF
178+
)"
179+
echo "Created PR for fallback_version bump to ${NEXT_FALLBACK}"
180+
181+
# -----------------------------------------------------------------------
182+
# Step C: Update npm lockfile on release branch via PR
183+
# -----------------------------------------------------------------------
184+
- name: Set up Node.js
185+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
186+
with:
187+
node-version: '20'
188+
registry-url: 'https://registry.npmjs.org'
189+
190+
- name: Create npm lockfile update PR for release branch
191+
env:
192+
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
193+
run: |
194+
VERSION="${{ steps.parse.outputs.version }}"
195+
RELEASE_BRANCH="${{ steps.parse.outputs.release_branch }}"
196+
BRANCH="post-release/npm-lockfile-${VERSION}"
197+
198+
# Check if release branch exists
199+
if ! git rev-parse "origin/${RELEASE_BRANCH}" >/dev/null 2>&1; then
200+
echo "Release branch ${RELEASE_BRANCH} does not exist, skipping npm update"
201+
exit 0
202+
fi
203+
204+
# Check if PR already exists
205+
EXISTING=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number')
206+
if [ -n "$EXISTING" ]; then
207+
echo "PR #${EXISTING} already exists for $BRANCH, skipping"
208+
exit 0
209+
fi
210+
211+
git checkout -b "$BRANCH" "origin/${RELEASE_BRANCH}"
212+
213+
# Regenerate uv lockfile so pre-commit passes
214+
uv lock
215+
216+
# Update npm lockfile
217+
cd src/llama_stack_ui
218+
npm install "llama-stack-client@^${VERSION}"
219+
cd ../..
220+
221+
# Check if there are changes
222+
if git diff --quiet; then
223+
echo "No npm lockfile changes, skipping PR"
224+
exit 0
225+
fi
226+
227+
git config --local user.name "github-actions[bot]"
228+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
229+
git add uv.lock src/llama_stack_ui/package.json src/llama_stack_ui/package-lock.json
230+
git commit -s -m "chore: update llama-stack-client to ^${VERSION} in UI lockfile"
231+
git push origin "$BRANCH"
232+
233+
gh pr create \
234+
--base "$RELEASE_BRANCH" \
235+
--head "$BRANCH" \
236+
--title "chore: update llama-stack-client to ^${VERSION} in UI lockfile" \
237+
--body "$(cat <<EOF
238+
Automated post-release npm lockfile update after v${VERSION}.
239+
240+
Updates llama-stack-client to ^${VERSION} in the UI package lockfile.
241+
242+
This PR was created automatically by the post-release workflow.
243+
EOF
244+
)"
245+
echo "Created PR for npm lockfile update on ${RELEASE_BRANCH}"
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# =============================================================================
2+
# Pre-Release Preparation
3+
# =============================================================================
4+
#
5+
# Triggered via workflow_dispatch to prepare a release branch for publishing.
6+
# Takes a version (e.g., "0.5.1") and target release branch as input.
7+
#
8+
# Automates:
9+
# - Updating fallback_version to the release version in both pyproject.toml files
10+
# - Updating llama-stack-client pins to the release version
11+
# - Committing all changes directly to the release branch
12+
#
13+
# =============================================================================
14+
15+
name: Prepare release
16+
17+
on:
18+
workflow_dispatch:
19+
inputs:
20+
version:
21+
description: 'Release version (e.g., "0.5.1")'
22+
required: true
23+
type: string
24+
release_branch:
25+
description: 'Target release branch (e.g., "release-0.5.x")'
26+
required: true
27+
type: string
28+
29+
permissions:
30+
contents: write
31+
32+
jobs:
33+
prepare-release:
34+
name: Prepare release v${{ inputs.version }}
35+
runs-on: ubuntu-latest
36+
steps:
37+
- name: Checkout repository
38+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
39+
with:
40+
ref: ${{ inputs.release_branch }}
41+
fetch-depth: 0
42+
43+
- name: Validate inputs
44+
run: |
45+
VERSION="${{ inputs.version }}"
46+
BRANCH="${{ inputs.release_branch }}"
47+
48+
# Validate version format (X.Y.Z)
49+
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
50+
echo "::error::Invalid version format: ${VERSION}. Expected X.Y.Z (e.g., 0.5.1)"
51+
exit 1
52+
fi
53+
54+
# Validate branch exists
55+
if ! git rev-parse "origin/${BRANCH}" >/dev/null 2>&1; then
56+
echo "::error::Branch ${BRANCH} does not exist"
57+
exit 1
58+
fi
59+
60+
echo "Preparing release v${VERSION} on ${BRANCH}"
61+
62+
- name: Update version files and commit directly to release branch
63+
run: |
64+
VERSION="${{ inputs.version }}"
65+
RELEASE_BRANCH="${{ inputs.release_branch }}"
66+
67+
# --- Update fallback_version in both pyproject.toml files ---
68+
sed -i "s/^fallback_version = .*/fallback_version = \"${VERSION}\"/" pyproject.toml
69+
sed -i "s/^fallback_version = .*/fallback_version = \"${VERSION}\"/" src/llama_stack_api/pyproject.toml
70+
71+
# --- Update llama-stack-client pins in pyproject.toml ---
72+
# Update the pin in [project.optional-dependencies] and [dependency-groups]
73+
# Match patterns like: "llama-stack-client>=X.Y.Z" or "llama-stack-client==X.Y.Z"
74+
sed -i -E "s/\"llama-stack-client[><=!]+[0-9]+\.[0-9]+\.[0-9]+[^\"]*\"/\"llama-stack-client==${VERSION}\"/" pyproject.toml
75+
76+
# Show what changed
77+
echo "=== Changes ==="
78+
git diff
79+
80+
# Check if there are changes
81+
if git diff --quiet; then
82+
echo "::warning::No changes detected. Version may already be set to ${VERSION}."
83+
exit 0
84+
fi
85+
86+
git config --local user.name "github-actions[bot]"
87+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
88+
git add pyproject.toml src/llama_stack_api/pyproject.toml
89+
git commit -s -m "chore: prepare release v${VERSION}"
90+
git push origin "HEAD:${RELEASE_BRANCH}"
91+
echo "Committed version updates directly to ${RELEASE_BRANCH}"

0 commit comments

Comments
 (0)