# Shared config loading: how the config layers compose. Sourced, never executed. # Precedence, weakest first: # defaults < versions.env < env.d/ < /rig.env < .env < caller's env. # Run from ctrl/. # Notes: docs/notes/config.md # Per-invocation overrides: restored after the files are read, so the caller wins. # NODES is deliberately not here (read from the kind config). CONFIG_OVERRIDABLE="PROFILE OVERLAY CLUSTER K8S_VERSION KIND_CONFIG ADDONS REGISTRY_MODE INGRESS_MODE DNS_MODE TILT_PORT SOURCE ARCH DEPS_SOURCE HTTP_PORT HTTPS_PORT REGISTRY_PORT MANIFESTS_DIR" # rig's own example, used when no overlay is named (relative to rig's root). DEFAULT_OVERLAY=examples/starter # A rig-root-relative path as seen from ctrl/; absolute paths pass through. _from_ctrl() { case "$1" in /*) echo "$1" ;; *) echo "../$1" ;; esac; } # The same, absolute. Empty if it does not exist. _abs_from_ctrl() { (cd "$(_from_ctrl "$1")" 2>/dev/null && pwd); } # The environment's folder — the overlay's when one is named, else rig's — # reduced to a DNS label kind accepts as a cluster name. default_cluster_name() { local n if [ -n "${OVERLAY:-}" ]; then n=$(basename "$(_abs_from_ctrl "$OVERLAY_DIR")") else n=$(basename "$(cd .. && pwd)") fi n=$(echo "$n" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-' '-') n=$(echo "$n" | sed 's/^-*//; s/-*$//') echo "${n:-rig}" } # Base of this environment's 10-port block; cksum so it is the same on every machine. derive_port_base() { local h; h=$(printf '%s' "$1" | cksum | awk '{print $1}') echo $((20000 + (h % 200) * 10)) } load_config() { local k saved="" for k in $CONFIG_OVERRIDABLE; do # ${!k+x} distinguishes "set but empty" from "unset" — an explicit # FOO= on the command line is a real choice and must survive. if [ -n "${!k+x}" ]; then saved+="$k=$(printf '%q' "${!k}")"$'\n' fi done set -a source ./versions.env # RIG_PORTABLE skips the machine-local .env (set by config_snapshot for kits). if [ -z "${RIG_PORTABLE:-}" ] && [ -f ./.env ]; then source ./.env; fi set +a # Re-apply overrides now so PROFILE is the caller's before we pick the file. _config_restore "$saved" # A profile is optional; naming one that does not exist is an error. local profile="${PROFILE:-}" layered="" if [ -n "$profile" ] && [ "$profile" != default ]; then if [ ! -f "./env.d/${profile}.env" ]; then echo "no such profile: env.d/${profile}.env" >&2 if [ -d "../examples/${profile}" ]; then echo " it is an example overlay now: OVERLAY=examples/${profile}" >&2 fi echo "available: $(config_profiles | tr '\n' ' ')" >&2 exit 1 fi set -a source "./env.d/${profile}.env" set +a layered=1 fi # The overlay: one folder, outside rig, holding a use case (docs/notes/overlay.md). # Named ones must exist; with none named, rig's own example is used if present. OVERLAY_DIR="" if [ -n "${OVERLAY:-}" ]; then OVERLAY_DIR="${OVERLAY%/}" if [ ! -d "$(_from_ctrl "$OVERLAY_DIR")" ]; then echo "no overlay at OVERLAY=${OVERLAY} (relative to rig's folder, or absolute)" >&2 exit 1 fi elif [ -d "../${DEFAULT_OVERLAY}" ]; then OVERLAY_DIR="$DEFAULT_OVERLAY" fi # Its rig.env may not choose the profile or the overlay (both are chosen before # it loads), and the paths it sets are relative to the overlay. local ov_env="" m_before k_before if [ -n "$OVERLAY_DIR" ]; then ov_env="$(_from_ctrl "$OVERLAY_DIR")/rig.env"; fi if [ -n "$ov_env" ] && [ -f "$ov_env" ]; then if grep -qE '^[[:space:]]*(export[[:space:]]+)?(PROFILE|OVERLAY)=' "$ov_env"; then echo "$ov_env: an overlay's rig.env cannot set PROFILE or OVERLAY (they choose it)" >&2 exit 1 fi m_before="${MANIFESTS_DIR-}" k_before="${KIND_CONFIG-}" set -a source "$ov_env" set +a if [ "${MANIFESTS_DIR-}" != "$m_before" ]; then case "$MANIFESTS_DIR" in /*|none|"") ;; *) MANIFESTS_DIR="${OVERLAY_DIR}/${MANIFESTS_DIR}" ;; esac fi if [ "${KIND_CONFIG-}" != "$k_before" ]; then case "$KIND_CONFIG" in /*|"") ;; *) KIND_CONFIG="$(dirname "$ov_env")/${KIND_CONFIG}" ;; esac fi layered=1 fi # The machine and the caller still win over both. if [ -n "$layered" ]; then set -a if [ -z "${RIG_PORTABLE:-}" ] && [ -f ./.env ]; then source ./.env; fi set +a _config_restore "$saved" fi # The defaults a profile would otherwise have to supply. Weakest of all: a # profile, ctrl/.env and the caller each override them. PROFILE_NAME="${PROFILE_NAME:-default}" ADDONS="${ADDONS-}" # local, not none: with no registry an unqualified image name means # docker.io/library/, and a default must not make that disclosure. REGISTRY_MODE="${REGISTRY_MODE:-local}" INGRESS_MODE="${INGRESS_MODE:-hostport}" DNS_MODE="${DNS_MODE:-hosts}" # The newest node image versions.env pins, found rather than restated, so # bumping the pins moves the default with them. if [ -z "${K8S_VERSION:-}" ]; then K8S_VERSION=$(compgen -v NODE_IMAGE_v | sort -V | tail -1) K8S_VERSION="${K8S_VERSION#NODE_IMAGE_}" fi # Identity follows the folder, so a renamed copy is a distinct environment. CLUSTER="${CLUSTER:-$(default_cluster_name)}" KUBECONTEXT="kind-${CLUSTER}" # Host ports: fill only the gaps from the derived block; anything already set wins. local base; base=$(derive_port_base "$CLUSTER") HTTP_PORT="${HTTP_PORT:-$base}" HTTPS_PORT="${HTTPS_PORT:-$((base + 1))}" TILT_PORT="${TILT_PORT:-$((base + 2))}" REGISTRY_PORT="${REGISTRY_PORT:-$((base + 3))}" # Where the workload's manifests live, relative to rig's folder (or absolute): # the overlay's k8s/overlays/dev unless something names another. `none`: rig # applies none (the overlay's Tiltfile does). A named folder must exist. if [ "${MANIFESTS_DIR:-}" = ctrl/k8s/overlays/dev ] && [ ! -d ../ctrl/k8s/overlays/dev ]; then # The old default, pinned by an older .env.example; rig's examples moved. STALE_MANIFESTS_DIR="$MANIFESTS_DIR" MANIFESTS_DIR="" fi if [ -z "${MANIFESTS_DIR:-}" ] && [ -n "$OVERLAY_DIR" ] \ && [ -d "$(_from_ctrl "$OVERLAY_DIR")/k8s/overlays/dev" ]; then MANIFESTS_DIR="$OVERLAY_DIR/k8s/overlays/dev" fi MANIFESTS_DIR="${MANIFESTS_DIR:-}" if [ "$MANIFESTS_DIR" = none ]; then MANIFESTS_DIR="" elif [ -n "$MANIFESTS_DIR" ] && [ ! -d "$(_from_ctrl "$MANIFESTS_DIR")" ]; then echo "no manifests at MANIFESTS_DIR=${MANIFESTS_DIR} (relative to rig's folder, or absolute)" >&2 exit 1 fi # Profiles name a k8s minor (v1_36); versions.env holds the pinned digest. local var="NODE_IMAGE_${K8S_VERSION}" NODE_IMAGE="${!var:-}" if [ -z "$NODE_IMAGE" ]; then echo "K8S_VERSION='${K8S_VERSION}' has no NODE_IMAGE_${K8S_VERSION} in versions.env" >&2 exit 1 fi # The cluster is one file: the overlay's kind-config.yaml.tpl if it has one, # else rig's k8s/kind-config.yaml.tpl. KIND_CONFIG is "use this file instead", # for a project that builds its own cluster through rig (relative to ctrl/, or absolute). if [ -z "${KIND_CONFIG:-}" ] && [ -n "$OVERLAY_DIR" ] \ && [ -f "$(_from_ctrl "$OVERLAY_DIR")/kind-config.yaml.tpl" ]; then KIND_CONFIG="$(_from_ctrl "$OVERLAY_DIR")/kind-config.yaml.tpl" fi KIND_CONFIG="${KIND_CONFIG:-./k8s/kind-config.yaml.tpl}" if [ ! -f "$KIND_CONFIG" ]; then echo "no kind config at KIND_CONFIG=${KIND_CONFIG}" >&2 exit 1 fi # Read the node count back out of the file rather than restating it: # check.sh and the memory tool size their budget on NODES. NODES=$(grep -c '^ - role:' "$KIND_CONFIG") # Measured MB per node (cluster alone, errs high for workers); shared by # check.sh, the memory tool and standalone kits. NODE_MB=800 } # Render the kind config to stdout with sed (not envsubst) over an explicit variable list. # HOST_WORKDIR and OVERLAY_DIR must be host paths: the host dockerd resolves hostPath entries. render_kind_config() { local host_workdir="${HOST_WORKDIR:-$(cd .. && pwd)}" overlay_dir="" if [ -n "$OVERLAY_DIR" ]; then overlay_dir=$(_abs_from_ctrl "$OVERLAY_DIR"); fi sed -e "s|\${CLUSTER}|${CLUSTER}|g" \ -e "s|\${NODE_IMAGE}|${NODE_IMAGE}|g" \ -e "s|\${HTTP_PORT}|${HTTP_PORT}|g" \ -e "s|\${HOST_WORKDIR}|${host_workdir}|g" \ -e "s|\${OVERLAY_DIR}|${overlay_dir}|g" \ "$KIND_CONFIG" } _config_restore() { local line while IFS= read -r line; do if [ -n "$line" ]; then eval "export $line" fi done <<< "$1" # A while loop returns its last body command's status; the trailing empty # line would otherwise make this return 1 and trip `set -e` in the caller. return 0 } # ── what a standalone kit needs to know ──────────────────────────────────── # The questions ctrl/standalone.sh asks, so it never knows how config is stored. # Every configuration rig can run as, one per line: each profile, or `default` # when there are none. Never empty. config_profiles() { local f found="" for f in ./env.d/*.env; do [ -e "$f" ] || continue f=${f##*/}; echo "${f%.env}"; found=1 done [ -n "$found" ] || echo default } # What load_config sets, minus the machine-local layer, as `declare -p` lines. # Usage: config_snapshot | --current (found by difference, not a list) config_snapshot() { local _rig_snap_choices if [ "$1" = --current ]; then _rig_snap_choices=$( ( load_config >/dev/null || exit 1 for _rig_snap_n in $CONFIG_OVERRIDABLE; do if [ -n "${!_rig_snap_n+x}" ]; then printf 'export %s=%q\n' "$_rig_snap_n" "${!_rig_snap_n}"; fi done ) ) || return 1 else _rig_snap_choices="export PROFILE=$(printf '%q' "$1")" fi ( # Nothing from the caller's shell may leak into a kit. for _rig_snap_n in $CONFIG_OVERRIDABLE; do unset "$_rig_snap_n"; done declare -A _rig_snap_was=() for _rig_snap_n in $(compgen -v); do _rig_snap_was[$_rig_snap_n]="${!_rig_snap_n-}" done eval "$_rig_snap_choices" RIG_PORTABLE=1 load_config >/dev/null for _rig_snap_n in $(compgen -v); do case "$_rig_snap_n" in _rig_snap_*|RIG_PORTABLE|BASH*|FUNCNAME|PIPESTATUS|LINENO|RANDOM|SRANDOM|\ SECONDS|EPOCH*|HISTCMD|COLUMNS|LINES|PWD|OLDPWD|_|SHLVL|OPTIND|OPTERR) continue ;; esac if [ -z "${_rig_snap_was[$_rig_snap_n]+x}" ] \ || [ "${_rig_snap_was[$_rig_snap_n]}" != "${!_rig_snap_n-}" ]; then declare -p "$_rig_snap_n" fi done ) } # The profile this machine runs, as load_config resolves it here. config_current_profile() { ( load_config >/dev/null && echo "$PROFILE_NAME" ); } # Names (never values) of .env keys an export does not carry, e.g. credentials. config_left_out() { [ -f ./.env ] || return 0 local k for k in $(sed -nE 's/^[[:space:]]*(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*)=.*/\2/p' ./.env | sort -u); do case " $(echo $CONFIG_OVERRIDABLE) " in *" $k "*) ;; *) echo "$k" ;; esac done } # Print a load_config with a resolution frozen in, for a standalone kit to carry. # The caller's env still wins; derived values (e.g. ports) stay fixed. config_freeze() { local snap snap=$(config_snapshot "$1") || return 1 cat <<'EOF' load_config() { local k saved="" for k in $CONFIG_OVERRIDABLE; do if [ -n "${!k+x}" ]; then saved+="$k=$(printf '%q' "${!k}")"$'\n'; fi done EOF printf '%s\n' "$snap" | sed -E 's/^declare --* / declare -g /; s/^declare -([a-zA-Z]+) / declare -g\1 /' cat <<'EOF' _config_restore "$saved" } EOF }