prole/etc/init_port_forwards.sh
chrisfu edbc6c5385 Checkpoint: Refactor installer UI and update Supabase deployment strategy
- Refactored install.py and installer package for improved UI and navigation.

- Replaced Supabase k8s manifests with a dedicated deployment script and port-wiring logic.

- Added new deployment pipeline and finalization scripts in etc/.

- Updated initialization scripts for Kerberos, authority, and port forwards.

- Updated port mappings and tests.
2026-02-03 14:55:05 -08:00

747 lines
22 KiB
Bash
Executable File

#!/usr/bin/env bash
set -u
# init_port_forwards.sh
# Portable-ish (macOS, Ubuntu, Raspberry Pi OS, Alpine) bash init-style script
# Manages kubectl port-forward daemons defined in an XML file.
PROG="init_port_forwards"
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck disable=SC1090
source "$SCRIPT_DIR/prole_cfg.sh"
PROLE_HOME="${PROLE_HOME:-/Users/chrisfu/dev/prole}"
VERBOSE=0
CONFIG_FILE="$PROLE_HOME/conf/port-mappings.properties"
usage() {
cat <<EOF
Usage:
$PROG [-v|--verbose] [-f|--force] [-c|--config-file=FILE] <start|stop|restart|status> [component]
Options:
-c, --config-file=FILE Path to local-ports.properties (XML)
-v, --verbose Verbose output
-f, --force Force: kill existing processes blocking ports
Examples:
$PROG -c ./port-mappings.properties start
$PROG stop openbao
$PROG --verbose status
EOF
}
TARGET_ID=""
FORCE=0
log() { printf '%s\n' "$*"; }
vlog() { [ "$VERBOSE" -eq 1 ] && printf '[verbose] %s\n' "$*" || true; }
err() { printf '[error] %s\n' "$*" >&2; }
have() { command -v "$1" >/dev/null 2>&1; }
# ---- Config helpers / derived defaults ----
cfg_file() {
if [ -n "${PROLE_CONF:-}" ] && [ -f "$PROLE_CONF/prole.cfg" ]; then
printf '%s' "$PROLE_CONF/prole.cfg"
return 0
fi
if [ -n "${PROLE_HOME:-}" ] && [ -f "$PROLE_HOME/conf/prole.cfg" ]; then
printf '%s' "$PROLE_HOME/conf/prole.cfg"
return 0
fi
return 1
}
CFG_FILE="$(cfg_file || true)"
cfg_value() {
local key="$1"
[ -n "${CFG_FILE:-}" ] || return 0
awk -F= -v k="$key" '
/^[[:space:]]*;/ {next}
/^[[:space:]]*#/ {next}
/^[[:space:]]*\\[/ {next}
{
kk=$1
sub(/^[[:space:]]+/, "", kk)
sub(/[[:space:]]+$/, "", kk)
}
kk == k {
v=$2
sub(/^[[:space:]]+/, "", v)
sub(/[[:space:]]+$/, "", v)
val=v
}
END { print val }
' "$CFG_FILE" 2>/dev/null
}
is_truthy() {
case "${1:-}" in
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
esac
return 1
}
port_in_use() {
local port="$1"
if have lsof; then
lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1 && return 0 || return 1
fi
if have ss; then
ss -lnt 2>/dev/null | awk '{print $4}' | grep -E "[:.]$port$" >/dev/null 2>&1 && return 0 || return 1
fi
if have netstat; then
netstat -an 2>/dev/null | grep -E "[.:]$port[[:space:]]" | grep -qi listen && return 0 || return 1
fi
return 1
}
find_available_port() {
local base="$1"
local max="${2:-50}"
local port="$base"
local i=0
while [ "$i" -lt "$max" ]; do
if ! port_in_use "$port"; then
printf '%s' "$port"
return 0
fi
port=$((port + 1))
i=$((i + 1))
done
printf '%s' "$base"
}
CFG_NAMESPACE="$(cfg_value "NAMESPACE")"
CFG_MONITORING_NAMESPACE="$(cfg_value "MONITORING_NAMESPACE")"
CFG_MANAGEMENT_NAMESPACE="$(cfg_value "MANAGEMENT_NAMESPACE")"
CFG_PROLE_MONITORING_NAMESPACE="$(cfg_value "PROLE_MONITORING_NAMESPACE")"
CFG_PROLE_MANAGEMENT_NAMESPACE="$(cfg_value "PROLE_MANAGEMENT_NAMESPACE")"
PF_MONITORING_NAMESPACE="${MONITORING_NAMESPACE:-${PROLE_MONITORING_NAMESPACE:-${CFG_MONITORING_NAMESPACE:-${CFG_PROLE_MONITORING_NAMESPACE:-${CFG_NAMESPACE:-}}}}}"
PF_MANAGEMENT_NAMESPACE="${MANAGEMENT_NAMESPACE:-${PROLE_MANAGEMENT_NAMESPACE:-${CFG_MANAGEMENT_NAMESPACE:-${CFG_PROLE_MANAGEMENT_NAMESPACE:-${CFG_NAMESPACE:-}}}}}"
if [ -z "${PF_MONITORING_NAMESPACE:-}" ] && [ -n "${NAMESPACE:-}" ]; then
PF_MONITORING_NAMESPACE="$NAMESPACE"
fi
if [ -z "${PF_MANAGEMENT_NAMESPACE:-}" ] && [ -n "${NAMESPACE:-}" ]; then
PF_MANAGEMENT_NAMESPACE="$NAMESPACE"
fi
# SUPABASE_NAMESPACE="${SUPABASE_NAMESPACE:-supabase}"
# SUPABASE_DB_TARGET="${SUPABASE_DB_TARGET:-${SUPABASE_DB_SERVICE:-svc/db}}"
# Supabase logic removed as it's handled in another script
SUPABASE_ENABLED_EFFECTIVE=0
PROLE_DB_ALT_PORT_BASE="${PROLE_DB_ALT_PORT_BASE:-15432}"
PROLE_DB_ALT_PORT_EFFECTIVE=""
if [ "$SUPABASE_ENABLED_EFFECTIVE" -eq 1 ]; then
if [ -n "${PROLE_DB_ALT_PORT:-}" ]; then
PROLE_DB_ALT_PORT_EFFECTIVE="$PROLE_DB_ALT_PORT"
else
PROLE_DB_ALT_PORT_EFFECTIVE="$(find_available_port "$PROLE_DB_ALT_PORT_BASE")"
fi
fi
# Choose a writable state dir for pid/log files:
# - Prefer XDG_RUNTIME_DIR if set and writable
# - Else /var/run if writable (rare without root)
# - Else ~/.local/state
# - Else /tmp
state_dir() {
if [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -w "${XDG_RUNTIME_DIR:-}" ]; then
printf '%s/%s' "$XDG_RUNTIME_DIR" "$PROG"
return
fi
if [ -d "/var/run" ] && [ -w "/var/run" ]; then
printf '/var/run/%s' "$PROG"
return
fi
if [ -n "${HOME:-}" ]; then
mkdir -p "$HOME/.local/state" >/dev/null 2>&1 || true
if [ -d "$HOME/.local/state" ] && [ -w "$HOME/.local/state" ]; then
printf '%s/.local/state/%s' "$HOME" "$PROG"
return
fi
fi
printf '/tmp/%s' "$PROG"
}
STATE_DIR="$(state_dir)"
PID_DIR="$STATE_DIR/pids"
LOG_DIR="$STATE_DIR/logs"
ensure_dirs() {
mkdir -p "$PID_DIR" "$LOG_DIR" 2>/dev/null || true
if [ ! -d "$PID_DIR" ] || [ ! -d "$LOG_DIR" ]; then
err "Unable to create state directories under: $STATE_DIR"
exit 1
fi
}
# Use kubectl for actions; kubecolor is used only for prettier status output.
KUBECTL="kubectl"
detect_kubectl() {
if have kubectl; then
KUBECTL="kubectl"
else
err "kubectl not found in PATH"
exit 1
fi
}
# Basic environment validation
validate_env() {
detect_kubectl
ensure_dirs
if ! "$KUBECTL" version --client >/dev/null 2>&1; then
err "kubectl seems broken or not executable"
exit 1
fi
# Can we reach the cluster?
if ! "$KUBECTL" cluster-info >/dev/null 2>&1; then
err "kubectl cannot reach a cluster (check KUBECONFIG/context)"
err "Try: kubectl config get-contexts && kubectl config use-context <ctx>"
exit 1
fi
}
# ---- Preflight: Docker + k3d awareness ----
# Return 0 when Docker CLI can talk to a running daemon.
docker_is_running() {
if ! have docker; then
return 1
fi
docker info >/dev/null 2>&1
}
# Hard-fail with clean guidance when Docker is not up (used for start/restart).
ensure_docker_running() {
if docker_is_running; then
return 0
fi
if ! have docker; then
err "Docker CLI not found. Please install Docker Desktop or docker CLI."
else
err "Docker is not running. Start Docker Desktop and wait until it is ready."
fi
err "Tip (macOS): open -a Docker"
err "Then re-run: $PROG start"
exit 3
}
# Detect k3d cluster name from env or current kubectl context.
# - If K3D_CLUSTER is set, use it.
# - Else, if current-context starts with 'k3d-', strip prefix to get name.
detect_k3d_context() {
if [ -n "${K3D_CLUSTER:-}" ]; then
printf '%s' "$K3D_CLUSTER"
return 0
fi
local ctx
ctx="$($KUBECTL config current-context 2>/dev/null || echo)"
case "$ctx" in
k3d-*) printf '%s' "${ctx#k3d-}"; return 0 ;;
*) return 1 ;;
esac
}
# Return 0 if the given k3d cluster exists and is running.
k3d_cluster_is_running() {
local name="$1"
have k3d || return 1
# Use json output when available; fall back to grep otherwise
if k3d cluster list -o json >/dev/null 2>&1; then
k3d cluster list -o json 2>/dev/null | grep -q '"name"\s*:\s*"'"$name"'"' && \
k3d cluster list -o json 2>/dev/null | sed -n 's/.*"name"\s*:\s*"\([^"]\+\)".*"serversRunning"\s*:\s*\([0-9]\+\).*/\1 \2/p' | awk -v n="$name" '$1==n {exit ($2>0)?0:1}'
return $?
else
k3d cluster list 2>/dev/null | grep -E "^$name\s" | grep -q running
return $?
fi
}
# If current context indicates k3d, ensure the cluster is up; otherwise no-op.
ensure_k3d_ready_if_applicable() {
local k3d_name
if ! k3d_name="$(detect_k3d_context)"; then
return 0
fi
if ! have k3d; then
err "k3d is not installed but kubectl context suggests k3d (context=$("$KUBECTL" config current-context))."
err "Install k3d: brew install k3d (macOS)"
err "Or switch context: kubectl config use-context <non-k3d-context>"
exit 4
fi
if ! k3d_cluster_is_running "$k3d_name"; then
err "k3d cluster '$k3d_name' is not running."
err "Start it: k3d cluster start $k3d_name"
err "Then re-run: $PROG start"
exit 4
fi
}
pid_file_for() { printf '%s/%s.pid' "$PID_DIR" "$1"; }
log_file_for() { printf '%s/%s.log' "$LOG_DIR" "$1"; }
is_pid_running() {
# Return 0 if pid exists and running, else 1
# kill -0 is portable.
local pid="$1"
[ -n "$pid" ] && kill -0 "$pid" >/dev/null 2>&1
}
read_pid() {
local pf="$1"
[ -f "$pf" ] || return 1
# shellcheck disable=SC2162
read pid <"$pf" || return 1
printf '%s' "$pid"
}
write_pid() {
local pf="$1" pid="$2"
printf '%s\n' "$pid" >"$pf"
}
remove_pidfile() {
local pf="$1"
rm -f "$pf" >/dev/null 2>&1 || true
}
# ---- XML parsing (simple attribute extraction) ----
# We parse lines containing: <mapping ... />
# Attributes must use double quotes in the XML (as in the sample).
get_attr() {
# $1 = line, $2 = attribute name
# outputs value or empty
local val
val=$(printf '%s\n' "$1" | sed -n "s/.*$2=\"\([^\"]*\)\".*/\1/p")
# Special override for postgres hostPort and namespace from prole.cfg
if [ "$2" = "hostPort" ] && [ "$(get_attr "$1" "id")" = "postgres" ] && [ -n "${DB_HOST_PORT:-}" ]; then
printf '%s' "$DB_HOST_PORT"
return
fi
if [ "$2" = "namespace" ] && [ "$(get_attr "$1" "id")" = "postgres" ] && [ -n "${NAMESPACE:-}" ]; then
printf '%s' "$NAMESPACE"
return
fi
printf '%s' "$val"
}
foreach_mapping() {
# Calls a provided function with mapping fields:
# callback id ns target address hostPort servicePort protocol description
local callback="$1"
[ -f "$CONFIG_FILE" ] || { err "Config file not found: $CONFIG_FILE"; exit 1; }
# Support both single-line and multi-line self-closing mapping tags, e.g.:
# <mapping id="x" ... /> OR lines spanning multiple lines until "/>".
local in_mapping=0 in_comment=0 buffer="" line
while IFS= read -r line; do
# Handle XML comments: skip anything between <!-- and -->
if [ $in_comment -eq 1 ]; then
case "$line" in
*"-->"*) in_comment=0; continue ;;
*) continue ;;
esac
fi
case "$line" in
*"<!--"*)
case "$line" in
*"-->"*)
# single-line comment; skip line
continue
;;
*)
in_comment=1
continue
;;
esac
;;
esac
if [ $in_mapping -eq 0 ]; then
case "$line" in
*"<mapping"*)
in_mapping=1
buffer="$line"
;;
*)
continue
;;
esac
else
# Accumulate lines until we see the closing '/>'
buffer="$buffer $line"
fi
if [ $in_mapping -eq 1 ] && printf '%s' "$line" | grep -q "/>"; then
# Normalize whitespace to make attribute extraction robust
local merged
merged=$(printf '%s\n' "$buffer" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g')
local id ns target address hostPort servicePort protocol description
id="$(get_attr "$merged" "id")"
ns="$(get_attr "$merged" "namespace")"
target="$(get_attr "$merged" "target")"
address="$(get_attr "$merged" "address")"
hostPort="$(get_attr "$merged" "hostPort")"
servicePort="$(get_attr "$merged" "servicePort")"
protocol="$(get_attr "$merged" "protocol")"
description="$(get_attr "$merged" "description")"
# Replace ${NAMESPACE} or ${PROLE_NAMESPACE} if NAMESPACE is set in env/cfg
if [ -n "${NAMESPACE:-}" ]; then
ns="${ns//\$\{NAMESPACE\}/$NAMESPACE}"
ns="${ns//\$\{PROLE_NAMESPACE\}/$NAMESPACE}"
fi
if [ -n "${PF_MONITORING_NAMESPACE:-}" ]; then
ns="${ns//\$\{MONITORING_NAMESPACE\}/$PF_MONITORING_NAMESPACE}"
ns="${ns//\$\{PROLE_MONITORING_NAMESPACE\}/$PF_MONITORING_NAMESPACE}"
fi
if [ -n "${PF_MANAGEMENT_NAMESPACE:-}" ]; then
ns="${ns//\$\{MANAGEMENT_NAMESPACE\}/$PF_MANAGEMENT_NAMESPACE}"
ns="${ns//\$\{PROLE_MANAGEMENT_NAMESPACE\}/$PF_MANAGEMENT_NAMESPACE}"
fi
# Mapping-specific overrides (removed hardcoded namespace overrides)
if [ "$id" = "postgres" ]; then
if [ -n "${DB_HOST_PORT:-}" ]; then
hostPort="$DB_HOST_PORT"
fi
# Supabase override removed
fi
# prole-db logic removed, now uses XML
# Basic validation
if [ -z "$id" ] || [ -z "$ns" ] || [ -z "$target" ] || [ -z "$hostPort" ] || [ -z "$servicePort" ]; then
err "Invalid mapping (missing required attributes): $merged"
exit 1
fi
# Filter by TARGET_ID if set
if [ -n "$TARGET_ID" ] && [ "$id" != "$TARGET_ID" ]; then
in_mapping=0
buffer=""
continue
fi
if [ -z "$address" ]; then address="127.0.0.1"; fi
if [ -z "$protocol" ]; then protocol="TCP"; fi
vlog "mapping: id=$id ns=$ns target=$target address=$address hostPort=$hostPort servicePort=$servicePort protocol=$protocol"
"$callback" "$id" "$ns" "$target" "$address" "$hostPort" "$servicePort" "$protocol" "$description"
# Reset accumulator
in_mapping=0
buffer=""
fi
done <"$CONFIG_FILE"
}
build_port_forward_cmd() {
# echo a command string
local ns="$1" target="$2" address="$3" hostPort="$4" servicePort="$5"
# kubectl port-forward -n <ns> --address <addr> <target> <local>:<remote>
printf '%s port-forward -n %s --address %s %s %s:%s' \
"$KUBECTL" "$ns" "$address" "$target" "$hostPort" "$servicePort"
}
start_port_forward() {
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
local pidfile logfile cmd pid
# Aggressively stop existing processes before starting to avoid port conflicts
stop_port_forward "$id" "$ns" "$target" "$address" "$hostPort" "$servicePort" "$protocol" "$description"
pidfile="$(pid_file_for "$id")"
logfile="$(log_file_for "$id")"
cmd="$(build_port_forward_cmd "$ns" "$target" "$address" "$hostPort" "$servicePort")"
log "Starting: $id $address:$hostPort -> $target:$servicePort ($ns) ${description:-}"
vlog "Command: $cmd"
# Start in background, keep output in log.
# nohup is available on macOS/Linux; redirect stdin from /dev/null to detach.
nohup sh -c "$cmd" >>"$logfile" 2>&1 </dev/null &
pid="$!"
write_pid "$pidfile" "$pid"
# Quick verification
sleep 0.2
if is_pid_running "$pid"; then
vlog "$id started (pid=$pid, log=$logfile)"
return 0
else
err "$id failed to start (see log: $logfile)"
remove_pidfile "$pidfile"
return 1
fi
}
stop_port_forward() {
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
local pidfile pid
pidfile="$(pid_file_for "$id")"
pid=""
if [ -f "$pidfile" ]; then
pid="$(read_pid "$pidfile" || true)"
fi
if [ -n "${pid:-}" ] && is_pid_running "$pid"; then
log "Stopping: $id (pid=$pid)"
kill "$pid" >/dev/null 2>&1 || true
# wait a moment, then SIGKILL if needed
local i
for i in 1 2 3 4 5; do
if ! is_pid_running "$pid"; then break; fi
sleep 0.2
done
if is_pid_running "$pid"; then
err "$id did not stop gracefully; sending SIGKILL"
kill -9 "$pid" >/dev/null 2>&1 || true
fi
else
if [ -f "$pidfile" ]; then
vlog "$id stale pidfile (pid=$pid not running)"
fi
fi
remove_pidfile "$pidfile"
# Aggressively remove any other matching kubectl port-forward processes
# Search for processes that match: kubectl port-forward -n <ns> ... <target> <hostPort>:<servicePort>
local extra_pids
extra_pids=$(ps -ef | grep "port-forward" | grep "\-n" | grep "$ns" | grep "$target" | grep "$hostPort:$servicePort" | grep -v grep | awk '{print $2}')
for epid in $extra_pids; do
if [ "$epid" != "$pid" ]; then
log "Cleaning up orphan process for $id (pid=$epid)"
kill -9 "$epid" >/dev/null 2>&1 || true
fi
done
}
status_one() {
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
local pidfile pid
pidfile="$(pid_file_for "$id")"
pid=""
if [ -f "$pidfile" ]; then
pid="$(read_pid "$pidfile" || true)"
fi
if [ -n "${pid:-}" ] && is_pid_running "$pid"; then
printf 'RUNNING %-12s pid=%-7s %s:%s -> %s:%s ns=%s %s\n' \
"$id" "$pid" "$address" "$hostPort" "$target" "$servicePort" "$ns" "${description:-}"
# ps details (portable flags vary; use a conservative format)
ps -p "$pid" -o pid=,ppid=,etime=,command= 2>/dev/null | sed 's/^/ /' || true
else
printf 'STOPPED %-12s (no live pid) %s:%s -> %s:%s ns=%s %s\n' \
"$id" "$address" "$hostPort" "$target" "$servicePort" "$ns" "${description:-}"
fi
}
scan_for_collisions() {
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
if port_in_use "$hostPort"; then
# find which process is using it
local pid_info=""
if have lsof; then
pid_info=$(lsof -nP -iTCP:"$hostPort" -sTCP:LISTEN -t 2>/dev/null | head -n 1)
fi
if [ -n "$pid_info" ]; then
# Check if this PID is already managed by us
local pidfile pid_managed
pidfile="$(pid_file_for "$id")"
pid_managed="$(read_pid "$pidfile" 2>/dev/null || true)"
if [ "$pid_info" = "$pid_managed" ]; then
vlog "Port $hostPort is in use by our own process ($id, pid=$pid_info). This is fine for start/restart."
return 0
fi
local proc_details
proc_details=$(ps -p "$pid_info" -o pid=,command= 2>/dev/null | sed 's/[[:space:]]\+/ /g' || echo "$pid_info")
if [ "$FORCE" -eq 1 ]; then
log "Port $hostPort is in use by: $proc_details"
log "Force enabled. Killing process $pid_info..."
if ! kill -9 "$pid_info" 2>/dev/null; then
err "Failed to kill process $pid_info. Permission denied?"
exit 1
fi
sleep 0.5
else
err "Port collision detected: Port $hostPort is already in use by another process."
err "Process details: $proc_details"
err "Use -f or --force to kill the offending process, or stop it manually."
exit 1
fi
else
# Port in use but we can't find PID (maybe another user's process)
err "Port collision detected: Port $hostPort is in use, but could not determine PID (check with sudo lsof -i :$hostPort)."
exit 1
fi
fi
}
do_start() {
validate_env
# Preflight: require Docker daemon and k3d (if applicable)
ensure_docker_running
ensure_k3d_ready_if_applicable
vlog "Scanning for port collisions..."
foreach_mapping scan_for_collisions
foreach_mapping start_port_forward
}
do_stop() {
# stop doesn't require cluster access, but it does need state dirs
ensure_dirs
foreach_mapping stop_port_forward
}
do_restart() {
validate_env
# Preflight: require Docker daemon and k3d (if applicable)
ensure_docker_running
ensure_k3d_ready_if_applicable
vlog "Scanning for port collisions (excluding our own)..."
# During restart, we'll stop them first anyway, but let's be safe.
foreach_mapping stop_port_forward
foreach_mapping scan_for_collisions
foreach_mapping start_port_forward
}
do_status() {
validate_env
# Non-fatal awareness messages
if docker_is_running; then
log "[OK] Docker daemon is running"
else
if have docker; then
log "[WARN] Docker is not running"
else
log "[WARN] Docker CLI not found"
fi
fi
local _k3d_name
if _k3d_name="$(detect_k3d_context)"; then
if have k3d; then
if k3d_cluster_is_running "$_k3d_name"; then
log "[OK] k3d cluster '$_k3d_name' is running"
else
log "[WARN] k3d cluster '$_k3d_name' is not running"
fi
else
log "[WARN] k3d not installed but context suggests k3d (cluster='$_k3d_name')"
fi
fi
log "== Context / Cluster =="
"$KUBECTL" config current-context 2>/dev/null | sed 's/^/ context: /' || true
"$KUBECTL" cluster-info 2>/dev/null | sed 's/^/ /' || true
log ""
log "== Port-forward processes =="
foreach_mapping status_one
log ""
log "== Quick k3d awareness checks (best-effort) =="
# If user is on k3d, current-context often includes k3d-... but not guaranteed.
# Show nodes + a few namespaces/services related to mappings (best effort).
"$KUBECTL" get nodes -o wide 2>/dev/null | sed 's/^/ /' || true
log ""
"$KUBECTL" get ns 2>/dev/null | sed 's/^/ /' || true
log ""
# For each mapping, try to show target existence
log "== Target existence (best-effort) =="
_target_check() {
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
# kubectl get <target> -n <ns>
# If target is like "svc/name", "deploy/name", etc.
if "$KUBECTL" get -n "$ns" "$target" >/dev/null 2>&1; then
printf 'OK %-12s %s (ns=%s)\n' "$id" "$target" "$ns"
else
printf 'MISSING %-12s %s (ns=%s)\n' "$id" "$target" "$ns"
fi
}
foreach_mapping _target_check
}
# ---- arg parsing ----
ACTION=""
while [ $# -gt 0 ]; do
case "$1" in
start|stop|restart|status)
if [ -z "$ACTION" ]; then
ACTION="$1"
else
TARGET_ID="$1"
fi
shift
;;
-v|--verbose)
VERBOSE=1
shift
;;
-f|--force)
FORCE=1
shift
;;
-c)
shift
[ $# -gt 0 ] || { err "-c requires a file path"; usage; exit 2; }
CONFIG_FILE="$1"
shift
;;
--config-file=*)
CONFIG_FILE="${1#*=}"
shift
;;
-h|--help)
usage
exit 0
;;
*)
if [ -z "$ACTION" ]; then
err "Unknown arg: $1"
usage
exit 2
fi
TARGET_ID="$1"
shift
;;
esac
done
[ -n "$ACTION" ] || { usage; exit 2; }
case "$ACTION" in
start) do_start ;;
stop) do_stop ;;
restart) do_restart ;;
status) do_status ;;
esac