Files
s3-gateway/docs/multi-version-pipeline.md
T
57_WolveandClaude Opus 5 73d5d2f3f1 patches: retire pre-v1.10.0 series and make the matrix an allowlist
v1.10.0 carries upstream CVE fixes that the older releases predate, so
stop building them. Their series move to patches/archive/<tag>/ rather
than being deleted — still available for a deliberate rebuild, just not
shipped — and ci/support-matrix.yaml now lists v1.10.0 alone.

Dropping the matrix entries was not enough on its own. Two gaps made
"not built" untrue:

* apply-patches.sh only consulted the matrix `if command -v yq`, and the
  stock golang CI container has no yq — so in CI the registry was never
  actually read. The tag fell through to patches/<tag> every time.

* An empty or missing patch directory was a warning and exit 0, i.e.
  "building vanilla upstream". Archiving the directories would therefore
  have made a dispatch for v1.8.0 publish an un-de-branded, unpatched,
  CVE-vulnerable build under this project's release name — the exact
  outcome we are trying to prevent.

So the matrix is now an enforced allowlist: a tag it does not list exits
1, as does a patch directory with no patches in it. It is parsed with yq
when present and with a small awk parse otherwise, so the allowlist holds
in the CI container too. If the parse ever broke, an unmatched tag is
refused rather than built, so the failure mode stays closed.

PATCH_DIR remains the one bypass, and is now the documented way to
rebuild an archived series on purpose. That path also needed a fix: the
patch list is globbed before we cd into the source tree, so a relative
PATCH_DIR — the form the new error message suggests — globbed fine and
then failed in git am. PATCH_DIR is now resolved to an absolute path,
with a missing directory reported as such.

Also re-pointed the runnable examples in README.md, ci/run.sh and the
workflow's dispatch hint from v1.5.0 to v1.10.0; following them verbatim
would now hit the refusal.

Verified by exercising every branch: v1.10.0 applies; v1.8.0 is refused
with exit 1; PATCH_DIR=patches/archive/v1.8.0 rebuilds the archived
series; an empty directory and a nonexistent one are both refused. The
awk parser was checked against defaulted `patches:` fields, several tags
sharing one directory, and comment lines containing decoy `- tag:` text.
All seven archived patch files are recorded as pure renames.

Note: existing published releases for the retired versions stay
downloadable until deleted by hand in Gitea.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-27 10:54:05 -05:00

162 lines
6.3 KiB
Markdown

# Multi-version build pipeline
How s3-gateway turns an upstream [versitygw](https://github.com/versity/versitygw)
release into a de-branded build, and how to keep it tracking new
upstream versions. This mirrors the scoutfs-build / scoutfs-notify
model, collapsed into one repo because the patch set is small.
CI builds **one version per run** — triggered by a tag push or a manual
dispatch, never on a schedule.
## The flow
Each build is four steps, run for a single upstream tag:
1. **`ci/clone-source.sh`** — shallow-clones upstream versitygw at a
tag into `src/`. Tag selection priority:
1. `VGW_TAG` (the dispatch input, or a manual run),
2. the pushed tag (`GITHUB_REF` = `refs/tags/...`),
3. the latest remote `vX.Y.Z` tag.
The checkout forces `core.autocrlf=false` so the tree is byte-for-
byte upstream (LF) and the LF patches apply on any host.
2. **`ci/apply-patches.sh`** — resolves the patch directory for the tag
from `ci/support-matrix.yaml` (`patches:` field, default = tag name)
and applies `patches/<dir>/*.patch` with `git am --whitespace=nowarn
--3way`, in filename order. The `--3way` fallback is what lets a
series rebase across minor upstream drift. A tag the matrix does not
list is **refused** (exit 1), as is a patch directory that turns out
to be empty — either would otherwise publish an un-de-branded or
superseded build.
3. **`ci/build.sh`** — runs `goreleaser release --clean
--skip=validate,publish` in `src/`, producing binaries + deb/rpm in
`src/dist/`. Because the patch commits sit on top of the tag, HEAD is
no longer exactly the tag, so we set `GORELEASER_CURRENT_TAG` to pin
the stamped version and `--skip=validate` to bypass the dirty/tag
checks. Upstream's `.goreleaser.yaml` is used unmodified — artifact
names stay `versitygw`.
4. **`ci/publish-release.sh`** — creates (or reuses) a Gitea release for
the tag in this repo and uploads `src/dist/*` as assets. Idempotent:
re-running for the same tag replaces existing assets.
## Patch registry: `ci/support-matrix.yaml`
```yaml
versitygw:
- tag: v1.10.0
patches: v1.10.0
```
- `tag` — upstream git tag.
- `patches` — directory under `patches/` to apply (defaults to `tag`).
Point several tags at one directory if the same series applies to a
range of upstream releases.
`apply-patches.sh` consults this to find the patch directory for the tag
being built; with the default `patches/<tag>/` naming it doubles as a
human-readable record of which versions are maintained.
It is also an **allowlist**, which is the mechanism that keeps
superseded versions from being rebuilt: a tag absent from the matrix
makes `apply-patches.sh` exit 1. Only `PATCH_DIR` bypasses it, and only
explicitly — that is how an archived series is rebuilt on purpose.
The matrix is read with `yq` when it is installed and with a small
`awk` parse otherwise, so the allowlist still applies in the stock
`golang` CI container, which has no `yq`.
## Adding a new upstream version
When upstream releases `vX.Y.Z`:
1. **Rebase the patch series** onto the new tag in a throwaway clone:
```bash
git clone https://github.com/versity/versitygw vgw && cd vgw
git -c core.autocrlf=false checkout -b debrand vX.Y.Z
git config user.email you@example.com && git config user.name you
git am --3way /path/to/s3-gateway/patches/<previous-tag>/*.patch
# If a hunk conflicts: fix files, `git add -A`, `git am --continue`.
mkdir -p /path/to/s3-gateway/patches/vX.Y.Z
git format-patch --binary vX.Y.Z..HEAD \
-o /path/to/s3-gateway/patches/vX.Y.Z
```
`--binary` is required because the series deletes PNG logo assets.
2. **Register it** in `ci/support-matrix.yaml`:
```yaml
versitygw:
- tag: vX.Y.Z
patches: vX.Y.Z
```
Leave the previous entry in place only if you still want builds of
it. If the new release supersedes it — a security fix, say — retire
the old one at the same time (see *Retiring a version* below).
3. **Commit and push** `ci/support-matrix.yaml` + `patches/vX.Y.Z/`.
4. **Build it** — either push a `vX.Y.Z` tag to this repo, or run the
**build** workflow manually and enter `vX.Y.Z`. CI builds that one
version and publishes it.
### Sanity-check a series before committing
```bash
VGW_REPO_URL="/path/to/local/versitygw" VGW_TAG=vX.Y.Z bash ci/run.sh
```
A clean `git am` plus a populated `src/dist/` means the series is good.
## Retiring a version
Drop its `ci/support-matrix.yaml` entry — that alone stops it being
built, since the matrix is the allowlist — and `git mv` its directory
into `patches/archive/<tag>/` rather than deleting it, so the series
stays available for a deliberate rebuild:
```bash
git mv patches/v1.8.0 patches/archive/v1.8.0
# then remove the v1.8.0 entry from ci/support-matrix.yaml
```
Update the table in [`patches/archive/README.md`](../patches/archive/README.md)
and the supported-versions table in the top-level README to match.
Releases already published in this repo are **not** affected: retiring
the patches stops future builds but leaves existing release assets
downloadable until you delete them by hand in Gitea.
To rebuild an archived version deliberately, pass `PATCH_DIR` instead
of re-adding it to the matrix:
```bash
PATCH_DIR=patches/archive/v1.8.0 VGW_TAG=v1.8.0 bash ci/apply-patches.sh
```
## CI configuration
- **Triggers**: a `v*` tag push (builds that tag) or manual dispatch
with a tag input (one-shot). No cron.
- **Runner**: any Linux runner — `runs-on: ubuntu-latest`. The Go
toolchain comes from the `golang:latest` container, so the runner
only needs Docker; nothing version-specific to install.
- **Container**: `golang:latest` — pure-Go build, GoReleaser's bundled
nfpm makes deb/rpm without rpmbuild/dpkg.
- **Secret `TOKEN_GITEA`**: Gitea PAT with **write:repository**. Used
both to clone this repo (the workflow does a manual token-rewritten
clone, since a stock container has no Node for `actions/checkout`) and
to publish releases.
- **Variable `VGW_REPO_URL`** (optional): override the upstream URL.
## Release tagging
Releases are created in this repo with the **upstream** tag name
(`v1.10.0`). If you need to ship a rebuild of the same upstream version
(e.g. a patch fix), set `RELEASE_TAG` in `ci/publish-release.sh`'s
environment to something like `v1.10.0-acg2` to avoid clobbering.