55 lines
3.2 KiB
Bash
55 lines
3.2 KiB
Bash
#!/usr/bin/env bash
|
|
# Clusev image-source resolver — sourced by install.sh. Defines functions only; sourcing has no
|
|
# side effects. Chooses whether to PULL a digest-pinned promoted image or BUILD locally, and ALWAYS
|
|
# leaves a coherent pin in CLUSEV_IMAGE / CLUSEV_TERMINAL_IMAGE / CLUSEV_PULL so a stale digest from
|
|
# a prior pull can never leak into a later `docker compose build` (which rejects a digest as a tag):
|
|
# env — CLUSEV_IMAGE already set (staging/CI passed it) → respect it, pull.
|
|
# pull — a public release-images.lock names ghcr.io digest refs → pull exactly those digests.
|
|
# build — no lock (dev/source tree) or an unusable lock → pin the local default build tags.
|
|
# The chosen mode is exposed via the exported global CLUSEV_RELEASE_MODE (read by the caller across
|
|
# the sourcing boundary); the caller persists the pin to .env. No file writes here (unit-testable).
|
|
|
|
# clusev_json_str FILE KEY -> value of a top-level "KEY": "VALUE" string. jq if present, else sed.
|
|
# The lock is our own single-object JSON, so the sed fallback is sufficient + dependency-free.
|
|
clusev_json_str() {
|
|
local file="$1" key="$2"
|
|
if command -v jq >/dev/null 2>&1; then
|
|
jq -er --arg k "$key" '.[$k] // empty' "$file" 2>/dev/null || true
|
|
else
|
|
sed -n "s/.*\"${key}\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$file" 2>/dev/null | head -n1
|
|
fi
|
|
}
|
|
|
|
# _clusev_ref_ok REF -> true ONLY for a ghcr.io digest-pinned ref (ghcr.io/<owner>/<name>@sha256:…).
|
|
# Anything looser — a bare tag, a non-ghcr host, a truncated or empty/garbage value — is rejected so
|
|
# the caller falls back to a local build instead of a doomed or attacker-directed pull. The promote
|
|
# CI always emits exactly this shape, so a rejected ref means a corrupt/tampered lock.
|
|
_clusev_ref_ok() { case "$1" in ghcr.io/*/*@sha256:?*) return 0 ;; *) return 1 ;; esac; }
|
|
|
|
# clusev_resolve_release_images [LOCK] -> sets + exports CLUSEV_IMAGE, CLUSEV_TERMINAL_IMAGE,
|
|
# CLUSEV_PULL and CLUSEV_RELEASE_MODE (env|pull|build). Always leaves a coherent pin.
|
|
clusev_resolve_release_images() {
|
|
local lock="${1:-release-images.lock}" mode appref termref
|
|
# Local build tags — must match the ${CLUSEV_IMAGE:-…}/${CLUSEV_TERMINAL_IMAGE:-…} defaults in
|
|
# docker-compose.prod.yml. Pinned on a build so `docker compose build` never inherits a digest.
|
|
local app_default="clusev-app:prod" term_default="clusev-terminal:prod"
|
|
if [ -n "${CLUSEV_IMAGE:-}" ]; then
|
|
# staging/CI already pinned the image via the environment — respect it, never override.
|
|
export CLUSEV_TERMINAL_IMAGE="${CLUSEV_TERMINAL_IMAGE:-$term_default}"
|
|
export CLUSEV_PULL="${CLUSEV_PULL:-1}"
|
|
# shellcheck disable=SC2209
|
|
mode="env"
|
|
elif [ -f "$lock" ] \
|
|
&& appref="$(clusev_json_str "$lock" app)" && _clusev_ref_ok "$appref" \
|
|
&& termref="$(clusev_json_str "$lock" terminal)" && _clusev_ref_ok "$termref"; then
|
|
# public tree: pull exactly the promoted digests.
|
|
export CLUSEV_IMAGE="$appref" CLUSEV_TERMINAL_IMAGE="$termref" CLUSEV_PULL=1
|
|
mode="pull"
|
|
else
|
|
# dev/source tree, or an unusable/tampered lock: build the local default tags (never a digest).
|
|
export CLUSEV_IMAGE="$app_default" CLUSEV_TERMINAL_IMAGE="$term_default" CLUSEV_PULL=0
|
|
mode="build"
|
|
fi
|
|
export CLUSEV_RELEASE_MODE="$mode"
|
|
}
|