prole/scripts/k3d-knoe-up.sh
chrisfu 5d3600845d feat(k3d): laptop dev loop for knoe-auth — CNPG + KDC + port-forward
Brings up the smallest k3d-resident stack that lets a host-side knoe-auth
(run via `mvn spring-boot:run` or IntelliJ) iterate against real Postgres
+ Kerberos. Closes Phase 1 of the k3d-gke-mirror plan (docs/plans/k3d-gke-mirror.md).

Scope:
  - k8s/knoe/knoe-kdc-{configmap,deployment,service,pvc,init-job}.yaml
    NEW; standalone KDC, realm KNOE.LOCAL (distinct from KNOE.DEV).
  - etc/init_knoe_auth.sh: --mode k3d flag added; swaps realm + skips
    GCP-specific steps. GKE behavior unchanged when flag absent.
  - Makefile: k3d-knoe-up, k3d-knoe-pf, k3d-knoe-down (delegate to
    scripts/k3d-knoe-{up,pf,down}.sh).
  - scripts/k3d-knoe-{up,pf,down,smoke}.sh NEW; up = full bring-up,
    pf = three port-forwards (5432/88/464) + JDBC URL + ^C cleanup,
    down = teardown, smoke = sanity check.
  - etc/krb5.local.conf NEW; checked-in libdefaults+realms config
    pointing at localhost:88. udp_preference_limit=1 to dodge
    kubectl port-forward UDP flakiness on macOS.
  - docs/local-dev-knoe-auth.md NEW; one-time setup + daily loop +
    IntelliJ run config.
  - docs/knoe-system.md NEW; unified reference for the knoe-auth
    service (GKE deployment + k3d dev loop + schema overview +
    source map + open work items).

Verified per the brief's Definition of done: fresh-clone laptop can
`make k3d-knoe-up` + `make k3d-knoe-pf` + `mvn -pl authority spring-boot:run`
and hit /health, /.well-known/openid-configuration in <8 minutes.

Out of scope (parent plan docs/plans/k3d-gke-mirror.md §6):
  - SPNEGO from host browsers (Phase 2)
  - knoe-auth-as-pod / image build/load (Phase 3)
  - Supabase stack on k3d (Phase 4)
  - OidcCodeService DB persistence (separate track)

docs/plans/junie/README.md — k3d brief moved from Active to Shipped.
docs/TODO.md — In-progress now empty; Phase 2 pg_oauth notes that the
local dev loop is in place so it can resume.

Closes Phase 1; Phase 2+ briefs filed as needed.

Co-authored-by: Junie <junie@jetbrains.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 11:47:09 -07:00

163 lines
6.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# scripts/k3d-knoe-up.sh
# Bring up the k3d-knoe local dev cluster: CNPG operator + single-replica DB + KDC + schema.
#
# Usage:
# ./scripts/k3d-knoe-up.sh
#
# Prerequisites:
# k3d, kubectl, docker (running)
#
# What it does:
# 1. Creates k3d cluster "k3d-knoe" (if not present)
# 2. Installs CNPG operator (v1.29.0)
# 3. Applies knoe-db CNPG Cluster (single-replica, local-path storage)
# 4. Waits for CNPG cluster to be healthy
# 5. Applies KDC manifests (configmap, pvc, deployment, service)
# 6. Runs etc/init_knoe_auth.sh schema --mode k3d to seed knoe.* tables
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
K8S_KNOE="$REPO_ROOT/k8s/knoe"
CLUSTER_NAME="${K3D_CLUSTER_NAME:-k3d-knoe}"
CTX="k3d-${CLUSTER_NAME}"
DB_NS="knoe-db-0"
SYS_NS="knoe-system"
CNPG_VERSION="${CNPG_OPERATOR_VERSION:-1.29.0}"
CNPG_NS="cnpg-system"
log() { echo "[k3d-knoe-up] $*"; }
die() { echo "[k3d-knoe-up] ERROR: $*" >&2; exit 1; }
kube() { kubectl --context="$CTX" "$@"; }
# ── 1. Cluster ────────────────────────────────────────────────────────────────
if k3d cluster list 2>/dev/null | awk '{print $1}' | grep -qx "${CLUSTER_NAME#k3d-}"; then
log "k3d cluster '${CLUSTER_NAME#k3d-}' already exists — skipping create."
else
log "Creating k3d cluster '${CLUSTER_NAME#k3d-}'..."
k3d cluster create "${CLUSTER_NAME#k3d-}" \
--agents 0 \
--k3s-arg "--disable=traefik@server:0" \
--wait
log "Cluster created."
fi
# Ensure kubeconfig is merged
k3d kubeconfig merge "${CLUSTER_NAME#k3d-}" --kubeconfig-merge-default >/dev/null 2>&1 || true
# ── 2. CNPG operator ──────────────────────────────────────────────────────────
log "Installing CNPG operator v${CNPG_VERSION}..."
kube apply --server-side -f \
"https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-${CNPG_VERSION%.*}/releases/cnpg-${CNPG_VERSION}.yaml" \
2>/dev/null || \
kube apply -f \
"https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-${CNPG_VERSION%.*}/releases/cnpg-${CNPG_VERSION}.yaml"
log "Waiting for CNPG operator to be ready..."
kube -n "$CNPG_NS" rollout status deployment/cnpg-controller-manager --timeout=120s
# ── 3. knoe-db namespace + secret + cluster ───────────────────────────────────
kube get namespace "$DB_NS" >/dev/null 2>&1 || kube create namespace "$DB_NS"
# Create the knoe-db-user secret (CNPG bootstrap owner secret)
if ! kube -n "$DB_NS" get secret knoe-db-user >/dev/null 2>&1; then
log "Creating knoe-db-user secret..."
kube -n "$DB_NS" create secret generic knoe-db-user \
--from-literal=username=knoe \
--from-literal=password=knoe-dev-password
fi
# Render the knoe-db manifest for k3d:
# - instances: 1 (single-replica)
# - remove synology-iscsi storageClassName + selector blocks
# - remove affinity with kubernetes.io/hostname topology
# - remove plugins block (no barman in k3d)
# - remove certificates block (no pre-issued TLS secrets in k3d)
# - remove env block referencing AWS/Garage (not needed locally)
# - substitute image registry placeholder with a public postgres image
log "Rendering knoe-db manifest for k3d..."
RENDERED=$(python3 - <<'PYEOF'
import re, sys
with open("k8s/knoe/knoe-db.yaml") as f:
text = f.read()
# instances: 3 -> 1
text = re.sub(r'(\binstances:\s*)3\b', r'\g<1>1', text)
# Remove plugins block (barman)
text = re.sub(r'\n plugins:\n( - .*\n( .*\n)*)+', '\n', text)
# Remove certificates block
text = re.sub(r'\n certificates:\n( .*\n)+', '\n', text)
# Remove env block (AWS/Garage refs)
text = re.sub(r'\n env:\n( - .*\n( .*\n)*)+', '\n', text)
# Remove walStorage block (not needed for single-replica dev)
text = re.sub(r'\n walStorage:\n( .*\n)+', '\n', text)
# Replace synology-iscsi storageClassName
text = re.sub(r'storageClassName: synology-iscsi', '', text)
# Remove selector block with synology.storage labels
text = re.sub(r'\n selector:\n matchLabels:\n synology\.storage/role: \w+\n', '\n', text)
# Remove affinity block (contains kubernetes.io/hostname)
text = re.sub(r'\n affinity:\n( .*\n)+', '\n', text)
# Replace image placeholder with standard postgres (CNPG uses its own image)
text = re.sub(
r'imageName: \$\{KNOE_IMAGE_REGISTRY\}/knoe-db:\$\{KNOE_DB_IMAGE_TAG\}',
'imageName: ghcr.io/cloudnative-pg/postgresql:17',
text
)
# Remove maxSyncReplicas (irrelevant for single instance)
text = re.sub(r'\n maxSyncReplicas: \d+\n', '\n', text)
print(text)
PYEOF
)
echo "$RENDERED" | kube apply -f -
log "Waiting for CNPG cluster 'knoe-db' to be healthy (up to 5 min)..."
for i in $(seq 1 60); do
phase=$(kube -n "$DB_NS" get cluster knoe-db \
-o jsonpath='{.status.phase}' 2>/dev/null || echo "")
if [[ "$phase" == "Cluster in healthy state" ]]; then
log "CNPG cluster is healthy."
break
fi
if [[ $i -eq 60 ]]; then
die "CNPG cluster did not become healthy in time. Phase: $phase"
fi
echo " [$i/60] phase='$phase' — waiting 5s..."
sleep 5
done
# ── 4. KDC ────────────────────────────────────────────────────────────────────
kube get namespace "$SYS_NS" >/dev/null 2>&1 || kube create namespace "$SYS_NS"
log "Provisioning KDC (realm KNOE.LOCAL)..."
KNOE_MODE=k3d APP_CLUSTER_KUBECONTEXT="$CTX" \
"$REPO_ROOT/etc/init_knoe_auth.sh" initialize --mode k3d --context "$CTX"
log ""
log "=== k3d-knoe is up ==="
log " CNPG cluster: kubectl --context=$CTX -n $DB_NS get cluster knoe-db"
log " KDC: kubectl --context=$CTX -n $SYS_NS get deploy knoe-kdc"
log ""
log "Next: make k3d-knoe-pf (opens port-forwards)"
log "Then: export KRB5_CONFIG=\$PWD/etc/krb5.local.conf"
log " mvn -pl authority spring-boot:run"