Authoring a Lab¶
A lab is a directory with a config.yaml (or whatever --file points at) plus whatever scripts/manifests/docs it references. This walks through building one from scratch, using examples/k8s-basics-01 as the worked reference.
Scaffolding New Content (For Teachers & Authors)¶
To make content creation fast, consistent, and collaborative, Astrona provides a built-in blueprint scaffolding command. By default, it dynamically pulls the template blueprint from a remote git repository based on the required <type> argument and replaces placeholders in filenames and contents with your customized arguments.
<type>(Required): Must be eitherats(Astrona Training Series - hands-on sandbox lab) oratp(Astrona Training Path - course path/section).[path](Optional): The target directory to initialize. If omitted, this defaults to the value of the--slugflag (creating a directory named after your slug).
Dynamic Blueprint Defaults¶
Depending on the content type specified, the tool clones a dedicated template repository if no custom --repo is defined:
- atp: Clones git@github.com:astrona-io/training-path-blueprint.git
- ats: Clones git@github.com:astrona-io/training-sandbox-blueprint.git
Custom Scaffolding Arguments¶
You can supply the following customization arguments via command-line flags to template the newly initialized content:
| Flag | Variable | Default | Description |
|---|---|---|---|
-i, --path-id |
path_id |
ATPxxx |
Unique identifier of the training path/lab (e.g., ATP010) |
-s, --slug |
slug |
path-slug |
URL-safe and directory-friendly slug name of the path (becomes default folder name) |
-t, --title |
title |
Path Title |
Human-readable title of the path |
-d, --description |
description |
(engaging summary) | A short summary of what the learner will achieve |
-v, --version |
version |
0.1.0 |
Initial version of the training content |
-g, --language |
language |
en |
Learning language (e.g., en, fr, de) |
-a, --author-name |
author_name |
Author Name |
Name of the author/teacher creating the content |
-l, --license |
license |
Apache-2.0 |
Content license (default is Apache 2.0 license) |
-r, --repo |
repo |
"" |
Custom template repository to clone from (overrides default blueprints) |
Example Scaffolding Commands¶
-
Initialize an ATP (Training Path) into a custom directory:
-
Initialize an ATS (Lab Sandbox) defaulting to a folder named after your slug:
This will automatically clone the sandbox template and create a new directoryastrona content init ats \ --path-id "ATS012" \ --slug "virtio-disk-discovery" \ --title "Virtio Disk Discovery Lab" \ --author-name "Bob Jones"./virtio-disk-discovery/.
Validating Content (For Teachers & Authors)¶
Once a Training Path (atp) has been scaffolded and filled in, validate its path.yaml before publishing:
<type>(Required):ats(a training repository, itsastrona.yaml) oratp(a training path, itspath.yaml).<source>(Required): Either a local folder path (the raw content files, containingpath.yaml) or a git repository URL (https://,git@host:,ssh://, or a.gitsuffix). Use--git-refto pin a git source to a branch or tag.
Validation:
- Confirms
path.yamlexists at<source>and parses as YAML. - Checks
apiVersioniscontent.astrona.io/v1alpha1andkindisTrainingPath. - Walks every
spec.stages[].content[]entry and resolves itsrepositoryatversion(a git branch or tag) via the same git cache used by--gitelsewhere in this CLI — this both verifies you have access to the referenced content repo and warms the local cache for later steps.
astrona content validate atp ./paths/my-custom-path
astrona content validate atp https://github.com/astrona-io/kubernetes-networking-atp.git --git-ref v1.2.0
Validating a training repository (ATS)¶
astrona content validate ats ./ATS014
astrona content validate ats https://github.com/astrona-io/ATS014.git --git-ref main
Checks astrona.yaml the way astrona.io's content sync reads it, and prints the totals (ATS014: 146 readings, 56 labs, 0 playgrounds, 20 quizzes (174 questions)). It fails (✗) on what would keep the training or an item off the site, and warns (!) about what is worth a look:
training.idis set; every item has a title, a knowntype—reading,lab,playgroundorquiz— and a path inside the repository that exists: a Markdown file for a reading, a directory with aconfig.yamlfor a lab or playground, a YAML question bank for a quiz.- A quiz (knowledge check) bank has
kind: question_bankandschema_version: 1, and each question an uniqueid, aprompt, 2–6 options with unique ids and a text, acorrect_optionthat is one of them, andtype: single_choice(or none). Itssource_readingsmust exist; one that is not a reading inastrona.yamlgets no "read it again" link (warning). The site uses at most 30 questions. Fields, an example and the JSON Schema: Writing a Question Bank. - The quiz item's
pass_scoreis a percentage above 0 and at most 100 (the site's default is 70); aquestion_countthat differs from the bank is a warning — the site counts the bank. - A bank with
status: draft, an option or question without an explanation, and aquiz.yamlthe manifest does not list are warnings.
A knowledge check looks like this in astrona.yaml, right after the module's summary:
- type: quiz
title: "Knowledge Check: How A Request Moves Through The Mesh"
path: sections/section-000/module-01/quiz.yaml
pass_score: 70
path.yaml shape (ATP)¶
apiVersion: content.astrona.io/v1alpha1
kind: TrainingPath
metadata:
id: ATP001
slug: linux-foundation-certified-sysadmin-lfcs
title: "Linux Foundation Certified System Administrator (LFCS)"
version: "1.0.0"
authors:
- name: "Alice Smith"
email: "alice@example.com"
spec:
stages:
- id: "ATP001-STG001"
title: "Operations Deployment"
weight: 25
content:
- ref: ATS002
repository: "git@github.com:astrona-io/ATS002.git"
path: "."
version: "1.0.0"
Each stages[].content[] entry references an external content repository (typically an ATS lab): repository is the git URL, version the branch/tag to check out, and path the subdirectory within it.
Because a built bundle lays content out as <output>/<stage-id>/<ref>, and path.yaml often comes from a cloned repo, validate and build reject, before fetching anything:
- a stage
idor contentrefthat isn't a single name of letters, digits,.,_and-(no/, no.., not empty); - an absolute
path, or one that escapes the content repository (../…).
A repository or version starting with - is also rejected before git runs.
Building Content (For Teachers & Authors)¶
Once a Training Path validates cleanly, materialize it into a self-contained bundle — every referenced ATS repo resolved and copied locally, ready to ship or serve:
<type>(Required): Currently onlyatpis implemented, same asvalidate.<source>(Required): Same asvalidate— a local folder path or a git repository URL, optionally pinned with--git-ref.-o, --output: Output directory for the bundle (default./dist).--clean: Remove the output directory first if it already exists and is not empty. Without it, build refuses to write into a non-empty output directory.
Build:
- Runs the same
path.yamlchecks asvalidate. - Resolves every
spec.stages[].content[]entry'srepositoryatversionvia the git cache (cloning/pulling as needed). - Copies the resolved content (or its
pathsubdirectory, if set) into<output>/<stage-id>/<ref>, skipping.git. - Copies
path.yamlitself to the bundle root.
astrona content build atp ./paths/my-custom-path --output ./dist
astrona content build atp https://github.com/astrona-io/kubernetes-networking-atp.git --git-ref v1.2.0 --output ./dist --clean
Starting from a working lab¶
Creates a complete kind lab you can run immediately and reshape into yours: config.yaml (editor schema, preloaded image, readiness gates, jsonpath / count / resourceExists checks with hints and points, commented addon and exam options), the four student docs, a bootstrap manifest, a reference solution in solution/ for astrona test, and .github/workflows/lab.yml running astrona validate + astrona test on every pull request. As generated it passes validate and test, and its step-by-step guide solves it. The sections below explain each part.
1. Metadata¶
metadata:
name: "k8s-basics-01"
docs:
prerequisites: "docs/prerequisites.md" # knowledge/tooling needed before attempting
question: "docs/exam-question.md" # formal, self-contained task statement
caseStudy: "docs/case-study.md" # softer, hint-driven version of the same task
solution: "docs/step-by-step-guide.md" # full walkthrough with the answer
metadata.name becomes the cluster/VM name (prefixed astro- by astrona). metadata.docs are Markdown files, relative to config.yaml, that students read with astrona docs. The block is optional: a doc it doesn't list is found by its usual file name next to config.yaml — question.md, solution.md, prerequisites.md, case-study.md — so this is enough:
labs/lab-01/
config.yaml # metadata: { name: "ats-015-lab-010-01" } — no docs block needed
question.md
solution.md
astrona docs # lists what the lab provides
astrona docs question # docs.question (question.md) — the task
astrona docs case-study # docs.caseStudy (case-study.md)
astrona docs prerequisites # docs.prerequisites (prerequisites.md)
astrona docs solution # docs.solution (solution.md) — shown with a "full solution" warning
They're rendered for the terminal (headings, code blocks, lists, inline code, bold, links) and paged; astrona run ends with "Your task: astrona docs question" when the lab has a question. Write them as plain Markdown — tables and HTML are shown as-is. A YAML front-matter block at the very top of a doc (a --- line, YAML such as estimated_duration: 20m, a --- line) is hidden; if it has a title, that's shown as the doc's heading. A --- further down is still a horizontal rule. Doc paths can't point outside the lab directory, and control characters (terminal escape sequences) are stripped before anything is printed. Name the cluster context as kind-astro-<metadata.name> in your docs — or point students at astrona shell <lab>.
Set an expected working time in the question document's front matter to show the UI's lab progress bar:
---
title: Configure a deployment
estimated_duration: 20m
---
Create a deployment with three replicas.
Use a positive duration with units, such as 45m or 1h30m. The UI compares
this estimate with active time spent and pauses it during idle periods.
Exceeding it does not stop the lab or change its grade. Missing or invalid
estimates show as unavailable. This display applies to labs, not playgrounds;
exam deadlines and playground expiry remain separate.
2. Pick a runtime¶
Omit runtime: entirely for a kind cluster (the default), or see Runtimes for a qemu VM-backed lab.
3. Bootstrap — what the student starts with¶
Keep this to whatever scaffolding the exercise needs before the student's own work — the actual task (creating the namespace, deploying the app, whatever it is) should not be pre-built here.
4. Testing — your reference solution¶
This only runs under astrona test, never for a student. Put the finished, correct solution here — the thing a student would produce if they solved the lab perfectly — so astrona test can prove your validation block actually passes against it.
5. Validation — how it's graded¶
validation:
checks:
- name: "lab-ns namespace exists"
type: "resourceExists"
resource: "namespace/lab-ns"
- name: "hello-config configmap exists"
type: "resourceExists"
resource: "configmap/hello-config -n lab-ns"
script:
name: "verify-configmap-content"
description: "Custom script check: confirms the configmap's actual content, not just that it exists"
type: "file"
source: "validate.sh"
Start with resourceExists/podReady checks for "does the thing exist," and reach for a script when you need to verify actual content or behavior (see Grading for the full check-type reference). A validation script exits 0 for pass, non-zero for fail — write it the same way you'd write a test assertion.
6. Teardown¶
Usually just keepCluster: false (or omitted — that's the default). Add init scripts only if you need to capture state before the cluster disappears.
Files the student needs: resources/¶
Instead of asking students to paste a script or a YAML file from question.md, put it in a resources/ folder next to config.yaml. When the lab starts, astrona copies it to ~/.astrona/resources/<lab>/ and lists what's there — nothing runs until the student asks for it:
sections/section-010/module-01/labs/lab-01/
config.yaml question.md solution.md
bootstrap/ solution/ validation/
resources/
setup-db.sh # runs with bash
broken-deployment.yaml # applied to the lab with kubectl
No config is needed; a resources: list adds descriptions and run commands — see resources.
7. Prove it works¶
astrona validate -c path/to/your-lab # seconds: typos, rules, missing files
astrona test -c path/to/your-lab --junit-xml=report.xml
astrona validate catches config mistakes — a misspelled field, a missing script — without creating anything; see editor support and validation for autocompletion in your editor.
Before publishing, check the lab isn't flaky:
--repeat N (up to 20) runs the whole lifecycle N times on fresh environments. A check that passes only sometimes almost always means grading raced the cluster — add a waitFor (in testing for the reference solution) for whatever the check depends on. Any failed or flaky run fails the command; with --junit-xml, each check is reported once with "passed k/N runs".
This is the whole point of the testing stage: it bootstraps your lab, applies your reference solution, submits it to the Proctor, and tears down — proving a student who does everything right will actually pass. Run this locally before publishing, and wire it into CI (see CI Integration) so a later edit to validation can't silently break the lab's own solution.
Full schema reference¶
See Lab Config Schema for every field, type, and default.