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>
162 lines
6.3 KiB
Markdown
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.
|