mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
Filed in response to the 2026-04-28 14:00 UTC backup outage. An
`install.sh --mode k3d` run with the shell pointed at GKE silently
overwrote the GKE cluster's GCS-backed ObjectStore + ScheduledBackup
with k3d-mode defaults; Garage filled up and CNPG backups failed for
hours before the next manual check. The class of bug is "config says
target cluster A, shell context says target cluster B, installer
proceeds against B without warning."
New shared bash helper at etc/preflight_kubecontext.sh with two
functions:
- verify_kubecontext_matches_config <cfg-path>
Strict gate. Reads [Global] APP_CLUSTER_KUBECONTEXT from the
config and exits 1 if `kubectl config current-context` differs.
Skipped silently when the config has no baked APP_CLUSTER_KUBECONTEXT
(e.g. fresh k3d.cfg) or when there's no live current-context.
- print_kubecontext_notice
Informational. Prints what's about to be inherited so the user
can abort before the TUI launches if it looks wrong. Never fails.
Wiring:
- deploy.sh sources the helper and calls the strict gate against
${PROLE_DEPLOY_CFG:-conf/gke.cfg} before invoking Python.
Unattended path -> hard refusal on mismatch.
- install.sh sources the helper and calls the informational notice
(gated on not-`--min`) right after entering the local-checkout
branch. The TUI is interactive, so the strict mode-aware gate is
a follow-up once the welcome screen records a mode in
state.inputs.
Bypass for deliberate cross-cluster maintenance:
KNOE_SKIP_KUBECONTEXT_GUARD=true ./deploy.sh
End-to-end verified:
- deploy.sh with current=cnpg-0, gke.cfg=app-0 -> exit 1, clear msg
- deploy.sh with KNOE_SKIP_...=true -> bypasses, prints
"skipping check"
- install.sh --min -> notice skipped
- install.sh (no flag) and install.sh --silent -> notice printed
Doc updates:
- CLAUDE.md §"Env-contamination warning" rewritten to describe the
live guard (was a forward-looking TODO).
- CLAUDE.md drift table row R4 removed; "Closed 2026-05-01" line added.
- docs/TODO.md queue item #1 archived to Done; R4 dropped from the
reality-vs-intent table. Queue numbering retained (no #1 placeholder)
so the docs/plans/junie/<NN>-...md filenames still match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
135 lines
4.8 KiB
Bash
Executable File
135 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# preflight_kubecontext.sh
|
|
#
|
|
# Shared helper to detect the "wrong shell context for the install mode"
|
|
# class of bug. Sourced by install.sh and deploy.sh.
|
|
#
|
|
# Exposes two functions:
|
|
#
|
|
# verify_kubecontext_matches_config <config-path>
|
|
# Strict gate. Reads APP_CLUSTER_KUBECONTEXT from the config's [Global]
|
|
# section and refuses to proceed if `kubectl config current-context`
|
|
# doesn't match. Used by deploy.sh (unattended path: silent mismatches
|
|
# are dangerous).
|
|
#
|
|
# Returns 0 on match (or when the config has no APP_CLUSTER_KUBECONTEXT,
|
|
# e.g. fresh k3d setup). Exits 1 with a clear error on mismatch.
|
|
# Set KNOE_SKIP_KUBECONTEXT_GUARD=true to bypass (escape hatch for
|
|
# deliberate cross-cluster maintenance).
|
|
#
|
|
# print_kubecontext_notice
|
|
# Informational. Prints the current kubectl context (or "(none)") so
|
|
# the user sees what install.sh is about to inherit before the TUI
|
|
# launches. Never exits or fails. Used by install.sh.
|
|
#
|
|
# History: this guard was filed in response to the 2026-04-28 14:00 UTC
|
|
# outage. An install.sh run in k3d mode with the shell pointed at GKE
|
|
# overwrote the GKE-cluster ObjectStore + ScheduledBackup with k3d-mode
|
|
# defaults, and CNPG backups silently failed for hours until the next
|
|
# manual check. See CLAUDE.md §"Env-contamination warning" and
|
|
# docs/TODO.md queue item #1 (drift R4).
|
|
|
|
# Read APP_CLUSTER_KUBECONTEXT from the [Global] section of an INI-style
|
|
# config. Empty string if absent. Quote-stripping is best-effort.
|
|
_kubectx_from_config() {
|
|
local cfg="$1"
|
|
[[ -f "$cfg" ]] || { echo ""; return 0; }
|
|
# awk: print value when we're in [Global] and key matches.
|
|
# Strips surrounding whitespace and quotes.
|
|
awk '
|
|
/^\[/{section=$0; next}
|
|
section=="[Global]" && /^[[:space:]]*APP_CLUSTER_KUBECONTEXT[[:space:]]*=/ {
|
|
sub(/^[^=]*=[[:space:]]*/, "", $0)
|
|
sub(/^"/, "", $0); sub(/"$/, "", $0)
|
|
sub(/^'\''/, "", $0); sub(/'\''$/, "", $0)
|
|
print $0
|
|
exit
|
|
}
|
|
' "$cfg"
|
|
}
|
|
|
|
# Read the live current-context, or empty if kubectl/config unavailable.
|
|
_kubectx_current() {
|
|
command -v kubectl >/dev/null 2>&1 || { echo ""; return 0; }
|
|
kubectl config current-context 2>/dev/null || true
|
|
}
|
|
|
|
# verify_kubecontext_matches_config <config-path>
|
|
# Strict gate. Exits 1 on mismatch unless KNOE_SKIP_KUBECONTEXT_GUARD=true.
|
|
verify_kubecontext_matches_config() {
|
|
local cfg_path="${1:-}"
|
|
if [[ -z "$cfg_path" ]]; then
|
|
echo "preflight_kubecontext: usage: verify_kubecontext_matches_config <config-path>" >&2
|
|
return 2
|
|
fi
|
|
if [[ "${KNOE_SKIP_KUBECONTEXT_GUARD:-false}" == "true" ]]; then
|
|
echo "preflight_kubecontext: KNOE_SKIP_KUBECONTEXT_GUARD=true — skipping check (escape hatch)." >&2
|
|
return 0
|
|
fi
|
|
|
|
local expected
|
|
expected="$(_kubectx_from_config "$cfg_path")"
|
|
|
|
# No baked context in the config (e.g. fresh k3d.cfg) → nothing to check.
|
|
if [[ -z "$expected" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local actual
|
|
actual="$(_kubectx_current)"
|
|
|
|
# No live current-context → user hasn't selected one; the config is
|
|
# authoritative and downstream code will pass --context explicitly.
|
|
if [[ -z "$actual" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$expected" == "$actual" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
cat >&2 <<EOF
|
|
|
|
==============================================================================
|
|
ABORTING: kubectl context mismatch
|
|
==============================================================================
|
|
|
|
Config: $cfg_path
|
|
Expected: APP_CLUSTER_KUBECONTEXT = $expected
|
|
Actual: kubectl config current-context = $actual
|
|
|
|
These don't match. The installer would dispatch kubectl operations
|
|
against the WRONG cluster, which has caused real outages in the past
|
|
(see CLAUDE.md §"Env-contamination warning"). Refusing to proceed.
|
|
|
|
To fix:
|
|
1) Switch context to the expected cluster:
|
|
kubectl config use-context $expected
|
|
Re-run this command afterward.
|
|
|
|
2) Or, if you really need to deploy against the active context, edit
|
|
\`$cfg_path\` and update [Global] APP_CLUSTER_KUBECONTEXT to match.
|
|
|
|
3) Override (only when you know what you're doing):
|
|
KNOE_SKIP_KUBECONTEXT_GUARD=true ./deploy.sh
|
|
|
|
==============================================================================
|
|
EOF
|
|
return 1
|
|
}
|
|
|
|
# print_kubecontext_notice
|
|
# Informational. Never fails; just shows the user what's about to be
|
|
# inherited so they can abort before the TUI launches if it looks wrong.
|
|
print_kubecontext_notice() {
|
|
local actual
|
|
actual="$(_kubectx_current)"
|
|
if [[ -z "$actual" ]]; then
|
|
echo "==> kubectl current-context: (none set)"
|
|
else
|
|
echo "==> kubectl current-context: $actual"
|
|
fi
|
|
echo " The mode you select must target this cluster, OR you must switch"
|
|
echo " context (kubectl config use-context …) before proceeding."
|
|
}
|