From 11064cbd5b1f078a750e71a92c1ff486bb8e6232 Mon Sep 17 00:00:00 2001 From: chrisfu Date: Sat, 23 May 2026 21:31:14 -0700 Subject: [PATCH] refactor(mock_val): rename prole_* shell lib and cfg tooling to knoe_* namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shell library (mock_val/lib/shell/): - Delete prole_cmd.sh, prole_env.sh, prole_guardrails.sh, prole_secrets.sh, prole_string.sh, prole_yaml.sh - Add knoe_* equivalents with same functionality Config tooling: - Delete mock_val/prole_cfg.sh, mock_val/sync-prole-cfg.py - Add mock_val/knoe_cfg.sh, mock_val/sync-knoe-cfg.py Mirrors the broader prole → knoe project rename. Co-authored-by: Junie --- mock_val/{prole_cfg.sh => knoe_cfg.sh} | 640 ++++++++++++++++-- .../lib/shell/{prole_cmd.sh => knoe_cmd.sh} | 0 .../lib/shell/{prole_env.sh => knoe_env.sh} | 0 ...prole_guardrails.sh => knoe_guardrails.sh} | 0 .../{prole_secrets.sh => knoe_secrets.sh} | 0 .../shell/{prole_string.sh => knoe_string.sh} | 0 .../lib/shell/{prole_yaml.sh => knoe_yaml.sh} | 0 .../{sync-prole-cfg.py => sync-knoe-cfg.py} | 2 +- 8 files changed, 584 insertions(+), 58 deletions(-) rename mock_val/{prole_cfg.sh => knoe_cfg.sh} (61%) rename mock_val/lib/shell/{prole_cmd.sh => knoe_cmd.sh} (100%) rename mock_val/lib/shell/{prole_env.sh => knoe_env.sh} (100%) rename mock_val/lib/shell/{prole_guardrails.sh => knoe_guardrails.sh} (100%) rename mock_val/lib/shell/{prole_secrets.sh => knoe_secrets.sh} (100%) rename mock_val/lib/shell/{prole_string.sh => knoe_string.sh} (100%) rename mock_val/lib/shell/{prole_yaml.sh => knoe_yaml.sh} (100%) rename mock_val/{sync-prole-cfg.py => sync-knoe-cfg.py} (93%) diff --git a/mock_val/prole_cfg.sh b/mock_val/knoe_cfg.sh similarity index 61% rename from mock_val/prole_cfg.sh rename to mock_val/knoe_cfg.sh index 70ecae2..0e3e188 100644 --- a/mock_val/prole_cfg.sh +++ b/mock_val/knoe_cfg.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Standard include for Knoe etc scripts. # - Loads environment from env.sh (if available) -# - Loads values from KNOE_CONF/knoe.cfg (INI-style) +# - 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 @@ -13,6 +13,11 @@ _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" @@ -24,6 +29,20 @@ elif [[ -f "$_knoe_cfg_home_guess/env.sh" ]]; then 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:]]*}"}" @@ -115,8 +134,23 @@ _knoe_kubeconfig_mark_insecure() { _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" token="|$key|" + 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" @@ -129,6 +163,10 @@ _knoe_cfg_set_default() { _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 @@ -141,8 +179,15 @@ _knoe_read_cfg() { key="$(_knoe_trim "${line%%=*}")" value="$(_knoe_trim "${line#*=}")" [[ -z "$key" ]] && continue - # Replace dots with underscores for shell compatibility - key="${key//./_}" + # 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 @@ -202,19 +247,114 @@ _knoe_cfg_realpath() { 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 f + 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="$env_dir/knoe.cfg" + 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")" == "knoe.cfg" ]] && 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 @@ -270,9 +410,27 @@ EOF _knoe_cfg_bootstrap_env_layout() { local conf_dir="$1" local entry="$conf_dir/knoe.cfg" + local existing_selected="" [[ -z "$conf_dir" ]] && return 0 - mkdir -p "$conf_dir/dev" "$conf_dir/service" "$conf_dir/prod" "$conf_dir/test" 2>/dev/null || true + 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 @@ -310,14 +468,52 @@ _knoe_cfg_bootstrap_env_layout() { env="$(_knoe_cfg_env_from_hint "$env_hint")" fi - [[ -z "$env" ]] && env="dev" + # 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 - local base="$conf_dir/$env/knoe.cfg" + # 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 - # Heal broken symlink. + # 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" - ln -snf "$env/knoe.cfg" "$entry" 2>/dev/null || true return 0 fi @@ -330,14 +526,12 @@ _knoe_cfg_bootstrap_env_layout() { ts=$(date +%s) legacy="$conf_dir/knoe.cfg.legacy.${ts}" mv "$entry" "$legacy" 2>/dev/null || true - ln -snf "$env/knoe.cfg" "$entry" 2>/dev/null || true + # No symlink created — Python resolves env path from CLUSTER_ENV directly. return 0 fi - - # If entrypoint is missing, create a base and link it. + # 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" - ln -snf "$env/knoe.cfg" "$entry" 2>/dev/null || true fi } @@ -396,12 +590,14 @@ knoe_render_manifest() { } _knoe_cfg_file="" -if [[ -n "${KNOE_CONF:-}" && -f "$KNOE_CONF/knoe.cfg" ]]; then - _knoe_cfg_file="$KNOE_CONF/knoe.cfg" -elif [[ -n "${KNOE_HOME:-}" && -f "$KNOE_HOME/conf/knoe.cfg" ]]; then - _knoe_cfg_file="$KNOE_HOME/conf/knoe.cfg" -elif [[ -f "$_knoe_cfg_home_guess/conf/knoe.cfg" ]]; then - _knoe_cfg_file="$_knoe_cfg_home_guess/conf/knoe.cfg" +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 @@ -410,9 +606,11 @@ if [[ -n "$_knoe_cfg_file" ]]; then export KNOE_CONF fi - # Ensure env-dir layout exists and a stable entrypoint symlink is active. - _knoe_cfg_bootstrap_env_layout "$KNOE_CONF" - _knoe_cfg_file="$KNOE_CONF/knoe.cfg" + # 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 @@ -432,7 +630,7 @@ if [[ -n "$_knoe_cfg_file" ]]; then export KERBEROS_ENABLED fi - # Always prefer namespace from knoe.cfg (single source of truth). + # 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" @@ -441,6 +639,17 @@ if [[ -n "$_knoe_cfg_file" ]]; then 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[@]}") @@ -458,11 +667,147 @@ if [[ -n "$_knoe_cfg_file" ]]; then export supabase_hostname="$_cfg_sbh" export SUPABASE_HOSTNAME="$_cfg_sbh" fi - _cfg_ctx=$(_knoe_cfg_extract_key_in_files "KUBECONTEXT" "${_knoe_cfg_files[@]}") - if [[ -n "$_cfg_ctx" ]]; then - export KUBECONTEXT="$_cfg_ctx" + + _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 - unset _cfg_ns _cfg_sns _cfg_sh _cfg_sbh _cfg_ctx _knoe_cfg_files _knoe_cfg_f + 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 @@ -538,7 +883,10 @@ if [[ -n "${PROLE_NAMESPACE:-}" ]]; then _knoe_mode_resolved=$(knoe_normalize_mode "${KNOE_MODE:-}") case "$_knoe_mode_resolved" in k3d) - if [[ -n "${LOCAL_REGISTRY_INTERNAL:-}" ]]; then + 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 @@ -556,7 +904,10 @@ if [[ -n "${PROLE_NAMESPACE:-}" ]]; then k3s|k8s) # Registry namespace is driven by config (SERVICE_NAMESPACE) unless explicitly overridden. _knoe_registry_ns="${REGISTRY_NAMESPACE:-${SERVICE_NAMESPACE:-${PROLE_NAMESPACE:-}}}" - if [[ -n "${LOCAL_REGISTRY_INTERNAL:-}" ]]; then + if [[ -n "${KNOE_IMAGE_REGISTRY:-}" ]]; then + LOCAL_REGISTRY_INTERNAL="${KNOE_IMAGE_REGISTRY}" + export LOCAL_REGISTRY_INTERNAL + elif [[ -n "${LOCAL_REGISTRY_INTERNAL:-}" ]]; then # Values like ".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 @@ -953,23 +1304,186 @@ knoe_ensure_kubeconfig() { 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:-}' 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: knoe_ensure_kube_context -knoe_ensure_kube_context() { +# 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) - if [[ -n "${KUBECONTEXT:-}" && "$current_context" != "$KUBECONTEXT" ]]; then - local desired_context - desired_context="$KUBECONTEXT" + 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:-}, server=${cluster_server:-}; 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:-}', desired-context='${desired_context}', kubeconfig='${kubeconfig_path:-}'" >&2 local context_names context_names=$(kubectl config get-contexts -o name 2>/dev/null || true) @@ -1002,9 +1516,30 @@ knoe_ensure_kube_context() { done <<<"$context_names" unset _ctx - if [[ $context_count -eq 1 && -n "${only_context:-}" ]]; then + 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:-}'" >&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 @@ -1012,19 +1547,13 @@ knoe_ensure_kube_context() { fi if [[ $do_switch -eq 1 ]]; then - echo "DEBUG: knoe_ensure_kube_context: Switching context to '${desired_context}'" >&2 - echo "INFO: Switching kubectl context to '${desired_context}' (knoe.cfg overrides local env)" >&2 - if kubectl config use-context "$desired_context" >/dev/null 2>&1; then - echo "INFO: kubectl context set to '${desired_context}'" >&2 - return 0 - else - echo "ERROR: Could not switch to context '${desired_context}'. Available contexts:" >&2 - kubectl config get-contexts --no-headers 2>/dev/null | awk '{print " "$2}' >&2 || true + if ! switch_kube_context "$desired_context" "configured kube context override" "$kubeconfig_path"; then return 1 fi + return 0 fi else - echo "DEBUG: knoe_ensure_kube_context: current_context='${current_context}', KUBECONTEXT='${KUBECONTEXT:-}'" >&2 + echo "DEBUG: ensure_kube_context: current_context='${current_context}', KUBE_CONTEXT_NAME='${desired_context:-}', kubeconfig='${kubeconfig_path:-}'" >&2 fi case "$mode" in @@ -1037,12 +1566,7 @@ knoe_ensure_kube_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:-}'" >&2 - echo "INFO: Switching kubectl context to '${target_context}' (knoe.cfg overrides local env)" >&2 - if kubectl config use-context "$target_context" >/dev/null 2>&1; then - echo "INFO: kubectl context set to '${target_context}'" >&2 - else - 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 + if ! switch_kube_context "$target_context" "k3d mode requires k3d context" "$kubeconfig_path"; then return 1 fi ;; @@ -1065,16 +1589,18 @@ knoe_ensure_kube_context() { kubectl config get-contexts --no-headers 2>/dev/null | awk '{print " "$2}' >&2 || true return 1 fi - if kubectl config use-context "$target_context" >/dev/null 2>&1; then - echo "INFO: kubectl context set to '${target_context}'" >&2 - else - echo "ERROR: Could not switch to context '${target_context}'" >&2 + 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:-}") diff --git a/mock_val/lib/shell/prole_cmd.sh b/mock_val/lib/shell/knoe_cmd.sh similarity index 100% rename from mock_val/lib/shell/prole_cmd.sh rename to mock_val/lib/shell/knoe_cmd.sh diff --git a/mock_val/lib/shell/prole_env.sh b/mock_val/lib/shell/knoe_env.sh similarity index 100% rename from mock_val/lib/shell/prole_env.sh rename to mock_val/lib/shell/knoe_env.sh diff --git a/mock_val/lib/shell/prole_guardrails.sh b/mock_val/lib/shell/knoe_guardrails.sh similarity index 100% rename from mock_val/lib/shell/prole_guardrails.sh rename to mock_val/lib/shell/knoe_guardrails.sh diff --git a/mock_val/lib/shell/prole_secrets.sh b/mock_val/lib/shell/knoe_secrets.sh similarity index 100% rename from mock_val/lib/shell/prole_secrets.sh rename to mock_val/lib/shell/knoe_secrets.sh diff --git a/mock_val/lib/shell/prole_string.sh b/mock_val/lib/shell/knoe_string.sh similarity index 100% rename from mock_val/lib/shell/prole_string.sh rename to mock_val/lib/shell/knoe_string.sh diff --git a/mock_val/lib/shell/prole_yaml.sh b/mock_val/lib/shell/knoe_yaml.sh similarity index 100% rename from mock_val/lib/shell/prole_yaml.sh rename to mock_val/lib/shell/knoe_yaml.sh diff --git a/mock_val/sync-prole-cfg.py b/mock_val/sync-knoe-cfg.py similarity index 93% rename from mock_val/sync-prole-cfg.py rename to mock_val/sync-knoe-cfg.py index 5e8c50f..f37b9b3 100644 --- a/mock_val/sync-prole-cfg.py +++ b/mock_val/sync-knoe-cfg.py @@ -15,7 +15,7 @@ def main(): token = get_secret("k3s-token", "credential").strip() if token: - print("Syncing K3S_TOKEN to conf/knoe.cfg...") + print("Syncing K3S_TOKEN to active config...") inst_config._update_knoe_cfg_value("Global", "PROLE_K3S_TOKEN", token) inst_config._update_knoe_cfg_value("Inputs", "init_cluster.k3s_token", token) inst_config._update_knoe_cfg_value("Service Cluster (k3s)", "K3S_TOKEN", token)