kubestronaut-sim runs each exam task on a host of its own - a kind (kubeadm) cluster in rootless podman, or a QEMU/KVM Ubuntu VM for the CKS tasks that need their own kernel (Falco, AppArmor) - and puts a browser desktop in front of them, the way killer.sh and sailor.sh do. - ksim, sim/: the engine (sessions, drills, study mode, grading, coverage, audit), host and VM lifecycle, the desktop - images/: exam host images (common, CKS variant with its tools, VM guest) - cka/ (131 tasks), ckad/ (105), cks/ (110): task banks, curricula with study notes, reference solutions and scripted solutions Every domain of every track has at least 17 pool tasks. `./ksim audit` proves a task three ways: it seeds cleanly on its own host, scores nothing before anyone has worked on it, and reaches full marks when its scripted solution is run on the host as `candidate`. All 346 tasks pass (CKA audited 2026-10-13, CKAD and CKS 2026-10-12). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZPd3GGWyxTgAkL6a4gAJz
47 lines
2.5 KiB
Python
47 lines
2.5 KiB
Python
"""Building blocks shared by the CKA task banks written for the per-task hosts (114 onwards): bash
|
|
for setups and checks. The generic ones - NET, PODS, PY, CAN - are sim/bank.py's.
|
|
|
|
The two rules of the CKS and CKAD banks hold here as well: a task scores nothing before anyone has
|
|
worked on it, and what can be tested for real - a file on a node, a name resolved from a Pod, a
|
|
request, a volume that gets provisioned - is tested for real rather than read off the object.
|
|
"""
|
|
from sim.bank import C, CAN, NET, NS, PODS, PY, ns_pick, name_pick # noqa: F401 (the banks import these from here)
|
|
|
|
# one FILE: the file's content with blanks and line ends removed - for a one-word answer
|
|
ONE = r"""
|
|
one() { tr -d ' \t\n\r' < "$1" 2>/dev/null; }
|
|
"""
|
|
|
|
# Volumes and claims.
|
|
STOR = ONE + r"""
|
|
# pvnode PV: the node a PersistentVolume is tied to (its node affinity)
|
|
pvnode() { j pv "$1" '{.spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[0]}'; }
|
|
# pvpath PV: its directory on that node (hostPath or local)
|
|
pvpath() { local p; p=$(j pv "$1" '{.spec.hostPath.path}'); [ -n "$p" ] || p=$(j pv "$1" '{.spec.local.path}'); echo "$p"; }
|
|
# bound NS CLAIM [SECONDS]: the claim is Bound, given a moment
|
|
bound() {
|
|
local i
|
|
for i in $(seq 1 $(( ${3:-45} / 3 ))); do [ "$(j -n "$1" pvc "$2" '{.status.phase}')" = Bound ] && return 0; sleep 3; done
|
|
return 1
|
|
}
|
|
# running NS POD [SECONDS]: the Pod is Running, given a moment
|
|
running() {
|
|
local i
|
|
for i in $(seq 1 $(( ${3:-60} / 3 ))); do [ "$(j -n "$1" pod "$2" '{.status.phase}')" = Running ] && return 0; sleep 3; done
|
|
return 1
|
|
}
|
|
# gone KIND NAME [-n NS]: the object does not exist (and the API server said so)
|
|
gone() { local out; out=$($K get "$@" 2>&1); case "$out" in *NotFound*) return 0 ;; esac; return 1; }
|
|
# newest NS SELECTOR: the name of the newest Running Pod the selector matches
|
|
newest() { $K -n "$1" get pod -l "$2" --field-selector status.phase=Running --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1:].metadata.name}' 2>/dev/null; }
|
|
"""
|
|
|
|
# Name lookups from a Pod.
|
|
DNS = r"""
|
|
# look NS POD NAME: the address the name resolves to from that Pod ("" when it does not)
|
|
look() { $K -n "$1" exec "$2" -- nslookup "$3" 2>/dev/null | awk '/^Name:/ {n=1} n && /^Address/ {print $NF; exit}'; }
|
|
# resolves NS POD NAME ADDRESS: it resolves to that address, given a moment (CoreDNS reloads its
|
|
# configuration by itself, but not at once)
|
|
resolves() { local i; for i in $(seq 1 10); do [ "$(look "$1" "$2" "$3")" = "$4" ] && return 0; sleep 3; done; return 1; }
|
|
"""
|