-
Notifications
You must be signed in to change notification settings - Fork 0
Contributing
keyseal/
cmd/keyseal/main.go - entry point; calls cli.Execute()
internal/
buildinfo/ - version variables injected by ldflags
cli/ - Cobra command wiring; one file per command
config/ - keyseal.yaml load, validate, defaults
doctor/ - health check engine
execenv/ - subprocess runner + env merge
fsutil/ - atomic writes, mode checks, path validation
gitutil/ - Git subprocess helpers for repo-aware workflows
render/ - dotenv/JSON/YAML formatters, multi-doc merge
repo/ - logical name ↔ file path mapping, discovery
schema/ - secret document struct, parser, validator
sopsconfig/ - .sops.yaml readiness inspection shared by doctor and updatekeys
sopsutil/ - SOPS library decrypt + subprocess wrappers (encrypt/edit/updatekeys/version)
templates/ - built-in starter documents
examples/
keyseal.yaml - reference config
.sops.yaml - reference SOPS config
production-platform-app.plaintext.yaml
.github/workflows/
ci.yml - CI: fmt-check + test + build
release.yml - release: check + dist + GitHub release
Makefile
go.mod / go.sum
The internal/ packages are not exported. All functionality is consumed through the cli/ package, which wires Cobra commands to the internal packages.
Each command is a file in internal/cli/:
| File | Command |
|---|---|
root.go |
Root command wiring, Execute(), --version flag |
init.go |
keyseal init |
add.go |
keyseal add |
edit.go |
keyseal edit |
status.go |
keyseal status |
diff.go |
keyseal diff |
history.go |
keyseal history |
commit.go |
keyseal commit |
updatekeys.go |
keyseal updatekeys |
rollback.go |
keyseal rollback |
render.go |
keyseal render |
exec.go |
keyseal exec |
doctor.go |
keyseal doctor |
verify.go |
keyseal verify |
version.go |
keyseal version |
Adding a new command means: add a file in internal/cli/, register it in root.go's Execute() function, and put the business logic in an appropriate internal/ package.
make testThis runs go test ./.... Tests are in _test.go files alongside their packages. There are no separate integration test directories - the approach in existing tests is to use real temp directories with stub SOPS binaries for subprocess-touching tests.
Git workflow tests follow the same pattern. internal/gitutil/gitutil_test.go and internal/cli/git_workflow_test.go create temporary Git repositories, configure a test author identity, and exercise real Git commands instead of mocking them.
The stub SOPS binary pattern is still used for mutating CLI tests. Read-only decrypt tests use real SOPS-encrypted fixtures and the official SOPS Go decrypt library so they prove render/exec/doctor do not shell out.
make fmt # format in place
make fmt-check # check without modifying (used in CI)CI enforces formatting. Any PR with unformatted Go files will fail the fmt-check step.
make checkThis runs fmt-check, test, and build in sequence. It is the same sequence CI runs before a release. If make check passes locally, the CI gate should pass too.
- Create
internal/cli/<command>.gowith anewXxxCommand() *cobra.Commandfunction - Register it in
root.go: addroot.AddCommand(newXxxCommand()) - Put business logic in an appropriate
internal/package, not directly in the CLI file - Add tests in
internal/cli/<command>_test.go
Keep CLI files thin - they should parse arguments, validate flags, call internal packages, and format output. Avoid putting logic that needs to be tested in isolation directly in the command function.
For Git-aware behavior, keep raw Git subprocess calls inside internal/gitutil/. CLI code should resolve logical names, decide which paths are relevant, and format the result, but not shell out to git directly.
Templates live in internal/templates/templates.go. The templateMap variable maps template names to map[string]string key/value pairs.
Add an entry to templateMap and a case in the switch inside Build(). Add the new name to any documentation that lists available templates.
Write a test in internal/templates/templates_test.go confirming the new template produces the expected keys.
Config is in internal/config/config.go. The struct, defaults, and validation are all in the same file.
If you add a new field:
- Add it to the struct with a
yaml:tag - Set a default in
Default() - Apply the default in
applyDefaults()(only if the field is zero-valued after unmarshal) - Validate it in
Validate()if there are constraints - Add a test in
internal/config/config_test.go
Schema is in internal/schema/schema.go. The document struct, validation, parser, and marshaler are there.
The schema version is currently 1. If you introduce breaking changes to the schema, increment SupportedVersion and update validation to reject documents with the old version number.
Doctor logic is split between internal/doctor/doctor.go (check functions) and internal/doctor/model.go (types and rendering). Shared SOPS config inspection is in internal/sopsconfig/, and external binary probing is in internal/toolcheck/.
Each check appends CheckResult values to the result via helper functions like appendOK, appendWarn, appendFail. The name field on a result is what appears in text and JSON output.
Tests for doctor checks are in internal/cli/doctor_test.go and internal/doctor/doctor_test.go. They use temp directories with known content and verify the check names, statuses, and summary text.
Keyseal depends on Cobra, YAML parsing, and the official SOPS Go module. Read-only decrypt uses the SOPS Go decrypt library; mutating encryption/edit/updatekeys workflows still use the external SOPS binary.
Run make tidy after changing go.mod.
Keyseal is licensed under GPL-3.0-only. Contributions are accepted under the same license.
Getting Started
Reference
Operations
Development