Files
parity-lib/ci/parity-lib.sh
T
Oleks 2201257e89 feat: shared per-archetype parity publish-app builders (v0.1.0)
Implements the shared parity flake-module library so the ~51 parity repos
consume one source of truth instead of hand-inlined publish shells.

- lib.mk{PyPiWheel,S390xNpm,GenericBinary,Nix2Container,GoBinary,Helm}Publish
  builders returning stage-<arch>/publish-<arch>/publish-index/publish/
  push-staged apps per the corrected emmett#44 standard (build-parity stages to
  ./.parity-stage with no registry contact; publish dry-runs by default;
  publish-index is build-free + fail-closed; :latest is the last digest copy).
- Shared ci/parity-lib.sh: token resolution ($REGISTRY_TOKEN + pass fallback,
  never printed), dev-tag guard, version derivation, dry-run gate, preflight.
- pipeline-doctor package/app asserting the parity contract (cluster #193).

Refs cluster #192, #193, #194, emmett#44.
2026-06-02 04:15:48 +03:00

150 lines
6.0 KiB
Bash

# shellcheck shell=bash
# shellcheck disable=SC2034 # vars/functions here are consumed by sourcing apps
#
# parity-lib shared shell helpers (cluster #192, emmett#44).
#
# This file is the single source of truth for the cross-cutting concerns every
# parity publish app shares: token resolution, version derivation, the dev-tag
# guard, argument parsing (the dry-run gate), registry preflight and the
# on-disk stage directory convention. Each archetype builder in lib/builders.nix
# generates a small writeShellApplication that sources THIS file and then does
# only its archetype-specific build + push.
#
# TOKEN HYGIENE: nothing here ever echoes the token. Scripts that source this
# MUST run under `set -euo pipefail` only and MUST NOT enable `set -x`.
# ---------------------------------------------------------------------------
# Conventions (overridable by the sourcing app before calling the helpers).
# ---------------------------------------------------------------------------
PARITY_PASS_ENTRY="${PARITY_PASS_ENTRY:-infra/gitea/personal_access_token_packages_rw}"
PARITY_REGISTRY_HOST="${PARITY_REGISTRY_HOST:-git.oleks.space}"
PARITY_REGISTRY_OWNER="${PARITY_REGISTRY_OWNER:-oleks}"
# Local on-disk stage. BUILD-parity artifacts land here; push-staged replays them.
PARITY_STAGE_DIR="${PARITY_STAGE_DIR:-${PWD}/.parity-stage}"
# ---------------------------------------------------------------------------
# Token resolution: $REGISTRY_TOKEN -> `pass` fallback -> named hard fail.
# Prints the token on stdout so a caller can `tok="$(parity_resolve_token)"`.
# The CALLER must capture it and never echo it. This function itself is silent.
# ---------------------------------------------------------------------------
parity_resolve_token() {
if [ -n "${REGISTRY_TOKEN:-}" ]; then
printf '%s' "$REGISTRY_TOKEN"
return 0
fi
if command -v pass >/dev/null 2>&1; then
local t
if t="$(pass show "$PARITY_PASS_ENTRY" 2>/dev/null || true)" && [ -n "$t" ]; then
printf '%s' "$t"
return 0
fi
fi
echo "BLOCKER(empty-token): set \$REGISTRY_TOKEN (CI from_secret) or store '$PARITY_PASS_ENTRY' in pass; refusing to publish without credentials." >&2
return 1
}
# ---------------------------------------------------------------------------
# Version derivation, identical for CI and local.
# Precedence: $VERSION -> strip-v + strip-trailing-increment of $CI_COMMIT_TAG
# -> the default passed as $1 (the flake's pinned version).
# Tag shape accepted: v<x.y.z> optionally with a -<N> build increment suffix.
# Prints the derived version on stdout.
# ---------------------------------------------------------------------------
parity_derive_version() {
local default_version="${1:-}"
if [ -n "${VERSION:-}" ]; then
printf '%s' "$VERSION"
return 0
fi
if [ -n "${CI_COMMIT_TAG:-}" ]; then
printf '%s' "$CI_COMMIT_TAG" | sed 's/^v//; s/-[0-9]*$//'
return 0
fi
if [ -n "$default_version" ]; then
printf '%s' "$default_version"
return 0
fi
echo "BLOCKER(no-version): no \$VERSION, no \$CI_COMMIT_TAG and no default version." >&2
return 1
}
# ---------------------------------------------------------------------------
# Dev-tag guard. Refuse a real (registry-mutating, :latest/release) publish
# unless an explicit $VERSION is set OR $CI_COMMIT_TAG looks like a v* tag.
# This stops an accidental local `--publish` from clobbering the registry with
# the flake's default development version.
# ---------------------------------------------------------------------------
parity_devtag_guard() {
if [ -n "${VERSION:-}" ]; then
return 0
fi
if printf '%s' "${CI_COMMIT_TAG:-}" | grep -Eq '^v[0-9]'; then
return 0
fi
echo "BLOCKER(dev-tag): refusing to publish without an explicit version." >&2
echo " Set VERSION=<x.y.z> or push a v* tag (CI_COMMIT_TAG) before publishing." >&2
return 1
}
# ---------------------------------------------------------------------------
# Argument gate. Sets the global PARITY_PUBLISH (0 = dry-run default, 1 = push).
# Honors $PUBLISH=1, --publish and --dry-run. --help prints usage and exits 0.
# Usage: parity_parse_args "<app-description>" "$@"
# ---------------------------------------------------------------------------
parity_parse_args() {
local desc="$1"
shift
PARITY_PUBLISH="${PUBLISH:-0}"
local a
for a in "$@"; do
case "$a" in
--publish) PARITY_PUBLISH=1 ;;
--dry-run) PARITY_PUBLISH=0 ;;
-h | --help)
echo "$desc"
echo ""
echo "Dry-run by default: builds/stages and prints what WOULD be pushed."
echo " --publish | PUBLISH=1 actually mutate the registry"
echo " --dry-run force dry-run"
echo "Env: VERSION=<x.y.z> override version; REGISTRY_TOKEN registry token"
echo " (fallback: pass $PARITY_PASS_ENTRY)."
exit 0
;;
*)
echo "error: unknown argument '$a' (try --help)" >&2
exit 2
;;
esac
done
}
# ---------------------------------------------------------------------------
# Registry preflight: name the blocker if the registry is unreachable BEFORE
# attempting any push (CI shares fate with the cluster the registry lives on).
# ---------------------------------------------------------------------------
parity_registry_preflight() {
if ! curl -fsS -o /dev/null --max-time 10 "https://${PARITY_REGISTRY_HOST}/api/v1/version" 2>/dev/null; then
echo "BLOCKER(registry-down): https://${PARITY_REGISTRY_HOST} unreachable — re-run when the registry is back; staged artifacts in ${PARITY_STAGE_DIR} are unchanged." >&2
return 1
fi
}
# ---------------------------------------------------------------------------
# Stage directory helpers. The convention: each staged artifact is written to
# ${PARITY_STAGE_DIR}/<arch>/ alongside a one-line meta file recording the
# version + intended registry coordinates, so push-staged can replay later.
# ---------------------------------------------------------------------------
parity_stage_path() {
# $1 = arch
printf '%s/%s' "$PARITY_STAGE_DIR" "$1"
}
parity_stage_reset() {
# $1 = arch ; wipes + recreates that arch's stage dir, prints the path.
local d
d="$(parity_stage_path "$1")"
rm -rf "$d"
mkdir -p "$d"
printf '%s' "$d"
}