Development Guide
Prerequisites
Section titled âPrerequisitesâRequired: Go 1.26.1+ (go version ⼠1.26.1), Docker (docker ps without errors), Git.
Optional: Node.js 24 (docs), golangci-lint, VSCode with Go extension.
Development Setup
Section titled âDevelopment Setupâ1. Clone the Repository
Section titled â1. Clone the Repositoryâgit clone https://github.com/devantler-tech/ksail.gitcd ksail2. Install Dependencies
Section titled â2. Install Dependenciesâgo mod downloadgo install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest # optionalcd docs/ && npm ci && cd .. # docs only3. Build KSail
Section titled â3. Build KSailâgo build -o ksail && ./ksail --version4. Run Tests
Section titled â4. Run Testsâgo test ./... # all testsgo test ./pkg/svc/provisioner/cluster/kind # specific packagego test -cover ./... # with coveragego test -bench=. -benchmem ./pkg/client/helm # benchmarks5. Run Linters
Section titled â5. Run Lintersâgolangci-lint run # all lintersgolangci-lint run --fix # auto-fix (import order, etc.)Project Structure
Section titled âProject StructureâSee CONTRIBUTING.md for the full package structure and descriptions of each package. internal/ packages can only be imported within the ksail module; pkg/ packages are public. Circular dependencies are not allowed.
Development Workflow
Section titled âDevelopment WorkflowâCreate a feature branch, make focused atomic changes, write tests, and update docs:
go test ./... && golangci-lint run && go build -o ksail./ksail cluster init # Smoke testgit add . && git commit -m "feat: add widget to cluster create" && git push origin feature/my-featureCommit types: feat, fix, docs, refactor, test, chore, perf. Open a PR with a clear description and ensure CI passes.
Coding Standards
Section titled âCoding StandardsâGo Style
Section titled âGo StyleâFollow Effective Go. Use gofmt for formatting, goimports for import order, and keep lines under 100 characters (enforced by golines).
Naming Conventions
Section titled âNaming Conventionsâ| Entity | Convention | Example |
|---|---|---|
| Packages | Lowercase, singular | provider, installer |
| Types, Interfaces | PascalCase; interfaces often end in -er | KindClusterProvisioner, Provider |
| Functions/Methods | PascalCase (exported), camelCase (unexported) | Create, validateConfig |
| Variables, Constants | camelCase (unexported), PascalCase (exported) | clusterName, DefaultTimeout |
Error Handling
Section titled âError HandlingâReturn errors explicitly (no panic in library code). Wrap errors with fmt.Errorf("context: %w", err) and use custom error types where appropriate. golangci-lint enforces that all errors are checked.
Security: Path Handling
Section titled âSecurity: Path HandlingâAll user-supplied file path arguments in CLI commands must be canonicalized with fsutil.EvalCanonicalPath before use. This resolves symlinks and produces an absolute path, preventing symlink-escape attacks in CI pipelines that process external manifests.
canonPath, err := fsutil.EvalCanonicalPath(inputPath)if err != nil { return fmt.Errorf("canonicalizing path %q: %w", inputPath, err)}// use canonPath for all subsequent file operationsWhen canonicalizing an output path that may not yet exist (e.g., --output), first create the parent directory of the output path with os.MkdirAll(filepath.Dir(outputPath), <mode>), then call EvalCanonicalPath on the output path. For safe path-constrained reads, use fsutil.ReadFileSafe instead of reimplementing containment checks.
Testing Patterns
Section titled âTesting PatternsâUnit Tests
Section titled âUnit TestsâUse table-driven tests with t.Parallel():
func TestMyFunction(t *testing.T) { t.Parallel() tests := []struct{ name string; wantErr bool }{ {"valid", false}, {"invalid", true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel(); /* test body */ }) }}DI Override Pattern for Command Tests
Section titled âDI Override Pattern for Command TestsâCluster command tests that depend on installer factories use an override pattern: each Set*ForTests helper applies a factory override against shared global state and returns a restore function. Register the restore function with t.Cleanup(restore) so overrides are always reset:
restore := cluster.Set<Component>InstallerFactoryForTests( func(_ *v1alpha1.Cluster) (installer.Installer, error) { return mockInstaller, nil },)t.Cleanup(restore)// test logic...Note: These helpers mutate shared global state, so tests that use them must not run in parallel (annotate with
//nolint:paralleltestand do not callt.Parallel()). Search forSet*ForTestsinpkg/cli/cmd/cluster/for all available helpers.
Mocking with Testify
Section titled âMocking with TestifyâKSail uses mockery v3.5+ for generating mocks via //go:generate mockery. Call mockProv.AssertExpectations(t) at the end of each test.
Benchmarks
Section titled âBenchmarksâUse for b.Loop() style (Go 1.26+). PRs are auto-benchmarked against main; see Benchmarks. Use ksail cluster create --benchmark for per-component timings.
Note: Never use
.Times(b.N)withfor b.Loop()âb.Nis 1 when evaluated before the loop. Omit.Times()for unlimited calls.
Documentation
Section titled âDocumentationâDocument all exported types, functions, and constants with complete sentences starting with the name being documented (e.g., // Provider defines the interface for infrastructure providers.). When adding features, update the relevant .mdx files in docs/src/content/docs/, README.md if needed, and CLI help text if adding commands.
Common Development Tasks
Section titled âCommon Development TasksâAll new components follow the same pattern: create a package under the relevant pkg/svc/ subdirectory, implement the corresponding interface, register in the factory, add tests, and update documentation.
| Task | Package path | Interface | Factory |
|---|---|---|---|
| New Provider | pkg/svc/provider/newprovider/ | Provider | pkg/svc/provider/factory.go |
| New Provisioner | pkg/svc/provisioner/cluster/newdist/ | Provisioner | pkg/svc/provisioner/cluster/factory.go |
| New Installer | pkg/svc/installer/newcomponent/ | Installer | pkg/cli/setup/ |
For a new Provisioner, also add a distribution guide in docs/src/content/docs/distributions/. For a new Installer, also add configuration to pkg/apis/cluster/v1alpha1/.
Updating Dependencies
Section titled âUpdating Dependenciesâgo get -u ./... # update allgo get -u github.com/some/package@latest # update specificgo mod tidy && go mod verify # tidy and verifygo test ./... # verifyWorking on Documentation
Section titled âWorking on Documentationâcd docs/npm ci # install dependenciesnpm run dev # serve at http://localhost:4321npm run build # build to docs/dist/npm run preview # preview production buildDebugging
Section titled âDebuggingâEnable Debug Logging
Section titled âEnable Debug LoggingâKSail doesnât have built-in debug logging yet. Use temporary fmt.Printf("DEBUG: value = %+v\n", value) statements or the Delve debugger:
go install github.com/go-delve/delve/cmd/dlv@latestdlv test -- -test.run TestName # debug a testdlv exec ./ksail -- cluster create # debug the binaryVSCode users can set breakpoints and press F5 with a .vscode/launch.json configuration.
Common Issues
Section titled âCommon Issuesâ| Error | Fix |
|---|---|
| âDocker not availableâ | Verify Docker is running: docker ps. Check Docker Desktop is started (macOS/Windows) or socket permissions (Linux). |
| âGo version mismatchâ | Run go version â must be 1.26.1+. Update from go.dev/dl. |
| âImport cycle not allowedâ | Move shared types to pkg/apis/ or a utility package. Avoid circular imports. |
| âLinter failuresâ | Run golangci-lint run --fix to auto-fix. Check .golangci.yml for enabled linters; some issues require manual fixes. |
GitHub Actions Workflows
Section titled âGitHub Actions Workflowsâci.yamlâ Builds and caches the KSail binary, generates schemas/reference docs, validates documentation builds, audits docs/vsce dependencies, packages the VS Code extension, runs system tests, and runs benchmark regression tests (for example via merge queue andworkflow_dispatch)update-skills.yamlâ Upgrades GitHub Copilot skills daily; opens a PR when updates are availablerelease.yamlâ Creates the next semantic version tag on pushes to maincd.yamlâ Triggered on version tag pushes; runs GoReleaser, publishes the MCP registry entry, deploys documentation to GitHub Pages, publishes the VSCode extension, and opens a Homebrew tap PR
Agentic Workflows
Section titled âAgentic WorkflowsâFive AI-powered workflows run on schedules or triggers (manageable via gh aw): repo-assist (issue triage, code quality, and repository maintenance â every 12h), daily-workflow-maintenance (CI/CD updates and optimization â daily), daily-docs (documentation sync and bloat reduction â daily/on push), weekly-strategy (roadmap planning and project promotion â weekly), and ci-doctor (CI failure investigation â on CI failure).
Release Process
Section titled âRelease ProcessâReleases are automated via .goreleaser.yaml, .github/workflows/release.yaml (tagging), and .github/workflows/cd.yaml (publishing):
- Prepare: Bump the version, update changelog, and open a PR targeting
main. - Merge triggers tagging: Push to
mainruns.github/workflows/release.yaml, which validates the release configuration and creates/pushes the version tag (for example,v5.x.x). The same tagging workflow can also be run manually viaworkflow_dispatchonrelease.yamlto create and push a release tag without a newmaincommit. - Tag-triggered CD: Any push of a version tag like
v5.x.x(whether created byrelease.yamlor manually viagit tag -a v5.x.x -m "v5.x.x" && git push origin v5.x.x) triggers.github/workflows/cd.yaml, which builds binaries for all platforms, generates checksums, creates the GitHub Release, publishes the MCP registry entry (after GoReleaser confirms the OCI image is available), deploys documentation to GitHub Pages, publishes the VSCode extension to the marketplace, and opens a PR to update the Homebrew cask indevantler-tech/homebrew-tap.
Getting Help
Section titled âGetting HelpâDiscussions ¡ Issues ¡ Documentation ¡ CONTRIBUTING.md
Code Review Guidelines
Section titled âCode Review GuidelinesâPRs should be focused on a single concern with clear commit messages, tests for new functionality, and updated documentation. Squash commits before merge. Checklist: code style ¡ tests added and passing ¡ docs updated ¡ commit messages clear ¡ CI passes ¡ no security issues