mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
Five fixes Junie surfaced while running the kdc-trust-reset-repeatable
Junie brief end-to-end (companion to commit 6f99f95). All hit during
the unattended `install.sh --mode k3s --reset` pipeline.
- knoe/core/milestones.py (KerberosMilestone):
For k3s and k3d modes, deploy the KDC pod via `init_kdc.sh start`
before running init_kerberos.sh. init_kerberos.sh only chains into
init_kdc.sh when PROLE_KDC_STANDALONE=1; without this hook the
cluster came up with no KDC pod and the cross-realm trust principals
had nowhere to land.
- knoe/milestone.py (Milestone._get_script_env):
Clear KUBECTL_CONTEXT in addition to KUBECONTEXT so stale entries
from a different machine's cfg don't override the kubeconfig's
own current-context.
- etc/knoe_cfg.sh (_knoe_read_cfg):
Skip KUBECTL_CONTEXT / KUBE_CONTEXT_NAME / KUBECONTEXT entries when
reading cfg in k3s mode. Same theme: kubeconfig current-context is
authoritative.
- etc/init_1password.sh + knoe/core/onepassword.py:
When running non-interactively (no TTY on stdin) and no `op`
session exists, skip rather than hang on `op signin`. Lets the
unattended pipeline proceed for k3s/k3d where in-cluster secrets
are managed separately from 1Password.
Co-authored-by: Junie <junie@jetbrains.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1715 lines
56 KiB
Bash
1715 lines
56 KiB
Bash
#!/usr/bin/env bash
|
|
# Standard include for Knoe etc scripts.
|
|
# - Loads environment from env.sh (if available)
|
|
# - Loads values from mode-specific config files in KNOE_CONF (INI-style)
|
|
# - Does not override already-set env vars
|
|
|
|
if [[ "${_PROLE_CFG_LOADED:-}" == "1" ]]; then
|
|
return 0 2>/dev/null || true
|
|
fi
|
|
_PROLE_CFG_LOADED=1
|
|
|
|
_knoe_cfg_script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
_knoe_cfg_home_guess=$(cd "$_knoe_cfg_script_dir/.." && pwd)
|
|
|
|
# 1. Try to load env.sh to establish base KNOE_HOME/KNOE_CONF
|
|
# If KNOE_HOME/KNOE_CONF/etc are already set by the caller (e.g. tests),
|
|
# do not allow env.sh to clobber them.
|
|
_knoe_cfg_preserve_home="${KNOE_HOME:-}"
|
|
_knoe_cfg_preserve_conf="${KNOE_CONF:-}"
|
|
_knoe_cfg_preserve_service="${KNOE_SERVICE:-}"
|
|
if [[ -n "${KNOE_HOME:-}" && -f "$KNOE_HOME/env.sh" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$KNOE_HOME/env.sh"
|
|
elif [[ -f "$HOME/.knoe/env.sh" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$HOME/.knoe/env.sh"
|
|
elif [[ -f "$_knoe_cfg_home_guess/env.sh" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$_knoe_cfg_home_guess/env.sh"
|
|
fi
|
|
|
|
if [[ -n "$_knoe_cfg_preserve_home" ]]; then
|
|
KNOE_HOME="$_knoe_cfg_preserve_home"
|
|
export KNOE_HOME
|
|
fi
|
|
if [[ -n "$_knoe_cfg_preserve_conf" ]]; then
|
|
KNOE_CONF="$_knoe_cfg_preserve_conf"
|
|
export KNOE_CONF
|
|
fi
|
|
if [[ -n "$_knoe_cfg_preserve_service" ]]; then
|
|
KNOE_SERVICE="$_knoe_cfg_preserve_service"
|
|
export KNOE_SERVICE
|
|
fi
|
|
unset _knoe_cfg_preserve_home _knoe_cfg_preserve_conf _knoe_cfg_preserve_service
|
|
|
|
_knoe_trim() {
|
|
local s="$1"
|
|
s="${s#"${s%%[![:space:]]*}"}"
|
|
s="${s%"${s##*[![:space:]]}"}"
|
|
printf '%s' "$s"
|
|
}
|
|
|
|
_knoe_cfg_expand() {
|
|
local value="$1"
|
|
local out="" rest="$value"
|
|
local prefix token suffix var repl
|
|
if [[ -z "$value" ]]; then
|
|
printf '%s' "$value"
|
|
return 0
|
|
fi
|
|
case "$value" in
|
|
'${OPENBAO:'*|'${KNOE_SECRET:'*)
|
|
printf '%s' "$value"
|
|
return 0
|
|
;;
|
|
esac
|
|
while [[ "$rest" =~ ^([^$]*)(\$[A-Za-z_][A-Za-z0-9_]*|\$\{[A-Za-z_][A-Za-z0-9_]*\})(.*)$ ]]; do
|
|
prefix="${BASH_REMATCH[1]}"
|
|
token="${BASH_REMATCH[2]}"
|
|
suffix="${BASH_REMATCH[3]}"
|
|
var="${token#\$}"
|
|
var="${var#\{}"
|
|
var="${var%\}}"
|
|
if [[ -n "${!var+x}" ]]; then
|
|
repl="${!var}"
|
|
else
|
|
repl="$token"
|
|
fi
|
|
out+="${prefix}${repl}"
|
|
rest="$suffix"
|
|
done
|
|
out+="$rest"
|
|
printf '%s' "$out"
|
|
}
|
|
|
|
_knoe_host_from_url() {
|
|
local val="${1:-}"
|
|
val="${val#http://}"
|
|
val="${val#https://}"
|
|
val="${val%%/*}"
|
|
val="${val%%:*}"
|
|
printf '%s' "$val"
|
|
}
|
|
|
|
knoe_is_in_cluster() {
|
|
[[ -n "${KUBERNETES_SERVICE_HOST:-}" && -f /var/run/secrets/kubernetes.io/serviceaccount/token ]]
|
|
}
|
|
|
|
_knoe_local_registry_enabled() {
|
|
local raw="${PROLE_ENABLE_LOCAL_REGISTRY:-${ENABLE_LOCAL_REGISTRY:-}}"
|
|
if [[ -n "$raw" ]]; then
|
|
_knoe_bool_true "$raw" && return 0
|
|
return 1
|
|
fi
|
|
local mode=""
|
|
if command -v knoe_normalize_mode >/dev/null 2>&1; then
|
|
mode=$(knoe_normalize_mode "${KNOE_MODE:-${DEPLOYMENT_MODE:-${CLUSTER_ENV:-}}}")
|
|
else
|
|
mode="${KNOE_MODE:-${DEPLOYMENT_MODE:-${CLUSTER_ENV:-}}}"
|
|
fi
|
|
[[ "$mode" == "k3d" || "$mode" == "k3s" ]]
|
|
}
|
|
|
|
_knoe_bool_true() {
|
|
case "${1:-}" in
|
|
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
_knoe_kubeconfig_mark_insecure() {
|
|
local cfg="${1:-${KUBECONFIG:-}}"
|
|
[[ -z "$cfg" || ! -f "$cfg" ]] && return 1
|
|
command -v kubectl >/dev/null 2>&1 || return 1
|
|
local clusters
|
|
clusters=$(KUBECONFIG="$cfg" kubectl config get-clusters 2>/dev/null | awk 'NR>1 {print $1}' || true)
|
|
[[ -z "$clusters" ]] && return 1
|
|
local c
|
|
for c in $clusters; do
|
|
KUBECONFIG="$cfg" kubectl config set-cluster "$c" --insecure-skip-tls-verify=true >/dev/null 2>&1 || true
|
|
done
|
|
return 0
|
|
}
|
|
|
|
_PROLE_CFG_SET_VARS="|"
|
|
|
|
_knoe_cfg_normalize_key() {
|
|
local key="${1:-}"
|
|
key="${key//./_}"
|
|
key="${key//[^A-Za-z0-9_]/_}"
|
|
if [[ "$key" =~ ^[0-9] ]]; then
|
|
key="_${key}"
|
|
fi
|
|
printf '%s' "$key"
|
|
}
|
|
|
|
_knoe_cfg_set_default() {
|
|
local key="$1" value="$2"
|
|
local token="|$key|"
|
|
if [[ ! "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
|
|
echo "[WARN] Skipping invalid config key '$key' (not a valid shell identifier)." >&2
|
|
return 0
|
|
fi
|
|
# Only set if currently empty OR if we were the ones who set it from config previously
|
|
if [[ -z "${!key:-}" || "$_PROLE_CFG_SET_VARS" == *"$token"* ]]; then
|
|
printf -v "$key" '%s' "$value"
|
|
export "$key"
|
|
if [[ "$_PROLE_CFG_SET_VARS" != *"$token"* ]]; then
|
|
_PROLE_CFG_SET_VARS="${_PROLE_CFG_SET_VARS}${key}|"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
_knoe_read_cfg() {
|
|
local cfg="$1" line key value
|
|
# In k3s mode the kubeconfig current-context is authoritative; never set
|
|
# KUBECTL_CONTEXT / KUBE_CONTEXT_NAME / KUBECONTEXT from cfg file entries.
|
|
local _knoe_read_cfg_mode
|
|
_knoe_read_cfg_mode=$(knoe_normalize_mode "${DEPLOYMENT_MODE:-${KNOE_MODE:-${CLUSTER_ENV:-${knoe_mode:-}}}}" 2>/dev/null || true)
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
line="$(_knoe_trim "$line")"
|
|
[[ -z "$line" ]] && continue
|
|
case "$line" in
|
|
\#*|\;*|\[*\])
|
|
continue
|
|
;;
|
|
esac
|
|
if [[ "$line" == *"="* ]]; then
|
|
key="$(_knoe_trim "${line%%=*}")"
|
|
value="$(_knoe_trim "${line#*=}")"
|
|
[[ -z "$key" ]] && continue
|
|
# Normalize to a valid shell variable key for safe export.
|
|
key="$(_knoe_cfg_normalize_key "$key")"
|
|
[[ -z "$key" ]] && continue
|
|
# k3s: kubeconfig current-context is authoritative; skip context overrides from cfg.
|
|
if [[ "$_knoe_read_cfg_mode" == "k3s" ]]; then
|
|
case "$key" in
|
|
KUBECTL_CONTEXT|KUBE_CONTEXT_NAME|KUBECONTEXT) continue ;;
|
|
esac
|
|
fi
|
|
value="$(_knoe_cfg_expand "$value")"
|
|
_knoe_cfg_set_default "$key" "$value"
|
|
fi
|
|
done <"$cfg"
|
|
}
|
|
|
|
_knoe_cfg_extract_key() {
|
|
local cfg="$1" key="$2"
|
|
local value
|
|
value=$(awk -F= -v k="$key" '
|
|
/^[[:space:]]*;/ {next}
|
|
/^[[:space:]]*#/ {next}
|
|
/^[[:space:]]*\[/ {next}
|
|
$1 ~ "^[[:space:]]*" k "[[:space:]]*$" {
|
|
v=$2
|
|
sub(/^[[:space:]]+/, "", v)
|
|
sub(/[[:space:]]+$/, "", v)
|
|
val=v
|
|
}
|
|
END { print val }
|
|
' "$cfg" 2>/dev/null || true)
|
|
value="$(_knoe_cfg_expand "$value")"
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
_knoe_cfg_extract_key_in_files() {
|
|
local key="$1"; shift
|
|
local value
|
|
if [[ $# -lt 1 ]]; then
|
|
return 0
|
|
fi
|
|
value=$(awk -F= -v k="$key" '
|
|
/^[[:space:]]*;/ {next}
|
|
/^[[:space:]]*#/ {next}
|
|
/^[[:space:]]*\[/ {next}
|
|
$1 ~ "^[[:space:]]*" k "[[:space:]]*$" {
|
|
v=$2
|
|
sub(/^[[:space:]]+/, "", v)
|
|
sub(/[[:space:]]+$/, "", v)
|
|
val=v
|
|
}
|
|
END { print val }
|
|
' "$@" 2>/dev/null || true)
|
|
value="$(_knoe_cfg_expand "$value")"
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
_knoe_cfg_realpath() {
|
|
local p="$1"
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$p" 2>/dev/null && return 0
|
|
fi
|
|
if command -v python >/dev/null 2>&1; then
|
|
python -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$p" 2>/dev/null && return 0
|
|
fi
|
|
# Fall back to the raw path.
|
|
printf '%s' "$p"
|
|
}
|
|
|
|
_knoe_cfg_named_file_for_env() {
|
|
case "${1:-}" in
|
|
dev) printf '%s' "k3d.cfg" ;;
|
|
service|services) printf '%s' "k3s.cfg" ;;
|
|
prod|production) printf '%s' "gke.cfg" ;;
|
|
test|testing) printf '%s' "test.cfg" ;;
|
|
*) printf '%s' "" ;;
|
|
esac
|
|
}
|
|
|
|
_knoe_cfg_named_file_for_mode() {
|
|
local mode="${1:-}"
|
|
if command -v knoe_normalize_mode >/dev/null 2>&1; then
|
|
mode=$(knoe_normalize_mode "$mode")
|
|
fi
|
|
case "$mode" in
|
|
k3d) printf '%s' "k3d.cfg" ;;
|
|
k3s) printf '%s' "k3s.cfg" ;;
|
|
k8s) printf '%s' "gke.cfg" ;;
|
|
*) printf '%s' "" ;;
|
|
esac
|
|
}
|
|
|
|
_knoe_cfg_select_cfg_file() {
|
|
local conf_dir="$1"
|
|
[[ -z "$conf_dir" ]] && return 0
|
|
|
|
# If an explicit config is passed via environment, prefer it above all else.
|
|
if [[ -n "${PROLE_DEPLOY_CFG:-}" && -f "${PROLE_DEPLOY_CFG}" ]]; then
|
|
printf '%s' "${PROLE_DEPLOY_CFG}"
|
|
return 0
|
|
fi
|
|
|
|
local mode_hint mode_file env_hint env_file cfg
|
|
mode_hint="${KNOE_MODE:-${DEPLOYMENT_MODE:-${CLUSTER_ENV:-}}}"
|
|
mode_file="$(_knoe_cfg_named_file_for_mode "$mode_hint")"
|
|
if [[ -n "$mode_file" && -f "$conf_dir/$mode_file" ]]; then
|
|
printf '%s' "$conf_dir/$mode_file"
|
|
return 0
|
|
fi
|
|
|
|
for cfg in k3d.cfg k3s.cfg gke.cfg test.cfg; do
|
|
if [[ -f "$conf_dir/$cfg" ]]; then
|
|
printf '%s' "$conf_dir/$cfg"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
env_hint="$(_knoe_cfg_env_from_hint "$mode_hint")"
|
|
if [[ -n "$env_hint" ]]; then
|
|
env_file="$(_knoe_cfg_named_file_for_env "$env_hint")"
|
|
if [[ -n "$env_file" && -f "$conf_dir/$env_hint/$env_file" ]]; then
|
|
printf '%s' "$conf_dir/$env_hint/$env_file"
|
|
return 0
|
|
fi
|
|
if [[ -f "$conf_dir/$env_hint/knoe.cfg" ]]; then
|
|
printf '%s' "$conf_dir/$env_hint/knoe.cfg"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
for cfg in dev service prod test; do
|
|
env_file="$(_knoe_cfg_named_file_for_env "$cfg")"
|
|
if [[ -n "$env_file" && -f "$conf_dir/$cfg/$env_file" ]]; then
|
|
printf '%s' "$conf_dir/$cfg/$env_file"
|
|
return 0
|
|
fi
|
|
if [[ -f "$conf_dir/$cfg/knoe.cfg" ]]; then
|
|
printf '%s' "$conf_dir/$cfg/knoe.cfg"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
if [[ -f "$conf_dir/knoe.cfg" ]]; then
|
|
printf '%s' "$conf_dir/knoe.cfg"
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
_knoe_cfg_layer_files() {
|
|
local entry="$1"
|
|
[[ -z "$entry" ]] && return 0
|
|
local resolved env_dir env_name base base_name f env_base
|
|
resolved=$(_knoe_cfg_realpath "$entry")
|
|
env_dir=$(cd "$(dirname "$resolved")" 2>/dev/null && pwd || true)
|
|
env_name=$(basename "$env_dir")
|
|
base_name=$(basename "$resolved")
|
|
if [[ "$base_name" =~ ^(k3d|k3s|gke|test)\.cfg$ ]]; then
|
|
printf '%s\n' "$resolved"
|
|
return 0
|
|
fi
|
|
|
|
base="$env_dir/$base_name"
|
|
if [[ "$env_name" =~ ^(dev|service|prod|test)$ ]]; then
|
|
env_base="$(_knoe_cfg_named_file_for_env "$env_name")"
|
|
if [[ "$base_name" == "knoe.cfg" && -n "$env_base" && -f "$env_dir/$env_base" ]]; then
|
|
base="$env_dir/$env_base"
|
|
elif [[ ! -f "$base" && -n "$env_base" && -f "$env_dir/$env_base" ]]; then
|
|
base="$env_dir/$env_base"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$env_name" =~ ^(dev|service|prod|test)$ && -f "$base" ]]; then
|
|
printf '%s\n' "$base"
|
|
for f in "$env_dir"/*.cfg; do
|
|
[[ -f "$f" ]] || continue
|
|
[[ "$(basename "$f")" == "$(basename "$base")" ]] && continue
|
|
[[ "$(basename "$f")" == "prod.cfg" ]] && continue # standalone GKE deploy config, not a knoe.cfg overlay
|
|
[[ "$(basename "$f")" == .* ]] && continue
|
|
printf '%s\n' "$f"
|
|
done
|
|
return 0
|
|
fi
|
|
printf '%s\n' "$entry"
|
|
}
|
|
|
|
_knoe_cfg_env_from_hint() {
|
|
local s
|
|
s=$(printf '%s' "${1:-}" | tr 'A-Z' 'a-z')
|
|
case "$s" in
|
|
dev|k3d|k3d-*|knoe-dev-*|knoe-dev-cluster|k3d-knoe-*) printf '%s' "dev"; return 0 ;;
|
|
service|k3s|k3s-*|knoe-service-*|knoe-system|k3d-knoe-system) printf '%s' "service"; return 0 ;;
|
|
prod|production|k8s|k8s-*|knoe-prod-*) printf '%s' "prod"; return 0 ;;
|
|
test|testing) printf '%s' "test"; return 0 ;;
|
|
esac
|
|
printf '%s' ""
|
|
}
|
|
|
|
_knoe_cfg_default_ns_for_env() {
|
|
case "${1:-}" in
|
|
test) printf '%s' "knoe-test" ;;
|
|
*) printf '%s' "knoe-db" ;;
|
|
esac
|
|
}
|
|
|
|
_knoe_cfg_default_sns_for_env() {
|
|
case "${1:-}" in
|
|
dev) printf '%s' "default" ;;
|
|
test) printf '%s' "knoe-test" ;;
|
|
*) printf '%s' "knoe-system" ;;
|
|
esac
|
|
}
|
|
|
|
_knoe_cfg_write_default_base() {
|
|
local base="$1" env="$2"
|
|
local ns sns
|
|
ns="$(_knoe_cfg_default_ns_for_env "$env")"
|
|
sns="$(_knoe_cfg_default_sns_for_env "$env")"
|
|
mkdir -p "$(dirname "$base")" 2>/dev/null || true
|
|
cat >"$base" <<EOF
|
|
; Knoe Master Configuration File
|
|
; Generated by knoe_cfg.sh
|
|
|
|
[Global]
|
|
CLUSTER_ENV = ${env}
|
|
NAMESPACE = ${ns}
|
|
SERVICE_NAMESPACE = ${sns}
|
|
EOF
|
|
}
|
|
|
|
_knoe_cfg_bootstrap_env_layout() {
|
|
local conf_dir="$1"
|
|
local entry="$conf_dir/knoe.cfg"
|
|
local existing_selected=""
|
|
[[ -z "$conf_dir" ]] && return 0
|
|
|
|
existing_selected="$(_knoe_cfg_select_cfg_file "$conf_dir")"
|
|
if [[ -n "$existing_selected" && "$(basename "$existing_selected")" != "knoe.cfg" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# Detect if conf_dir is already an env-specific directory (e.g. conf/service).
|
|
# In that case, skip creating env subdirectories to avoid self-referential
|
|
# nesting like conf/service/service/.
|
|
local conf_basename
|
|
conf_basename=$(basename "$conf_dir")
|
|
local is_env_dir=false
|
|
case "$conf_basename" in
|
|
dev|service|prod|test|k3d|k3s|k8s) is_env_dir=true ;;
|
|
esac
|
|
|
|
if [[ "$is_env_dir" == false ]]; then
|
|
mkdir -p "$conf_dir/dev" "$conf_dir/service" "$conf_dir/prod" "$conf_dir/test" 2>/dev/null || true
|
|
fi
|
|
|
|
# If we already have a valid symlink entrypoint, nothing to do.
|
|
if [[ -L "$entry" && -e "$entry" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local env_hint=""
|
|
local env=""
|
|
|
|
# If entry is a legacy regular file, infer env from its contents.
|
|
if [[ -f "$entry" && ! -L "$entry" ]]; then
|
|
env_hint="$(_knoe_cfg_extract_key "$entry" "DEPLOYMENT_MODE")"
|
|
env="$(_knoe_cfg_env_from_hint "$env_hint")"
|
|
if [[ -z "$env" ]]; then
|
|
env_hint="$(_knoe_cfg_extract_key "$entry" "CLUSTER_ENV")"
|
|
env="$(_knoe_cfg_env_from_hint "$env_hint")"
|
|
fi
|
|
if [[ -z "$env" ]]; then
|
|
env_hint="$(_knoe_cfg_extract_key "$entry" "ENVIRONMENT")"
|
|
env="$(_knoe_cfg_env_from_hint "$env_hint")"
|
|
fi
|
|
if [[ -z "$env" ]]; then
|
|
# Legacy key used by older configs.
|
|
env_hint="$(_knoe_cfg_extract_key "$entry" "knoe.mode")"
|
|
env="$(_knoe_cfg_env_from_hint "$env_hint")"
|
|
fi
|
|
fi
|
|
|
|
# If still unknown, fall back to any already-exported mode/cluster hint.
|
|
if [[ -z "$env" ]]; then
|
|
env_hint="${KNOE_MODE:-${DEPLOYMENT_MODE:-${CLUSTER_ENV:-}}}"
|
|
if command -v knoe_normalize_mode >/dev/null 2>&1; then
|
|
env_hint=$(knoe_normalize_mode "$env_hint")
|
|
fi
|
|
env="$(_knoe_cfg_env_from_hint "$env_hint")"
|
|
fi
|
|
|
|
# NO DEFAULT FALLBACK TO "dev" here. If env is unknown, we must fail or remain unknown
|
|
# so that explicit contexts (like k8s) aren't clobbered by a "dev" (k3d) default.
|
|
if [[ -z "$env" ]]; then
|
|
if [[ "${_PROLE_CFG_STRICT:-}" == "1" ]]; then
|
|
echo "ERROR: Could not resolve environment (dev/service/prod/test) from hints or config." >&2
|
|
exit 1
|
|
fi
|
|
# If not strict, we don't set a default env, allowing caller to handle it.
|
|
fi
|
|
|
|
# If conf_dir is already the env directory, the base config lives here directly.
|
|
local base
|
|
local env_cfg
|
|
env_cfg="$(_knoe_cfg_named_file_for_env "$env")"
|
|
[[ -z "$env_cfg" ]] && env_cfg="knoe.cfg"
|
|
if [[ "$is_env_dir" == true ]]; then
|
|
base="$conf_dir/$env_cfg"
|
|
else
|
|
base="$conf_dir/$env_cfg"
|
|
fi
|
|
|
|
# If conf_dir is already the env dir, the entry IS the base — just ensure it exists.
|
|
if [[ "$is_env_dir" == true ]]; then
|
|
if [[ -L "$entry" ]]; then
|
|
# Remove stale symlink so we can replace it with the real file.
|
|
local target_file
|
|
target_file=$(readlink "$entry" 2>/dev/null || true)
|
|
local resolved="$conf_dir/$target_file"
|
|
if [[ -f "$resolved" ]]; then
|
|
cp "$resolved" "$entry.tmp" 2>/dev/null || true
|
|
rm -f "$entry" 2>/dev/null || true
|
|
mv "$entry.tmp" "$entry" 2>/dev/null || true
|
|
else
|
|
rm -f "$entry" 2>/dev/null || true
|
|
_knoe_cfg_write_default_base "$base" "$env"
|
|
fi
|
|
elif [[ ! -f "$entry" ]]; then
|
|
_knoe_cfg_write_default_base "$base" "$env"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
# Remove stale broken symlink (no replacement symlink — Python resolves env path directly).
|
|
if [[ -L "$entry" && ! -e "$entry" ]]; then
|
|
rm -f "$entry" 2>/dev/null || true
|
|
[[ -f "$base" ]] || _knoe_cfg_write_default_base "$base" "$env"
|
|
return 0
|
|
fi
|
|
|
|
# Migrate legacy regular-file entrypoint into env base.
|
|
if [[ -f "$entry" && ! -L "$entry" ]]; then
|
|
if [[ ! -f "$base" ]] || grep -q "Generated by knoe_conf" "$base" 2>/dev/null || grep -q "Generated by knoe_cfg.sh" "$base" 2>/dev/null; then
|
|
cp "$entry" "$base" 2>/dev/null || true
|
|
fi
|
|
local ts legacy
|
|
ts=$(date +%s)
|
|
legacy="$conf_dir/knoe.cfg.legacy.${ts}"
|
|
mv "$entry" "$legacy" 2>/dev/null || true
|
|
# No symlink created — Python resolves env path from CLUSTER_ENV directly.
|
|
return 0
|
|
fi
|
|
# If entrypoint is missing, create the env-specific base (no symlink needed).
|
|
if [[ ! -e "$entry" ]]; then
|
|
[[ -f "$base" ]] || _knoe_cfg_write_default_base "$base" "$env"
|
|
fi
|
|
}
|
|
|
|
knoe_normalize_mode() {
|
|
local s
|
|
s=$(printf '%s' "${1:-}" | tr 'A-Z' 'a-z')
|
|
case "$s" in
|
|
dev|k3d|k3d-*) echo "k3d"; return 0 ;;
|
|
service|k3s|k3s-*) echo "k3s"; return 0 ;;
|
|
prod|production|k8s|k8s-*) echo "k8s"; return 0 ;;
|
|
knoe-dev-cluster|k3d-knoe-dev-cluster) echo "k3d"; return 0 ;;
|
|
knoe-dev-cluster|k3d-knoe-dev-cluster) echo "k3d"; return 0 ;;
|
|
knoe-service-cluster) echo "k3s"; return 0 ;;
|
|
knoe-prod-cluster) echo "k8s"; return 0 ;;
|
|
esac
|
|
echo "$s"
|
|
}
|
|
|
|
knoe_set_mode() {
|
|
local raw="$1"
|
|
if [[ -z "$raw" ]]; then
|
|
echo "ERROR: --mode requires a value (k3d, k3s, k8s)" >&2
|
|
exit 2
|
|
fi
|
|
local norm
|
|
norm=$(knoe_normalize_mode "$raw")
|
|
case "$norm" in
|
|
k3d|k3s|k8s)
|
|
KNOE_MODE="$norm"
|
|
export KNOE_MODE
|
|
;;
|
|
*)
|
|
echo "ERROR: Unsupported mode '$raw' (use k3d, k3s, or k8s)" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
}
|
|
|
|
knoe_render_manifest() {
|
|
local src="$1"
|
|
if [[ "${KNOE_MODE:-}" != "k3d" ]]; then
|
|
cat "$src"
|
|
return 0
|
|
fi
|
|
local renderer=""
|
|
if [[ -n "${KNOE_SERVICE:-}" && -f "$KNOE_SERVICE/render_manifest.py" ]]; then
|
|
renderer="$KNOE_SERVICE/render_manifest.py"
|
|
elif [[ -n "${KNOE_HOME:-}" && -f "$KNOE_HOME/etc/render_manifest.py" ]]; then
|
|
renderer="$KNOE_HOME/etc/render_manifest.py"
|
|
fi
|
|
if [[ -n "$renderer" ]]; then
|
|
python3 "$renderer" "$src"
|
|
return $?
|
|
fi
|
|
cat "$src"
|
|
}
|
|
|
|
_knoe_cfg_file=""
|
|
if [[ -n "${KNOE_CONF:-}" ]]; then
|
|
_knoe_cfg_file="$(_knoe_cfg_select_cfg_file "$KNOE_CONF")"
|
|
fi
|
|
if [[ -z "$_knoe_cfg_file" && -n "${KNOE_HOME:-}" ]]; then
|
|
_knoe_cfg_file="$(_knoe_cfg_select_cfg_file "$KNOE_HOME/conf")"
|
|
fi
|
|
if [[ -z "$_knoe_cfg_file" ]]; then
|
|
_knoe_cfg_file="$(_knoe_cfg_select_cfg_file "$_knoe_cfg_home_guess/conf")"
|
|
fi
|
|
|
|
if [[ -n "$_knoe_cfg_file" ]]; then
|
|
if [[ -z "${KNOE_CONF:-}" ]]; then
|
|
KNOE_CONF=$(cd "$(dirname "$_knoe_cfg_file")" && pwd)
|
|
export KNOE_CONF
|
|
fi
|
|
|
|
# Migrate legacy knoe.cfg layouts only when a legacy entrypoint is selected.
|
|
if [[ "$(basename "$_knoe_cfg_file")" == "knoe.cfg" ]]; then
|
|
_knoe_cfg_bootstrap_env_layout "$KNOE_CONF"
|
|
_knoe_cfg_file="$(_knoe_cfg_select_cfg_file "$KNOE_CONF")"
|
|
fi
|
|
|
|
_knoe_cfg_files=()
|
|
while IFS= read -r _knoe_cfg_f || [[ -n "$_knoe_cfg_f" ]]; do
|
|
[[ -n "$_knoe_cfg_f" ]] || continue
|
|
_knoe_cfg_files+=("$_knoe_cfg_f")
|
|
done < <(_knoe_cfg_layer_files "$_knoe_cfg_file")
|
|
if [[ ${#_knoe_cfg_files[@]} -eq 0 ]]; then
|
|
_knoe_cfg_files=("$_knoe_cfg_file")
|
|
fi
|
|
for _knoe_cfg_f in "${_knoe_cfg_files[@]}"; do
|
|
[[ -f "$_knoe_cfg_f" ]] || continue
|
|
_knoe_read_cfg "$_knoe_cfg_f"
|
|
done
|
|
|
|
if [[ -z "${KERBEROS_ENABLED:-}" && -n "${ENABLED:-}" ]]; then
|
|
KERBEROS_ENABLED="$ENABLED"
|
|
export KERBEROS_ENABLED
|
|
fi
|
|
|
|
# Always prefer namespace from the selected config file(s) (single source of truth).
|
|
_cfg_ns=$(_knoe_cfg_extract_key_in_files "NAMESPACE" "${_knoe_cfg_files[@]}")
|
|
if [[ -n "$_cfg_ns" ]]; then
|
|
export PROLE_NAMESPACE="$_cfg_ns"
|
|
fi
|
|
_cfg_sns=$(_knoe_cfg_extract_key_in_files "SERVICE_NAMESPACE" "${_knoe_cfg_files[@]}")
|
|
if [[ -n "$_cfg_sns" ]]; then
|
|
export SERVICE_NAMESPACE="$_cfg_sns"
|
|
fi
|
|
|
|
# Always prefer deployment/mode hint from selected config file(s) (single source of truth).
|
|
# This prevents leaked environment values (e.g. KNOE_MODE=k3d) from forcing the wrong mode.
|
|
_cfg_dm=$(_knoe_cfg_extract_key_in_files "DEPLOYMENT_MODE" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_dm" ]]; then
|
|
_cfg_dm=$(_knoe_cfg_extract_key_in_files "knoe.mode" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_dm" ]]; then
|
|
DEPLOYMENT_MODE="$_cfg_dm"
|
|
export DEPLOYMENT_MODE
|
|
fi
|
|
_cfg_sh=$(_knoe_cfg_extract_key_in_files "SERVICE_HOSTNAME" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_sh" ]]; then
|
|
_cfg_sh=$(_knoe_cfg_extract_key_in_files "service_hostname" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_sh" ]]; then
|
|
export SERVICE_HOSTNAME="$_cfg_sh"
|
|
fi
|
|
|
|
# Supabase front-door hostname (canonical external entrypoint).
|
|
_cfg_sbh=$(_knoe_cfg_extract_key_in_files "supabase_hostname" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_sbh" ]]; then
|
|
_cfg_sbh=$(_knoe_cfg_extract_key_in_files "SUPABASE_HOSTNAME" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_sbh" ]]; then
|
|
export supabase_hostname="$_cfg_sbh"
|
|
export SUPABASE_HOSTNAME="$_cfg_sbh"
|
|
fi
|
|
|
|
_cfg_sapi=$(_knoe_cfg_extract_key_in_files "SUPABASE_API_HOSTNAME" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_sapi" ]]; then
|
|
_cfg_sapi=$(_knoe_cfg_extract_key_in_files "supabase_api_hostname" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_sapi" ]]; then
|
|
export SUPABASE_API_HOSTNAME="$_cfg_sapi"
|
|
fi
|
|
|
|
_cfg_sstudio=$(_knoe_cfg_extract_key_in_files "SUPABASE_STUDIO_HOSTNAME" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_sstudio" ]]; then
|
|
_cfg_sstudio=$(_knoe_cfg_extract_key_in_files "supabase_studio_hostname" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_sstudio" ]]; then
|
|
export SUPABASE_STUDIO_HOSTNAME="$_cfg_sstudio"
|
|
fi
|
|
|
|
_cfg_sing=$(_knoe_cfg_extract_key_in_files "SUPABASE_INGRESS_CLASS" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_sing" ]]; then
|
|
_cfg_sing=$(_knoe_cfg_extract_key_in_files "supabase_ingress_class" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_sing" ]]; then
|
|
export SUPABASE_INGRESS_CLASS="$_cfg_sing"
|
|
fi
|
|
|
|
_cfg_gitdom=$(_knoe_cfg_extract_key_in_files "GITLAB_DOMAIN" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_gitdom" ]]; then
|
|
_cfg_gitdom=$(_knoe_cfg_extract_key_in_files "gitlab_domain" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -z "$_cfg_gitdom" ]]; then
|
|
_cfg_gitdom=$(_knoe_cfg_extract_key_in_files "GITLAB_HOSTNAME" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_gitdom" ]]; then
|
|
export GITLAB_DOMAIN="$_cfg_gitdom"
|
|
fi
|
|
|
|
_cfg_githosts=$(_knoe_cfg_extract_key_in_files "GITLAB_PUBLIC_HOSTS" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_githosts" ]]; then
|
|
_cfg_githosts=$(_knoe_cfg_extract_key_in_files "gitlab_public_hosts" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_githosts" ]]; then
|
|
export GITLAB_PUBLIC_HOSTS="$_cfg_githosts"
|
|
fi
|
|
|
|
_cfg_ging=$(_knoe_cfg_extract_key_in_files "GITLAB_INGRESS_CLASS" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_ging" ]]; then
|
|
_cfg_ging=$(_knoe_cfg_extract_key_in_files "gitlab_ingress_class" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_ging" ]]; then
|
|
export GITLAB_INGRESS_CLASS="$_cfg_ging"
|
|
fi
|
|
|
|
_cfg_auth_enabled=$(_knoe_cfg_extract_key_in_files "AUTHORITY_ENABLED" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_auth_enabled" ]]; then
|
|
_cfg_auth_enabled=$(_knoe_cfg_extract_key_in_files "authority_enabled" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_auth_enabled" ]]; then
|
|
export AUTHORITY_ENABLED="$_cfg_auth_enabled"
|
|
export FRONTDOOR_AUTH_ENABLED="$_cfg_auth_enabled"
|
|
fi
|
|
|
|
_cfg_auth_host=$(_knoe_cfg_extract_key_in_files "AUTH_HOSTNAME" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_auth_host" ]]; then
|
|
_cfg_auth_host=$(_knoe_cfg_extract_key_in_files "auth_hostname" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_auth_host" ]]; then
|
|
export AUTH_HOSTNAME="$_cfg_auth_host"
|
|
fi
|
|
|
|
_cfg_auth_verify=$(_knoe_cfg_extract_key_in_files "AUTH_VERIFY_PATH" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_auth_verify" ]]; then
|
|
_cfg_auth_verify=$(_knoe_cfg_extract_key_in_files "auth_verify_path" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_auth_verify" ]]; then
|
|
export AUTH_VERIFY_PATH="$_cfg_auth_verify"
|
|
fi
|
|
|
|
_cfg_auth_login=$(_knoe_cfg_extract_key_in_files "AUTH_LOGIN_PATH" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_auth_login" ]]; then
|
|
_cfg_auth_login=$(_knoe_cfg_extract_key_in_files "auth_login_path" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_auth_login" ]]; then
|
|
export AUTH_LOGIN_PATH="$_cfg_auth_login"
|
|
fi
|
|
|
|
_cfg_auth_headers=$(_knoe_cfg_extract_key_in_files "AUTH_RESPONSE_HEADERS" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_auth_headers" ]]; then
|
|
_cfg_auth_headers=$(_knoe_cfg_extract_key_in_files "auth_response_headers" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_auth_headers" ]]; then
|
|
export AUTH_RESPONSE_HEADERS="$_cfg_auth_headers"
|
|
fi
|
|
|
|
_cfg_ctx=$(_knoe_cfg_extract_key_in_files "KUBE_CONTEXT_NAME" "${_knoe_cfg_files[@]}")
|
|
if [[ -z "$_cfg_ctx" ]]; then
|
|
_cfg_ctx=$(_knoe_cfg_extract_key_in_files "KUBECTL_CONTEXT" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -z "$_cfg_ctx" ]]; then
|
|
_cfg_ctx=$(_knoe_cfg_extract_key_in_files "KUBECONTEXT" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
_cfg_mode_hint=$(knoe_normalize_mode "${DEPLOYMENT_MODE:-${KNOE_MODE:-${CLUSTER_ENV:-${knoe_mode:-}}}}")
|
|
_cfg_app_ctx="${APP_CLUSTER_KUBECONTEXT:-${init_cluster_app_cluster_kubecontext:-}}"
|
|
if [[ "$_cfg_mode_hint" == "k8s" ]]; then
|
|
if [[ -z "$_cfg_app_ctx" ]]; then
|
|
_cfg_app_ctx=$(_knoe_cfg_extract_key_in_files "APP_CLUSTER_KUBECONTEXT" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -z "$_cfg_app_ctx" ]]; then
|
|
_cfg_app_ctx=$(_knoe_cfg_extract_key_in_files "app_cluster_kubecontext" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -z "$_cfg_app_ctx" ]]; then
|
|
_cfg_app_ctx=$(_knoe_cfg_extract_key_in_files "init_cluster.app_cluster_kubecontext" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -z "$_cfg_app_ctx" ]]; then
|
|
_cfg_app_ctx=$(_knoe_cfg_extract_key_in_files "init_cluster_app_cluster_kubecontext" "${_knoe_cfg_files[@]}")
|
|
fi
|
|
if [[ -n "$_cfg_app_ctx" ]]; then
|
|
_cfg_ctx="$_cfg_app_ctx"
|
|
fi
|
|
fi
|
|
if [[ -n "$_cfg_ctx" ]]; then
|
|
_runtime_ctx="${KUBECTL_CONTEXT:-${KUBE_CONTEXT_NAME:-${KUBECONTEXT:-}}}"
|
|
_override_runtime_ctx=0
|
|
# Respect explicitly provided runtime context (e.g., installer per-role env),
|
|
# but in k8s split-cluster mode override legacy aliases with APP context.
|
|
if [[ -z "$_runtime_ctx" ]]; then
|
|
_override_runtime_ctx=1
|
|
elif [[ "$_cfg_mode_hint" == "k8s" && -n "$_cfg_app_ctx" ]]; then
|
|
case "$_runtime_ctx" in
|
|
prod|production|knoe-prod-cluster)
|
|
_override_runtime_ctx=1
|
|
;;
|
|
esac
|
|
fi
|
|
if [[ $_override_runtime_ctx -eq 1 && "$_cfg_mode_hint" != "k3s" ]]; then
|
|
export KUBE_CONTEXT_NAME="$_cfg_ctx"
|
|
export KUBECTL_CONTEXT="$_cfg_ctx"
|
|
export KUBECONTEXT="$_cfg_ctx"
|
|
fi
|
|
unset _runtime_ctx _override_runtime_ctx
|
|
fi
|
|
unset _cfg_ns _cfg_sns _cfg_dm _cfg_sh _cfg_sbh _cfg_sapi _cfg_sstudio _cfg_sing _cfg_gitdom _cfg_githosts _cfg_ging _cfg_auth_enabled _cfg_auth_host _cfg_auth_verify _cfg_auth_login _cfg_auth_headers _cfg_ctx _cfg_mode_hint _cfg_app_ctx _knoe_cfg_files _knoe_cfg_f
|
|
fi
|
|
|
|
if [[ -z "${KNOE_HOME:-}" && -d "$_knoe_cfg_home_guess" ]]; then
|
|
KNOE_HOME="$_knoe_cfg_home_guess"
|
|
export KNOE_HOME
|
|
fi
|
|
|
|
# Final check for critical variables
|
|
if [[ -z "${KNOE_SERVICE:-}" && -n "${KNOE_HOME:-}" ]]; then
|
|
export KNOE_SERVICE="$KNOE_HOME/etc"
|
|
fi
|
|
|
|
# PROLE_NAMESPACE must be set from knoe.cfg only — never from environment or kubectl context
|
|
if [[ -n "${PROLE_NAMESPACE:-}" ]]; then
|
|
export PROLE_NAMESPACE
|
|
fi
|
|
if [[ -z "${SERVICE_NAMESPACE:-}" ]]; then
|
|
if [[ -n "${PROLE_NAMESPACE:-}" ]]; then
|
|
SERVICE_NAMESPACE="$PROLE_NAMESPACE"
|
|
export SERVICE_NAMESPACE
|
|
fi
|
|
fi
|
|
|
|
# Safety: knoe.cfg must override any pre-set KNOE_MODE from the environment.
|
|
# Otherwise a leaked KNOE_MODE=k3d can trigger k3d-only manifest stripping (e.g., removing
|
|
# `storageClassName: synology-iscsi`) while targeting a k3s cluster.
|
|
_knoe_cfg_mode_hint=""
|
|
if [[ -n "${knoe_mode:-}" ]]; then
|
|
_knoe_cfg_mode_hint=$(knoe_normalize_mode "$knoe_mode")
|
|
elif [[ -n "${DEPLOYMENT_MODE:-}" ]]; then
|
|
_knoe_cfg_mode_hint=$(knoe_normalize_mode "$DEPLOYMENT_MODE")
|
|
elif [[ -n "${CLUSTER_ENV:-}" ]]; then
|
|
_knoe_cfg_mode_hint=$(knoe_normalize_mode "$CLUSTER_ENV")
|
|
fi
|
|
case "${_knoe_cfg_mode_hint}" in
|
|
k3d|k3s|k8s)
|
|
if [[ -n "${KNOE_MODE:-}" ]]; then
|
|
_knoe_env_mode=$(knoe_normalize_mode "$KNOE_MODE")
|
|
if [[ "${_knoe_env_mode}" != "${_knoe_cfg_mode_hint}" ]]; then
|
|
echo "WARN: Overriding KNOE_MODE='${_knoe_env_mode}' with knoe.cfg mode '${_knoe_cfg_mode_hint}'." >&2
|
|
KNOE_MODE="${_knoe_cfg_mode_hint}"
|
|
export KNOE_MODE
|
|
fi
|
|
unset _knoe_env_mode
|
|
fi
|
|
;;
|
|
esac
|
|
unset _knoe_cfg_mode_hint
|
|
|
|
if [[ -z "${KNOE_MODE:-}" ]]; then
|
|
if [[ -n "${knoe_mode:-}" ]]; then
|
|
_knoe_mode_guess=$(knoe_normalize_mode "$knoe_mode")
|
|
case "$_knoe_mode_guess" in
|
|
k3d|k3s|k8s) KNOE_MODE="$_knoe_mode_guess"; export KNOE_MODE ;;
|
|
esac
|
|
unset _knoe_mode_guess
|
|
elif [[ -n "${DEPLOYMENT_MODE:-}" ]]; then
|
|
_knoe_mode_guess=$(knoe_normalize_mode "$DEPLOYMENT_MODE")
|
|
case "$_knoe_mode_guess" in
|
|
k3d|k3s|k8s) KNOE_MODE="$_knoe_mode_guess"; export KNOE_MODE ;;
|
|
esac
|
|
unset _knoe_mode_guess
|
|
elif [[ -n "${CLUSTER_ENV:-}" ]]; then
|
|
_knoe_mode_guess=$(knoe_normalize_mode "$CLUSTER_ENV")
|
|
case "$_knoe_mode_guess" in
|
|
k3d|k3s|k8s) KNOE_MODE="$_knoe_mode_guess"; export KNOE_MODE ;;
|
|
esac
|
|
unset _knoe_mode_guess
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${PROLE_NAMESPACE:-}" ]]; then
|
|
_knoe_mode_resolved=$(knoe_normalize_mode "${KNOE_MODE:-}")
|
|
case "$_knoe_mode_resolved" in
|
|
k3d)
|
|
if [[ -n "${KNOE_IMAGE_REGISTRY:-}" ]]; then
|
|
LOCAL_REGISTRY_INTERNAL="${KNOE_IMAGE_REGISTRY}"
|
|
export LOCAL_REGISTRY_INTERNAL
|
|
elif [[ -n "${LOCAL_REGISTRY_INTERNAL:-}" ]]; then
|
|
if [[ "${LOCAL_REGISTRY_INTERNAL}" == *.localhost:5000 ]]; then
|
|
LOCAL_REGISTRY_INTERNAL="${LOCAL_REGISTRY_INTERNAL%.localhost:5000}:5000"
|
|
export LOCAL_REGISTRY_INTERNAL
|
|
elif [[ "${LOCAL_REGISTRY_INTERNAL}" == *.localhost ]]; then
|
|
LOCAL_REGISTRY_INTERNAL="${LOCAL_REGISTRY_INTERNAL%.localhost}"
|
|
export LOCAL_REGISTRY_INTERNAL
|
|
fi
|
|
elif _knoe_local_registry_enabled; then
|
|
LOCAL_REGISTRY_INTERNAL="k3d-knoe-registry:5000"
|
|
export LOCAL_REGISTRY_INTERNAL
|
|
fi
|
|
;;
|
|
esac
|
|
case "$_knoe_mode_resolved" in
|
|
k3s|k8s)
|
|
# Registry namespace is driven by config (SERVICE_NAMESPACE) unless explicitly overridden.
|
|
_knoe_registry_ns="${REGISTRY_NAMESPACE:-${SERVICE_NAMESPACE:-${PROLE_NAMESPACE:-}}}"
|
|
if [[ -n "${KNOE_IMAGE_REGISTRY:-}" ]]; then
|
|
LOCAL_REGISTRY_INTERNAL="${KNOE_IMAGE_REGISTRY}"
|
|
export LOCAL_REGISTRY_INTERNAL
|
|
elif [[ -n "${LOCAL_REGISTRY_INTERNAL:-}" ]]; then
|
|
# Values like "<container>.localhost:5000" are host-only (k3d) and are not reachable from
|
|
# k3s/k8s nodes. Treat them as unset so we can auto-resolve a usable in-cluster registry.
|
|
if [[ "${LOCAL_REGISTRY_INTERNAL}" == *.localhost:5000 || "${LOCAL_REGISTRY_INTERNAL}" == *.localhost ]]; then
|
|
unset LOCAL_REGISTRY_INTERNAL
|
|
fi
|
|
fi
|
|
if [[ -n "${LOCAL_REGISTRY_INTERNAL:-}" ]]; then
|
|
:
|
|
elif _knoe_local_registry_enabled; then
|
|
_knoe_registry_host="$(_knoe_host_from_url "${PROLE_K3S_SERVER:-${K3S_SERVER_URL:-}}")"
|
|
if [[ -n "${_knoe_registry_host:-}" ]]; then
|
|
LOCAL_REGISTRY_INTERNAL="${_knoe_registry_host}:5000"
|
|
export LOCAL_REGISTRY_INTERNAL
|
|
elif [[ -n "${_knoe_registry_ns:-}" ]]; then
|
|
LOCAL_REGISTRY_INTERNAL="registry.${_knoe_registry_ns}.svc.cluster.local:5000"
|
|
export LOCAL_REGISTRY_INTERNAL
|
|
fi
|
|
unset _knoe_registry_host
|
|
fi
|
|
unset _knoe_registry_ns
|
|
;;
|
|
esac
|
|
unset _knoe_mode_resolved
|
|
fi
|
|
|
|
_knoe_is_secret_ref() {
|
|
case "${1:-}" in
|
|
'${KNOE_SECRET:'*|'${OPENBAO:'*) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
_knoe_is_openbao_ref() {
|
|
case "${1:-}" in
|
|
'${OPENBAO:'*) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
_knoe_decrypt_knoe_secret() {
|
|
local value="${1:-}"
|
|
case "$value" in
|
|
'${KNOE_SECRET:'*) ;;
|
|
*) printf '%s' "$value"; return 0 ;;
|
|
esac
|
|
python3 - "$value" <<'PY'
|
|
import base64
|
|
import getpass
|
|
import platform
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
KNOE_SECRET_PREFIX = "${KNOE_SECRET:"
|
|
KNOE_SECRET_SUFFIX = "}"
|
|
KNOE_SECRET_VERSION = "v1"
|
|
KNOE_SECRET_SERVICE = "knoe-installer"
|
|
KNOE_SECRET_KEY_FILE = Path.home() / ".knoe" / "secrets" / "installer.key"
|
|
|
|
def get_keychain_key(service: str, account: str) -> bytes:
|
|
try:
|
|
res = subprocess.run(
|
|
["security", "find-generic-password", "-a", account, "-s", service, "-w"],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
if res.returncode == 0 and res.stdout.strip():
|
|
return base64.urlsafe_b64decode(res.stdout.strip().encode("utf-8"))
|
|
except Exception:
|
|
pass
|
|
return b""
|
|
|
|
def get_file_key(path: Path) -> bytes:
|
|
if not path.exists():
|
|
return b""
|
|
raw = path.read_text().strip()
|
|
try:
|
|
return base64.urlsafe_b64decode(raw.encode("utf-8"))
|
|
except Exception:
|
|
return b""
|
|
|
|
def get_secret_key() -> bytes:
|
|
system = platform.system()
|
|
account = getpass.getuser() or "knoe"
|
|
if system == "Darwin":
|
|
return get_keychain_key(KNOE_SECRET_SERVICE, account)
|
|
return get_file_key(KNOE_SECRET_KEY_FILE)
|
|
|
|
def decrypt_knoe_secret(value: str) -> str:
|
|
if not (value.startswith(KNOE_SECRET_PREFIX) and value.endswith(KNOE_SECRET_SUFFIX)):
|
|
return value
|
|
inner = value[len(KNOE_SECRET_PREFIX):-len(KNOE_SECRET_SUFFIX)]
|
|
parts = inner.split(":")
|
|
if len(parts) != 3 or parts[0] != KNOE_SECRET_VERSION:
|
|
return ""
|
|
key = get_secret_key()
|
|
if not key:
|
|
return ""
|
|
try:
|
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
nonce = base64.urlsafe_b64decode(parts[1].encode("utf-8"))
|
|
ciphertext = base64.urlsafe_b64decode(parts[2].encode("utf-8"))
|
|
aesgcm = AESGCM(key)
|
|
return aesgcm.decrypt(nonce, ciphertext, None).decode("utf-8")
|
|
except Exception:
|
|
return ""
|
|
|
|
value = sys.argv[1]
|
|
out = decrypt_knoe_secret(value)
|
|
print(out)
|
|
PY
|
|
}
|
|
|
|
_knoe_resolve_openbao_ref() {
|
|
local value="${1:-}"
|
|
case "$value" in
|
|
'${OPENBAO:'*) ;;
|
|
*) printf '%s' "$value"; return 0 ;;
|
|
esac
|
|
python3 - "$value" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.request
|
|
import urllib.parse
|
|
|
|
OPENBAO_PREFIX = "${OPENBAO:"
|
|
OPENBAO_SUFFIX = "}"
|
|
|
|
def resolve_openbao_ref(value: str) -> str:
|
|
if not (value.startswith(OPENBAO_PREFIX) and value.endswith(OPENBAO_SUFFIX)):
|
|
return value
|
|
inner = value[len(OPENBAO_PREFIX):-len(OPENBAO_SUFFIX)]
|
|
if "#" not in inner:
|
|
return ""
|
|
path, key = inner.split("#", 1)
|
|
if not path or not key:
|
|
return ""
|
|
mount = "kv"
|
|
secret_path = path
|
|
if "/" in path:
|
|
mount, secret_path = path.split("/", 1)
|
|
|
|
token = os.environ.get("OPENBAO_ROOT_TOKEN", "")
|
|
if not token:
|
|
knoe_service = os.environ.get("KNOE_SERVICE", "")
|
|
if knoe_service:
|
|
token_path = os.path.join(knoe_service, "secrets", "openbao-root-token")
|
|
try:
|
|
if os.path.exists(token_path):
|
|
token = open(token_path, "r", encoding="utf-8").read().strip()
|
|
except Exception:
|
|
token = ""
|
|
if not token:
|
|
return ""
|
|
|
|
url = os.environ.get("PROLE_OPENBAO_URL")
|
|
if not url:
|
|
# In k3s mode, do NOT fall back to localhost/port-forward. Derive the external
|
|
# OpenBao URL from the k3s API server URL when available.
|
|
mode = (os.environ.get("KNOE_MODE") or os.environ.get("DEPLOYMENT_MODE") or "").strip().lower()
|
|
if mode == "k3s":
|
|
k3s_server = (os.environ.get("PROLE_K3S_SERVER") or os.environ.get("K3S_SERVER_URL") or "").strip()
|
|
if k3s_server:
|
|
try:
|
|
parsed = urllib.parse.urlparse(k3s_server)
|
|
host = parsed.hostname or ""
|
|
if host:
|
|
url = f"http://{host}:8200"
|
|
except Exception:
|
|
url = ""
|
|
|
|
if not url:
|
|
for p in ["8200", "8200"]:
|
|
try:
|
|
with urllib.request.urlopen(f"http://127.0.0.1:{p}/v1/sys/health", timeout=0.5) as r:
|
|
if r.getcode() == 200:
|
|
url = f"http://127.0.0.1:{p}"
|
|
break
|
|
except Exception:
|
|
pass
|
|
if not url:
|
|
mode = (os.environ.get("KNOE_MODE") or os.environ.get("DEPLOYMENT_MODE") or "").strip().lower()
|
|
if mode != "k3s":
|
|
url = "http://127.0.0.1:8200"
|
|
else:
|
|
return ""
|
|
url = url.rstrip("/")
|
|
try:
|
|
req = urllib.request.Request(f"{url}/v1/{mount}/data/{secret_path}")
|
|
req.add_header("X-Vault-Token", token)
|
|
with urllib.request.urlopen(req, timeout=4) as resp:
|
|
payload = json.loads(resp.read().decode("utf-8"))
|
|
return payload.get("data", {}).get("data", {}).get(key, "") or ""
|
|
except Exception:
|
|
return ""
|
|
|
|
value = sys.argv[1]
|
|
print(resolve_openbao_ref(value))
|
|
PY
|
|
}
|
|
|
|
_knoe_usable_dir() {
|
|
local dir="$1"
|
|
if [[ -z "$dir" ]]; then return 1; fi
|
|
if [[ -w "$dir" ]] || [[ ! -e "$dir" && -w "$(dirname "$dir" 2>/dev/null)" ]]; then
|
|
printf '%s' "$dir"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
_knoe_kubeconfig_path() {
|
|
local base=""
|
|
# Try KNOE_SERVICE, then KNOE_HOME, then guess
|
|
local candidates=("${KNOE_SERVICE:-}/secrets" "${KNOE_HOME:-}" "${_knoe_cfg_home_guess:-}")
|
|
for b in "${candidates[@]}"; do
|
|
if [[ -z "$b" ]]; then continue; fi
|
|
if _knoe_usable_dir "$b" >/dev/null; then
|
|
base="$b"
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Fallback to ~/.knoe if everything else failed
|
|
if [[ -z "$base" ]]; then
|
|
base="$HOME/.knoe/secrets"
|
|
mkdir -p "$base" 2>/dev/null || true
|
|
fi
|
|
|
|
if [[ "$base" == */secrets ]]; then
|
|
printf '%s\n' "${base}/k3s.kubeconfig"
|
|
else
|
|
printf '%s\n' "${base}/knoe-k3s.kubeconfig"
|
|
fi
|
|
}
|
|
|
|
_knoe_token_is_bearer() {
|
|
local token="$1"
|
|
[[ -z "$token" ]] && return 1
|
|
# K3s node tokens include '::' and are not valid API bearer tokens.
|
|
[[ "$token" == *"::"* ]] && return 1
|
|
# JWT-style tokens contain two dots.
|
|
case "$token" in
|
|
*.*.*) return 0 ;;
|
|
esac
|
|
# Bootstrap tokens are usually id.secret with a dot separator.
|
|
if [[ "$token" == *.* ]]; then
|
|
local id="${token%%.*}"
|
|
local secret="${token#*.}"
|
|
if [[ ${#id} -ge 6 && ${#secret} -ge 16 ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
_knoe_kubeconfig_from_token() {
|
|
local server token ns
|
|
server="${PROLE_K3S_SERVER:-${K3S_SERVER_URL:-}}"
|
|
token="${PROLE_K3S_TOKEN:-${K3S_TOKEN:-}}"
|
|
ns="${SERVICE_NAMESPACE:-${NAMESPACE:-default}}"
|
|
|
|
if _knoe_is_openbao_ref "$token"; then
|
|
token=$(_knoe_resolve_openbao_ref "$token")
|
|
elif [[ "$token" == '${KNOE_SECRET:'* ]]; then
|
|
token=$(_knoe_decrypt_knoe_secret "$token")
|
|
fi
|
|
|
|
if [[ -z "$server" ]]; then
|
|
echo "DEBUG: _knoe_kubeconfig_from_token: server is empty" >&2
|
|
return 1
|
|
fi
|
|
if [[ -z "$token" ]]; then
|
|
echo "DEBUG: _knoe_kubeconfig_from_token: token is empty" >&2
|
|
return 1
|
|
fi
|
|
if ! _knoe_token_is_bearer "$token"; then
|
|
echo "DEBUG: _knoe_kubeconfig_from_token: token is not a bearer token" >&2
|
|
return 1
|
|
fi
|
|
if [[ "$server" != http* ]]; then
|
|
server="https://$server"
|
|
fi
|
|
local cfg_path
|
|
cfg_path="$(_knoe_kubeconfig_path)" || return 1
|
|
# Never overwrite a kubeconfig that uses client-certificate auth
|
|
if [[ -f "$cfg_path" ]] && grep -q "client-certificate-data" "$cfg_path" 2>/dev/null; then
|
|
export KUBECONFIG="$cfg_path"
|
|
return 0
|
|
fi
|
|
mkdir -p "$(dirname "$cfg_path")"
|
|
cat >"$cfg_path" <<EOF
|
|
apiVersion: v1
|
|
kind: Config
|
|
clusters:
|
|
- name: knoe-k3s
|
|
cluster:
|
|
server: ${server}
|
|
insecure-skip-tls-verify: true
|
|
users:
|
|
- name: knoe-k3s
|
|
user:
|
|
token: ${token}
|
|
contexts:
|
|
- name: knoe-k3s
|
|
context:
|
|
cluster: knoe-k3s
|
|
user: knoe-k3s
|
|
namespace: ${ns}
|
|
current-context: knoe-k3s
|
|
EOF
|
|
chmod 600 "$cfg_path" 2>/dev/null || true
|
|
export KUBECONFIG="$cfg_path"
|
|
return 0
|
|
}
|
|
|
|
knoe_ensure_kubeconfig() {
|
|
if [[ "${KNOE_MODE:-}" == "k3d" ]]; then
|
|
return 0
|
|
fi
|
|
local _knoe_insecure_flag
|
|
_knoe_insecure_flag="${PROLE_K3S_SKIP_TLS_VERIFY:-${K3S_SKIP_TLS_VERIFY:-${PROLE_K3S_INSECURE:-${K3S_INSECURE:-}}}}"
|
|
_knoe_kubeconfig_maybe_insecure() {
|
|
if _knoe_bool_true "${_knoe_insecure_flag:-}"; then
|
|
_knoe_kubeconfig_mark_insecure "${KUBECONFIG:-}" >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
local orig_kubeconfig="${KUBECONFIG:-}"
|
|
|
|
_verify_kubeconfig() {
|
|
if [[ -n "${KUBECONFIG:-}" && -f "${KUBECONFIG:-}" ]]; then
|
|
if KUBECONFIG="${KUBECONFIG:-}" kubectl cluster-info >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Prefer an existing valid kubeconfig (e.g. Ansible-fetched with client certs)
|
|
# over generating a new token-based one from knoe.cfg.
|
|
if [[ -n "${KUBECONFIG:-}" && -f "${KUBECONFIG:-}" ]]; then
|
|
_knoe_kubeconfig_maybe_insecure
|
|
if _verify_kubeconfig; then return 0; fi
|
|
fi
|
|
if [[ -n "${KNOE_SERVICE:-}" && -f "${KNOE_SERVICE}/secrets/k3s.kubeconfig" ]]; then
|
|
export KUBECONFIG="${KNOE_SERVICE}/secrets/k3s.kubeconfig"
|
|
_knoe_kubeconfig_maybe_insecure
|
|
if _verify_kubeconfig; then return 0; fi
|
|
fi
|
|
if [[ -n "${PROLE_K3S_KUBECONFIG:-}" && -f "${PROLE_K3S_KUBECONFIG}" ]]; then
|
|
export KUBECONFIG="${PROLE_K3S_KUBECONFIG}"
|
|
_knoe_kubeconfig_maybe_insecure
|
|
if _verify_kubeconfig; then return 0; fi
|
|
fi
|
|
if [[ -n "${PROLE_KUBECONFIG:-}" && -f "${PROLE_KUBECONFIG}" ]]; then
|
|
export KUBECONFIG="${PROLE_KUBECONFIG}"
|
|
_knoe_kubeconfig_maybe_insecure
|
|
if _verify_kubeconfig; then return 0; fi
|
|
fi
|
|
if [[ -n "${KNOE_HOME:-}" && -f "${KNOE_HOME}/knoe-k3s.kubeconfig" ]]; then
|
|
export KUBECONFIG="${KNOE_HOME}/knoe-k3s.kubeconfig"
|
|
_knoe_kubeconfig_maybe_insecure
|
|
if _verify_kubeconfig; then return 0; fi
|
|
fi
|
|
if [[ -f "${_knoe_cfg_home_guess:-}/knoe-k3s.kubeconfig" ]]; then
|
|
export KUBECONFIG="${_knoe_cfg_home_guess:-}/knoe-k3s.kubeconfig"
|
|
_knoe_kubeconfig_maybe_insecure
|
|
if _verify_kubeconfig; then return 0; fi
|
|
fi
|
|
if [[ -r "/etc/rancher/k3s/k3s.yaml" ]]; then
|
|
export KUBECONFIG="/etc/rancher/k3s/k3s.yaml"
|
|
_knoe_kubeconfig_maybe_insecure
|
|
if _verify_kubeconfig; then return 0; fi
|
|
fi
|
|
|
|
_knoe_kubeconfig_from_token
|
|
_knoe_kubeconfig_maybe_insecure
|
|
if _verify_kubeconfig; then return 0; fi
|
|
|
|
# Final fallback: if nothing worked, restore original or unset so system default is used
|
|
if [[ -n "$orig_kubeconfig" ]]; then
|
|
export KUBECONFIG="$orig_kubeconfig"
|
|
else
|
|
unset KUBECONFIG
|
|
fi
|
|
|
|
# One last check for the system default
|
|
if kubectl cluster-info >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Resolve the effective kubeconfig path used by kubectl.
|
|
# If KUBECONFIG contains multiple files, use the first file for mutation checks.
|
|
resolve_kubeconfig_path() {
|
|
local kubeconfig_raw="${KUBECONFIG:-}"
|
|
if [[ -n "$kubeconfig_raw" ]]; then
|
|
printf '%s' "${kubeconfig_raw%%:*}"
|
|
return 0
|
|
fi
|
|
|
|
if [[ -n "${HOME:-}" && -f "${HOME}/.kube/config" ]]; then
|
|
printf '%s' "${HOME}/.kube/config"
|
|
return 0
|
|
fi
|
|
|
|
printf '%s' ""
|
|
return 0
|
|
}
|
|
|
|
resolve_kube_context_name() {
|
|
if [[ -n "${KUBE_CONTEXT_NAME:-}" ]]; then
|
|
printf '%s' "${KUBE_CONTEXT_NAME}"
|
|
return 0
|
|
fi
|
|
if [[ -n "${KUBECTL_CONTEXT:-}" ]]; then
|
|
printf '%s' "${KUBECTL_CONTEXT}"
|
|
return 0
|
|
fi
|
|
printf '%s' "${KUBECONTEXT:-}"
|
|
return 0
|
|
}
|
|
|
|
kubeconfig_path_is_writable() {
|
|
local kubeconfig_path="$1"
|
|
if [[ -z "$kubeconfig_path" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ -e "$kubeconfig_path" ]]; then
|
|
[[ -w "$kubeconfig_path" ]]
|
|
return $?
|
|
fi
|
|
|
|
local parent_dir
|
|
parent_dir=$(dirname "$kubeconfig_path")
|
|
[[ -w "$parent_dir" ]]
|
|
}
|
|
|
|
detect_local_cluster_node() {
|
|
local current_context="${1:-}"
|
|
local cluster_server="${2:-}"
|
|
local kubeconfig_path="${3:-}"
|
|
|
|
if [[ -z "$current_context" ]]; then
|
|
current_context=$(kubectl config current-context 2>/dev/null || true)
|
|
fi
|
|
if [[ -z "$cluster_server" ]]; then
|
|
cluster_server=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null || true)
|
|
fi
|
|
if [[ -z "$kubeconfig_path" ]]; then
|
|
kubeconfig_path=$(resolve_kubeconfig_path)
|
|
fi
|
|
|
|
if [[ "$kubeconfig_path" == "/etc/rancher/k3s/k3s.yaml" && "$current_context" == "default" ]]; then
|
|
case "$cluster_server" in
|
|
https://127.0.0.1:6443|https://localhost:6443)
|
|
return 0
|
|
;;
|
|
esac
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
switch_kube_context() {
|
|
local target_context="$1"
|
|
local switch_reason="$2"
|
|
local kubeconfig_path="$3"
|
|
|
|
if [[ -z "$target_context" ]]; then
|
|
echo "ERROR: Refusing empty kubectl context switch target" >&2
|
|
return 1
|
|
fi
|
|
|
|
if ! kubeconfig_path_is_writable "$kubeconfig_path"; then
|
|
echo "ERROR: kubeconfig '${kubeconfig_path:-<default>}' is not writable; context switch to '${target_context}' is required (${switch_reason})." >&2
|
|
return 1
|
|
fi
|
|
|
|
echo "INFO: Writable remote kubeconfig mode; switching kubectl context to '${target_context}' (${switch_reason})" >&2
|
|
if kubectl config use-context "$target_context" >/dev/null 2>&1; then
|
|
echo "INFO: kubectl context set to '${target_context}'" >&2
|
|
return 0
|
|
fi
|
|
|
|
echo "ERROR: Could not switch to context '${target_context}'. Available contexts:" >&2
|
|
kubectl config get-contexts --no-headers 2>/dev/null | awk '{print " "$2}' >&2 || true
|
|
return 1
|
|
}
|
|
|
|
_knoe_list_kubeconfig_candidates() {
|
|
local -a candidates
|
|
|
|
if [[ -n "${KUBECONFIG:-}" ]]; then
|
|
IFS=':' read -r -a _kcfg_parts <<<"${KUBECONFIG}"
|
|
for _kcfg in "${_kcfg_parts[@]}"; do
|
|
[[ -n "${_kcfg:-}" ]] && candidates+=("$_kcfg")
|
|
done
|
|
unset _kcfg_parts _kcfg
|
|
fi
|
|
|
|
[[ -n "${PROLE_K3S_KUBECONFIG:-}" ]] && candidates+=("${PROLE_K3S_KUBECONFIG}")
|
|
[[ -n "${PROLE_KUBECONFIG:-}" ]] && candidates+=("${PROLE_KUBECONFIG}")
|
|
[[ -n "${KNOE_HOME:-}" ]] && candidates+=("${KNOE_HOME}/knoe-k3s.kubeconfig")
|
|
[[ -n "${_knoe_cfg_home_guess:-}" ]] && candidates+=("${_knoe_cfg_home_guess}/knoe-k3s.kubeconfig")
|
|
[[ -n "${HOME:-}" ]] && candidates+=("${HOME}/.kube/config")
|
|
[[ -n "${KNOE_SERVICE:-}" ]] && candidates+=("${KNOE_SERVICE}/secrets/k3s.kubeconfig")
|
|
candidates+=("/etc/rancher/k3s/k3s.yaml")
|
|
|
|
local -A seen
|
|
local candidate
|
|
for candidate in "${candidates[@]}"; do
|
|
[[ -z "${candidate:-}" ]] && continue
|
|
[[ -f "$candidate" ]] || continue
|
|
[[ -n "${seen[$candidate]+x}" ]] && continue
|
|
seen[$candidate]=1
|
|
printf '%s\n' "$candidate"
|
|
done
|
|
}
|
|
|
|
_knoe_select_kubeconfig_for_context() {
|
|
local desired_context="$1"
|
|
[[ -n "$desired_context" ]] || return 1
|
|
|
|
local candidate contexts
|
|
while IFS= read -r candidate; do
|
|
[[ -n "${candidate:-}" ]] || continue
|
|
contexts=$(KUBECONFIG="$candidate" kubectl config get-contexts -o name 2>/dev/null || true)
|
|
if grep -Fxq "$desired_context" <<<"$contexts"; then
|
|
export KUBECONFIG="$candidate"
|
|
echo "INFO: Selected kubeconfig '$candidate' for context '${desired_context}'" >&2
|
|
return 0
|
|
fi
|
|
done < <(_knoe_list_kubeconfig_candidates)
|
|
|
|
return 1
|
|
}
|
|
|
|
# Validate and switch the active kubectl context to match KNOE_MODE set in knoe.cfg.
|
|
# knoe.cfg always overrides the local environment.
|
|
# Call this after knoe_ensure_kubeconfig in every init_*.sh script.
|
|
# Usage: ensure_kube_context
|
|
ensure_kube_context() {
|
|
local mode
|
|
mode="${KNOE_MODE:-}"
|
|
if [[ -z "$mode" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local desired_context
|
|
desired_context=$(resolve_kube_context_name)
|
|
if [[ -n "$desired_context" ]]; then
|
|
export KUBE_CONTEXT_NAME="$desired_context"
|
|
export KUBECONTEXT="$desired_context"
|
|
fi
|
|
|
|
local current_context
|
|
current_context=$(kubectl config current-context 2>/dev/null || true)
|
|
|
|
local cluster_server
|
|
cluster_server=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}' 2>/dev/null || true)
|
|
|
|
local kubeconfig_path
|
|
kubeconfig_path=$(resolve_kubeconfig_path)
|
|
|
|
if detect_local_cluster_node "$current_context" "$cluster_server" "$kubeconfig_path"; then
|
|
echo "INFO: Detected local cluster-node kubeconfig; current-context=${current_context:-<none>}, server=${cluster_server:-<unknown>}; skipping context mutation" >&2
|
|
return 0
|
|
fi
|
|
|
|
if [[ -n "$desired_context" && "$current_context" != "$desired_context" ]]; then
|
|
echo "DEBUG: Remote kubeconfig mode; current-context='${current_context:-<none>}', desired-context='${desired_context}', kubeconfig='${kubeconfig_path:-<default>}'" >&2
|
|
|
|
local context_names
|
|
context_names=$(kubectl config get-contexts -o name 2>/dev/null || true)
|
|
|
|
local do_switch
|
|
do_switch=1
|
|
|
|
# If the requested context isn't present, try to map common shorthand to real contexts.
|
|
# For k3d, contexts are typically named `k3d-<cluster_name>`.
|
|
if ! grep -Fxq "$desired_context" <<<"$context_names"; then
|
|
if [[ "$mode" == "k3d" && "$desired_context" != k3d-* ]]; then
|
|
local prefixed
|
|
prefixed="k3d-${desired_context}"
|
|
if grep -Fxq "$prefixed" <<<"$context_names"; then
|
|
desired_context="$prefixed"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# k3s-generated kubeconfigs often contain a single context called `default`.
|
|
# If the config only has one context, use it even when the configured KUBECONTEXT name
|
|
# doesn't match.
|
|
if ! grep -Fxq "$desired_context" <<<"$context_names"; then
|
|
local only_context=""
|
|
local context_count=0
|
|
while IFS= read -r _ctx; do
|
|
[[ -z "${_ctx:-}" ]] && continue
|
|
context_count=$((context_count + 1))
|
|
only_context="$_ctx"
|
|
done <<<"$context_names"
|
|
unset _ctx
|
|
|
|
if [[ $context_count -eq 1 && -n "${only_context:-}" && "$only_context" != "default" ]]; then
|
|
echo "WARN: Requested kubectl context '${desired_context}' not found in active KUBECONFIG; using only available context '${only_context}'" >&2
|
|
desired_context="$only_context"
|
|
elif [[ $context_count -eq 1 && "${only_context:-}" == "default" ]]; then
|
|
if _knoe_select_kubeconfig_for_context "$desired_context"; then
|
|
context_names=$(kubectl config get-contexts -o name 2>/dev/null || true)
|
|
if grep -Fxq "$desired_context" <<<"$context_names"; then
|
|
only_context=""
|
|
context_count=0
|
|
while IFS= read -r _ctx; do
|
|
[[ -z "${_ctx:-}" ]] && continue
|
|
context_count=$((context_count + 1))
|
|
only_context="$_ctx"
|
|
done <<<"$context_names"
|
|
unset _ctx
|
|
fi
|
|
else
|
|
echo "ERROR: Requested kubectl context '${desired_context}' not found in active KUBECONFIG." >&2
|
|
echo " The only available context is 'default' which is not valid for knoe operations." >&2
|
|
echo " Ensure KUBECONFIG includes a kubeconfig with context '${desired_context}'." >&2
|
|
echo " Available kubeconfigs: KUBECONFIG='${KUBECONFIG:-<system default>}'" >&2
|
|
echo " Run: export KUBECONFIG=~/.kube/config (or the file containing '${desired_context}')" >&2
|
|
return 1
|
|
fi
|
|
else
|
|
echo "WARN: Requested kubectl context '${desired_context}' not found in active KUBECONFIG; skipping explicit context switch and falling back to mode-based selection" >&2
|
|
do_switch=0
|
|
fi
|
|
fi
|
|
|
|
if [[ $do_switch -eq 1 ]]; then
|
|
if ! switch_kube_context "$desired_context" "configured kube context override" "$kubeconfig_path"; then
|
|
return 1
|
|
fi
|
|
return 0
|
|
fi
|
|
else
|
|
echo "DEBUG: ensure_kube_context: current_context='${current_context}', KUBE_CONTEXT_NAME='${desired_context:-<not-set>}', kubeconfig='${kubeconfig_path:-<default>}'" >&2
|
|
fi
|
|
|
|
case "$mode" in
|
|
k3d)
|
|
# k3d contexts are always named k3d-<cluster_name>
|
|
if [[ "$current_context" == k3d-* ]]; then
|
|
return 0
|
|
fi
|
|
local cluster_name target_context
|
|
cluster_name="${K3D_CLUSTER_NAME:-knoe-dev-cluster}"
|
|
target_context="k3d-${cluster_name}"
|
|
echo "WARN: knoe.cfg mode is 'k3d' but current kubectx is '${current_context:-<none>}'" >&2
|
|
if ! switch_kube_context "$target_context" "k3d mode requires k3d context" "$kubeconfig_path"; then
|
|
return 1
|
|
fi
|
|
;;
|
|
k3s|k8s)
|
|
# k3s/k8s contexts must NOT be a k3d context
|
|
if [[ "$current_context" != k3d-* ]]; then
|
|
return 0
|
|
fi
|
|
echo "WARN: knoe.cfg mode is '${mode}' but current kubectx is '${current_context}' (a k3d context)" >&2
|
|
echo "INFO: Switching kubectl context to match KUBECONFIG='${KUBECONFIG:-<default>}' (knoe.cfg overrides local env)" >&2
|
|
local target_context
|
|
if [[ -n "${KUBECONTEXT:-}" ]]; then
|
|
target_context="${KUBECONTEXT}"
|
|
else
|
|
target_context=$(kubectl config get-contexts --no-headers 2>/dev/null \
|
|
| awk '{print $2}' | grep -v '^k3d-' | head -1 || true)
|
|
fi
|
|
if [[ -z "$target_context" ]]; then
|
|
echo "ERROR: No non-k3d context found in KUBECONFIG='${KUBECONFIG:-<default>}'. Available contexts:" >&2
|
|
kubectl config get-contexts --no-headers 2>/dev/null | awk '{print " "$2}' >&2 || true
|
|
return 1
|
|
fi
|
|
if ! switch_kube_context "$target_context" "k3s/k8s mode requires non-k3d context" "$kubeconfig_path"; then
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Backward compatibility for existing scripts/tests that still call the legacy name.
|
|
knoe_ensure_kube_context() {
|
|
ensure_kube_context "$@"
|
|
}
|
|
|
|
if _knoe_is_secret_ref "${PROLE_K3S_TOKEN:-}"; then
|
|
if _knoe_is_openbao_ref "${PROLE_K3S_TOKEN:-}"; then
|
|
_decoded=$(_knoe_resolve_openbao_ref "${PROLE_K3S_TOKEN:-}")
|
|
else
|
|
_decoded=$(_knoe_decrypt_knoe_secret "${PROLE_K3S_TOKEN:-}")
|
|
fi
|
|
if [[ -n "$_decoded" ]]; then
|
|
PROLE_K3S_TOKEN="$_decoded"
|
|
export PROLE_K3S_TOKEN
|
|
else
|
|
unset PROLE_K3S_TOKEN
|
|
fi
|
|
fi
|
|
if _knoe_is_secret_ref "${K3S_TOKEN:-}"; then
|
|
if _knoe_is_openbao_ref "${K3S_TOKEN:-}"; then
|
|
_decoded=$(_knoe_resolve_openbao_ref "${K3S_TOKEN:-}")
|
|
else
|
|
_decoded=$(_knoe_decrypt_knoe_secret "${K3S_TOKEN:-}")
|
|
fi
|
|
if [[ -n "$_decoded" ]]; then
|
|
K3S_TOKEN="$_decoded"
|
|
export K3S_TOKEN
|
|
else
|
|
unset K3S_TOKEN
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "${KUBECONFIG:-}" ]]; then
|
|
_knoe_mode_guess=""
|
|
if command -v knoe_normalize_mode >/dev/null 2>&1; then
|
|
_knoe_mode_guess=$(knoe_normalize_mode "${KNOE_MODE:-${DEPLOYMENT_MODE:-${CLUSTER_ENV:-}}}")
|
|
else
|
|
_knoe_mode_guess="${KNOE_MODE:-${DEPLOYMENT_MODE:-${CLUSTER_ENV:-}}}"
|
|
fi
|
|
if [[ "$_knoe_mode_guess" == "k3s" ]]; then
|
|
knoe_ensure_kubeconfig || true
|
|
fi
|
|
unset _knoe_mode_guess
|
|
fi
|
|
|
|
_knoe_child_ns="${PROLE_NAMESPACE:-}"
|
|
_knoe_child_id=""
|
|
if [[ -n "${PROLE_CHILD_ID:-}" ]]; then
|
|
_knoe_child_id="${PROLE_CHILD_ID}"
|
|
elif [[ -n "$_knoe_child_ns" && "$_knoe_child_ns" =~ ^knoe-db-([A-Za-z0-9]+)$ ]]; then
|
|
_knoe_child_id=$(printf '%s' "${BASH_REMATCH[1]}" | tr '[:lower:]' '[:upper:]')
|
|
fi
|
|
|
|
_knoe_parent_realm="${PROLE_PARENT_REALM:-${REALM:-${KRB5_REALM:-${kerberos_config_realm:-}}}}"
|
|
if [[ -n "$_knoe_parent_realm" ]]; then
|
|
_knoe_parent_realm=$(printf '%s' "$_knoe_parent_realm" | tr '[:lower:]' '[:upper:]')
|
|
if [[ -z "${PROLE_PARENT_REALM:-}" ]]; then
|
|
PROLE_PARENT_REALM="$_knoe_parent_realm"
|
|
export PROLE_PARENT_REALM
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$_knoe_child_id" ]]; then
|
|
PROLE_CHILD_ID="${PROLE_CHILD_ID:-$_knoe_child_id}"
|
|
export PROLE_CHILD_ID
|
|
if [[ -n "${PROLE_CHILD_REALM:-}" ]]; then
|
|
:
|
|
elif [[ -n "$_knoe_parent_realm" ]]; then
|
|
if [[ "$_knoe_parent_realm" == "${PROLE_CHILD_ID}."* ]]; then
|
|
PROLE_CHILD_REALM="$_knoe_parent_realm"
|
|
if [[ -z "${PROLE_PARENT_REALM:-}" ]]; then
|
|
PROLE_PARENT_REALM="${_knoe_parent_realm#${PROLE_CHILD_ID}.}"
|
|
export PROLE_PARENT_REALM
|
|
fi
|
|
else
|
|
PROLE_CHILD_REALM="${PROLE_CHILD_ID}.${_knoe_parent_realm}"
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "${PROLE_CHILD_REALM:-}" ]]; then
|
|
export PROLE_CHILD_REALM
|
|
PROLE_CHILD_WORKGROUP="${PROLE_CHILD_WORKGROUP:-${PROLE_CHILD_ID}}"
|
|
PROLE_CHILD_NETBIOS_NAME="${PROLE_CHILD_NETBIOS_NAME:-${PROLE_CHILD_ID}}"
|
|
PROLE_CHILD_SERVER_STRING="${PROLE_CHILD_SERVER_STRING:-${PROLE_CHILD_REALM} AD DC}"
|
|
export PROLE_CHILD_WORKGROUP PROLE_CHILD_NETBIOS_NAME PROLE_CHILD_SERVER_STRING
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "${PROLE_USE_CHILD_REALM:-}" && -n "${PROLE_CHILD_REALM:-}" ]]; then
|
|
PROLE_USE_CHILD_REALM=1
|
|
export PROLE_USE_CHILD_REALM
|
|
fi
|
|
|
|
if _knoe_bool_true "${PROLE_USE_CHILD_REALM:-}" && [[ -n "${PROLE_CHILD_REALM:-}" ]]; then
|
|
if [[ -z "${KRB5_REALM:-}" || ( -n "${PROLE_PARENT_REALM:-}" && "${KRB5_REALM}" == "${PROLE_PARENT_REALM}" ) ]]; then
|
|
KRB5_REALM="${PROLE_CHILD_REALM}"
|
|
export KRB5_REALM
|
|
fi
|
|
if [[ -z "${REALM:-}" || ( -n "${PROLE_PARENT_REALM:-}" && "${REALM}" == "${PROLE_PARENT_REALM}" ) ]]; then
|
|
REALM="${PROLE_CHILD_REALM}"
|
|
export REALM
|
|
fi
|
|
fi
|
|
|
|
knoe_register_port_forward() {
|
|
local id="$1" ns="$2" target="$3" hostPort="$4" servicePort="$5"
|
|
local addr="${6:-0.0.0.0}"
|
|
local proto="${7:-TCP}"
|
|
local desc="${8:-}"
|
|
echo "PORT_FORWARD_MAPPING: id=$id;namespace=$ns;target=$target;address=$addr;hostPort=$hostPort;servicePort=$servicePort;protocol=$proto;description=$desc"
|
|
}
|
|
|
|
unset _knoe_cfg_script_dir
|
|
unset _knoe_cfg_home_guess
|
|
unset _knoe_cfg_file
|
|
unset _PROLE_CFG_SET_VARS
|