Pre-Phase-1 platform work, approved and tested earlier but not yet committed: - Terminal banner: "RootNode Academy Lab — node: k8s-lab", upstream KubeKosh attribution left untouched in footer/README/LICENSE. - RootNode curriculum mode: scenarios/rootnode-path.json (Foundation -> Workloads -> Networking -> Configuration -> Troubleshooting -> Real Ops -> Capstone), built from existing scenario content grouped by category, no new scenarios invented. New GET /api/curriculum/path endpoint. - Student roster + per-student progress: instructor-roster, pick-your-name login (no password), students/environments-adjacent progress table migrated to a (student_id, scenario_id) composite key so practice progress and exam sessions are isolated per student instead of one global bucket. New /api/roster, /api/login endpoints. - scenarios/data/*.json: added isolation_type metadata (shared_namespace vs ephemeral_cluster) from an audit of which scenarios execute cluster-scoped or node-level commands. This was Tier-1-isolation groundwork that got superseded same-day by the full-ephemeral-per-student architecture (see docs/LAB_ARCHITECTURE.md) — the field is kept as descriptive metadata only, per the note now in scenarios/SCHEMA.md. Not wasted: it's an accurate audit of which scenarios touch cluster-scoped resources, useful independent of isolation model. - .gitignore added (node_modules/, frontend/dist/, *.db, /data/, .env) — none existed before; without it, npm installs during testing would have landed in a commit. Known limitation still open: no frontend UI for any of this yet (login screen, roster-add form, curriculum view) — API-only. No auth on the roster-add endpoint. Co-Authored-By: Claude Sonnet 5 <[email protected]>
229 lines
9.2 KiB
Markdown
229 lines
9.2 KiB
Markdown
# KubeKosh Configuration Schema Reference
|
|
|
|
KubeKosh uses two primary directories to define its curriculum, learning paths, and exam configurations:
|
|
1. **Bundles (`scenarios/bundles`)**: Each file defines one high-level study bundle (e.g., CKA, CKAD, CKS), including active highlights, exam duration, ordered list of included scenarios, and other fields.
|
|
2. **Scenarios (`scenarios/data`)**: Each file defines one individual exercise — a hands-on task or multiple-choice question (MCQ). Each scenario contains an environment setup script, cleanup script, hints, problem statement, automated validation script, and other fields.
|
|
|
|
---
|
|
|
|
## 1. Bundles Schema (`scenarios/bundles`)
|
|
|
|
Each file contains a **single bundle object**. The filename must match the bundle's `id` field (e.g., `k8s-basics.json`).
|
|
|
|
### Schema Fields
|
|
* **`id`** *(string, required)*: A unique, kebab-case identifier for the bundle (e.g., `k8s-basics`).
|
|
* **`name`** *(string, required)*: The human-readable name of the bundle shown in navigation (e.g., `Kubernetes Basics`).
|
|
* **`icon`** *(string, required)*: An emoji or glyph representing the bundle (e.g., `🌱`).
|
|
* **`tagline`** *(string, required)*: A short summary of the bundle's objectives.
|
|
* **`color`** *(string, required)*: Hex color code representing the bundle's UI identity/accent color (e.g., `#3fb950`).
|
|
* **`colorDim`** *(string, required)*: Translucent RGBA color matching the accent color at low opacity, used for UI row highlighting (e.g., `rgba(63,185,80,0.12)`).
|
|
* **`exam_minutes`** *(number, required)*: The time limit allocated for the mock exam in minutes (e.g., `60`).
|
|
* **`scenario_ids`** *(array of strings, required)*: List of scenario IDs belonging to this bundle in the order they should appear.
|
|
|
|
### Example Bundle
|
|
```json
|
|
{
|
|
"id": "k8s-basics",
|
|
"name": "Kubernetes Basics",
|
|
"icon": "🌱",
|
|
"tagline": "Core concepts for beginners",
|
|
"color": "#3fb950",
|
|
"colorDim": "rgba(63,185,80,0.12)",
|
|
"exam_minutes": 60,
|
|
"scenario_ids": [
|
|
"pod-basics-mcq",
|
|
"kubectl-essentials-mcq",
|
|
"namespaces-basics",
|
|
"deploy-nginx"
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Scenarios Schema (`scenarios/data`)
|
|
|
|
Each file contains a **single scenario object**. The filename must match the scenario's `id` field (e.g., `k8s-basics.json`).
|
|
|
|
Scenarios are defined as JSON objects. A scenario can be either a hands-on console challenge (`"task"`) or a multiple-choice question (`"mcq"`).
|
|
|
|
### Common Fields (All Types)
|
|
```jsonc
|
|
{
|
|
"id": "unique-kebab-case-id", // string — unique scenario identifier
|
|
"title": "Human-readable Title", // string — shown in sidebar list
|
|
"category": "Workloads", // string — groups scenarios in sidebar accordion
|
|
"isolation_type": "shared_namespace", // "shared_namespace" | "ephemeral_cluster" — see below
|
|
"difficulty": "Easy", // "Easy" | "Medium" | "Hard"
|
|
"type": "task", // "task" | "mcq"
|
|
"weight": 7, // number — points value (used for final grade scoring)
|
|
"description": "## Markdown...", // string — problem statement supporting GitHub-flavored Markdown
|
|
"hints": [...], // array — see Hints schema below
|
|
"setup_commands": [...], // array<object> — commands run on environment preparation
|
|
"teardown_commands": [...], // array<object> — optional — cleanup commands run after scenario completes
|
|
"default_namespace": "default" // string — optional — default active namespace for the terminal
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### `isolation_type` — informational, not a security boundary
|
|
|
|
Earlier draft of this schema used `isolation_type` (`shared_namespace` | `ephemeral_cluster`)
|
|
to decide which scenarios could safely run under shared-cluster RBAC. That namespace-isolation
|
|
approach was superseded before it shipped — see `docs/LAB_ARCHITECTURE.md`. Every student now
|
|
gets a fully separate disposable environment with its own nested cluster, so no scenario
|
|
content is unsafe for any other student's environment; the field no longer gates anything.
|
|
It's kept on scenario records as descriptive metadata only (which scenarios exercise
|
|
cluster-scoped/node-level operations), not read by any authorization logic.
|
|
|
|
---
|
|
|
|
### Hints Schema
|
|
Each hint is rendered as a collapsible card inside the Hints tab of the UI:
|
|
```jsonc
|
|
{
|
|
"title": "Short title for the hint card",
|
|
"body": "Explanation text (plain text, no markdown format).",
|
|
"command": "kubectl run nginx --image=nginx" // optional — renders a copyable code block
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Setup & Teardown Commands
|
|
* **`setup_commands`**: Executed sequentially on the Kubernetes cluster when the user starts a scenario or clicks **"Prepare Environment"**. Useful for pre-deploying resources or injecting bugs.
|
|
* **`teardown_commands`**: Optional cleanup commands run when moving away from or resetting a scenario.
|
|
* Commands must be **objects** with a `command` key:
|
|
```jsonc
|
|
"setup_commands": [
|
|
{ "command": "kubectl create namespace debug" },
|
|
{ "command": "kubectl create deployment broken-app --image=nginx:1.25 -n debug" }
|
|
]
|
|
```
|
|
* *Note:* Non-zero exit codes are tolerated (e.g., "namespace already exists" errors won't halt the pipeline). All commands execute as `root`.
|
|
|
|
---
|
|
|
|
### Type: `"task"` — Hands-On Scenario
|
|
Requires the user to run shell commands in the interactive terminal. The system runs an automated validation sequence to check the cluster state.
|
|
|
|
```jsonc
|
|
{
|
|
"type": "task",
|
|
"validation": {
|
|
"description": "Check that deployment has been correctly configured",
|
|
"commands": [
|
|
{
|
|
"description": "Checks the running pods count",
|
|
"command": "kubectl get deploy nginx -o jsonpath='{.status.readyReplicas}'",
|
|
"expected_output": "3",
|
|
"match": "exact" // "exact" | "contains" | "regex"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Match Modes
|
|
| Mode | Behaviour |
|
|
| :--- | :--- |
|
|
| `exact` | Trimmed stdout must exactly equal `expected_output`. |
|
|
| `contains` | stdout must contain `expected_output` as a substring. |
|
|
| `not_contains` | stdout must **not** contain `expected_output` as a substring. |
|
|
| `regex` | stdout must match the regular expression in `expected_output`. |
|
|
|
|
---
|
|
|
|
### Type: `"mcq"` — Multiple Choice Question
|
|
Renders a questionnaire block. No terminal is shown. The user answers by selecting an option.
|
|
|
|
```jsonc
|
|
{
|
|
"type": "mcq",
|
|
"options": [
|
|
{ "id": "a", "text": "Option A explanation" },
|
|
{ "id": "b", "text": "Option B explanation" },
|
|
{ "id": "c", "text": "Option C explanation" },
|
|
{ "id": "d", "text": "Option D explanation" }
|
|
],
|
|
"correct_option": "c", // must match one of the option IDs
|
|
"explanation": "Detailed explanation of why C is the correct answer." // shown after submitting
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Full Examples
|
|
|
|
### Full Example — Hands-On Task Scenario
|
|
```json
|
|
{
|
|
"id": "scale-deployment",
|
|
"title": "Scale a Deployment",
|
|
"category": "Workloads",
|
|
"isolation_type": "shared_namespace",
|
|
"difficulty": "Easy",
|
|
"type": "task",
|
|
"weight": 4,
|
|
"description": "## Scale the Deployment\n\nA deployment named `myapp` exists in the `default` namespace.\n\n**Scale it to 5 replicas.**",
|
|
"hints": [
|
|
{
|
|
"title": "Using kubectl scale",
|
|
"body": "The scale subcommand lets you change the replica count imperatively.",
|
|
"command": "kubectl scale deployment myapp --replicas=5"
|
|
}
|
|
],
|
|
"setup_commands": [
|
|
{ "command": "kubectl create deployment myapp --image=nginx:1.25 --replicas=1" }
|
|
],
|
|
"teardown_commands": [
|
|
{ "command": "kubectl delete deployment myapp --ignore-not-found" }
|
|
],
|
|
"default_namespace": "default",
|
|
"validation": {
|
|
"description": "Checks that myapp has 5 ready replicas.",
|
|
"commands": [
|
|
{
|
|
"description": "myapp has 5 ready replicas",
|
|
"command": "kubectl get deployment myapp -o jsonpath='{.status.readyReplicas}'",
|
|
"expected_output": "5",
|
|
"match": "exact"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Full Example — MCQ Scenario
|
|
```json
|
|
{
|
|
"id": "service-types-mcq",
|
|
"title": "Kubernetes Service Types",
|
|
"category": "Networking",
|
|
"isolation_type": "shared_namespace",
|
|
"difficulty": "Easy",
|
|
"type": "mcq",
|
|
"weight": 3,
|
|
"description": "## Kubernetes Service Types\n\nWhich `kubectl` command creates a ClusterIP service named `my-svc` exposing port 80 for a deployment named `my-app`?",
|
|
"options": [
|
|
{ "id": "a", "text": "kubectl expose deployment my-app --name=my-svc --port=80 --type=ClusterIP" },
|
|
{ "id": "b", "text": "kubectl create service my-svc --port=80" },
|
|
{ "id": "c", "text": "kubectl apply service my-app --port=80" },
|
|
{ "id": "d", "text": "kubectl expose pod my-app --name=my-svc --port=80 --type=NodePort" }
|
|
],
|
|
"correct_option": "a",
|
|
"explanation": "`kubectl expose deployment` is the correct imperative command. It creates a Service targeting the deployment's pods. `--type=ClusterIP` is the default but explicit here for clarity.",
|
|
"hints": [
|
|
{
|
|
"title": "kubectl expose syntax",
|
|
"body": "Use kubectl expose to create a Service from an existing resource. Specify the resource type, name, port, and service type.",
|
|
"command": "kubectl expose deployment my-app --name=my-svc --port=80 --type=ClusterIP"
|
|
}
|
|
],
|
|
"setup_commands": [],
|
|
"teardown_commands": [],
|
|
"default_namespace": "default"
|
|
}
|
|
```
|