rig major updates
This commit is contained in:
@@ -23,6 +23,17 @@ check() { # name, expected, actual
|
||||
|
||||
note() { printf '\n%s\n' "$1"; }
|
||||
|
||||
# A scratch copy of rig for a check to change freely. local/ (overlays, possibly
|
||||
# someone else's) and def/ (scratch) never ride along, and neither do this
|
||||
# machine's PROFILE/OVERLAY/CLUSTER choices: a check sets what it tests.
|
||||
copy_rig() { # dest-dir
|
||||
mkdir -p "$1"
|
||||
tar -C .. --exclude=./local --exclude=./def -cf - . | tar -C "$1" -xf -
|
||||
if [ -f "$1/ctrl/.env" ]; then
|
||||
sed -i '/^PROFILE=/d; /^OVERLAY=/d; /^CLUSTER=/d; /^MANIFESTS_DIR=/d' "$1/ctrl/.env"
|
||||
fi
|
||||
}
|
||||
|
||||
# Resolve one key the way every rig script does, in a clean shell so the
|
||||
# caller's exported value is the only thing in play.
|
||||
resolved() {
|
||||
@@ -33,12 +44,12 @@ resolved() {
|
||||
note "rig needs no profile"
|
||||
# No env.d/ must still resolve and generate a kit; an unknown profile stays an error.
|
||||
NP="$(mktemp -d)"
|
||||
cp -r .. "$NP/rig"; rm -rf "$NP/rig/ctrl/env.d"; sed -i '/^PROFILE=/d' "$NP/rig/ctrl/.env" 2>/dev/null
|
||||
copy_rig "$NP/rig"; rm -rf "$NP/rig/ctrl/env.d"
|
||||
check "no env.d: config resolves" "default" \
|
||||
"$(cd "$NP/rig/ctrl" && bash -c 'source ./lib/config.sh; load_config >/dev/null && echo "$PROFILE_NAME"' 2>&1)"
|
||||
check "no env.d: the k8s version comes from the pins" "yes" \
|
||||
"$(cd "$NP/rig/ctrl" && bash -c 'source ./lib/config.sh; load_config >/dev/null && [ -n "$NODE_IMAGE" ] && echo yes' 2>&1)"
|
||||
check "no env.d: ports.sh active works" "7" \
|
||||
check "no env.d: ports.sh active works" "8" \
|
||||
"$(cd "$NP/rig/ctrl" && bash ports.sh active 2>/dev/null | wc -w)"
|
||||
check "no env.d: a kit is generated for the defaults" "yes" \
|
||||
"$( (cd "$NP/rig/ctrl" && rm -rf ../standalone/*/ && bash standalone.sh write >/dev/null 2>&1) && [ -f "$NP/rig/standalone/default/rigdeps.sh" ] && echo yes || echo no)"
|
||||
@@ -50,13 +61,14 @@ rm -rf "$NP"
|
||||
note "the ports.sh active contract"
|
||||
# ports.sh active is read positionally by the Makefile and Tiltfile: pin field count and order.
|
||||
FACTS="$(bash ports.sh active)"
|
||||
check "active: exactly 7 fields" "7" "$(printf '%s' "$FACTS" | wc -w)"
|
||||
read -r F_CLUSTER F_CTX F_HTTP F_HTTPS F_TILT F_REG F_MANIFESTS <<< "$FACTS"
|
||||
check "active: exactly 8 fields" "8" "$(printf '%s' "$FACTS" | wc -w)"
|
||||
read -r F_CLUSTER F_CTX F_HTTP F_HTTPS F_TILT F_REG F_MANIFESTS F_OVERLAY <<< "$FACTS"
|
||||
check "active: field 2 is kind-<cluster>" "kind-$F_CLUSTER" "$F_CTX"
|
||||
check "active: fields 3-6 are numeric" "yes" \
|
||||
"$([[ "$F_HTTP$F_HTTPS$F_TILT$F_REG" =~ ^[0-9]+$ ]] && echo yes || echo no)"
|
||||
check "active: field 7 is a path" "yes" \
|
||||
"$([ -n "$F_MANIFESTS" ] && [ "${F_MANIFESTS#-}" = "$F_MANIFESTS" ] && echo yes || echo no)"
|
||||
# Absolute, or - when there is none: an empty field would shift every later one.
|
||||
check "active: fields 7-8 are absolute paths or -" "yes" \
|
||||
"$(for f in "$F_MANIFESTS" "$F_OVERLAY"; do case "$f" in -|/*) ;; *) echo no; exit; esac; done; echo yes)"
|
||||
# derive answers a different question and must keep its own shape: it reports
|
||||
# what the directory name implies, ignoring ctrl/.env, so nothing should
|
||||
# configure itself from it.
|
||||
@@ -76,7 +88,9 @@ test_value() {
|
||||
KIND_CONFIG) echo "$PWD/k8s/kind-config.yaml.tpl" ;;
|
||||
*_PORT) echo "19999" ;;
|
||||
CLUSTER) echo "selftest-name" ;;
|
||||
MANIFESTS_DIR) echo "../elsewhere/overlays/dev" ;;
|
||||
# Named folders must exist, and must not be the default.
|
||||
MANIFESTS_DIR) echo "examples/starter/k8s/base" ;;
|
||||
OVERLAY) echo "examples/data" ;;
|
||||
ADDONS) echo "metallb" ;;
|
||||
*) echo "selftest-sentinel" ;;
|
||||
esac
|
||||
@@ -110,8 +124,8 @@ trap 'rm -rf "$TMP"' EXIT
|
||||
mkdir -p "$TMP/My_Proj"
|
||||
cp -r . "$TMP/My_Proj/ctrl"
|
||||
# A pinned CLUSTER in .env would be an override, not a derivation, and this
|
||||
# check is about the derivation.
|
||||
sed -i '/^CLUSTER=/d' "$TMP/My_Proj/ctrl/.env" 2>/dev/null
|
||||
# check is about the derivation. (An OVERLAY would be another derivation.)
|
||||
sed -i '/^CLUSTER=/d; /^OVERLAY=/d' "$TMP/My_Proj/ctrl/.env" 2>/dev/null
|
||||
COPY="$(cd "$TMP/My_Proj/ctrl" && bash ports.sh active)"
|
||||
check "a dir named My_Proj derives a DNS label" "my-proj" "$(awk '{print $1}' <<< "$COPY")"
|
||||
check "and a context to match" "kind-my-proj" "$(awk '{print $2}' <<< "$COPY")"
|
||||
@@ -129,9 +143,88 @@ check "derive_port_base my-proj" "21030" "$(derive_port_base my-proj)"
|
||||
note "rig stays standalone"
|
||||
# rig must be copyable out of its host project: no references to the host.
|
||||
# The pattern is assembled from fragments so this file does not match itself.
|
||||
HOST_PAT="$(printf '%s' 'sole' 'print' '|\b' 'sp' 'r\b')"
|
||||
# The host project's word for a backing service counts too: rig described its
|
||||
# workload addons with it until they left. local/ holds overlays, which may say anything.
|
||||
HOST_PAT="$(printf '%s' 'sole' 'print' '|\b' 'sp' 'r\b' '|' 'cab' 'inet')"
|
||||
check "no host-project references" "0" \
|
||||
"$(cd .. && grep -rIl -iE "$HOST_PAT" . --exclude-dir=def 2>/dev/null | wc -l)"
|
||||
"$(cd .. && grep -rIl -iE "$HOST_PAT" . --exclude-dir=def --exclude-dir=local 2>/dev/null | wc -l)"
|
||||
|
||||
|
||||
note "what runs is an overlay; rig only reads it"
|
||||
# docs/notes/overlay.md. Every check runs in a scratch copy with its own overlay.
|
||||
OV="$TMP/overlay-proof"; copy_rig "$OV/rig"
|
||||
OVR="$OV/rig"
|
||||
mkdir -p "$OVR/local/My_Env/addons" "$OVR/local/My_Env/k8s/prod" "$OVR/ctrl/env.d"
|
||||
printf 'ADDONS="from-profile"\nDATA_NAMESPACE=from-profile\n' > "$OVR/ctrl/env.d/selftest.env"
|
||||
cat > "$OVR/local/My_Env/rig.env" <<'EOF'
|
||||
ADDONS="metallb"
|
||||
DATA_NAMESPACE=from-overlay
|
||||
MANIFESTS_DIR=k8s/prod
|
||||
SELFTEST_SENTINEL=selftest-overlay-sentinel
|
||||
EOF
|
||||
printf 'resources: []\n' > "$OVR/local/My_Env/k8s/prod/kustomization.yaml"
|
||||
printf '#!/usr/bin/env bash\necho "overlay-metallb from $PWD with ${RIG_CTRL:-no RIG_CTRL}"\n' \
|
||||
> "$OVR/local/My_Env/addons/metallb.sh"
|
||||
in_ov() { (cd "$OVR/ctrl" && "$@"); }
|
||||
ov_key() { # key [env assignments...]
|
||||
local k="$1"; shift
|
||||
in_ov env "$@" bash -c 'source ./lib/config.sh; load_config >/dev/null 2>&1; printf "%s" "${!1}"' _ "$k"
|
||||
}
|
||||
|
||||
# With nothing named, rig behaves as it did before overlays: same name, ports, addons, nodes.
|
||||
check "no overlay: the cluster, ports and addons of before" "rig kind-rig 20310 20311 20312 20313" \
|
||||
"$(in_ov bash ports.sh active | awk '{print $1, $2, $3, $4, $5, $6}')"
|
||||
check "no overlay: no addons, one node, rig's own kind config" "|1|./k8s/kind-config.yaml.tpl" \
|
||||
"$(ov_key ADDONS)|$(ov_key NODES)|$(ov_key KIND_CONFIG)"
|
||||
|
||||
# The overlay's rig.env sits between the profile and ctrl/.env; the caller beats all.
|
||||
check "rig.env beats the profile" "from-overlay" \
|
||||
"$(ov_key DATA_NAMESPACE PROFILE=selftest OVERLAY=local/My_Env)"
|
||||
echo 'DATA_NAMESPACE=from-dotenv' >> "$OVR/ctrl/.env"
|
||||
check "ctrl/.env beats rig.env" "from-dotenv" \
|
||||
"$(ov_key DATA_NAMESPACE PROFILE=selftest OVERLAY=local/My_Env)"
|
||||
sed -i '/^DATA_NAMESPACE=from-dotenv$/d' "$OVR/ctrl/.env"
|
||||
check "the caller beats rig.env" "from-caller" \
|
||||
"$(ov_key ADDONS OVERLAY=local/My_Env ADDONS=from-caller)"
|
||||
|
||||
# Identity follows the overlay's folder, so one rig serves several without collisions.
|
||||
check "identity follows the overlay's folder" "my-env kind-my-env" \
|
||||
"$(in_ov env OVERLAY=local/My_Env bash ports.sh active | awk '{print $1, $2}')"
|
||||
check "paths in rig.env are relative to the overlay" "$OVR/local/My_Env/k8s/prod" \
|
||||
"$(in_ov env OVERLAY=local/My_Env bash ports.sh active | awk '{print $7}')"
|
||||
check "a named overlay that does not exist is an error" "yes" \
|
||||
"$(in_ov env OVERLAY=local/nope bash ports.sh active >/dev/null 2>&1 && echo no || echo yes)"
|
||||
printf 'PROFILE=x\n' > "$OV/bad-rig.env"; mkdir -p "$OVR/local/bad"; cp "$OV/bad-rig.env" "$OVR/local/bad/rig.env"
|
||||
check "rig.env may not choose the profile or the overlay" "yes" \
|
||||
"$(in_ov env OVERLAY=local/bad bash ports.sh active >/dev/null 2>&1 && echo no || echo yes)"
|
||||
|
||||
# Addons: the overlay's is found before rig's own, and runs from rig's ctrl/.
|
||||
check "an overlay's addon comes before rig's of the same name" \
|
||||
"overlay-metallb from $OVR/ctrl with $OVR/ctrl" \
|
||||
"$(in_ov env OVERLAY=local/My_Env ADDONS=metallb bash addons.sh install 2>&1 | grep '^overlay-metallb')"
|
||||
|
||||
# ctrl/.env is this rig's: a pinned block would follow every overlay.
|
||||
check "ports.sh persist refuses while an overlay is set" "yes" \
|
||||
"$(in_ov env OVERLAY=local/My_Env bash ports.sh persist >/dev/null 2>&1 && echo no || echo yes)"
|
||||
|
||||
# make's $(shell) must see an OVERLAY given as a make argument (make < 4.4 does not pass it).
|
||||
check "make -n tilt OVERLAY=... asks for the overlay's context" "kind-data" \
|
||||
"$(cd .. && make --no-print-directory -n tilt OVERLAY=examples/data 2>/dev/null | grep -m1 'tilt ' | sed -n 's/.*--context \([^ ]*\).*/\1/p')"
|
||||
|
||||
# rig reads an overlay and never writes into it; its values never reach a committed kit.
|
||||
sum_ov() { (cd "$OVR/local/My_Env" && find . -type f | sort | xargs sha256sum | sha256sum); }
|
||||
before=$(sum_ov)
|
||||
echo 'OVERLAY=local/My_Env' >> "$OVR/ctrl/.env"
|
||||
in_ov bash ports.sh active >/dev/null 2>&1
|
||||
in_ov bash addons.sh list >/dev/null 2>&1
|
||||
in_ov bash -c 'source ./lib/config.sh; load_config >/dev/null; render_kind_config >/dev/null' 2>/dev/null
|
||||
in_ov bash standalone.sh write >/dev/null 2>&1
|
||||
in_ov bash standalone.sh export "$OV/export" >/dev/null 2>&1
|
||||
check "rig writes nothing into an overlay" "$before" "$(sum_ov)"
|
||||
check "an overlay's values never reach a committed kit" "0" \
|
||||
"$(grep -rlE 'selftest-overlay-sentinel|local/My_Env' "$OVR/standalone" 2>/dev/null | wc -l)"
|
||||
check "a committed kit holds no path of this machine" "0" \
|
||||
"$(grep -rlF "$OVR" "$OVR/standalone" 2>/dev/null | wc -l)"
|
||||
|
||||
|
||||
note "the Tiltfile hardcodes nothing"
|
||||
@@ -139,6 +232,7 @@ note "the Tiltfile hardcodes nothing"
|
||||
check "no literal kind-<name>" "0" "$(grep -cE "['\"]kind-[a-z0-9]" Tiltfile)"
|
||||
check "guards on the variable" "1" "$(grep -c 'allow_k8s_contexts(CTX)' Tiltfile)"
|
||||
check "asks ports.sh for facts" "1" "$(grep -c "local('bash ports.sh active'" Tiltfile)"
|
||||
check "hands over to the overlay's Tiltfile" "1" "$(grep -c "include(OVERLAY + '/Tiltfile')" Tiltfile)"
|
||||
|
||||
|
||||
note "standalone kits are generated, current, and call only real verbs"
|
||||
@@ -168,7 +262,8 @@ check "there is a kit for every profile" "$(config_profiles | wc -l)" "$kits"
|
||||
|
||||
# An export carries this machine's choices but never its credentials; committed kits carry neither.
|
||||
# Proven with sentinel values in a scratch copy, since the real ctrl/.env may leave them empty.
|
||||
SX="$TMP/export-proof"; mkdir -p "$SX"; cp -r .. "$SX/rig"
|
||||
SX="$TMP/export-proof"; copy_rig "$SX/rig"
|
||||
mkdir -p "$SX/selftest-sentinel-choice/overlays/dev" # a named MANIFESTS_DIR must exist
|
||||
cat >> "$SX/rig/ctrl/.env" <<'EOF'
|
||||
REGISTRY_USER=selftest-sentinel-user
|
||||
REGISTRY_PASSWORD=selftest-sentinel-password
|
||||
@@ -187,16 +282,87 @@ check "export: refuses to write inside the repository" "yes" \
|
||||
"$( (bash standalone.sh export ../standalone/selftest-mine >/dev/null 2>&1) && echo no || echo yes)"
|
||||
|
||||
|
||||
note "optional — needs tilt and this rig's cluster"
|
||||
# Tilt needs a cluster context to parse the Tiltfile, so this is skipped without one.
|
||||
if ! command -v tilt >/dev/null; then
|
||||
printf ' skip tilt is not installed\n'
|
||||
elif ! kubectl config get-contexts -o name 2>/dev/null | grep -qx "$F_CTX"; then
|
||||
printf " skip no %s context — run 'make cluster up' to include this\n" "$F_CTX"
|
||||
note "rig's addons apply verified files, never URLs"
|
||||
# The offline profile must need no network for manifests: each addon asks deps.sh for a
|
||||
# pinned manifest, verified on disk (versions.md). Their images still need preloading.
|
||||
check "no rig addon applies a URL" "0" \
|
||||
"$(cat addons/*.sh | grep -cE 'apply -f "?https?://')"
|
||||
check "every manifest an addon asks for is pinned with a sum" "" \
|
||||
"$(for n in $(grep -ohE 'deps\.sh manifest [A-Z_]+' addons/*.sh | awk '{print $3}' | sort -u); do
|
||||
grep -q "^${n}_MANIFEST_URL=" versions.env && grep -q "^${n}_MANIFEST_SHA256=[0-9a-f]\{64\}$" versions.env \
|
||||
|| printf '%s ' "$n"; done)"
|
||||
|
||||
|
||||
note "withdrawn stays withdrawn (STALE.md)"
|
||||
# One check per entry; the reasoning is in STALE.md, not here.
|
||||
check "✖ S1 rig's Tiltfile has no Images section of its own" "0" "$(grep -c '^# ── Images' Tiltfile)"
|
||||
check "✖ S2 local/ is where overlays live, and ignored" "yes" \
|
||||
"$(grep -qx '/local/' ../.gitignore && echo yes || echo no)"
|
||||
# Patterns assembled from fragments so this file does not match itself.
|
||||
COPIES_PAT="$(printf '%s' 'ac' 'me-rig|ac' 'mebank')"
|
||||
HOUSE_PAT="$(printf '%s' 'semes' 'ter|local' '\.ar\b')"
|
||||
check "✖ S2 no example environment name from the copies era" "0" \
|
||||
"$(cd .. && grep -rIlE "$COPIES_PAT" . --exclude-dir=def --exclude-dir=local --exclude=STALE.md 2>/dev/null | wc -l)"
|
||||
check "✖ S3 ctrl/addons makes the cluster work, nothing more" "cert-manager metallb metrics-server" \
|
||||
"$(ls addons/ | sed 's/\.sh$//' | sort | xargs)"
|
||||
check "✖ S3 versions.env pins no workload image" "0" \
|
||||
"$(grep -cE '^(POSTGRES|REDIS|AIRFLOW)_IMAGE=' versions.env)"
|
||||
check "✖ S4 no namespace named after the cluster" "0" "$(grep -c "CLUSTER + ':namespace'" Tiltfile)"
|
||||
check "✖ S5 rig's examples left ctrl/k8s" "no" "$([ -d k8s/overlays ] && echo yes || echo no)"
|
||||
check "✖ S5 .env.example does not pin MANIFESTS_DIR" "0" "$(grep -c '^MANIFESTS_DIR=' .env.example)"
|
||||
check "✖ S6 no client or data example profile" "0" \
|
||||
"$(ls env.d/ | grep -cE '^(client|data)\.')"
|
||||
check "✖ S7 no house path or host name in rig" "0" \
|
||||
"$(cd .. && grep -rIlE "$HOUSE_PAT" . --exclude-dir=def --exclude-dir=local --exclude=STALE.md 2>/dev/null | wc -l)"
|
||||
|
||||
|
||||
note "the dev loop parses — needs tilt and kubectl, not a cluster"
|
||||
# A throwaway kubeconfig with kind-named entries (Tilt trusts kind contexts) and a
|
||||
# kubectl that swallows `apply`: the Tiltfile evaluates for real, nothing is contacted.
|
||||
# The second run is a copy under another name, the case that once failed at load.
|
||||
if ! command -v tilt >/dev/null || ! command -v kubectl >/dev/null; then
|
||||
printf ' skip tilt or kubectl is not installed\n'
|
||||
else
|
||||
FK="$TMP/fake-kube"; mkdir -p "$FK"
|
||||
real_kubectl=$(command -v kubectl)
|
||||
printf '#!/usr/bin/env bash\nfor a in "$@"; do [ "$a" = apply ] && { cat >/dev/null; exit 0; }; done\nexec %q "$@"\n' \
|
||||
"$real_kubectl" > "$FK/kubectl"
|
||||
chmod +x "$FK/kubectl"
|
||||
parses() { # cluster-name [env...] -> the manifests Tilt would deploy, or the error
|
||||
local name="$1"; shift
|
||||
cat > "$FK/kubeconfig" <<EOF
|
||||
apiVersion: v1
|
||||
kind: Config
|
||||
clusters: [{name: kind-$name, cluster: {server: "https://127.0.0.1:9"}}]
|
||||
contexts: [{name: kind-$name, context: {cluster: kind-$name, user: kind-$name}}]
|
||||
users: [{name: kind-$name, user: {token: selftest}}]
|
||||
current-context: kind-$name
|
||||
EOF
|
||||
env "$@" KUBECONFIG="$FK/kubeconfig" PATH="$FK:$PATH" \
|
||||
timeout 120 tilt alpha tiltfile-result --context "kind-$name" > "$FK/out.json" 2> "$FK/err" \
|
||||
&& grep -o '"Name": *"[^"]*"' "$FK/out.json" | sed 's/.*"\([^"]*\)"$/\1/' | sort -u | xargs \
|
||||
|| grep -m1 -iE 'error|no object' "$FK/err"
|
||||
}
|
||||
check "the starter overlay parses" "example-service infra uncategorized" "$(parses rig)"
|
||||
check "and under another name" "example-service infra uncategorized" \
|
||||
"$(parses selftest-copy CLUSTER=selftest-copy)"
|
||||
check "the data overlay parses" "items-api uncategorized" "$(parses data OVERLAY=examples/data)"
|
||||
fi
|
||||
|
||||
|
||||
note "the examples are overlays that work as shipped"
|
||||
# They are what a real overlay is copied from, so they must at least parse.
|
||||
bad=""
|
||||
for f in ../examples/*/addons/*.sh; do [ -e "$f" ] && { bash -n "$f" 2>/dev/null || bad+="$f "; }; done
|
||||
check "every example addon parses" "" "$bad"
|
||||
if command -v python3 >/dev/null; then
|
||||
bad=""
|
||||
for f in ../examples/*/dags/*.py; do
|
||||
[ -e "$f" ] && { python3 -c 'import ast, sys; ast.parse(open(sys.argv[1]).read())' "$f" 2>/dev/null || bad+="$f "; }
|
||||
done
|
||||
check "every example DAG parses" "" "$bad"
|
||||
else
|
||||
out="$(tilt alpha tiltfile-result --context "$F_CTX" 2>&1)"
|
||||
check "Tiltfile evaluates" "yes" \
|
||||
"$(printf '%s' "$out" | grep -q '"Manifests"' && echo yes || echo "no: $(printf '%s' "$out" | tail -1)")"
|
||||
printf ' skip python3 is not installed\n'
|
||||
fi
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user