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>
This commit is contained in:
57_WolveandClaude Opus 5 committed 2026-10-27 10:54:05 -05:00
1 parent 095fc37d49
commit 73d5d2f3f1
14 files changed
+185 -47

No files matched your search

+56 -11
View File
@@ -7,10 +7,17 @@
# to the tag name). Patches are git-format-patch files applied with
# `git am --3way` in filename order — same model as scoutfs-notify.
#
# The matrix is an ALLOWLIST: a tag that is not listed is refused, so a
# stray tag push or a mistyped dispatch cannot publish a build of a
# superseded (and possibly vulnerable) upstream release. Superseded
# series stay on disk under patches/archive/<tag>/ and can be rebuilt
# deliberately by passing PATCH_DIR.
#
# Environment:
# VGW_SRC_DIR source tree (default: src; set by clone-source.sh)
# VGW_TAG upstream tag being built (required)
# PATCH_DIR override the patch directory (optional)
# PATCH_DIR build this directory instead of consulting the matrix;
# the escape hatch for patches/archive/<tag> (optional)
set -euo pipefail
VGW_SRC_DIR="${VGW_SRC_DIR:-src}"
@@ -19,19 +26,55 @@ VGW_SRC_DIR="${VGW_SRC_DIR:-src}"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SRC="$(cd "${VGW_SRC_DIR}" && pwd)"
# Resolve the patch directory: explicit override, else matrix lookup,
# else the tag name.
if [[ -z "${PATCH_DIR:-}" ]]; then
PATCH_NAME=""
MATRIX="${REPO_ROOT}/ci/support-matrix.yaml"
# Emit "<tag> <patch-dir>" for every row of the support matrix. Prefer
# yq, but fall back to a small awk parse of the fixed shape this repo
# writes: the stock golang CI container ships no yq, and the matrix is
# the allowlist, so it has to be readable either way.
matrix_entries() {
if command -v yq >/dev/null 2>&1; then
PATCH_NAME="$(yq -r \
".versitygw[] | select(.tag == \"${VGW_TAG}\") | .patches // \"\"" \
"${REPO_ROOT}/ci/support-matrix.yaml" 2>/dev/null || true)"
yq -r '.versitygw[] | "\(.tag) \(.patches // .tag)"' "${MATRIX}" 2>/dev/null && return
fi
sed 's/#.*//' "${MATRIX}" | awk '
/^[[:space:]]*-[[:space:]]*tag:[[:space:]]*[^[:space:]]/ {
if (tag != "") print tag, (pat != "" ? pat : tag)
tag = $NF; pat = ""; next
}
/^[[:space:]]*patches:[[:space:]]*[^[:space:]]/ { if (tag != "") pat = $NF }
END { if (tag != "") print tag, (pat != "" ? pat : tag) }
'
}
# An explicit PATCH_DIR is a deliberate override (rebuilding something
# out of patches/archive/). Otherwise the matrix decides, and a tag it
# does not list is refused — never silently built as vanilla upstream.
if [[ -z "${PATCH_DIR:-}" ]]; then
PATCH_NAME="$(matrix_entries | awk -v t="${VGW_TAG}" '$1 == t { print $2; exit }')"
if [[ -z "${PATCH_NAME}" ]]; then
{
echo "ERROR: ${VGW_TAG} is not listed in ci/support-matrix.yaml."
echo " Supported: $(matrix_entries | awk '{print $1}' | tr '\n' ' ')"
echo " Superseded series are archived under patches/archive/ and"
echo " are deliberately not rebuilt — they predate the fixes in"
echo " the supported release. To build one anyway, pass it"
echo " explicitly:"
echo " PATCH_DIR=patches/archive/${VGW_TAG} bash ci/apply-patches.sh"
} >&2
exit 1
fi
[[ -n "${PATCH_NAME}" && "${PATCH_NAME}" != "null" ]] || PATCH_NAME="${VGW_TAG}"
PATCH_DIR="${REPO_ROOT}/patches/${PATCH_NAME}"
fi
# git am runs from inside the source tree, so the patch paths have to be
# absolute: a relative PATCH_DIR (the documented archive invocation is
# one) would still glob here but no longer resolve after the cd below.
if [[ ! -d "${PATCH_DIR}" ]]; then
echo "ERROR: patch directory ${PATCH_DIR} does not exist." >&2
exit 1
fi
PATCH_DIR="$(cd "${PATCH_DIR}" && pwd)"
echo ">>> target versitygw tag: ${VGW_TAG}"
echo ">>> patch directory: ${PATCH_DIR}"
@@ -40,8 +83,10 @@ patches=( "${PATCH_DIR}"/*.patch )
shopt -u nullglob
if (( ${#patches[@]} == 0 )); then
echo ">>> WARNING: no patches in ${PATCH_DIR} — building vanilla upstream"
exit 0
echo "ERROR: no *.patch files in ${PATCH_DIR}." >&2
echo " Refusing to continue — that would publish an un-de-branded" >&2
echo " vanilla upstream build under this project's release name." >&2
exit 1
fi
echo ">>> applying ${#patches[@]} patch(es) with git am --3way:"
+2 -2
View File
@@ -3,12 +3,12 @@
# Runs the same ci/ steps CI does, in a single shell so VGW_* env flows
# between them.
#
# VGW_TAG=v1.5.0 bash ci/run.sh
# VGW_TAG=v1.10.0 bash ci/run.sh
#
# Optional: point VGW_REPO_URL at a local clone for fast offline builds.
set -euo pipefail
: "${VGW_TAG:?set VGW_TAG, e.g. VGW_TAG=v1.5.0 bash ci/run.sh}"
: "${VGW_TAG:?set VGW_TAG, e.g. VGW_TAG=v1.10.0 bash ci/run.sh}"
export VGW_TAG
export VGW_SRC_DIR="${VGW_SRC_DIR:-src}"
+7 -8
View File
@@ -1,5 +1,9 @@
# s3-gateway build support matrix — single source of truth for CI.
#
# This is an ALLOWLIST. ci/apply-patches.sh refuses to build any tag
# that is not listed here, so a stale or vulnerable upstream release
# cannot be published by a stray tag push or a mistyped dispatch.
#
# Each entry maps an upstream versitygw release tag to the patch set
# (a directory under patches/) applied on top of that tag before
# building. Add a row when upstream cuts a release you want a
@@ -8,17 +12,12 @@
#
# The highest-version entry is treated as "latest".
#
# Superseded series are kept under patches/archive/<tag>/ rather than
# deleted — see that directory's README to rebuild one deliberately.
#
# tag upstream versitygw git tag (github.com/versity/versitygw)
# patches directory under patches/ to apply (defaults to `tag`)
versitygw:
- tag: v1.10.0
patches: v1.10.0
- tag: v1.8.0
patches: v1.8.0
- tag: v1.7.0
patches: v1.7.0
- tag: v1.6.0
patches: v1.6.0
- tag: v1.5.0
patches: v1.5.0