Development Guide

Development Guide

This guide covers development workflows and tooling for the Launcher project.

Quick Start

# Get help with all available commands
make help

# Quick development cycle
make check

Prerequisites

  • mise — tool version manager
  • Go 1.26.9 (managed by mise)
  • golangci-lint 2.14.0 (managed by mise)
# Install mise, then:
mise install

Contributing Workflow

The main branch is protected — all changes must go through pull requests.

Branch Workflow

  1. Create a feature branch from main:

    git checkout -b feat/my-feature main

    Use branch prefixes: feat/, fix/, docs/, chore/

    Changing the shape or meaning of a launcher.gokure.dev/v1alpha1 document (app.yaml/kurel.yaml/cluster.yaml/environments.yaml, or a CapabilityDefinition)? Use the format commit scope (feat(format):, fix(format):, docs(format):) — it groups under the changelog’s Document Format heading. See docs/oam/design-gvk.md § Document-Format Lifecycle: until the first stable GA release, a format commit may change or newly reject existing documents if its subject (the changelog entry) names which ones and the PR description discloses the output change.

  2. Develop and test locally:

    make check       # Quick validation
    make precommit   # Full pre-commit checks
  3. Push and create a pull request:

    git push -u origin feat/my-feature
    gh pr create

    PR checks build refs/pull/<n>/merge (the branch merged into main), not the branch. If a check is red while make precommit is green on the branch, rebuild what CI built: mise run verify-merge <n> (exit 1 = merge ref fails, 2 = not computable, never a pass; make verify-merge PR=<n> gives pass/fail only, as make exits 2 on any failure). See docs/github-workflows.md § Which tree a PR run builds.

  4. Pass required CI checks: lint, test, build, pr-review / AI Code Review

  5. Merge 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

# Install dependencies
make deps

# Install development tools
make tools

2. Development Cycle

# Format code
make fmt

# Run quick checks (lint, vet, short tests, kure dep sync, tool pins)
make check

# Run all tests
make test

# Run tests with coverage
make test-coverage

3. Building

# Build kurel
make build

Validating example manifests against flux-schema

# Build examples/*.yaml and validate the output against fluxcd/flux-schema's
# default (embedded) catalog plus its ecosystem catalog (fetched from
# schemas.fluxoperator.dev — network access required). Requires the `flux` CLI
# (mise-managed, see mise.toml) — the script installs/upgrades the `schema` plugin itself.
make validate-manifests

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

# Self-test the gate, then check every marked YAML fence in the tracked *.md files.
# Builds bin/kurel first; needs yq (mise-managed, see mise.toml).
make check-doc-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:

```yaml {check="build" profile="examples/cluster-profiles/minimal.yaml"}
```yaml {check="snippet"}
```yaml {check="template"}
ModeUse it forThe check
builda complete Application a reader can copykurel build must succeed against profile, a ClusterProfile path relative to the repository root (required)
snippeta fragment: an envelope header, one stanzawell-formed YAML; not expected to be a whole document
templatea package app.yaml with unresolved ${...} placeholderswell-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

# Run all tests
make test

# Run tests with verbose output
go test -v ./...

# 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-integration

5. Code Quality

# Run all linting
make lint

# Format code
make fmt

# Run go vet
make vet

# Tidy modules
make tidy

Pre-commit Workflow

Before committing changes, run:

make precommit

This will:

  • Format code with go fmt and goimports (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.sh against a throwaway fixture repository
  • Run scripts/check-pin-impact.sh’s hermetic cases (scripts/test/cases/; stub curl, 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 message
  • info - Display project information
  • clean - Clean build artifacts and caches

Dependencies

  • deps - Download and tidy Go modules
  • deps-upgrade - Upgrade all dependencies
  • tools - Install development tools
  • outdated - Check for outdated dependencies

Building

  • build / build-kurel - Build kurel executable
  • validate-manifests - Build example manifests and validate against flux-schema
  • check-doc-fences - Check the documentation’s marked YAML fences (self-test, then the tree)

Testing

  • test - Run all tests
  • test-race - Run tests with race detection
  • test-short - Run short tests only
  • test-coverage - Run tests with coverage report
  • test-benchmark - Run benchmark tests
  • test-integration - Run integration tests

Code Quality

  • lint - Run all linters
  • fmt - Format Go code
  • vet - Run go vet
  • tidy - Tidy modules
  • vuln - Run govulncheck

CI/CD

  • check - Quick code quality check
  • precommit - Run all pre-commit checks
  • ci - Run full CI pipeline
  • verify-merge PR=<n> - Build and test the PR’s merge ref (refs/pull/<n>/merge), the tree its CI builds
  • test-verify-merge - Self-test scripts/verify-merge.sh
  • test-pin-impact - Run scripts/check-pin-impact.sh’s hermetic cases (bash scripts/test/run-tests.sh)
  • check-issue-refs - Reject bare #N and ownerless name#N references (self-test, the tree, then commit messages since ISSUE_REFS_BASE, default origin/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 writes CHANGELOG.md)

Active Linters

The .golangci.yml enables these linters (the full go-kure linter set):

LinterCategoryPurpose
errcheckDefaultUnchecked errors
govetDefaultSuspicious constructs
ineffassignDefaultIneffectual assignments
staticcheckDefaultComprehensive static analysis
unusedDefaultUnused code
bodycloseRequiredHTTP response body closed
durationcheckRequiredtime.Duration mistakes
errorlintRequiredError wrapping issues
exhaustiveRequiredExhaustive enum switches
misspellRequiredCommon misspellings
nilerrRequiredNil error returns
unconvertRequiredUnnecessary conversions
whitespaceRequiredUnnecessary whitespace
gosecOptionalSecurity checks

Formatters: gofmt, goimports (with github.com/go-kure/launcher as local prefix).

Troubleshooting

Build Issues

# Clean everything and rebuild
make clean
make build

# Check Go installation and environment
make info

Test Failures

# Run tests with verbose output for more details
go test -v ./...

# Run specific test
go test -v ./pkg/specific/package -run TestSpecific

Dependency Issues

# Update dependencies
make deps-upgrade

# Check for outdated or vulnerable dependencies
make outdated
make vuln