# Shared config loading. Sourced, never executed. # # The ecosystem convention is that scripts are standalone with no shared log # library — that still holds. This file is not a logging lib; it is the single # definition of how the config layers compose, which every script has to agree # on exactly. Precedence, weakest first: # # ctrl/versions.env pinned toolchain + image digests (committed) # ctrl/env.d/ cluster shape (committed) # ctrl/.env machine-local values and secrets (gitignored) # the caller's env `make cluster up PROFILE=client` (always wins) # # That last rule is why this is more than a few `source` lines: .env sets # PROFILE, so without snapshotting it would silently override the PROFILE the # user just typed on the command line. # # Run from ctrl/. # Values a user can reasonably override per-invocation. Anything set in the # environment when load_config runs is restored after the files are read. # NODES and AUDIT are deliberately NOT here: they are properties of the chosen # ctrl/k8s/kind-config*.yaml.tpl and are read back out of it below, so there is # one place that decides the shape of the cluster rather than two that can drift. # # REGISTRY_PORT and MANIFESTS_DIR were missing here while ctrl/.env set them, so # the caller's env silently LOST to the file for those two — the one precedence # rule this header states. Both are now listed; the other twelve are unchanged. CONFIG_OVERRIDABLE="PROFILE 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" # The containing folder's name, reduced to something kind accepts as a cluster # name (a DNS label: lowercase alphanumerics and dashes). Run from ctrl/, so the # repo root is the parent. default_cluster_name() { local n n=$(basename "$(cd .. && pwd)") 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 is used rather than $RANDOM or # bash hashing because it is POSIX and returns the same value on every machine, # which is what makes the block reproducible instead of merely unique. 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 [ -f ./.env ] && source ./.env set +a # Re-apply overrides now so PROFILE is the caller's before we pick the file. _config_restore "$saved" local profile="${PROFILE:-minimal}" if [ ! -f "./env.d/${profile}.env" ]; then echo "no such profile: env.d/${profile}.env" >&2 echo "available: $(ls env.d/*.env 2>/dev/null | xargs -n1 basename | sed 's/\.env$//' | tr '\n' ' ')" >&2 exit 1 fi set -a source "./env.d/${profile}.env" [ -f ./.env ] && source ./.env set +a _config_restore "$saved" # Identity follows the FOLDER, so copying this directory somewhere else and # renaming it yields a distinct environment with no further edits. Without # this, two copies would share one cluster and `make cluster down` in either # would destroy the other's. CLUSTER="${CLUSTER:-$(default_cluster_name)}" KUBECONTEXT="kind-${CLUSTER}" # Host ports are a single shared namespace, so unlike the cluster name they # cannot just follow the directory — they have to be spread out. Anything # already set (ctrl/.env, a profile, the command line) wins; only the gaps # are filled. See ports.sh for the reasoning. 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, repo-root relative. Defaulted here so # it is always resolved rather than sometimes-set: it is the seam that lets # the real manifests be versioned away from the installer, and a consumer # should not have to know whether anyone filled it in. See k8s/README.md. MANIFESTS_DIR="${MANIFESTS_DIR:-ctrl/k8s/overlays/dev}" # 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's shape is a file in ctrl/k8s/, named by the profile. Adding a # shape is adding a file; there is no dispatcher to edit. # # A host that needs its own shape — extra port mappings, more nodes — passes # an absolute path instead, and rig renders it exactly like one of its own: # ${CLUSTER} and ${NODE_IMAGE} are substituted either way. The shape stays in # the host's tree, because what a host's cluster needs is the host's business; # rig only knows how to build whatever it is handed. KIND_CONFIG="${KIND_CONFIG:-kind-config.yaml.tpl}" case "$KIND_CONFIG" in /*) KIND_CONFIG_PATH="$KIND_CONFIG"; KIND_CONFIG_SHOWN="$KIND_CONFIG" ;; *) KIND_CONFIG_PATH="./k8s/${KIND_CONFIG}"; KIND_CONFIG_SHOWN="ctrl/k8s/${KIND_CONFIG}" ;; esac if [ ! -f "$KIND_CONFIG_PATH" ]; then echo "no such cluster shape: ${KIND_CONFIG_SHOWN}" >&2 echo "rig's own: $(ls k8s/kind-config*.yaml.tpl 2>/dev/null | xargs -n1 basename | tr '\n' ' ')" >&2 echo "or pass an absolute path to a shape of your own" >&2 exit 1 fi # Read the shape back out of the YAML rather than trusting a profile to # restate it. check.sh sizes the memory warning on NODES, and cluster.sh # prints AUDIT before spending minutes building something that cannot be # changed afterwards — both would mislead if the numbers drifted. NODES=$(grep -c '^ - role:' "$KIND_CONFIG_PATH") if grep -q 'audit-policy-file' "$KIND_CONFIG_PATH"; then AUDIT=on; else AUDIT=off; fi } # Render a cluster shape to stdout. sed rather than envsubst: envsubst is # gettext-base, absent from a minimal Debian, and Docker is meant to be the only # prerequisite. The variable list is explicit so a template cannot quietly start # depending on something the caller does not set. # # hostPath entries are resolved by the HOST dockerd, so HOST_WORKDIR must stay a # host path even when this runs inside the installer container. render_kind_config() { local host_workdir="${HOST_WORKDIR:-$(cd .. && pwd)}" 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" \ "$KIND_CONFIG_PATH" } _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 }