Skip to content

Commit 11e1a02

Browse files
committed
ci: support LTS patching of old versions
Make the release workflow branch-aware so old minor lines can receive patch releases from their long-lived rel/X.Y.0 maintenance branches, needed now that we offer LTS support for CN / on-prem deployments. - master: major/minor only -- creates rel/X.Y.0, merges back, tags vX.Y.0 - rel/X.Y.0: patch only -- bumps and commits onto the branch, tags vX.Y.Z, with no new branch and no merge back to master - validation fails fast on wrong branch/bump combinations Multiple patches accumulate on the same rel/X.Y.0 branch; bump_version.py increments from the branch's current version (unchanged). To keep old-line patches from mislabelling the newest release: - scripts/is_latest_tag.sh reports whether a tag is the highest semver - build-release.yaml sets make_latest dynamically, so old-line patches publish to PyPI but produce a non-latest GitHub release - netlify-deploy.yaml gates the docs deploy on is_latest (manual workflow_dispatch always deploys), so old-line patches skip docs MAINTENANCE.md documents the consolidated release flow and the LTS patch process (fix to master -> cherry-pick onto rel/X.Y.0 -> dispatch patch).
1 parent 7bd20b2 commit 11e1a02

5 files changed

Lines changed: 108 additions & 28 deletions

File tree

.github/workflows/build-release.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ jobs:
6363
permissions:
6464
contents: write
6565
steps:
66+
- name: Checkout
67+
uses: actions/checkout@v5
68+
with:
69+
fetch-depth: 0
70+
- name: Determine if this tag is the latest release
71+
id: latest
72+
run: echo "is_latest=$(bash scripts/is_latest_tag.sh ${{ github.ref_name }})" >> "$GITHUB_OUTPUT"
6673
- name: Obtain artifacts
6774
uses: actions/download-artifact@v6
6875
with:
@@ -83,7 +90,7 @@ jobs:
8390
token: "${{ secrets.GITHUB_TOKEN }}"
8491
draft: false
8592
prerelease: false
86-
make_latest: true
93+
make_latest: ${{ steps.latest.outputs.is_latest }}
8794
files: |
8895
dist/**/*.whl
8996
dist/**/*.tar.gz

.github/workflows/bump-version.yaml

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@ jobs:
2929
with:
3030
token: ${{ secrets.TOKEN_GITHUB_YENKINS_ADMIN }} # needed to push to the protected branch
3131

32+
- name: Validate branch and bump type
33+
run: |
34+
REF="${{ github.ref_name }}"
35+
BUMP="${{ github.event.inputs.bump_type }}"
36+
if [ "$REF" = "master" ]; then
37+
if [ "$BUMP" = "patch" ]; then
38+
echo "::error::Patches must be released from the line's rel/X.Y.0 branch, not master."
39+
exit 1
40+
fi
41+
elif [[ "$REF" == rel/* ]]; then
42+
if [ "$BUMP" != "patch" ]; then
43+
echo "::error::Only patch releases are allowed on rel/* branches."
44+
exit 1
45+
fi
46+
else
47+
echo "::error::Releases can only run on master or a rel/* branch (got: $REF)."
48+
exit 1
49+
fi
50+
3251
- name: Install uv
3352
uses: astral-sh/setup-uv@v7
3453

@@ -50,26 +69,26 @@ jobs:
5069
run: |
5170
make release-ci VERSION=${{ steps.bump.outputs.new_version }}
5271
53-
- name: Specify release branch
54-
id: branch
55-
run: |
56-
if [ "${{ github.event.inputs.bump_type }}" == "patch" ]; then
57-
RELEASE_BRANCH="patch/${{ steps.bump.outputs.new_version }}"
58-
else
59-
RELEASE_BRANCH="rel/${{ steps.bump.outputs.new_version }}"
60-
fi
61-
echo "release_branch=$RELEASE_BRANCH" >> $GITHUB_OUTPUT
62-
63-
- name: Create and push the new version ${{steps.bump.outputs.new_version}}
72+
- name: Commit, tag and push the new version ${{steps.bump.outputs.new_version}}
6473
run: |
6574
git config user.name github-actions
6675
git config user.email github-actions@github.com
67-
git checkout -b ${{ steps.branch.outputs.release_branch }}
68-
git add -A
69-
git commit -m "Release ${{steps.bump.outputs.new_version}}"
70-
git push origin ${{ steps.branch.outputs.release_branch }}
71-
git checkout master
72-
git merge ${{ steps.branch.outputs.release_branch }}
73-
git push origin master
74-
git tag v${{ steps.bump.outputs.new_version }}
75-
git push origin v${{ steps.bump.outputs.new_version }}
76+
NEW_VERSION="${{ steps.bump.outputs.new_version }}"
77+
REF="${{ github.ref_name }}"
78+
if [ "$REF" = "master" ]; then
79+
RELEASE_BRANCH="rel/${NEW_VERSION}"
80+
git checkout -b "$RELEASE_BRANCH"
81+
git add -A
82+
git commit -m "Release ${NEW_VERSION}"
83+
git push origin "$RELEASE_BRANCH"
84+
git checkout master
85+
git merge "$RELEASE_BRANCH"
86+
git push origin master
87+
else
88+
# rel/* patch release: commit onto the maintenance branch, no master merge
89+
git add -A
90+
git commit -m "Release ${NEW_VERSION}"
91+
git push origin "HEAD:${REF}"
92+
fi
93+
git tag "v${NEW_VERSION}"
94+
git push origin "v${NEW_VERSION}"

.github/workflows/netlify-deploy.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,27 @@ on:
66
- v*.*.*
77

88
jobs:
9+
check-latest:
10+
runs-on: ubuntu-latest
11+
outputs:
12+
is_latest: ${{ steps.latest.outputs.is_latest }}
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v5
16+
with:
17+
fetch-depth: 0
18+
- name: Determine if this deploy is for the latest release
19+
id: latest
20+
run: |
21+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
22+
echo "is_latest=true" >> "$GITHUB_OUTPUT"
23+
else
24+
echo "is_latest=$(bash scripts/is_latest_tag.sh ${{ github.ref_name }})" >> "$GITHUB_OUTPUT"
25+
fi
26+
927
netlify-deploy:
28+
needs: check-latest
29+
if: needs.check-latest.outputs.is_latest == 'true'
1030
runs-on: ubuntu-latest
1131
steps:
1232
- name: Checkout

MAINTENANCE.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
# Repository maintenance and release
22

33
## How to release
4-
* manually run [Bump version & trigger release](.github/workflows/bump-version.yaml) workflow
5-
* after the previous workflow finishes, dispatch the GitHub workflow [Netlify Deploy](.github/workflows/netlify-deploy.yaml) on the `master` branch (takes ~15 minutes)
6-
* The styling of the documentation is taken from the `master` branch. For more details see [generate.sh](scripts/generate.sh).
7-
* after the previous workflow finishes, push tag
8-
* the version should be the same as the one in [Bump version & trigger release](.github/workflows/bump-version.yaml) workflow log
9-
* checkout latest master branch and tag it `vX.Y.Z`
10-
* push the tag to the gooddata/gooddata-python-sdk repository (e.g. `git push <remote> vX.Y.Z`)
4+
5+
Releases are fully automated by a single workflow dispatch.
6+
7+
### Standard release (major / minor)
8+
1. Manually run the [Release](.github/workflows/bump-version.yaml) workflow on the `master` branch, choosing `major` or `minor`.
9+
2. That single run bumps the version, creates the long-lived `rel/X.Y.0` maintenance branch, merges back to `master`, and pushes the `vX.Y.0` tag.
10+
3. The tag push automatically fans out — no manual steps remain:
11+
* [Build Python Package and Create Release](.github/workflows/build-release.yaml) builds all components, publishes them to PyPI, creates the GitHub release, and notifies Slack.
12+
* [Netlify Deploy](.github/workflows/netlify-deploy.yaml) rebuilds and deploys the documentation (styling is taken from the `master` branch; see [generate.sh](scripts/generate.sh)).
13+
14+
> `patch` is intentionally rejected on `master` — patches are released from the line's `rel/X.Y.0` branch (see below).
15+
16+
### Releasing an LTS patch (patching an old version)
17+
Old minor lines (e.g. for CN / on-prem LTS support) are patched from their long-lived `rel/X.Y.0` maintenance branch:
18+
1. Merge the fix to `master` via a normal PR.
19+
2. Cherry-pick the fix onto the target `rel/X.Y.0` branch.
20+
3. Run the [Release](.github/workflows/bump-version.yaml) workflow with the branch selector set to `rel/X.Y.0` and `bump_type = patch` (only `patch` is accepted on `rel/*`).
21+
4. The workflow bumps the patch on that branch (e.g. `1.5.0``1.5.1`), commits it onto `rel/X.Y.0`, and pushes the `vX.Y.Z` tag. It does **not** create a new branch or merge to `master`.
22+
5. The tag publishes every component to PyPI and creates a **non-latest** GitHub release. Documentation is intentionally **not** redeployed for old-line patches.
23+
24+
Subsequent patches repeat steps 1-4 on the same `rel/X.Y.0` branch; each reads the branch's current version and increments the patch component (`1.5.1``1.5.2` → …).
1125

1226

1327
### How-to dev release

scripts/is_latest_tag.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
# (C) 2026 GoodData Corporation
3+
# Prints "true" if the given tag is the highest semver among all v*.*.* tags,
4+
# otherwise "false". Requires the repository's tags to be present locally
5+
# (callers should checkout with fetch-depth: 0).
6+
# Usage: is_latest_tag.sh vX.Y.Z
7+
set -e
8+
9+
tag="$1"
10+
if [ -z "$tag" ]; then
11+
echo "Usage: is_latest_tag.sh vX.Y.Z" >&2
12+
exit 1
13+
fi
14+
15+
highest=$(git tag -l 'v*.*.*' | sort -V | tail -1)
16+
if [ "$tag" = "$highest" ]; then
17+
echo "true"
18+
else
19+
echo "false"
20+
fi

0 commit comments

Comments
 (0)