feat(install): resolve prod image from release-images.lock (pull vs build)
parent
383f2fe74a
commit
a3cc563883
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env bash
|
||||
# Clusev image-source resolver — sourced by install.sh. Defines functions only; sourcing has no
|
||||
# side effects. Decides whether to PULL a digest-pinned promoted image or BUILD locally:
|
||||
# env — CLUSEV_IMAGE already set (staging/CI passed it) → respect it, pull.
|
||||
# pull — a public release-images.lock names valid image refs → pull exactly those digests.
|
||||
# build — no lock (dev/source tree) or an unusable lock → build locally (the tree ships Dockerfile).
|
||||
# The result is exposed via the global CLUSEV_RELEASE_MODE + exported CLUSEV_IMAGE/…/CLUSEV_PULL;
|
||||
# the caller persists the pin to .env. No file writes here (keeps it unit-testable without root).
|
||||
#
|
||||
# CLUSEV_RELEASE_MODE is a plain string flag (env|pull|build) read by the caller across the sourcing
|
||||
# boundary, so its literal assignments are deliberate and its "use" is external to this file:
|
||||
# shellcheck disable=SC2209,SC2034
|
||||
|
||||
# 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 when REF is a plausible registry ref (non-empty, contains a "/" path).
|
||||
# Catches an empty/garbage lock so it falls back to a local build instead of a doomed `pull`.
|
||||
_clusev_ref_ok() { case "$1" in */*) [ -n "$1" ] ;; *) return 1 ;; esac; }
|
||||
|
||||
# clusev_resolve_release_images [LOCK] [ENVFILE] -> sets CLUSEV_RELEASE_MODE (env|pull|build) and,
|
||||
# on pull/env, exports CLUSEV_PULL (and on pull, CLUSEV_IMAGE/CLUSEV_TERMINAL_IMAGE). ENVFILE is
|
||||
# accepted for signature symmetry with the caller but intentionally NOT written here.
|
||||
clusev_resolve_release_images() {
|
||||
local lock="${1:-release-images.lock}"
|
||||
CLUSEV_RELEASE_MODE=build
|
||||
# 1) staging/CI already pinned the image via the environment — never override it.
|
||||
if [ -n "${CLUSEV_IMAGE:-}" ]; then
|
||||
CLUSEV_RELEASE_MODE=env
|
||||
export CLUSEV_PULL="${CLUSEV_PULL:-1}"
|
||||
return 0
|
||||
fi
|
||||
# 2) dev/source tree has no lock — build (unchanged).
|
||||
[ -f "$lock" ] || return 0
|
||||
# 3/4) public tree: pull the exact promoted digests, or build if the lock is unusable.
|
||||
local appref termref
|
||||
appref="$(clusev_json_str "$lock" app)"
|
||||
termref="$(clusev_json_str "$lock" terminal)"
|
||||
if _clusev_ref_ok "$appref" && _clusev_ref_ok "$termref"; then
|
||||
export CLUSEV_IMAGE="$appref" CLUSEV_TERMINAL_IMAGE="$termref" CLUSEV_PULL=1
|
||||
CLUSEV_RELEASE_MODE=pull
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env bash
|
||||
# Unit test for scripts/release-images.sh — pure resolver, no root/Docker.
|
||||
set -euo pipefail
|
||||
here="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
# shellcheck source=scripts/release-images.sh
|
||||
. "$here/scripts/release-images.sh"
|
||||
|
||||
tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT
|
||||
pass=0; fail=0
|
||||
ok() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1)); printf 'FAIL %s: want=[%s] got=[%s]\n' "$1" "$3" "$2"; fi; }
|
||||
|
||||
reset_env() { unset CLUSEV_IMAGE CLUSEV_TERMINAL_IMAGE CLUSEV_PULL CLUSEV_RELEASE_MODE; }
|
||||
|
||||
valid_lock="$tmp/valid.lock"
|
||||
printf '{ "version": "v0.11.0", "app": "ghcr.io/o/clusev@sha256:aaa", "terminal": "ghcr.io/o/clusev-terminal@sha256:bbb" }\n' > "$valid_lock"
|
||||
corrupt_lock="$tmp/corrupt.lock"
|
||||
printf '{ "version": "v0.11.0", "app": "", "terminal": "ghcr.io/o/clusev-terminal@sha256:bbb" }\n' > "$corrupt_lock"
|
||||
|
||||
# 1) no lock -> build
|
||||
reset_env
|
||||
clusev_resolve_release_images "$tmp/nope.lock" "$tmp/env1"
|
||||
ok "no-lock mode" "${CLUSEV_RELEASE_MODE:-}" "build"
|
||||
ok "no-lock pull" "${CLUSEV_PULL:-unset}" "unset"
|
||||
|
||||
# 2) valid lock -> pull, exports set
|
||||
reset_env
|
||||
clusev_resolve_release_images "$valid_lock" "$tmp/env2"
|
||||
ok "valid mode" "${CLUSEV_RELEASE_MODE:-}" "pull"
|
||||
ok "valid image" "${CLUSEV_IMAGE:-}" "ghcr.io/o/clusev@sha256:aaa"
|
||||
ok "valid terminal" "${CLUSEV_TERMINAL_IMAGE:-}" "ghcr.io/o/clusev-terminal@sha256:bbb"
|
||||
ok "valid pull" "${CLUSEV_PULL:-}" "1"
|
||||
|
||||
# 3) corrupt lock (empty app) -> build fallback, nothing exported
|
||||
reset_env
|
||||
clusev_resolve_release_images "$corrupt_lock" "$tmp/env3"
|
||||
ok "corrupt mode" "${CLUSEV_RELEASE_MODE:-}" "build"
|
||||
ok "corrupt image" "${CLUSEV_IMAGE:-unset}" "unset"
|
||||
|
||||
# 4) env preset wins over lock
|
||||
reset_env
|
||||
export CLUSEV_IMAGE="ghcr.io/preset/clusev@sha256:zzz"
|
||||
clusev_resolve_release_images "$valid_lock" "$tmp/env4"
|
||||
ok "env mode" "${CLUSEV_RELEASE_MODE:-}" "env"
|
||||
ok "env image" "${CLUSEV_IMAGE:-}" "ghcr.io/preset/clusev@sha256:zzz"
|
||||
ok "env pull" "${CLUSEV_PULL:-}" "1"
|
||||
|
||||
# 5) json_str: known key + missing key
|
||||
reset_env
|
||||
ok "json known" "$(clusev_json_str "$valid_lock" app)" "ghcr.io/o/clusev@sha256:aaa"
|
||||
ok "json missing" "$(clusev_json_str "$valid_lock" nope)" ""
|
||||
|
||||
printf '\n%d passed, %d failed\n' "$pass" "$fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
Loading…
Reference in New Issue