git clone https://github.com/TsekNet/converge.git && cd converge
go build -o bin/converge ./cmd/converge
go test -race ./...Requires Go 1.26+. No other dependencies.
| Directory | Visibility | Purpose |
|---|---|---|
dsl/ |
Public | SDK for blueprint authors (Run, opts, enums). Platform-specific methods in build-tagged files. |
extensions/ |
Public | OS interaction: one subdirectory per resource type (see directory for full list) |
blueprints/ |
Public | Built-in blueprints including CIS benchmarks |
internal/ |
Private | Engine, output, platform detection, logging |
cmd/converge/ |
Binary | Cobra CLI entry point with build-tagged blueprint registration |
See docs/extensions.md for the full guide including platform-specific build tags.
Short version: implement the Extension interface, drop a file in the right extensions/ subdirectory, add tests, open a PR.
- Go 1.26 -- use
slices,maps, range-over-int - Table-driven tests everywhere
- Build tags -- use
_linux,_darwin,_windows(not_unixor!windows). Go 1.17+ requires the//go:builddirective forgo vetand IDE support, even when the_linux/_darwin/_windowssuffix also restricts compilation. - No stubs -- platform-specific DSL methods live in build-tagged files; if a platform doesn't need an extension, the DSL doesn't expose it
- Native APIs -- prefer
golang.org/x/sys/windows,/proc/sys/,howett.net/plistover shelling out - Error wrapping with
fmt.Errorf("...: %w", err) - Logging via google/deck -- syslog on Linux, Event Log on Windows
- Builds via GoReleaser -- see .goreleaser.yml
The internal/testutil package provides shared test infrastructure. Extensions should use these instead of rolling their own mocks.
| Helper | Purpose | Example |
|---|---|---|
MapFS |
In-memory extensions.FS for testing Check/Apply without root or real filesystem |
mfs := testutil.NewMapFS(); mfs.Set("/etc/conf", data, 0644) |
AssertConverges(t, ext) |
Verifies the Check/Apply/Check contract: not-in-sync, Apply succeeds, in-sync | testutil.AssertConverges(t, myFile) |
AssertInSync(t, ext) |
Verifies Check reports in-sync | testutil.AssertInSync(t, myFile) |
AssertDrifted(t, ext) |
Verifies Check reports drift with changes | testutil.AssertDrifted(t, myFile) |
MockCmd |
Records exec.Command calls and returns scripted output |
mock := testutil.NewMockCmd(); mock.SetOutput("modprobe", "", nil) |
MockPackageManager |
In-memory pkg.PackageManager for package extension tests |
mgr := testutil.NewMockPackageManager("mock") |
Extensions that do file I/O should accept an FS extensions.FS field in their Opts struct. When nil, extensions.RealFS(nil) falls back to the real OS filesystem. In tests, inject a MapFS to verify behavior without touching disk.
-
go test ./... -racepasses -
go vet ./...passes - New code has table-driven tests
- No new dependencies without discussion
- Extension changes don't touch
internal/ - Platform-specific files use
_linux,_darwin, or_windowssuffixes
Tag and push -- the release workflow builds MSI, deb, and pkg installers:
git tag v0.0.3
git push origin v0.0.3