Skip to content

Release tooling: tag-based versioning, milestone prompt, and Docker images via GitHub Actions - #821

Merged
wu-sheng merged 7 commits into
mainfrom
fix/release-script-version-arg
Aug 12, 2026
Merged

Release tooling: tag-based versioning, milestone prompt, and Docker images via GitHub Actions#821
wu-sheng merged 7 commits into
mainfrom
fix/release-script-version-arg

Conversation

@wu-sheng

@wu-sheng wu-sheng commented Aug 12, 2026

Copy link
Copy Markdown
Member

Supersedes #820, which is folded in here. Three fixes to the release flow, all hit while
releasing 9.7.0.


1. Identify the release by its tag, not the checked-out branch

Every step after prepare derived the version from the working branch:

version=$(git describe --tags --abbrev=0 | sed 's/^v//')

Release tags are created on release/x.y.z and never become ancestors of main, so
git describe cannot see them from main. By the time vote-passed runs — days later,
after the release PR is merged and the branch auto-deleted — describe walks straight past
every release tag to an unrelated ancient one:

$ git describe --tags --abbrev=0 origin/main
v3.2.6

Not a loud failure: promote would svn mv a 3.2.6 path, and docker would push
apache/skywalking-java-agent:3.2.6-* to Docker Hub under real-looking version tags.

The tag is the only thing that still pins a release once the branch is gone, and it is
already what the rest of the flow acts on — release:perform builds from it, the source
tar is cut from it, the vote email quotes its commit IDs. resolve_version() now takes an
explicit argument, else $RELEASE_VERSION, else the highest vX.Y.Z tag; it validates the
shape and refuses to run when the tag does not exist. Wired into stage, upload,
email, promote, docker, each of which accepts an optional version argument.

vote-passed also prints what it is about to publish and asks for confirmation, since SVN
dist/release and Docker Hub are both public and awkward to walk back.

2. Prompt for the next milestone ID (was #820)

Resetting CHANGES.md for the next development version wrote a placeholder nothing ever
filled in:

All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/xxx?closed=1)

Unless the release manager spotted it, the placeholder rode the release PR into main and
the next change log shipped pointing at a dead link — which is what happened for 9.7.0 and
had to be corrected by hand.

prepare now asks for the ID, up front next to the version confirmation rather than after
the multi-minute release:prepare build. The answer is validated as numeric and
cross-checked against the milestone title on apache/skywalking, so reusing last release's
ID is caught:

[WARN]   Milestone 249 is 'Java - 9.7.0', expected 'Java - 9.8.0'. Double-check it.

NEXT_MILESTONE=<id> answers non-interactively; a blank answer keeps the old placeholder
behaviour but now warns.

3. Publish release Docker images from GitHub Actions

Official images were pushed by the release manager running make docker.push.* locally: a
multi-arch build of six variants on a laptop, over whatever network it happens to be on,
with a personal Docker Hub session and no record of what ran.

publish-docker.yaml already builds these images on every push to main — it just sends
them to ghcr.io tagged with the commit SHA. It now also has a release: released trigger
whose path targets Docker Hub, matching apache/skywalking. Published tags are unchanged:

apache/skywalking-java-agent:x.y.z-{alpine,java8,java11,java17,java21,java25}
  • Trigger is released, not published, so a pre-release does not ship official images.
  • The matrix gains alpine on releases only; per-commit development images are untouched.
  • QEMU and buildx are set up explicitly, since the Makefile builds linux/amd64 and linux/arm64.

Creating the GitHub Release is what fires it, so that becomes a release step:
github-release publishes the tag using changes/changes-x.y.z.md as its notes, and
vote-passed runs it where it used to run docker. docker remains as a documented
fallback.

Important

This needs DOCKERHUB_USER and DOCKERHUB_TOKEN repository secrets, which
apache/skywalking already has but this repository does not yet. Until they are added,
the release path will fail at login and the fallback release.sh docker x.y.z is needed.

The three third-party actions are pinned to the same reviewed SHAs already used in
apache/skywalking, so they are on the ASF allow-list.


Testing

  • bash -n tools/releasing/release.sh passes; workflow YAML parses.
  • resolve_version exercised from a branch cut off main, where git describe returns the broken v3.2.6:
Input Result
(none) 9.7.0
9.7.0 / v9.7.0 9.7.0
RELEASE_VERSION=9.7.0 9.7.0
9.6.0 9.6.0 — older releases stay addressable
9.9.9 aborts, tag does not exist
abc aborts, bad shape
  • Milestone prompt exercised against the live GitHub API across correct / stale / nonexistent / non-numeric / blank inputs.
  • Generated CHANGES.md is byte-identical to the hand-written 9.7.0 correction.
  • Matrix expression renders 6 jobs on release, 5 on push.

🤖 Generated with Claude Code

wu-sheng and others added 3 commits August 12, 2026 17:31
Every step after `prepare` derived the version from the working branch:

    version=$(git describe --tags --abbrev=0 | sed 's/^v//')

Release tags are created on release/x.y.z and never become ancestors of main, so
`git describe` cannot see them from main. By the time `vote-passed` runs - days
later, after the release PR is merged and the branch auto-deleted - describe walks
straight past every release tag to an unrelated ancient one, v3.2.6 in this
repository. That is not a loud failure: `promote` would svn mv a 3.2.6 path, and
`docker` would push apache/skywalking-java-agent:3.2.6-{alpine,java8,...} to
Docker Hub under real version tags.

The tag is the only thing that still pins a release once the branch is gone, and
it is already what the rest of the flow acts on: release:perform builds from the
tag, the source tar is cut from the tag, and the vote email quotes the tag's
commit IDs. Resolve the version from the tag list too - branch-independent, and it
outlives the release branch.

resolve_version() takes an explicit argument, else $RELEASE_VERSION, else the
highest vX.Y.Z tag. It validates the x.y.z shape and refuses to run when the tag
does not exist, so a typo aborts instead of addressing a path that was never
released. Wired into stage, upload, email, promote and docker, each of which now
accepts an optional version argument; prepare-vote threads its version through
explicitly rather than re-deriving it.

vote-passed additionally prints what it is about to publish - tag, SVN move,
Docker Hub tags, old version being removed - and asks for confirmation, since both
destinations are public and awkward to walk back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When cmd_prepare resets CHANGES.md for the next development version it wrote a
literal placeholder:

  All issues and pull requests are [here](https://github.com/apache/skywalking/milestone/xxx?closed=1)

Nothing in the release flow ever filled that in, so the placeholder rode the
release PR into main unless someone noticed and edited it by hand, leaving the
next version's change log pointing at a dead milestone link.

Ask for the milestone ID instead. The prompt is raised up front, next to the
version confirmation, rather than after release:prepare, so the release does not
stop for input in the middle of a multi-minute build. The answer is validated as
numeric and cross-checked against the milestone's title on apache/skywalking, so
a typo or a stale ID (for example last release's milestone) is reported before
anything is committed. NEXT_MILESTONE=<id> answers non-interactively for scripted
runs, and a blank answer keeps the old placeholder behaviour but now warns.

gh api writes its error body to stdout on failure, so the title lookup gates on
gh's exit status; otherwise a 404 payload would be reported as the milestone name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Official images were pushed by the release manager running `make docker.push.*`
locally, from `vote-passed`. That means a multi-arch build of six variants on a
laptop, over whatever network it happens to be on, authenticated with a personal
Docker Hub session, with no record of what ran.

publish-docker.yaml already builds these images for every push to main; it just
sends them to ghcr.io tagged with the commit SHA. Give it a `release: released`
trigger and point that path at Docker Hub instead, matching what apache/skywalking
does. On a release it uses HUB=apache, NAME=skywalking-java-agent and the tag name
with `v` stripped, so the published tags are unchanged:
apache/skywalking-java-agent:x.y.z-{alpine,java8,java11,java17,java21,java25}.

The trigger is `released`, not `published`, so cutting a pre-release does not ship
official images. The matrix gains alpine on releases only, leaving the per-commit
development images as they are. QEMU and buildx are set up explicitly because the
Makefile builds linux/amd64 and linux/arm64.

Creating the GitHub Release is what fires this, so the script now does that as a
release step: `github-release` publishes the tag with changes/changes-x.y.z.md as
its notes, and vote-passed runs it where it used to run `docker`. `docker` stays as
a documented fallback for when the workflow fails.

Requires DOCKERHUB_USER and DOCKERHUB_TOKEN repository secrets, which
apache/skywalking already has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wu-sheng wu-sheng changed the title Identify the release by its tag, not the checked-out branch Release tooling: tag-based versioning, milestone prompt, and Docker images via GitHub Actions Aug 12, 2026
The previous local `cmd_docker` extracted
apache-skywalking-java-agent-x.y.z.tgz - the artifact that was signed, uploaded to
dist and voted on - and fed that directory to the Dockerfile as ARG DIST. Moving
the push into GitHub Actions quietly dropped that: build-tar ran `make build`, so
the published image would have contained a recompile of the tag rather than the
bits the PMC approved.

Skip build-tar on release events and download the tarball from the Apache
distribution area instead, then prove it is the right one before it goes into an
image: sha512 rules out a truncated download, and verifying the detached signature
against the project KEYS file rules out anything the release manager did not sign.
`release.sh promote` does the svn mv from dist/dev to dist/release immediately
before the GitHub Release that triggers this workflow, so the file is in place by
the time the job runs.

build-docker needs `always()` in its condition, since a skipped build-tar would
otherwise skip it as well. Development images are unaffected and still come from
the artifact build-tar uploads.

Verified against the real 9.7.0 artifact: sha512 matches, gpg reports a good
signature from the release manager's key, and the tarball unpacks to
skywalking-agent/, which is what the Makefile passes as ARG DIST.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wu-sheng

Copy link
Copy Markdown
Member Author

Pushed 02a563e after review feedback: the release path was rebuilding the agent rather than shipping the artifact that was voted on.

The previous local cmd_docker extracted apache-skywalking-java-agent-x.y.z.tgz — the tarball that was signed, uploaded to dist, and voted on — and fed that directory to the Dockerfile as ARG DIST. Moving the push into Actions quietly dropped that property: build-tar ran make build, so the published image would have contained a recompile of the tag rather than the approved bits.

Now build-tar is skipped on release events, and the tarball is downloaded from the Apache distribution area and verified before it can enter an image:

curl -fsSL -O "${BASE}/${TARBALL}"        # dist/release/skywalking/java-agent/x.y.z
curl -fsSL -O "${BASE}/${TARBALL}.asc"
curl -fsSL -O "${BASE}/${TARBALL}.sha512"

sha512sum -c "${TARBALL}.sha512"
curl -fsSL https://downloads.apache.org/skywalking/KEYS | gpg --import
gpg --verify "${TARBALL}.asc" "${TARBALL}"

sha512 rules out a truncated download; the signature check rules out anything the release manager did not sign. promote does the svn mv from dist/dev to dist/release immediately before the GitHub Release that triggers the workflow, so the file is in place when the job runs.

build-docker needed always() in its condition, since a skipped build-tar would otherwise skip it too. Development images are unchanged and still come from the artifact build-tar uploads.

Verified against the real 9.7.0 artifact:

apache-skywalking-java-agent-9.7.0.tgz: OK
gpg:                using RSA key D360AB2AB20B28403270E2CBE8608938DB25E06B
gpg: Good signature from "Sheng Wu <wusheng@apache.org>"
top-level: skywalking-agent/

All three dist URLs return HTTP 200 and the KEYS file carries 29 public keys.

wu-sheng and others added 3 commits August 12, 2026 21:57
DOCKERHUB_USER and DOCKERHUB_TOKEN are provisioned by ASF INFRA on request and are
not set on this repository yet, so the first release to use this workflow would
have died inside docker/login-action with nothing pointing at the cause.

Check them at the top of the release path and say what is missing, where they come
from, and how to publish in the meantime. Document the request process alongside,
since .asf.yaml cannot set secrets and it has to go through an INFRA ticket.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The six image variants differ only in the JRE they sit on. The Dockerfile takes
BASE_IMAGE and ADDs the same DIST directory, and the agent is Java 8 bytecode that
runs on all of them, so one package serves every variant - which is what the old
local `make docker.push.alpine docker.push.java8 ...` did from a single extracted
tarball.

The matrix I added ignored that and had each of the six jobs download and verify
its own copy of the 46MB release tarball: 276MB per release pulled from
dist.apache.org, which is SVN-backed rather than a CDN, plus six redundant
signature checks.

Fold the acquisition back into the single upstream job, which now either compiles
the agent (development images) or downloads and verifies the voted tarball
(releases), and hands the result to the matrix as an artifact. That also drops the
`always()` condition the skipped-job arrangement needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wu-sheng wu-sheng added this to the 9.8.0 milestone Aug 12, 2026
@wu-sheng wu-sheng added the enhancement New feature or request label Aug 12, 2026
@wu-sheng
wu-sheng merged commit afede1b into main Aug 12, 2026
10 checks passed
@wu-sheng
wu-sheng deleted the fix/release-script-version-arg branch August 12, 2026 22:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants