Development Guide
Development Guide
This guide covers development workflows and tooling for the Launcher project.
Quick Start
Prerequisites
- mise — tool version manager
- Go 1.26.9 (managed by mise)
- golangci-lint 2.14.0 (managed by mise)
Contributing Workflow
The main branch is protected — all changes must go through pull requests.
Branch Workflow
Create a feature branch from
main:Use branch prefixes:
feat/,fix/,docs/,chore/Changing the shape or meaning of a
launcher.gokure.dev/v1alpha1document (app.yaml/kurel.yaml/cluster.yaml/environments.yaml, or aCapabilityDefinition)? Use theformatcommit scope (feat(format):,fix(format):,docs(format):) — it groups under the changelog’s Document Format heading. Seedocs/oam/design-gvk.md§ Document-Format Lifecycle: until the first stable GA release, aformatcommit may change or newly reject existing documents if its subject (the changelog entry) names which ones and the PR description discloses the output change.Develop and test locally:
Push and create a pull request:
PR checks build
refs/pull/<n>/merge(the branch merged intomain), not the branch. If a check is red whilemake precommitis green on the branch, rebuild what CI built:mise run verify-merge <n>(exit1= merge ref fails,2= not computable, never a pass;make verify-merge PR=<n>gives pass/fail only, as make exits2on any failure). Seedocs/github-workflows.md§ Which tree a PR run builds.Pass required CI checks:
lint,test,build,pr-review / AI Code ReviewMerge via the merge queue (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 the Releasing page (docs/releasing.md).
Development Workflow
1. Initial Setup
2. Development Cycle
3. Building
Validating example manifests against flux-schema
This runs in CI as the non-required validate-manifests job (see
docs/github-workflows.md) — not yet in the required-checks list below while it
completes its first non-blocking cycle (go-kure/launcher#292).
Checking documentation YAML fences
A YAML fence in the documentation is checked only when it is marked, with a check
attribute after the language in the fence’s info string. Hugo renders the attribute as
an HTML attribute and GitHub ignores it, so the page looks the same either way:
| Mode | Use it for | The check |
|---|---|---|
build | a complete Application a reader can copy | kurel build must succeed against profile, a ClusterProfile path relative to the repository root (required) |
snippet | a fragment: an envelope header, one stanza | well-formed YAML; not expected to be a whole document |
template | a package app.yaml with unresolved ${...} placeholders | well-formed YAML; the placeholders need the package’s parameters and values to resolve |
Unmarked fences are not checked. Mark a fence build whenever it is a whole
application: that is the only mode that runs the envelope and the handlers, and it is
what catches a defect like go-kure/launcher#417 (traits: [] at spec level). Spaces
around = are allowed, as Hugo allows them. A marker that cannot be honoured fails
rather than being skipped: a check attribute whose value cannot be read ({check},
{check: "build"}), an unknown mode, a marker on a non-YAML fence, a build fence with
no profile or a missing one, an unclosed fence.
Failures are reported as <file>:<line of the opening fence>: <reason>.
This runs in CI in the docs-build job (see docs/github-workflows.md), which runs on
documentation-only changes.
4. Testing
5. Code Quality
Pre-commit Workflow
Before committing changes, run:
This will:
- Format code with
go fmtandgoimports(both leave testdata and hidden or underscore directories alone) - Tidy modules
- Run linters
- Run all tests
- Check kure dependency sync
- Check tool-version pins (golangci-lint, govulncheck) stay consistent across Makefile, CI and docs
- Self-test
scripts/verify-merge.shagainst a throwaway fixture repository - Run
scripts/check-pin-impact.sh’s hermetic cases (scripts/test/cases/; stubcurl, no network)
It builds the branch. A PR’s CI builds the branch merged into main; to reproduce that tree
locally, run mise run verify-merge <n> after pushing.
CI/CD Pipeline
The project uses GitHub Actions workflows:
Main CI Pipeline (.github/workflows/ci.yml)
- Triggers: Push to main/develop/
release/*, PRs to any branch, merge_group (merge queue) - Jobs: validate (lint), test, security, coverage-check, build, cross-platform, analyze-changes
- Runner:
autops-kube-kure(self-hosted)
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. launcher’s Publish produces the kurel
binaries for linux × amd64/arm64, checksums, an SBOM per archive and a cosign signature of the
checksums.
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) 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.
Direct dependencies shared with the imported kure are never proposed here at
all — launcher must not lead the kure release it imports (see AGENTS.md
§ shared dependencies; CI enforces it via site/scripts/check-kure-dep-sync.sh).
The disable list lives in renovate.json and must be kept in step with that
guard’s shared-direct set.
k8s.io/kube-openapi is not proposed either, by a rule of its own: it is not
shared-direct, but it has no releases, so a lone digest bump can run ahead of
k8s.io/apimachinery and break the build. It moves by MVS when the modules that
require it move, in practice the k8s.io/* modules. A vulnerability alert still
proposes it: the shared preset’s vulnerabilityAlerts overrides the rule.
Makefile Targets Reference
Development
help- Display help messageinfo- 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
Building
build/build-kurel- Build kurel executablevalidate-manifests- Build example manifests and validate against flux-schemacheck-doc-fences- Check the documentation’s marked YAML fences (self-test, then the tree)
Testing
test- Run all teststest-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 lintersfmt- Format Go codevet- Run go vettidy- Tidy modulesvuln- Run govulncheck
CI/CD
check- Quick code quality checkprecommit- Run all pre-commit checksci- Run full CI pipelineverify-merge PR=<n>- Build and test the PR’s merge ref (refs/pull/<n>/merge), the tree its CI buildstest-verify-merge- Self-testscripts/verify-merge.shtest-pin-impact- Runscripts/check-pin-impact.sh’s hermetic cases (bash scripts/test/run-tests.sh)check-issue-refs- Reject bare#Nand ownerlessname#Nreferences (self-test, the tree, then commit messages sinceISSUE_REFS_BASE, defaultorigin/main;AGENTS.md§ “Issue references”)
Release
No target releases or previews a release: run the Release workflow, with Dry run ticked for
a preview (docs/releasing.md).
release-snapshot- Test GoReleaser locally (no tag, no publish)changelog-preview- Preview unreleased entries (prints only; no target writesCHANGELOG.md)
Active Linters
The .golangci.yml enables these linters (the full go-kure linter set):
| Linter | Category | Purpose |
|---|---|---|
errcheck | Default | Unchecked errors |
govet | Default | Suspicious constructs |
ineffassign | Default | Ineffectual assignments |
staticcheck | Default | Comprehensive static analysis |
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 |
Formatters: gofmt, goimports (with github.com/go-kure/launcher as local prefix).