Skip to main content

Project

Contributing

Development setup, the PR bar and the review checklist.

CONTRIBUTING.md
On this page

Thanks for your interest in SnowOps Labs - the flight simulator for platform engineers. This project thrives on community-contributed scenarios, platform modules, documentation, and code. This guide explains how to contribute and what to expect.

New here? The highest-leverage place to start is content - a new scenario or incident. Start with Your First Scenario and look for issues labelled good first issue.

Code of Conduct

By participating you agree to our Code of Conduct.

Sign-off (DCO)

SnowOps Labs uses the Developer Certificate of Origin - a lightweight, no-paperwork way to certify you wrote (or have the right to submit) your contribution. Just add a Signed-off-by line to each commit by committing with -s:

git commit -s -m "your message"

The DCO check in CI verifies every commit in a PR is signed off. That's the only agreement required, no CLA, no bot account, no click-through.

What you can contribute

Type Where it lives Review owner
Scenarios scenarios/ scenario maintainers
Platform modules platform/<category>/<provider>/ platform maintainers
Incidents / learning / challenges incidents/, learn/, challenges/ scenario maintainers
Documentation docs/ docs maintainers
Engine / CLI / SDK src/cmd/, src/internal/, src/pkg/, src/engine/ lead maintainer (see GOVERNANCE.md)

Changes to the engine, the public SDK (pkg/), or the scenario schema require an RFC first - a short markdown PR under docs/rfcs/ that a maintainer approves before implementation. This keeps architectural direction coherent.

Development setup

End users clone the latest release (--branch stable) and download a matching labctl with ./install.sh - see the README. Contributors clone main and build labctl from source so they can test their changes. You need Go 1.25+ and Node 22+. The steps are identical on macOS, Linux, and Windows/WSL2: on Windows run them inside your WSL2 distro, in your Linux home, never native PowerShell.

Distro packages are often older than that (Ubuntu 24.04 ships Go 1.22 and an older Node). None of the fixes need sudo:

  • Go: with Go 1.21 or later installed, export GOTOOLCHAIN=auto and the build downloads the Go version src/go.mod asks for.
  • Node: unpack the Node 22 tarball from nodejs.org under your home and put its bin/ first on PATH (or use a version manager such as nvm).
  • bats (for make test-shell): npm install -g bats with that Node.
git clone https://github.com/sagar2395/snowopslabs.git
cd snowopslabs
 
# 1. Build the CLI, UI embedded (no committed binary). The Go module lives under
#    src/; the root make targets delegate there and the binary lands at bin/labctl.
make cli-build
 
# 2. Run the gates. All four test layers are mandatory - see docs/TESTING.md.
make test          # Go unit + shell (bats), race detector, coverage gates, test-check
make test-ui       # vitest component tests
make test-e2e      # playwright journeys
make lint          # gofmt, golangci-lint, gosec, govulncheck, shellcheck,
                   # shfmt, the portability gate, and TypeScript strict
 
# 3. Bring up a local cluster + platform to test changes end to end. Use the CLI
#    you just built - it drives the same loop end users run:
bin/labctl init                    # setup-tools + create cluster + install platform
bin/labctl scenario up observability-sre

Other targets:

make cli-install   # build labctl, install it over the one install.sh put in ~/.local/bin, and record this clone as the lab
make ui-dev        # serve src/ui/dist live without rebuilding Go (LABCTL_UI_DIR)
make docs-check    # the docs agree with the code and the website's manifest
make fmt           # gofmt the tree

labctl finds the lab in this order: --project-dir; the clone around the working directory (it walks up to the directory with scenarios/ and runtimes/); SNOWOPS_LAB_DIR; and the clone install.sh or make cli-install last recorded in ~/.snowops/lab-dir. Inside your checkout your edits take effect without reinstalling. Lab state (active scenarios, history, progress, snapshots) lives in ~/.snowops/state/<cluster>/, outside every clone, so all of them see the same cluster. main carries a -dev LAB_VERSION, and a release labctl warns when pointed at it.

Repository layout

# User-facing content at the root - what a learner's clone runs
scenarios/ incidents/     declarative content
learn/ challenges/
platform/<cat>/<prov>/    install.sh / uninstall.sh / status.sh / values.yaml
runtimes/<profile>/       k3d | kind | incluster (+ runtimes/_lib shared helpers)
apps/<name>/              sample workloads
bootstrap/                tool installation
config/                   versions.env, footprints.yaml, .env.example
docs/                     PRODUCT, ROADMAP, TESTING, architecture/, adr/, runbooks/
install.sh                installs the labctl release that matches LAB_VERSION
LAB_VERSION               the release this checkout belongs to (-dev on main)
scripts/                  footprint measurement, docs checks, release
 
# The labctl Go module - self-contained under src/
src/cmd/labctl/           entrypoint
src/internal/             implementation (cli, httpapi, service, run, store, …)
src/pkg/                  public SDK: checks, scenario types, extension seam
src/ui/                   React SPA, embedded into the binary
src/engine/ src/services/ app build/deploy scripts and shared services (shipped)
src/test/shell/           bats suites with kubectl/helm stubbed

Scenario and fault footprints

config/footprints.yaml holds the measured memory of the baseline lab, each Helm release scenarios install, platform components and apps; a scenario's own extra goes in its requirements.memory. The capacity check that decides whether a scenario fits is built on them, so re-measure when you add or change a scenario:

labctl init                               # a fresh lab, nothing active
scripts/measure-footprints.sh <scenario>  # prints what the scenario adds

Releasing

A release ships the binary and the content together; see RELEASING.md.

New to the repo? Work through R00 - Environment & Build once; it verifies your setup and shows you each gate biting.

Before you start: the traps

Every one of these was learned by something failing silently on a real cluster, and each is an afternoon you do not have to lose:

  • Helm: upgrade and uninstall both ignore CRDs; most of a StatefulSet spec is immutable: R05.
  • Metrics, logs and traces: empty-selector fallbacks, Tempo's real port, Promtail relabelling, k6's units: R13.
  • Writing a scenario: values ownership, chart pinning, checks that teach: scenario schema.

docs/AGENT-CONTEXT.md is the index: it says which single document answers which task, and lists every invariant in one place. It is written for AI agents, but it is the fastest orientation for a human too.

The golden rules (please read before a code/content PR)

  1. Cross-platform. Must run on macOS (Apple Silicon + Intel) and modern Linux. No GNU-only shell flags (grep -P, sed -i without a backup suffix, readlink -f, date -d), and no cgo - it breaks cross-compilation. make lint-shell enforces this automatically.
  2. Go orchestrates, scripts do the work. Don't move shell/helm/kubectl logic into Go.
  3. Declarative content. Scenarios, faults, learning paths and checks are YAML + scripts, never hardcoded in Go.
  4. Idempotent everything. helm upgrade --install, kubectl apply, safe re-activation. Interrupting an operation and re-running it must converge.
  5. Every operation is cancellable and durable. Anything that shells out goes through internal/run with a context, a timeout and a lock key. Never call exec.Command(...).Run() directly.
  6. Every change ships its tests, and a bug fix starts with a failing test. Go unit (table-driven, hermetic, ≥80%, cancellation covered), bats for scripts with logic, contract tests for endpoints and commands, Vitest and Playwright for UI. docs/TESTING.md says what each kind of change needs. CI blocks a change that brings no test or leaves more than 15% of its lines untested; a change that needs none says why in a Tests-Not-Needed: <reason> commit trailer.
  7. Docs and runbook in the same PR. Update the relevant docs/, add or update the runbook, and write an ADR if you made a notable decision. Documentation is the source of truth here - the code is expected to match it, and make docs-check gates the links and the pages the website publishes.
  8. Comments follow the comment rule in CLAUDE.md: what the code does and why it is needed, never the story behind a change.

Changing Go code? docs/GO-CONVENTIONS.md covers the everyday choices: which layer owns the code, how to handle contexts and errors, the handler and command patterns, and the lint set. It ends with a pre-PR checklist.

Pull-request workflow

fork → branch → make your change → run lint + tests → commit with -s (DCO) →
open a PR → CI green → CODEOWNERS review → maintainer merge
  • Keep PRs focused and small where possible.
  • Use Conventional Commits (feat:, fix:, docs:, ci:, chore:).
  • Fill out the PR template (what/why/how-tested).
  • CI must pass every gate: Go build/vet/test on macOS and Linux with the race detector, the per-package coverage gate, the changed-lines gate, test-check, golangci-lint, gosec, govulncheck, a fuzz smoke run, bats, shellcheck, the portability gate, TypeScript strict, vitest, Playwright, the license scan, and content validation. None of them are advisory.
  • A maintainer or domain code owner reviews; the lead maintainer holds final merge authority on engine/SDK/schema changes (see GOVERNANCE.md).

Reporting bugs / requesting features

Use the issue templates. For security vulnerabilities, do not open a public issue - follow SECURITY.md.

Licensing of contributions

All contributions are licensed under Apache-2.0 (the same license as the project); your Signed-off-by line certifies you have the right to submit them under it (see DCO.md). New source files should carry an SPDX header:

// SPDX-License-Identifier: Apache-2.0

Thank you for helping build the open platform-engineering simulator. ✈️

Get updates

New scenarios and faults, release notes, and news about hosted labs if and when there is some. A few emails a month at most.

We'll email you about new releases, new lab content and paid offerings such as hosted labs, courses and workshops. Unsubscribe any time.