Linting and Code Quality¶
Three checkers run against every commit:
| Tool | Config | Invoked by |
|---|---|---|
golangci-lint |
.golangci.yml |
make lint |
arch-go |
arch-go.yml |
make lint (auto-installs if missing) |
govulncheck |
— | make audit |
make check-all runs all three plus the full test suite — the same set of checks CI runs on every MR. make lint-fix applies golangci-lint's auto-fixes where possible.
golangci-lint¶
The config is in v2 format (version: "2"), so formatters and linters are separated:
Formatters (gofmt, goimports) auto-format source on make lint-fix.
Linters (≈30 enabled, grouped below). The authoritative list is .golangci.yml; this page just classifies them:
| Purpose | Linters |
|---|---|
| Correctness | errcheck, govet, staticcheck (includes gosimple), ineffassign, unused, bodyclose, errchkjson, nilerr, nilnil |
| Security | gosec |
| Style | revive, gocritic, misspell, unconvert, unparam, nakedret, whitespace, godot, importas, goprintffuncname |
| Complexity | gocyclo, goconst, dupl |
| Performance | prealloc, copyloopvar |
| Hygiene | godox (BUG/FIXME/HACK), asciicheck, bidichk, dogsled, makezero, nolintlint |
| Tests | thelper |
Plus one project-local analyzer built in tools/linters/eventimmutability that enforces the event immutability contract — it flags assignments that mutate the fields of an event struct in pkg/controller/events after creation. The analyzer is compiled and invoked as part of make lint.
Project rules worth knowing¶
importasenforces canonical aliases for Kubernetes and haproxytech packages (corev1,metav1,apierrors,corev1client,haproxy). Deviations fail CI.revivecaps function length at 50 lines and cognitive complexity at 20.exportedandpackage-commentsrules are off for internal packages.gocyclorejects cyclomatic complexity > 20.gosecallowlists G114 (HTTP timeout — set at infra level), G204 (the onlyexecbinary name is the hardcoded"haproxy"resolved viaLookPath), and G404 (non-crypto RNG — not used for secrets). G304, G118, and G108 are allowlisted on specific file paths/messages, not globally.- Generated code (
zz_generated.*.go,codegen/**/*.gen.go,pkg/generated/**) and test files run with relaxed rules; see the per-path overrides at the bottom of.golangci.yml.
Never add a global ignore rule to silence findings, and do not suppress them with nolint directives on individual lines — fix the code, or add a scoped per-path exclusion in .golangci.yml if the rule is genuinely wrong for that file.
arch-go¶
The DAG rules in arch-go.yml prevent the coordination layer from leaking into pure libraries. The high-level shape:
pkg/controller/**may import anything underpkg/pluscodegen/.pkg/core/**may not importcontroller,dataplane,k8s,templating,httpstore,introspection,webhook.pkg/events/**must not import any otherpkg/**package.pkg/stores/**is isolated frompkg/k8s/**; the two declare structurally identicalStoreinterfaces andpkg/stores.TypesStoreAdapterbridges them.- Domain libraries (
pkg/k8s,pkg/dataplane,pkg/templating) may not cross-import each other.
The exact allow/deny lists evolve with new packages, so consult arch-go.yml rather than memorising the rules.
govulncheck¶
make audit runs govulncheck against the dependency graph. If it reports a vulnerability:
If the finding is in the standard library, bump the Go toolchain in .tool-versions and verify CI images pick up the new version.
Pre-commit hooks¶
.pre-commit-config.yaml wires make lint and make audit to run on every commit via the pre-commit framework:
pip install pre-commit # or: brew install pre-commit
pre-commit install # installs .git/hooks/pre-commit
Once installed, they run automatically on git commit. To run them against the whole tree outside of a commit:
Warning
Do not bypass the hooks with git commit --no-verify. CI runs the same checks, so a bypassed commit just turns into a failed pipeline. If a hook fails spuriously, fix the root cause — see CLAUDE.md for the project-wide policy.
Tooling¶
Linter versions are pinned via Go's tool directive in go.mod; make install-tools rebuilds the local cache. The Go version is pinned in .tool-versions (asdf) — see tests/README.md for the env -u GOROOT note if you invoke Go commands directly.
References¶
.golangci.yml— authoritative linter configarch-go.yml— authoritative dependency rules- golangci-lint v2 docs
- arch-go
- govulncheck