From c562b9e8bfd8d54b7d2eb425dfe1de568f81280b Mon Sep 17 00:00:00 2001 From: Prakriti Gupta Date: Wed, 4 Nov 2026 13:19:15 -0800 Subject: [PATCH] Post release notes to Slack on tag push --- .github/workflows/docs.yml | 3 + .github/workflows/release-slack.yml | 86 +++++++++++++++++++++++++++++ .github/workflows/tests.yml | 3 + docs/source/conf.py | 2 +- docs/source/index.md | 1 + docs/source/releasing.md | 54 ++++++++++++++++++ pyproject.toml | 13 ++++- 7 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/release-slack.yml create mode 100644 docs/source/releasing.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ec5f114..5a10b17 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -26,7 +26,10 @@ jobs: build: runs-on: ubuntu-latest steps: + # Full history and tags so setuptools-scm can derive the version. - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-python@v5 with: diff --git a/.github/workflows/release-slack.yml b/.github/workflows/release-slack.yml new file mode 100644 index 0000000..5536feb --- /dev/null +++ b/.github/workflows/release-slack.yml @@ -0,0 +1,86 @@ +name: Release Announcement + +on: + push: + tags: + - "libby-[0-9]*" + workflow_dispatch: + inputs: + tag: + description: "Existing release tag to announce, e.g. libby-0.1.0" + required: true + type: string + +permissions: + contents: read + +jobs: + announce: + name: Post release notes to Slack + runs-on: ubuntu-latest + + env: + TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} + + steps: + - name: Check out tag + uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} + fetch-depth: 0 + + # actions/checkout can leave the tag as a lightweight ref with no + # message, so fetch the annotated tag object explicitly. + - name: Fetch annotated tag + run: | + case "${TAG}" in + libby-[0-9]*) ;; + *) echo "::error::${TAG} does not look like libby-X.Y.Z"; exit 1 ;; + esac + git fetch --force origin "refs/tags/${TAG}:refs/tags/${TAG}" + if [ "$(git cat-file -t "refs/tags/${TAG}")" != "tag" ]; then + echo "::error::${TAG} is not an annotated tag; nothing to announce" + exit 1 + fi + echo "VERSION=${TAG#libby-}" >> "${GITHUB_ENV}" + + # The package version is derived from the tag by setuptools-scm, so make + # sure it reads this tag the way we expect before announcing it. + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Check package version matches tag + run: | + pip install --quiet "setuptools-scm>=8" + scm_version="$(python -m setuptools_scm)" + echo "setuptools-scm version: ${scm_version}" + if [ "${scm_version}" != "${VERSION}" ]; then + echo "::error::Tag ${TAG} gives package version ${scm_version}, expected ${VERSION}" + exit 1 + fi + + # The release notes are the tag message itself. Slack treats &, < and > + # as markup, so escape them in the notes. + - name: Compose Slack message + run: | + git for-each-ref "refs/tags/${TAG}" --format='%(contents)' > release-notes.txt + jq -n --rawfile notes release-notes.txt \ + --arg version "${VERSION}" \ + --arg tag "${TAG}" \ + --arg url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/tree/${TAG}" \ + '{ text: ( "*Libby \($version) released*\n\n" + + ($notes | sub("\n+$"; "") | gsub("&"; "&") | gsub("<"; "<") | gsub(">"; ">")) + + "\n\n<\($url)|View \($tag) on GitHub>" ) }' > slack-payload.json + cat slack-payload.json + + - name: Post to Slack + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + run: | + if [ -z "${SLACK_WEBHOOK_URL}" ]; then + echo "::error::SLACK_WEBHOOK_URL secret is not set" + exit 1 + fi + curl -fsS -X POST -H 'Content-type: application/json' \ + --data @slack-payload.json "${SLACK_WEBHOOK_URL}" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 181291a..a2b7dd8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,7 +24,10 @@ jobs: test: runs-on: ubuntu-latest steps: + # Full history and tags so setuptools-scm can derive the version. - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-python@v5 with: diff --git a/docs/source/conf.py b/docs/source/conf.py index f527660..f330759 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,7 +22,7 @@ try: release = _pkg_version("libby") except PackageNotFoundError: - release = "0.1.0" + release = "unknown" version = release # -- General configuration ---------------------------------------------------- diff --git a/docs/source/index.md b/docs/source/index.md index 85bd5d4..69db8e3 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -29,6 +29,7 @@ cli daemon keygrabber telemetry +releasing api/index ``` diff --git a/docs/source/releasing.md b/docs/source/releasing.md new file mode 100644 index 0000000..8b67d99 --- /dev/null +++ b/docs/source/releasing.md @@ -0,0 +1,54 @@ +# Releasing + +A libby release is an annotated git tag named `libby-MAJOR.MINOR.PATCH`, for +example `libby-0.2.0`. Pushing the tag does two things: + +- The package version becomes `0.2.0`. libby has no version number written in + the source: [setuptools-scm](https://setuptools-scm.readthedocs.io/) reads it + from the latest `libby-*` tag. A commit after a tag installs as a dev version + such as `0.2.1.dev3+g1a2b3c4`. +- The **Release Announcement** GitHub Actions workflow posts the tag message to + Slack as the release notes. + +## Cutting a release + +```bash +git switch main && git pull +git log --oneline libby-0.1.0..HEAD # what changed since the last release +git tag -a libby-0.2.0 # your editor opens; write the release notes +git push origin libby-0.2.0 +``` + +The tag message is posted to Slack word for word, under the heading +*Libby 0.2.0 released*. Slack formatting works in it: `*bold*`, `_italic_`, +`` `code` `` and `•` bullets. + +Example tag message: + +```text +• Add optional OpenTelemetry tracing and request metrics (#44) +• Add the telemetry backend: pinned releases, configs and unit templates (#48) +• Remove the libby argument from LibbyDaemon (#49) +``` + +## Rules + +- Always use an annotated tag (`git tag -a`). A lightweight tag has no + message, so the workflow fails and nothing is posted. +- Use exactly three numbers after `libby-`. Before posting, the workflow checks + that setuptools-scm reads the tag as the same version, and it fails if not. +- Never move or reuse a tag once it is pushed. If a release is wrong, tag a new + patch version. + +## Re-posting an announcement + +If the Slack post failed, for example because the webhook secret was missing, go +to **Actions → Release Announcement → Run workflow** and enter the existing tag +(e.g. `libby-0.2.0`). + +## One-time setup + +The workflow posts through a Slack +[incoming webhook](https://api.slack.com/messaging/webhooks). The webhook URL +must be stored as the repository secret `SLACK_WEBHOOK_URL` (Settings → Secrets +and variables → Actions). diff --git a/pyproject.toml b/pyproject.toml index 39deb23..6ea2c92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["setuptools>=68", "wheel"] +requires = ["setuptools>=68", "setuptools-scm>=8", "wheel"] build-backend = "setuptools.build_meta" [project] name = "libby" -version = "0.1.0" +dynamic = ["version"] description = "Libby: a tiny messaging library which uses bamboo with a ZMQ transport" readme = "README.md" requires-python = ">=3.9" @@ -43,6 +43,15 @@ Homepage = "https://caltechopticalobservatories.github.io/libby/" Repository = "https://github.com/CaltechOpticalObservatories/libby" "Bug Tracker" = "https://github.com/CaltechOpticalObservatories/libby/issues" +# The version comes from the most recent libby-X.Y.Z git tag (see +# docs/source/releasing.md); untagged commits get X.Y.(Z+1).devN+g. +[tool.setuptools_scm] +tag_regex = "^libby-(?P\\d+\\.\\d+\\.\\d+.*)$" +fallback_version = "0.0.0+unknown" + +[tool.setuptools_scm.scm.git] +describe_command = "git describe --dirty --tags --long --match 'libby-*'" + [tool.setuptools.packages.find] where = ["."] include = ["libby", "libby.*"]