146 lines
4.7 KiB
Bash
Executable File
146 lines
4.7 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)"
|
|
DIST_DIR="${DWS_PACKAGE_DIST_DIR:-$ROOT/dist}"
|
|
VERSION="${1:-${DWS_PACKAGE_VERSION:-}}"
|
|
|
|
[ -n "$VERSION" ] || { printf 'expected release version is required\n' >&2; exit 2; }
|
|
SEMVER="${VERSION#v}"
|
|
CHECKSUMS="$DIST_DIR/checksums.txt"
|
|
EXPECTED_PLATFORM_ASSETS="
|
|
dws-darwin-amd64.tar.gz
|
|
dws-darwin-arm64.tar.gz
|
|
dws-linux-amd64.tar.gz
|
|
dws-linux-arm64.tar.gz
|
|
dws-windows-amd64.zip
|
|
dws-windows-arm64.zip
|
|
"
|
|
EXPECTED_ASSETS="$EXPECTED_PLATFORM_ASSETS
|
|
dws-skills.zip
|
|
"
|
|
|
|
[ -f "$CHECKSUMS" ] || { printf 'missing checksums.txt in %s\n' "$DIST_DIR" >&2; exit 1; }
|
|
|
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/dws-release-binary.XXXXXX")"
|
|
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
|
|
|
|
# The public asset namespace is an exact set. GoReleaser may also leave local
|
|
# metadata files (artifacts.json/config.yaml/metadata.json), but the upload and
|
|
# npm staging globs never publish them.
|
|
printf '%s\nchecksums.txt\n' "$EXPECTED_ASSETS" | sed '/^$/d' | LC_ALL=C sort > "$tmp/expected-root"
|
|
for path in "$DIST_DIR"/dws-*.tar.gz "$DIST_DIR"/dws-*.zip "$CHECKSUMS"; do
|
|
[ -f "$path" ] || continue
|
|
basename "$path"
|
|
done | LC_ALL=C sort > "$tmp/actual-root"
|
|
if ! diff -u "$tmp/expected-root" "$tmp/actual-root"; then
|
|
printf 'public release assets must contain exactly the supported files\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
checksum_format_ok=1
|
|
awk '
|
|
NF != 2 { bad = 1; next }
|
|
$1 !~ /^[0-9a-fA-F]{64}$/ { bad = 1; next }
|
|
{ print $2 }
|
|
END { if (bad) exit 1 }
|
|
' "$CHECKSUMS" > "$tmp/checksum-assets" || checksum_format_ok=0
|
|
[ "$checksum_format_ok" -eq 1 ] || {
|
|
printf 'checksums.txt must contain only SHA-256 and filename pairs\n' >&2
|
|
exit 1
|
|
}
|
|
|
|
skills_checksum_count="$(awk '$2 == "dws-skills.zip" { count++ } END { print count + 0 }' "$CHECKSUMS")"
|
|
[ "$skills_checksum_count" -eq 1 ] || {
|
|
printf 'checksums.txt must contain dws-skills.zip exactly once (found %s)\n' "$skills_checksum_count" >&2
|
|
exit 1
|
|
}
|
|
printf '%s\n' "$EXPECTED_ASSETS" | sed '/^$/d' | LC_ALL=C sort > "$tmp/expected-checksums"
|
|
LC_ALL=C sort "$tmp/checksum-assets" > "$tmp/actual-checksums"
|
|
if ! diff -u "$tmp/expected-checksums" "$tmp/actual-checksums"; then
|
|
printf 'checksums.txt must describe exactly the supported release assets\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
(cd "$DIST_DIR" && sha256sum --check checksums.txt)
|
|
elif command -v shasum >/dev/null 2>&1; then
|
|
(cd "$DIST_DIR" && shasum -a 256 --check checksums.txt)
|
|
else
|
|
printf 'sha256sum or shasum is required\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
verify_binary_archive() {
|
|
asset="$1"
|
|
extract_dir="$tmp/extract-${asset}"
|
|
mkdir -p "$extract_dir"
|
|
case "$asset" in
|
|
*.tar.gz)
|
|
tar -xzf "$DIST_DIR/$asset" -C "$extract_dir"
|
|
binary="$extract_dir/dws"
|
|
;;
|
|
*.zip)
|
|
unzip -q "$DIST_DIR/$asset" -d "$extract_dir"
|
|
binary="$extract_dir/dws.exe"
|
|
;;
|
|
*)
|
|
printf 'unsupported release archive: %s\n' "$asset" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
[ -f "$binary" ] || {
|
|
printf '%s does not contain the expected dws binary\n' "$asset" >&2
|
|
return 1
|
|
}
|
|
LC_ALL=C grep -aFq "v$SEMVER" "$binary" || {
|
|
printf '%s binary does not contain expected version marker v%s\n' \
|
|
"$asset" "$SEMVER" >&2
|
|
return 1
|
|
}
|
|
# Execute one native artifact and validate the CLI's public version contract.
|
|
# `strings` output has no symbol boundaries, so requiring the injected version
|
|
# to appear on an exact line can reject a correct Go binary when adjacent
|
|
# printable bytes are coalesced into the same output line.
|
|
if [ "$asset" = dws-linux-amd64.tar.gz ]; then
|
|
command -v python3 >/dev/null 2>&1 || {
|
|
printf 'python3 is required to validate the release binary version\n' >&2
|
|
return 1
|
|
}
|
|
version_json="$extract_dir/version.json"
|
|
isolated_home="$extract_dir/home"
|
|
mkdir -p "$isolated_home"
|
|
if ! env -i HOME="$isolated_home" PATH=/usr/bin:/bin LANG=C.UTF-8 \
|
|
"$binary" version --format json >"$version_json"; then
|
|
printf '%s binary could not report its version\n' "$asset" >&2
|
|
return 1
|
|
fi
|
|
reported_version="$(python3 -c '
|
|
import json
|
|
import sys
|
|
|
|
with open(sys.argv[1], encoding="utf-8") as handle:
|
|
payload = json.load(handle)
|
|
version = payload.get("version")
|
|
if not isinstance(version, str) or not version:
|
|
raise SystemExit(1)
|
|
print(version)
|
|
' "$version_json")" || {
|
|
printf '%s binary returned an invalid version payload\n' "$asset" >&2
|
|
return 1
|
|
}
|
|
[ "$reported_version" = "v$SEMVER" ] || {
|
|
printf '%s binary reports version %s, expected v%s\n' \
|
|
"$asset" "$reported_version" "$SEMVER" >&2
|
|
return 1
|
|
}
|
|
fi
|
|
}
|
|
|
|
for asset in $EXPECTED_PLATFORM_ASSETS; do
|
|
verify_binary_archive "$asset"
|
|
done
|
|
|
|
printf 'Release artifacts verified for v%s.\n' "$SEMVER"
|