Development#
Commands#
make build # bin/manent (client) and bin/manent-server with the web interface (NOUI=1 skips it)
make web # web interface in internal/webui/dist (Node >= 20 or a node:22 container)
make web-dev # web interface on http://127.0.0.1:5173 against a local manent
make web-test # types, component tests (Vitest) and alignment with api/openapi.yaml
make web-api # regenerates the TypeScript types from api/openapi.yaml
make plugins # external plugins in bin/
make test # go test ./... (SQLite)
make test-postgres # the same tests on Postgres (starts a container if needed)
make test-e2e # remote-copy plugin against a real SSH server in Docker
make test-load # load and chaos: several workers, one goes down halfway
make test-usecases # end-to-end use cases on Docker Compose (see Use cases)
make image # container image
make lint # gofmt + go vet
make fmt # gofmt -w
make proto # regenerates the gRPC code of external plugins
make docs # documentation site in docs/site
make docs-serve # documentation preview with live reload on http://<host>:8000/manent/ (DOCS_ADDR to change it)
make clean # removes binaries, working directory, SQLite database and generated site
A single test:
make proto requires protoc, protoc-gen-go and protoc-gen-go-grpc:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Tests#
- Engine: tests start a real DBOS on SQLite in a temporary directory. With
MANENT_TEST_DATABASE_URL(a Postgres server on which the user can create databases) each test instead uses a Postgres database created for it and dropped at the end (internal/testdb):make test-postgresdoes this with a container.scaling_test.gostarts several workers on the same database to check recovery byexecutor_id, serialization per environment and the absence of double deploys from schedules. They use a lowSchedulePollInterval: with the 30-second default a new schedule would start only after the next reload. - Recovery and upgrades:
recovery_test.gosimulates a worker going down (unrecorded shutdown) and checks automatic and manual recovery, the refusal of an alive worker, the migration of workflows to a new version and drain.load_test.go(make test-load,MANENT_TEST_LOAD=1) enqueues many deploys on three workers and takes one down while it runs: all deploys must complete, never two at once in the same environment. - Use cases:
test/usecasesruns API, workers, client and SSH targets on Docker Compose and goes through deploys, failures, rollbacks and wave rollouts. See Use cases. - Web interface: Vitest and Testing Library check the views against a fake API built from the OpenAPI types (
web/src/test);internal/webuithe security headers and the SPA fallback;internal/api/openapi_test.gothatapi/openapi.yamldescribes exactly the routes of the server. Playwright (web/e2e) runs in UC8 against the lab, with an OIDC identity provider for tests and axe for accessibility. - Real SSH:
plugins/remotecopy/e2e_test.goruns only withMANENT_TEST_SSH_ADDR;scripts/e2e-ssh.shstarts a Debian sshd in Docker (test/e2e/sshd) and checks the releases layout, cleanup, rollback and checksum. - External plugins: the
internal/extplugintests compiletestdata/fakepluginand start real processes. They check secrets, environment isolation, log forwarding, restart after a crash and sha256 verification. - Plugins:
remotecopyreplaces SSH with theDialerandSessioninterfaces. - Callbacks: tests use an
httptestserver and check the four authentication modes and the URL policy. - Access:
internal/authchecks OIDC tokens against a fake identity provider (discovery, JWKS and JWTs signed in memory);internal/authzpermissions and audit on a fake service;internal/apithe 401/403 responses. - Vault: tests use an
httptestserver that simulates KV v1/v2, AppRole login and token revocation. - Rollback:
internal/engine/rollback_test.gochecks the order of checkpoints and compensations, stopping at the first failed compensation and dry-run;internal/rollbackthe per-artifact-type policies.
CI and releases#
.github/workflows/ci.yml runs on every push and pull request: lint and go mod tidy, tests on SQLite, tests on Postgres with load and chaos, end-to-end SSH tests, site build, helm lint and image build.
A release starts from a vX.Y.Z tag (.github/workflows/release.yml):
- move the
Unreleasedsection ofCHANGELOG.mdto## [X.Y.Z] - YYYY-MM-DDand updateappVersionindeploy/helm/manent/Chart.yaml; git tag vX.Y.Z && git push --tags;- GoReleaser (
.goreleaser.yaml) builds the binaries, creates archives with SBOMs, signs the checksums and the image with cosign (keyless) and publishes the release with the CHANGELOG notes (scripts/release-notes.sh).
What counts as incompatible is described in Versions and upgrades.
Conventions#
- Every service is defined through interfaces, to ease mocks and tests.
- Every component receives the logger by injection. Logs reach both the application log and the SIEM as
rfc5424.Message. - Code comments are in English, concise but complete.
internal/appis the only composition point: new internal plugins are registered inapp.NewPlugins, new steps inapp.NewSteps.- A new step that must work in a dry-run implements
pipeline.DryRunner. Otherwise it is skipped. - A new step that changes environments implements
pipeline.Compensator(and, if it must record the previous state,pipeline.Checkpointer), so rollback can undo it. - An error that a new attempt cannot fix (policy, validation) is marked with
pipeline.Permanent: the step fails immediately without retries. - A new secret provider implements
secrets.Providerand is registered by scheme inapp.NewSecretProvider. - A new rollback locator implements
plugin.ReleaseLocatorand is registered inapp.NewLocators. - DBOS workflow names (
manent.deploy,manent.scheduled-deploy,manent.approval,manent.deploy-pass) must not change. An incompatible change to a workflow's structure (steps in a different order, different input) requires a new workflow version (app.version), to be noted in the CHANGELOG. - In step code, logs are emitted with the step's context (
InfoContext(ctx, ...)): only then do they reach the deploy log. - The documentation in
docs/contentis updated together with features, in English (page.md, the default language) and Italian (page.it.md). Seedocs/README.mdfor the site structure.
Repository layout#
cmd/manent/ client (API only)
cmd/manent-server/ server: API, worker, admin
cmd/manent-plugin-remote-copy/ remote-copy as an external plugin
deploy/helm/manent/ Helm chart
deploy/systemd/ systemd units
scripts/ end-to-end tests and release notes
test/e2e/ images for end-to-end tests
internal/ engine (not importable from outside)
pkg/plugin/ public plugin contract
pkg/plugin/external/ SDK and gRPC protocol of external plugins
plugins/remotecopy/ reference plugin
api/openapi.yaml contract of the HTTP API
web/ web interface (React, TypeScript, Vite)
internal/webui/ embeds and serves the build of web/
configs/ example configuration
examples/ example descriptors and environment configurations
docs/ this site