Development
Prerequisites#
- Go 1.25+
- Docker (for building the
glci:localimage and running integration/e2e tests) - Make
Build#
git clone https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci
cd glci
go build -o glci ./cmd/glci
make docker # required: builds glci:local image
The glci:local Docker image contains the mock server binary. It must match the CLI binary’s build commit — the daemon refuses to start if there is a version mismatch. Always re-run make docker after pulling new changes or switching branches.
The build embeds the git commit hash via linker flags so the daemon can detect version mismatches:
make build # builds ./glci with embedded commit hash
make install # installs to $GOPATH/bin with embedded commit hash
Project structure#
glci/
├── cmd/glci/ # CLI entry point and subcommands
├── pkg/
│ ├── config/ # YAML parsing (offline + API-assisted), inputs, matrix expansion
│ ├── daemon/ # Background daemon: server, client, executor, eventbus,
│ │ # transport, crash recovery, execution state, registry
│ ├── gitutil/ # Git helper utilities (diff, clean env)
│ ├── glciconfig/ # Config file parsing: ~/.glci/config.toml + .glciconfig.toml
│ ├── history/ # Pipeline persistence (~/.glci/projects/{key}/pipelines/)
│ ├── logparse/ # Trace output parsing: strip timestamps, colorize sections
│ ├── mockserver/ # Mock GitLab API, git HTTP, artifacts, cache, auth
│ ├── muxproto/ # Binary protocol for docker exec transport
│ ├── pipeline/ # Pipeline graph rendering (ANSI, JSON, watch mode)
│ ├── planner/ # Execution plan: stages, DAG, filtering, auto-deps
│ ├── rules/ # rules: if: expression evaluation (boolean, regex, variables)
│ ├── runner/ # gitlab-runner integration: job spec JSON, config.toml generation
│ ├── scheduler/ # Concurrent job dispatch, DAG-aware, resource groups
│ ├── tui/ # Interactive TUI (bubbletea): app, graph, log viewer
│ ├── variables/ # CI variable generation, API fetch, slugify, context simulation
│ └── version/ # Build-time version metadata
├── e2e/ # End-to-end and compatibility tests
│ └── testdata/ # Test fixtures (comprehensive CI config, golden fixtures)
├── docs/plan/ # Planning and sprint documents
└── Makefile
Test#
Unit tests require no Docker and no network access:
make test # run all unit tests
make test-v # verbose output
go test ./pkg/... # run unit tests for a specific package subtree
go test ./pkg/config/ # run tests for a single package
Lint:
go vet ./...
Test tiers#
Tests are organized into tiers of increasing scope. Each tier is a separate Make target with no overlap between targets.
Tier 0: Unit tests (always run)#
make test # no Docker, no network
Tier 1: Parse and golden tests#
make test-e2e-parse # parse comprehensive pipeline fixture (~1m18s)
make test-e2e-golden # compare pipeline JSON against golden fixtures (~1m20s)
These validate CI config parsing and pipeline structure without running any containers.
Tier 2: TUI tests#
make test-tui # TUI and CLI rendering tests (~2m14s)
Tier 3: Integration tests#
These run real pipelines with Docker containers:
make test-integration # stateless pipeline execution (~7m, Docker required)
make test-integration-light # artifact CLI, CLI commands, daemon logs (~1m27s, no Docker)
make test-integration-cli # CLI functional tests: lint, jobs, doctor, history (~1m)
make test-integration-cli-daemon # CLI daemon-dependent tests: run, ps, log, registry (~3m, Docker)
make test-integration-docker # Docker infra, DinD, containerized pipelines (~7m, Docker)
make test-integration-registry # Registry push/pull/mirror tests (~7m, Docker)
make test-integration-lifecycle # Crash recovery, force stop, daemon lifecycle (~7m, Docker)
make test-e2e-simulate # Simulate comprehensive pipeline (~5m, Docker)
Tier 4: Comprehensive compatibility#
make test-e2e-comprehensive # full compat run (~4m, Docker, requires glci:local)
make test-e2e-comprehensive-release # release pipeline compat (~3m, Docker)
These targets require the glci:local Docker image. Build with make docker first.
Tier 5: External (network required)#
make test-e2e-external # real external repos (~50m, Docker + network)
Only run when explicitly needed. These clone external repositories and run full pipelines.
What to run for your changes#
| Changed area | Run |
|---|---|
Anything in pkg/ | make test |
| CLI commands (cobra) | make test + make test-integration-cli + make test-integration-cli-daemon |
| TUI | make test + make test-tui |
| Config parsing, rules, planner | make test + make test-e2e-parse |
| Pipeline JSON, when/allow_failure | make test + make test-e2e-golden |
| Mock server, scheduler, DAG | make test + make test-e2e-simulate |
| Pipeline execution, daemon | make test + make test-integration |
| Docker executor, DinD | make test + make test-integration-docker |
| Registry, push-through | make test + make test-integration-registry |
| Daemon lifecycle, recovery | make test + make test-integration-lifecycle |
| Broad or uncertain | make test + make test-e2e-comprehensive |
Override the timeout for long-running tests:
make test-e2e-comprehensive COMPAT_TIMEOUT=30m
Tail daemon logs during tests#
E2e tests run isolated daemon instances with their own GLCI_HOME. To tail the active test daemon’s log:
make test-logs
This finds the most recent daemon.log under .glci-compat-*/ or .glci-e2e-*/ and tails it.
Golden fixture updates#
If you change pipeline JSON output or parsing behavior, golden fixtures may need updating:
make golden-update # regenerate golden fixtures from the GitLab API (network required)
Then verify the diff makes sense before committing.
Adding comprehensive test fixtures#
When adding a new CI keyword, runner config field, or mock server capability:
- Add a job exercising it to
e2e/testdata/comprehensive/ci/jobs.yml - Update the parse test (
compat_pipeline_test.go) - Update the full-run test (
compat_test.go) - Update all three golden fixtures in
e2e/testdata/golden/comprehensive/*.json - Rebuild
glci:localwithmake docker - Run
make test-e2e-comprehensive
Key dependencies#
glci prefers external libraries over reimplementing. Key dependencies (see go.mod):
| Library | Purpose |
|---|---|
cobra | CLI framework |
yaml.v3 | YAML parsing |
BurntSushi/toml | TOML config files |
bubbletea / bubbles / lipgloss | Interactive TUI |
go-containerregistry | OCI registry implementation |
testify | Test assertions |
fsnotify | File watching |
schollz/progressbar | Progress bars |
mattn/go-isatty | TTY detection |
stdlib net/http | HTTP routing (no framework) |
Development plan#
Development is organized in incremental sprints documented in docs/plan/. Each sprint has a dedicated design document covering the problem, approach, and implementation details.
Architecture and design decisions are documented in ARCHITECTURE.md.
Git hooks#
Install a pre-push hook that runs a quick lint pipeline before each push:
make install-hooks
This installs a pre-push hook that runs glci run -p precheck.
Release process#
Releases are automated via Make:
make release # bump minor version, generate changelog, tag, push
make release BUMP=patch # bump patch version instead
make release BUMP=major # bump major version
This generates a changelog from GitLab merge request labels, commits it, creates a git tag, and pushes.
Demo recordings#
The home page features terminal demo videos recorded with VHS. Tape files (recording scripts) live in demos/tapes/, numbered in display order:
demos/
├── tapes/
│ ├── 01-lint.tape # CI Lint
│ ├── 02-run.tape # Run Pipeline
│ ├── 03-context.tape # Context Simulation
│ ├── 04-artifacts.tape # Artifact Download
│ ├── 05-registry.tape # Local Docker Registry
│ ├── 06-pages.tape # Pages Preview
│ └── 07-tui.tape # Interactive TUI
├── output/ # Generated mp4 files
└── project/ # Demo project with .gitlab-ci.yml
Prerequisites#
- glci (the demos record real pipeline runs)
- VHS (
go install github.com/charmbracelet/vhs@latest) - Docker (the demo pipelines run real CI jobs)
Make targets#
make demos # build only changed demos + copy to site/static/demos/
make demos-rebuild # clean all + rebuild everything
make demos-clean # delete all generated mp4s
Adding a new demo#
- Create
demos/tapes/NN-name.tapefollowing the existing tape format (sameSet Theme,Set FontSize 36,Set Width 2400,Set Height 1200) - Set the output path:
Output ../output/NN-name.mp4 - If the demo needs a custom CI config, add it to
demos/project/(e.g..gitlab-ci.registry.yml) - Add a tab button and video panel in
site/layouts/home.html - Run
make demosto record and copy tosite/static/demos/
Tape conventions#
All tapes share identical settings for visual consistency:
- Resolution: 2400×1200 at font size 36 (Retina-crisp)
- Theme: Custom JSON theme matching the docs site dark palette
- Typing speed: 40ms per character
- Cursor blink: disabled
Playback speed varies per demo (Set PlaybackSpeed). The site JS also applies 1.5× playback on videos, so the effective speed is PlaybackSpeed × 1.5.
Rebuilding a single demo#
cd demos/project
vhs ../../demos/tapes/01-lint.tape
cp ../output/01-lint.mp4 ../../site/static/demos/
License#
MIT