-
Notifications
You must be signed in to change notification settings - Fork 70
ci: drive the whole release from a single tag push #1766
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
d516949
8404e47
42ba9a1
9b06388
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| # (C) 2026 GoodData Corporation | ||
| name: Release tag checks | ||
| description: > | ||
| Answers the two questions the release workflows ask about the tag that triggered them. | ||
| They are not the same question, and they disagree exactly on the backport cases: | ||
|
|
||
| is_latest -- is this the highest version released so far? Decides the "Latest" badge | ||
| on the GitHub release. A patch of an older line (v1.60.1 while v1.73.0 | ||
| exists) must not take it. | ||
| is_on_master -- is this tag an ancestor of the default branch? Decides whether the | ||
| documentation is rebuilt. A patch is branched from a release branch and | ||
| never merged back, so its tree is behind master; deploying it with --prod | ||
| would revert any documentation merged since that release. | ||
|
|
||
| Requires the repository to be checked out with fetch-depth: 0, so that every tag and the | ||
| default branch are present. A shallower checkout fails the ancestry check loudly rather | ||
| than answering either question wrongly. On a non-tag ref (e.g. a manual workflow_dispatch) | ||
| both outputs are 'true'. | ||
|
|
||
| outputs: | ||
| is_latest: | ||
| description: "'true' when the triggering tag is the highest v*.*.* tag, otherwise 'false'" | ||
| value: ${{ steps.check.outputs.is_latest }} | ||
| is_on_master: | ||
| description: "'true' when the triggering tag is an ancestor of the default branch, otherwise 'false'" | ||
| value: ${{ steps.check.outputs.is_on_master }} | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - id: check | ||
| shell: bash | ||
| env: | ||
| TAG: ${{ github.ref_name }} | ||
| REF_TYPE: ${{ github.ref_type }} | ||
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| if [ "$REF_TYPE" != "tag" ]; then | ||
| echo "Ref '$TAG' is not a tag; nothing to guard against." | ||
| echo "is_latest=true" >> "$GITHUB_OUTPUT" | ||
| echo "is_on_master=true" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Only stable vX.Y.Z tags count. The trigger glob v*.*.* would also match something | ||
| # like v0.0.1-test, which sort -V could pick as the highest -- marking every real | ||
| # release from then on as not-latest. A non-stable TAG never equals a stable | ||
| # $highest, so it correctly comes out as not-latest without a separate check. | ||
| highest=$(git tag -l 'v*.*.*' | { grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' || true; } | sort -V | tail -n 1) | ||
| if [ -n "$highest" ] && [ "$TAG" = "$highest" ]; then is_latest=true; else is_latest=false; fi | ||
|
|
||
| if git merge-base --is-ancestor "$TAG" "origin/$DEFAULT_BRANCH"; then | ||
| is_on_master=true | ||
| else | ||
| is_on_master=false | ||
| fi | ||
|
|
||
| echo "tag=$TAG highest=$highest is_latest=$is_latest is_on_master=$is_on_master" | ||
| { | ||
| echo "is_latest=$is_latest" | ||
| echo "is_on_master=$is_on_master" | ||
| } >> "$GITHUB_OUTPUT" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,110 @@ | ||
| # Repository maintenance and release | ||
|
|
||
| ## How to release | ||
| * manually run [Bump version & trigger release](.github/workflows/bump-version.yaml) workflow | ||
| * after the previous workflow finishes, dispatch the GitHub workflow [Netlify Deploy](.github/workflows/netlify-deploy.yaml) on the `master` branch (takes ~15 minutes) | ||
| * The styling of the documentation is taken from the `master` branch. For more details see [generate.sh](scripts/generate.sh). | ||
| * after the previous workflow finishes, push tag | ||
| * the version should be the same as the one in [Bump version & trigger release](.github/workflows/bump-version.yaml) workflow log | ||
| * checkout latest master branch and tag it `vX.Y.Z` | ||
| * push the tag to the gooddata/gooddata-python-sdk repository (e.g. `git push <remote> vX.Y.Z`) | ||
| Manually run the [Bump version & trigger release](.github/workflows/bump-version.yaml) workflow and pick the | ||
| bump type. That is the whole release. | ||
|
|
||
| The workflow bumps the version, creates the `rel/X.Y.Z` branch, merges it to `master`, and pushes the tag | ||
| `vX.Y.Z`. That tag push triggers two workflows in parallel: | ||
|
|
||
| * [Build Python Package and Create Release](.github/workflows/build-release.yaml) — builds every component, | ||
| creates the GitHub release, publishes to PyPI, and posts to `#releases`. | ||
| * [Netlify Deploy](.github/workflows/netlify-deploy.yaml) — builds and publishes the documentation | ||
| (takes ~15 minutes, so the packages reach PyPI well before the docs go live). | ||
|
|
||
| The styling of the documentation is taken from the `master` branch. For more details see | ||
| [generate.sh](scripts/generate.sh). | ||
|
|
||
| ### Recovering a stuck release | ||
| Both downstream workflows key off the tag, so a release that stalled can be resumed by hand: | ||
|
|
||
| * if the tag was never pushed, check out the `Release X.Y.Z` commit on `master`, tag it `vX.Y.Z`, and push the | ||
| tag to the gooddata/gooddata-python-sdk repository (e.g. `git push <remote> vX.Y.Z`) | ||
| * if only the documentation failed, dispatch [Netlify Deploy](.github/workflows/netlify-deploy.yaml) manually; | ||
| it does not need the tag | ||
|
|
||
| The tag has to be pushed with a personal access token. GitHub does not trigger workflows from pushes made with | ||
| the default `GITHUB_TOKEN`, so a tag pushed by a workflow using it would silently start nothing. | ||
|
|
||
| ## How to patch an already released version | ||
| Use this whenever a release must contain a specific fix and *not* everything currently on `master` — whether | ||
| that is an old line (1.60 while `master` is at 1.73) or the newest one. | ||
|
|
||
| Do **not** use the [Bump version & trigger release](.github/workflows/bump-version.yaml) workflow for this. Its | ||
| last step is `git checkout master && git merge`, which would drag the old code and version numbers onto | ||
| `master`. Its `patch` bump type means "release master as a patch", not "patch the released line". | ||
|
|
||
| Only the tagging is automated; the rest is manual by nature. | ||
|
|
||
| **Prerequisite:** the fix is already merged to `master`. The patch branch is never merged back, so this is what | ||
| keeps the fix from being lost in the next release. | ||
|
|
||
| 1. **Pick the base and the new version.** List what the line already has with | ||
| `git branch -rl '<remote>/rel/1.60.*'`. The base is the newest of them, and the new version increments the | ||
| patch component **of that base** — so `rel/1.60.0` gives `1.60.1`, but if the line was already patched to | ||
| `rel/1.60.2` the next one is `1.60.3`. The steps below use `1.60.1`; substitute your version throughout. | ||
|
|
||
| 2. **Create the release branch first**, so the fix has somewhere to be reviewed into: | ||
| ```bash | ||
| git fetch <remote> | ||
| git checkout -b rel/1.60.1 <remote>/rel/1.60.0 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Branch repeated patches from the selected base. When the newest base is Proposed correction- git checkout -b rel/1.60.1 <remote>/rel/1.60.0
+ git checkout -b rel/1.60.3 <remote>/rel/1.60.2🤖 Prompt for AI Agents |
||
| git push <remote> rel/1.60.1 | ||
| ``` | ||
|
|
||
| 3. **Cherry-pick the fix through a pull request:** | ||
| ```bash | ||
| git checkout -b fix/backport-1.60 rel/1.60.1 | ||
| git cherry-pick <sha-on-master> | ||
| git push <remote> fix/backport-1.60 | ||
| ``` | ||
| Open the PR against `rel/1.60.1`. The [pre-merge pipeline](.github/workflows/pre-merge.yaml) runs because it | ||
| triggers on `rel/**`. Merge once it is green. | ||
|
|
||
| 4. **Bump the version on the release branch.** These commands mirror the *Install dependencies* through | ||
| *Bump version in codebase* steps of [bump-version.yaml](.github/workflows/bump-version.yaml) — if that | ||
| workflow gains or reorders a step, update this block with it: | ||
| ```bash | ||
| git checkout rel/1.60.1 && git pull | ||
| uv sync --only-group release --locked | ||
| uv run python ./scripts/bump_doc_dependencies.py 1.60.1 | ||
| make release-ci VERSION=1.60.1 | ||
| git add -A && git commit -m "Release 1.60.1" | ||
| git push <remote> rel/1.60.1 | ||
| ``` | ||
| `git add -A` rather than `commit -am`, matching the workflow, so a newly created file is not dropped. On an | ||
| older line `uv sync --locked` can fail if the lock file predates the current uv; re-lock if so. | ||
|
|
||
| 5. **Tag it.** This is the only trigger; everything after it is automatic: | ||
| ```bash | ||
| git tag v1.60.1 | ||
| git push <remote> v1.60.1 | ||
| ``` | ||
|
|
||
| The release is then built and published exactly like any other. Two things differ, and | ||
| [release-tag-checks](.github/actions/release-tag-checks/action.yaml) handles both: the GitHub release does not | ||
| take the "Latest" badge from the newest version, and the documentation is not rebuilt — the docs build checks | ||
| out the triggering tag, so publishing from one would put an outdated site live. | ||
|
|
||
| > **A tag runs the workflows as they exist *at that tag*, not on master.** Release lines branched before the | ||
| > release automation was added therefore run their own older copies, in which `make_latest` is hardcoded to | ||
| > `true`. Before tagging such a line, cherry-pick `.github/workflows/build-release.yaml` and | ||
| > `.github/actions/release-tag-checks/` onto `rel/X.Y.Z` — otherwise the patch takes the "Latest" badge, which | ||
| > also changes what `GET /releases/latest` returns. If you only notice afterwards, untick "Set as the latest | ||
| > release" on the GitHub release by hand. Those older copies have no tag trigger on the docs workflow, so the | ||
| > documentation is safe either way. | ||
|
|
||
| ### What the documentation will show | ||
| The docs site keeps the four newest release branches, sorted by `major.minor`, and a section is named after the | ||
| `major.minor` only. Consequences worth knowing before someone goes looking: | ||
|
|
||
| * A patch never publishes its own documentation — the deploy is gated on the tag being on master. `rel/1.72.1` | ||
| does take over the `1.72` section from `rel/1.72.0`, but only at the next deploy from master: the following | ||
| release, or a manual dispatch of [Netlify Deploy](.github/workflows/netlify-deploy.yaml) if you need it | ||
| sooner. | ||
| * Both branches still occupy a slot of the four, so one patch inside the window drops the site from four | ||
| displayed versions to three. | ||
| * Patching an old line (`rel/1.60.1` while `master` is at 1.73) falls outside the window entirely and never | ||
| appears in the docs. | ||
|
|
||
| ### How-to dev release | ||
| To publish current master as a dev release version, use [Dev release from master](.github/workflows/dev-release.yaml) GitHub workflow. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Reject non-stable tags before release workflows continue.
v*.*.*also matches tags such asv1.2.3-rc1. This code marks such a tag as not latest but exits successfully.github_releasecan then create a non-prerelease GitHub release, and a tag onmastercan deploy documentation to production.Fail this action for tag names that do not match the stable version format, or expose an
is_stableoutput and require it in both release jobs.Proposed fix
🤖 Prompt for AI Agents