Skip to content

Development Guide

This guide covers setting up your development environment, understanding the codebase, and making contributions to KSail.

  1. Go 1.26.0+ — KSail is written in Go

    • Install from go.dev/dl
    • Verify: go version should show 1.26.0 or higher
  2. Docker — Required for local cluster testing

    • Install Docker Desktop or Docker Engine
    • Verify: docker ps should run without errors
  3. Git — Version control

Node.js 22 (docs), golangci-lint v2.8.0 (linting via go install), and VSCode with the Go extension are useful but not required.

Terminal window
git clone https://github.com/devantler-tech/ksail.git
cd ksail
Terminal window
# Go dependencies (automatically downloaded on first build)
go mod download
# Development tools (optional)
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.8.0
# Documentation dependencies (if working on docs)
cd docs/
npm ci
cd ..
Terminal window
# Development build
go build -o ksail
# Verify the build
./ksail --version
Terminal window
# Unit tests
go test ./...
# Specific package
go test ./pkg/svc/provisioner/cluster/kind
# With coverage
go test -cover ./...
# Benchmarks
go test -bench=. -benchmem ./pkg/client/helm
Terminal window
# Run all linters
golangci-lint run
# Run specific linter
golangci-lint run --enable-only=gofmt
# Auto-fix issues (import order, etc.)
golangci-lint run --fix
ksail/
├── .github/ # CI/CD workflows and agentic workflows
│ ├── workflows/ # GitHub Actions workflows (.yaml + .md)
│ └── copilot-instructions.md # Copilot coding instructions
├── docs/ # Documentation (Astro/Starlight)
│ ├── src/content/docs/ # Markdown documentation files
│ └── public/ # Static assets
├── internal/ # Internal (private) packages
│ └── buildmeta/ # Build metadata (version, commit, date)
├── pkg/ # Public packages
│ ├── apis/ # API types and schemas
│ ├── cli/ # CLI commands and UI
│ ├── client/ # Embedded tool clients
│ ├── svc/ # Core services
│ │ ├── provider/ # Infrastructure providers
│ │ ├── provisioner/ # Distribution provisioners
│ │ └── installer/ # Component installers
│ └── [utilities] # di, envvar, fsutil, k8s, notify, etc.
├── schemas/ # JSON schemas for validation
├── vsce/ # VSCode extension
├── main.go # Application entry point
├── go.mod # Go module definition
└── .golangci.yml # Linter configuration

internal/ packages can only be imported within the ksail module; pkg/ packages are public. Circular dependencies are not allowed.

Terminal window
git checkout -b feature/my-feature

Keep changes focused and atomic. Write tests, update documentation, and follow existing code patterns:

Terminal window
go test ./... # Run unit tests
golangci-lint run # Run linters
go build -o ksail # Build
./ksail cluster init # Smoke test
git add . && git commit -m "feat: add new feature"
git push origin feature/my-feature

Commit types: feat, fix, docs, refactor, test, chore, perf. Open a PR with a clear description and ensure CI passes.

Follow Effective Go. Use gofmt for formatting, goimports for import order, and keep lines under 100 characters (enforced by golines).

EntityConventionExample
PackagesLowercase, singularprovider, installer
Types, InterfacesPascalCase; interfaces often end in -erKindClusterProvisioner, Provider
Functions/MethodsPascalCase (exported), camelCase (unexported)Create, validateConfig
Variables, ConstantscamelCase (unexported), PascalCase (exported)clusterName, DefaultTimeout

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.

Use table-driven tests with t.Parallel():

func TestProvisionerCreate(t *testing.T) {
t.Parallel()
tests := []struct {
name string
wantErr bool
}{
{name: "successful creation", wantErr: false},
{name: "creation fails", wantErr: true},
}
for _, testCase := range tests {
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
m := mocks.NewMockProvisioner(t)
if testCase.wantErr {
m.On("Create", mock.Anything).Return(errors.New("create failed"))
} else {
m.On("Create", mock.Anything).Return(nil)
}
err := m.Create(context.Background())
if (err != nil) != testCase.wantErr {
t.Errorf("Create() error = %v, wantErr %v", err, testCase.wantErr)
}
})
}
}

Use export_test.go to expose unexported symbols to _test packages without leaking them into production binaries. This file is compiled only during go test because its name ends with _test.go. It uses the regular package name (for example, package provisioner) so the exported aliases are built along with the package-under-test and can then be accessed from an external package provisioner_test:

pkg/svc/provisioner/export_test.go
package provisioner
var (
ExportValidateConfig = validateConfig
ExportGenerateManifests = generateManifests
)

Test from package provisioner_test for clean black-box testing. Add a //nolint:gochecknoglobals annotation above these exported variables when required to satisfy golangci-lint, unless a path-specific exclusion for test exports is configured.

KSail uses mockery for generating mocks via //go:generate go run github.com/vektra/mockery/v2. Use generated mocks in tests:

func TestWithMock(t *testing.T) {
mockProv := new(MockProvider)
mockProv.On("Create", mock.Anything).Return(nil)
// Use mockProv...
mockProv.AssertExpectations(t)
}

Important: Never use .Times(b.N) with for b.Loop() in benchmarks — b.N is 1 when evaluated before the loop. Omit .Times() for unlimited calls.

func BenchmarkProvisionerCreate(b *testing.B) {
p := setupProvisioner()
ctx := context.Background()
for b.Loop() { // Go 1.26+ style
if err := p.Create(ctx); err != nil {
b.Fatal(err)
}
}
}

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.

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.

TaskPackage pathInterfaceFactory
New Providerpkg/svc/provider/newprovider/Providerpkg/svc/provider/factory.go
New Provisionerpkg/svc/provisioner/cluster/newdist/Provisionerpkg/svc/provisioner/cluster/factory.go
New Installerpkg/svc/installer/newcomponent/Installerpkg/cli/setup/

For a new Provisioner, also add a getting-started guide in docs/src/content/docs/getting-started/. For a new Installer, also add configuration to pkg/apis/cluster/v1alpha1/.

Terminal window
# Update all dependencies
go get -u ./...
# Update specific dependency
go get -u github.com/some/package@latest
# Tidy and verify
go mod tidy
go mod verify
# Test after update
go test ./...
Terminal window
cd docs/
# Install dependencies
npm ci
# Start development server
npm run dev
# Visit http://localhost:4321
# Build production site
npm run build
# Output: docs/dist/
# Preview production build
npm run preview

Use clear, concise language with code examples. Follow existing structure and GitHub Flavored Markdown.

KSail doesn’t have built-in debug logging yet. Use temporary fmt.Printf("DEBUG: value = %+v\n", value) statements or the Delve debugger:

Terminal window
go install github.com/go-delve/delve/cmd/dlv@latest
dlv test -- -test.run TestName # debug a test
dlv exec ./ksail -- cluster create # debug the binary

VSCode users can set breakpoints and press F5 with a .vscode/launch.json configuration.

ErrorFix
”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.0+. 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.

KSail uses several CI workflows:

  • ci.yaml — Runs on every PR and push to main; builds KSail, runs unit and system tests, uploads coverage to Codecov
  • test-pages.yaml — Validates documentation builds on docs changes
  • publish-pages.yaml — Deploys documentation to GitHub Pages on main branch pushes
  • release.yaml — Builds release binaries and publishes to GitHub Releases; triggered by push to main or workflow_dispatch

KSail uses five AI-powered daily workflows for continuous improvement (scheduled at 02:00, 10:00, 14:00, 18:00, and 22:00 UTC):

  • daily-code-quality.md — Refactoring, performance optimization, and test coverage improvements
  • daily-plan.md — Triages new issues and creates/prioritizes backlog issues from the roadmap
  • daily-builder.md — Implements roadmap features and tackles backlog issues
  • daily-workflow-maintenance.md — Updates and optimizes CI workflows
  • daily-docs.md — Keeps documentation in sync and reduces bloat

These run on schedules and can be triggered manually via gh aw commands.

Releases are automated via .goreleaser.yaml and .github/workflows/release.yaml:

  1. Prepare a release PR targeting main:

    • Bump the version in code/docs as needed (e.g. v5.x.x)
    • Update changelog / release notes in the repository
    • Open a pull request against main and get it reviewed/merged
  2. When the PR is merged and changes land on main, the release workflow runs:

    • Triggered by the push to main (or manually via workflow_dispatch by maintainers)
    • Builds binaries for all platforms/architectures
    • Generates checksums
    • Creates a GitHub Release with artifacts
    • Publishes the VSCode extension to the marketplace
  3. Optional tagging (for maintainers): Create and push an annotated tag (git tag -a v5.x.x -m "v5.x.x" && git push origin v5.x.x) and update release notes on GitHub if needed.

PRs should be focused on a single concern with clear commit messages, tests for new functionality, and updated documentation. Squash commits before merge (or allow maintainer to).

Review checklist: code style ✓ | tests added and passing ✓ | docs updated ✓ | commit messages clear ✓ | CI passes ✓ | no security issues ✓