# Waits for the k3s API to become ready by polling /readyz. # Retries up to $k3sReadyRetries times with $k3sReadySleep seconds between attempts. function k3sReady() { local tries=${k3sReadyRetries:-10} local delay=${k3sReadySleep:-2} local i for ((i=1; i<=tries; i++)); do "$k3sCmd" kubectl get --raw=/readyz >/dev/null 2>&1 && return 0 sleep "$delay" done appError "k3s API not ready after $tries tries" return 1 } # Validates a YAML file against the Kubernetes API (dry-run). # $1 (file): path to the YAML file. function k3sYamlCheck() { local file="$1" [[ -n "$file" ]] || { appError "File not specified"; return 1; } [[ -f "$file" ]] || { appError "File not found: $file"; return 1; } k3sReady runFail "$k3sCmd" kubectl apply --dry-run=client -f "$file" } # Applies a YAML configuration to Kubernetes. # $1 (file): path to the YAML configuration file. function k3sYamlApply() { local file="$1" output error [[ -n "$file" ]] || { appError "File not specified"; return 1; } run output error "$k3sCmd" kubectl apply -f "$file" --validate=false --request-timeout=60s || { appError "Error applying YAML: ${error:-$output}" return 1 } } # Runs kubectl in $k3sNamespace after ensuring k3s is ready. function k3sRun() { k3sReady || return 1 "$k3sCmd" kubectl -n "$k3sNamespace" "$@" } # Lists pod names sorted alphabetically, optionally filtered by app label. # [$1] (app): app label value to filter by (optional). function k3sPodList() { local args=() [[ -n "${1-}" ]] && args+=(-l "app=$1") k3sRun get pods "${args[@]}" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' --sort-by='{.metadata.name}' } # Lists pods with detailed status: name|status|phase|reason. # status is 1 when the pod is fully ready (phase Running/Succeeded), 0 otherwise. # phase/reason describe the state for humans — a fine pod (status=1) can still show # different phase/reason combinations, so gate logic must use status, not phase. # reason is a comma-separated, deduplicated list of not-True condition types (excluding # the generic PodReadyToStartContainers/Initialized/ContainersReady/Ready chain, which # is always false together during startup and adds nothing beyond phase) plus each # not-ready container's (including init containers) waiting/terminated reason # (CrashLoopBackOff, ImagePullBackOff, OOMKilled, PodInitializing, ...). # [$1] (app): app label value to filter by (optional). function k3sPodListStatus() { local args=() [[ -n "${1-}" ]] && args+=(-l "app=$1") k3sRun get pods "${args[@]}" -o json --sort-by='{.metadata.name}' | jq -r ' def dedupe: reduce .[] as $x ([]; if any(.[]; . == $x) then . else . + [$x] end); .items[] | (.metadata.name) as $name | (.status.phase // "Unknown") as $phaseRaw | (.status.conditions // []) as $conditions | ([$conditions[] | select(.type == "Ready")][0]) as $ready | ([$conditions[] | select(.type == "DisruptionTarget")][0]) as $disruption | ((.status.initContainerStatuses // []) + (.status.containerStatuses // [])) as $containers | (["PodReadyToStartContainers", "Initialized", "ContainersReady", "Ready"]) as $noisyConditionTypes | ( if $phaseRaw == "Running" and ($disruption.status // "") == "True" then "Terminating" elif $phaseRaw == "Running" and ($ready.status // "False") != "True" then "NotReady" else $phaseRaw end ) as $phase | (if $phase == "Running" or $phase == "Succeeded" then 1 else 0 end) as $status | ( [$conditions[] | (if .type == "DisruptionTarget" then (select(.status == "True")) else (select(.status != "True")) end) | select(.type as $t | $noisyConditionTypes | index($t) | not) | .type ] ) as $badConditionTypes | ( [$containers[] | select(.ready == false) | (.state.waiting.reason // .state.terminated.reason) | select(. != null) ] ) as $containerReasons | ((if $status == 1 then [] else ($badConditionTypes + $containerReasons | dedupe) end) | join(",")) as $reason | "\($name)|\($status)|\($phase)|\($reason)" ' } # Captures the current list of pod names into an array variable. # $1 (varname): name of the array variable to populate. function k3sPodsSnapshot() { local -n __out="$1" mapfile -t __out < <( k3sRun get pods --no-headers 2>/dev/null | awk '{print $1}' ) }