Development Guide
Development Guide
This guide covers development workflows and tooling for the Kure project.
Quick Start
# Get help with all available commands
make help
# Run all standard development tasks
make all
# Quick development cycle
make checkContributing Workflow
The main branch is protected — all changes must go through pull requests.
Branch Workflow
Create a feature branch from
main:git checkout -b feat/my-feature mainUse branch prefixes:
feat/,fix/,docs/,chore/Develop and test locally:
make check # Quick validation make precommit # Full pre-commit checksPush and create a pull request:
git push -u origin feat/my-feature gh pr createFill out the PR template (
.github/PULL_REQUEST_TEMPLATE.md).Pass required CI checks:
lint,test,build,pr-review / AI Code ReviewResolve all conversations (no approving review is required)
Merge (linear history required — rebase, no merge commits)
Branch Protection Rules
Enforced via the main-protection repository ruleset
:
- Required status checks:
lint,test,build,pr-review / AI Code Review - Merge queue: merging goes through a GitHub merge queue (rebase method) that rebases and tests the merged result before landing — no manual rebasing, no auto-rebase force-pushes
- Pull requests required: all changes must go through a PR
- Conversation resolution: all review threads must be resolved
- Linear history: enforced (rebase only, no merge commits)
- Force pushes: disabled
- Branch deletion: disabled
- Bypass actors:
kure-release-bot(GitHub App) — allowed to push release commits directly
Release branches (release/vX.Y) have their own release-protection ruleset with the same rules,
required checks and bot bypass, but no merge queue: a pull request must be up to date with the
branch before it merges. See Releasing
.
Development Workflow
1. Initial Setup
Prerequisites:
goandmise— requiredmake,git— requiredbash4+ — requiredyq— required byscripts/sync-versions.sh; pinned inmise.tomlmktemp— required byscripts/sync-versions.shandscripts/vendor-guard.sh, and also by thescripts/test/harness itself (lib.sh’snew_fixture/_run). An external coreutils utility, not a bash builtin, but present on virtually every host with coreutils installed; the two scripts name the failure clearly on the rare host without it.timeout(or macOS Homebrew’sgtimeout) from GNU coreutils — split by scope, not one blanket status:- optional for
scripts/sync-versions.sh check(production runtime): the two bounded probes it uses degrade gracefully to running unbounded, with a startup warning, when neither binary is found. - required to run the full local gate (
mise run verify/scripts/test/run-tests.sh): harness cases09-mvs-floor-hang-timeout.sh(pre-existing) and36-timeout-present-no-warning.sh(new) both need a realtimeout/gtimeoutto pass — seedocs/dependency-updates.md’s harness section for the caveat.
- optional for
# Install dependencies
make deps
# Install development tools
make tools2. Development Cycle
# Format code
make fmt
# Run quick checks (lint, vet, short tests)
make check
# Run all tests
make test
# Run tests with coverage
make test-coverage3. Testing
# Run all tests
make test
# Run tests with verbose output
make test-verbose
# Run tests with race detection
make test-race
# Run only short tests (good for quick feedback)
make test-short
# Run tests with coverage report
make test-coverage
# Run benchmark tests
make test-benchmark
# Run integration tests (when available)
make test-integrationGolden fixtures and schema validation
The builder families under pkg/kubernetes keep their expected output as golden fixtures in
testdata/. internal/kuretest.Golden validates an object against the pinned
CustomResourceDefinition schema (and the API server’s ObjectMeta rules) before comparing it
with its fixture, and rewrites the fixture under -update only when it validates:
GOWORK=off go test ./pkg/kubernetes/cnpg/ -run TestGolden -updateA fixture the pinned schema rejects is a defect in the test or the builder, not a fixture to
update. The validator reads each pinned module’s definitions from the module cache through
go list, so it needs the module graph as go.mod states it: under a parent go.work that
replaces a pinned module, the version check fails and its message names GOWORK=off (the
Makefile exports it already), and a replace directive that changes a pinned module’s path or
version is refused the same way. The same validation runs on the Flux bootstrap output of
pkg/stack/fluxcd and on everything examples/getting-started writes, so a generator that
drifts from the pinned schema fails make test, not a cluster. Which kinds are held to a
schema and which get ObjectMeta validation only is listed in pkg/kubernetes/README.md
§ Schema validation test.
A second pass holds the objects to each other rather than to a schema: AssertConsistent,
AssertConsistentYAML and AssertConsistentDir fail when a Flux sourceRef, chartRef or
dependsOn names nothing in the tree, or an object or a Kustomization’s targetNamespace
names a namespace the tree does not create — unless the test declares it with External or
Namespace, or it is one the cluster makes itself. On a written directory, AssertConsistentDir
reads every .yaml and .yml file but one a kustomization.yaml names as data only, and every
file one lists as a resource whatever its name, as kustomize applies it, and a .yaml or .yml
one named as data under a Flux Kustomization’s spec.path that has no kustomization.yaml, but
not in a subdirectory with its own, since Flux’s generator lists it there and takes such a
subdirectory whole; it also fails when a Kustomization’s spec.path or an entry of a
kustomization.yaml names nothing in the tree, or an entry of a kustomization.yaml or of a
Kustomization’s spec.components leaves it, and builds what each Flux Kustomization applies the
way kustomize-controller builds it, on a copy of the tree — nothing, once an entry leaves the tree.
4. Code Quality
# Run all linting
make lint
# Format code
make fmt
# Run go vet
make vet
# Tidy modules
make tidy
# Run Qodana static analysis (requires Docker)
make qodanaPre-commit Workflow
Before committing changes, run:
make precommitThis will:
- Format code with
go fmt - Tidy modules
- Run linters
- Run
go vet - Run all tests
CI/CD Pipeline
The project uses several GitHub Actions workflows:
Main CI Pipeline (.github/workflows/ci.yml)
- Triggers: Push to main/develop, PRs
- Jobs:
- Test (unit, race, coverage)
- Lint and format check
- Integration tests (main branch only)
- Security scanning
- Dependency vulnerability checks
Qodana Code Quality (.github/workflows/code_quality.yml)
- Triggers: Push, PRs
- Purpose: Static analysis with JetBrains Qodana
- Uses:
make depsfor setup
Release (.github/workflows/release.yml) and Release / Publish (.github/workflows/release-publish.yml)
Run Release from the Actions tab: pick the branch (main, or release/vX.Y for a patch), what
to do (release by default), and tick Dry run for a preview. It makes the release commits and
the tag, and Release / Publish then runs by itself on the tag. Nothing is tagged by hand. The
Releasing
page (docs/releasing.md, shared by every go-kure
repository) explains every option, release branches, and what to do when a release fails. kure’s
Publish creates the GitHub release object only: kure is a library, so a complete release carries no
assets and the tag is the artifact.
PR Checks (.github/workflows/pr-checks.yml)
- Triggers: PR events
- Jobs:
- Quick validation with
make check - Security and dependency checks
- Test coverage validation
- Changed files analysis
- Performance benchmarks (when labeled)
- Documentation validation
- Quick validation with
Renovate Management
Dependency updates come from Renovate (renovate.json, extending the shared
go-kure/.github preset). The Dependency Dashboard issue is the control
surface:
- Gated updates (every major, all Go-toolchain updates, Flux minors) sit under Pending Approval — tick the checkbox to let Renovate open the PR. Nothing gated is ever proposed on its own.
- Deferring an update: leave its dashboard checkbox unticked; there is nothing to close. To reopen a closed/ignored update, tick its checkbox on the dashboard.
- Rebasing a PR: tick the “rebase/retry” checkbox in the PR body, or the per-PR entry on the dashboard. Renovate also rebases automatically when the PR falls behind the base branch.
- Closing a PR: closing it normally tells Renovate not to recreate that version; the dashboard lists it under Closed/Ignored.
Renovate regenerates docs/compatibility.md on its own branch after gomod or
mise bumps (postUpgradeTasks running ./scripts/sync-versions.sh generate),
so its PRs pass the validate drift check without manual help. The same
postUpgradeTasks rule also runs sh scripts/sync-tool-versions.sh, which
keeps the golangci-lint pin in Makefile, .github/workflows/ci.yml and
docs/github-workflows.md in step with mise.toml, and sh scripts/sync-go-version.sh, which keeps go.mod’s own go directive in step
with mise.toml’s go pin — a bot PR touching those files, or a
check-tool-versions/check-go-version failure on one, is this same
automation. The rule’s last command, ./scripts/gen-builders.sh generate,
regenerates the per-kind constructor wrappers under pkg/kubernetes
(zz_generated_*.go) from the registered scheme, so an API-module bump that
adds or drops a kind arrives with its wrapper; validate runs
./scripts/gen-builders.sh check and fails on drift. Locally:
make gen-builders / make check-builders (or mise run builders:generate /
builders:check).
For the full dependency update process (review, bundling, version tracking), see Dependency Updates .
Makefile Targets Reference
Development
help- Display help messageall- Run all standard development tasksinfo- Display project informationclean- Clean build artifacts and caches
Dependencies
deps- Download and tidy Go modulesdeps-upgrade- Upgrade all dependenciestools- Install development toolsoutdated- Check for outdated dependencies
Testing
test- Run all teststest-verbose- Run tests with verbose outputtest-race- Run tests with race detectiontest-short- Run short tests onlytest-coverage- Run tests with coverage reporttest-benchmark- Run benchmark teststest-integration- Run integration tests
Code Quality
lint- Run all linterslint-go- Run golangci-lintfmt- Format Go codevet- Run go vettidy- Tidy modulesqodana- Run Qodana static analysis
CI/CD
ci- Run CI pipeline tasksci-coverage- Run CI with coverageci-integration- Run CI with integration testscheck- Quick code quality checkprecommit- Run all pre-commit checks
Release
No target releases or previews a release: run the Release workflow, with Dry run ticked for
a preview (Releasing
). changelog-preview prints the unreleased
changelog entries.
Utilities
generate- Run go generatemod-graph- Display module dependency graphlist-packages- List all packages
Environment Variables
Set from the environment (?=, so VAR=value make <target> and an exported VAR both work):
GOROOT- Go root directory (default:go env GOROOT)VERSION- Version string for builds (default:git describe, elsedev)TEST_TIMEOUT- Test timeout, applied per test binary (default:15m, the same budget CI grants)
Assigned with :=, so the environment does not reach them — override with a make command-line
assignment instead, as in make test TEST_PACKAGES=./pkg/errors/:
GO- Go command (default:go)TEST_PACKAGES- Package pattern the test targets run (default:./...)BUILD_DIR,OUTPUT_DIR,COVERAGE_DIR- directoriescleanremoves (bin,out,coverage);COVERAGE_DIRis also wheretest-coveragewrites its profile and HTML report
COVERAGE_THRESHOLD (Makefile:30) is defined and never referenced — setting it changes
nothing. It also disagrees with the gate that does run: it reads 80, while the coverage-check CI
job hardcodes 90 for both total and per-package coverage (.github/workflows/ci.yml:768-769).
Treat 90 as the number that matters.
Development Tips
Testing Strategy
- Use
make test-shortfor quick feedback during development - Use
make test-coverageto check coverage before PRs - Use
make test-raceto catch concurrency issues - Use
make checkfor quick pre-commit validation - Refresh a golden fixture with
-update(Golden fixtures and schema validation, above); the helper refuses to write one the pinned schema rejects
Code Quality
- CI gates test coverage — see the
COVERAGE_THRESHOLDnote above for the number that applies - All code must pass golangci-lint checks
- Code must be properly formatted with
go fmt - Modules must be tidy
Active Linters
The .golangci.yml enables these linters, aligned with the shared Go standard:
| Linter | Category | Purpose |
|---|---|---|
errcheck | Default | Unchecked errors |
govet | Default | Suspicious constructs |
ineffassign | Default | Ineffectual assignments |
staticcheck | Default | Comprehensive static analysis (includes gosimple S* checks) |
unused | Default | Unused code |
bodyclose | Required | HTTP response body closed |
durationcheck | Required | time.Duration mistakes |
errorlint | Required | Error wrapping issues |
exhaustive | Required | Exhaustive enum switches |
misspell | Required | Common misspellings |
nilerr | Required | Nil error returns |
unconvert | Required | Unnecessary conversions |
whitespace | Required | Unnecessary whitespace |
gosec | Optional | Security checks (kure-specific) |
Formatters: gofmt, goimports (with github.com/go-kure/kure as local prefix).
Performance
- Benchmark tests can be run with
make test-benchmark - PR checks include performance benchmarks when labeled with
performance - Build targets include optimized release builds with
-s -wflags
Troubleshooting
Build Issues
# Clean everything and rebuild
make clean all
# Check Go installation and environment
make infoTest Failures
# Run tests with verbose output for more details
make test-verbose
# Run specific test
go test -v ./pkg/specific/package -run TestSpecificDependency Issues
# Update dependencies
make deps-upgrade
# Check for outdated or vulnerable dependencies
make outdatedThis development guide provides a comprehensive overview of the development workflow using the Makefile and CI/CD pipeline.
Documentation Updates
When modifying a package’s public API, update documentation in the same PR:
- Package README — Update the
README.mdin the package directory (e.g.,pkg/stack/README.md) - Guides — Check the reverse mapping in
AGENTS.mdfor guides that reference the changed package
To verify the docs site builds correctly:
# Check all mounted files exist
bash site/scripts/check-mounts.sh
# Build site
mise run site:buildConsumer Compatibility
Kure’s public packages are consumed by external projects. Keep public APIs stable when possible, describe integration requirements as reusable library capabilities, and follow the organization API stability contract for deprecations or breaking changes.
For local co-development, a consumer can use a Go workspace without adding a committed replace directive:
go work init
go work use ./kure ./consumerBefore publishing a Kure change needed by a consumer, verify Kure with GOWORK=off, then update
and verify the consumer against the released Kure version in its own repository.