Rickshaw is a benchmark orchestration framework that manages container image builds, benchmark execution, and result collection across multiple endpoints (Kubernetes, remote hosts).
rickshaw-run.py— Main orchestrator script. Parses CLI args, validates environment, coordinates image sourcing, and manages benchmark execution across endpoints.rickshaw-source-images-client.py(Python, no pip deps) — CLI bridge that translates local files into HTTP API calls to the source-images-service.source-images-service/(Python/FastAPI) — Web service for container image building. SeeSOURCE-IMAGES-SERVICE-OVERVIEW.mdfor detailed architecture.endpoints/— Endpoint implementations (kube, remotehosts, etc.) in Python.engine/— Engine scripts for benchmark/tool execution inside containers.userenvs/— User environment definitions (JSON files describing container base images).schema/— JSON schemas for validation (run.json,source-images-input.json,source-images-output.json, etc.).util/— Utility scripts (CI job generation, etc.).
- Python 3.10+:
rickshaw-run.py,rickshaw-post-process-bench.py,rickshaw-post-process-tools.py,rickshaw-gen-docs.py,rickshaw-source-images-client.py,source-images-service/,endpoints/,engine/(engine.py, engine_lib.py, bootstrap.py),util/ - Bash:
engine/bootstrap(legacy, retained for fallback),engine/engine-script+engine/engine-script-library(legacy, retained for fallback). Benchmark and tool scripts called by the engine remain Bash. - JSON: Schema definitions, configuration files
The engine scripts that run inside benchmark/tool containers are Python (engine.py, engine_lib.py). The engine.runtime setting in rickshaw-settings.json controls which files are staged ("python" or "bash", default "python"). The bash bootstrap auto-detects which files were staged and execs the appropriate entry point. The Engine class in engine_lib.py uses Fabric/paramiko for SSH file transfer and Invoke for local command execution. Benchmark and tool scripts remain Bash — the Python engine runs them as subprocesses.
- CLI arguments use
--kebab-case(e.g.,--workshop-dir,--bench-params) - JSON keys use
kebab-case(e.g.,"force-builds","workshop-script") - Python Pydantic models use
snake_casefields withalias="kebab-case"for JSON serialization - Commit messages follow conventional commits:
feat:,fix:, etc. - Schema validation is enforced at boundaries between components
Located in source-images-service/. Uses FastAPI + Pydantic.
pip install -e source-images-service/ # editable install
Key modules under source_images_service/:
models/requests.py— Pydantic request modelsmodels/responses.py— Pydantic response modelscore/workspace.py— Temp directory materialization from base64 request contentcore/image_sourcer.py— Core image sourcing enginecore/hash_calculator.py— Image tag hash computationcore/workshop_runner.py— Workshop script subprocess executioncore/requirements_builder.py— Multi-stage build requirement orderingcore/registry_ops.py— Container registry operations (skopeo, buildah)core/build_coordinator.py— Cross-job build deduplication
The workshop script (workshop.py) builds container images. The --workshop-script flag (or WORKSHOP_SCRIPT env var) can override the default. This flows through:
rickshaw-run→ input JSON (workshop.script)rickshaw-source-images-client.py→ reads from JSON/envsource-images-service→WorkshopConfig.workshop_scriptfield
Endpoints detect and report their CPU architecture(s) during validation via the arch keyword (e.g., arch x86_64 aarch64). The kube endpoint reads node.status.nodeInfo.architecture from kubectl get nodes --output json and normalizes K8s names to Linux names (amd64 → x86_64, arm64 → aarch64). The remotehosts endpoint runs uname -m on each remote host.
rickshaw-run.py collects required architectures across all endpoints and routes image sourcing requests to per-arch service URLs read from image-sourcing-urls.json (written by crucible's bin/_main). When multiple architectures are needed, sourcing runs in parallel. Image specifications are passed to endpoints via a structured JSON file (image-map.json, schema in schema/image-map.json) using --image-map=<filepath>. The JSON maps bench → role → userenv → arch → {image, auth-file}. The get_image() and get_engine_id_image() functions in endpoints/endpoints.py accept an optional arch parameter and return a dict with image (URL) and optional auth-file keys, or None.
For the kube endpoint, the arch setting in schema/kube.json lets users target a specific architecture without writing a raw kubernetes.io/arch nodeSelector. On multi-arch clusters without explicit arch constraints, the endpoint defaults to the controller's native architecture and adds a kubernetes.io/arch nodeSelector automatically.
The source-images-service validates that request.arch matches platform.machine() at the start of each job, preventing architecture mismatches from producing incorrectly-tagged images. The /api/v1/health endpoint reports the service's native architecture.
All rickshaw scripts accept --log-level with a standard vocabulary: normal, verbose, debug, verbose-debug. When crucible run --log-level <level> is invoked with a non-default level, rickshaw-run.py overrides the endpoints.log-level and roadblock.log-level values from rickshaw-settings.json and updates the settings dict before saving it, so engine scripts also pick up the override via the saved settings file. The verbose-debug level enables roadblock's ultra-verbose mode end-to-end (controller, endpoints, and engine-side roadblock invocations).
GitHub Actions workflows in .github/workflows/:
crucible-ci.yaml/faux-crucible-ci.yaml— Integration testsunittest.yaml/faux-unittest.yaml— Unit testsrun-crucible-tracking.yaml— Tracking runs
CI jobs are generated by util/generate-ci-jobs.py.