Little helper to run CNCF's k3s in Docker

RAW Rules

CLAUDE.md

# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

k3d creates containerized k3s (lightweight Kubernetes) clusters using Docker. It runs multi-node k3s clusters on a single machine. The Go module is `github.com/k3d-io/k3d/v5`.

## Build & Development Commands

```bash
make build              # Build for local platform -> bin/k3d
make build-debug        # Build with debug symbols
make build-cross        # Cross-compile for all platforms (needs gox)
make install-tools      # Install required tools (golangci-lint, gox)
```

Dependencies are vendored (`-mod=vendor`). The go.work workspace includes `.`, `./docgen`, and `./tools`.

### Local Helper Image Overrides

When developing helper images locally, use these env vars to point k3d at your local builds instead of the published ones (see `pkg/types/env.go`):

```bash
K3D_IMAGE_LOADBALANCER=ghcr.io/k3d-io/k3d-proxy:dev   # Override the load balancer (proxy) image
K3D_IMAGE_TOOLS=ghcr.io/k3d-io/k3d-tools:dev           # Override the tools helper image
```

## Testing

```bash
make test                                           # Run all unit tests
go test ./pkg/config/... -run TestProcessConfig     # Run a specific test
make e2e                                            # Run E2E tests (requires Docker, uses DIND)
make e2e -e E2E_INCLUDE="test_basic"                # Run specific E2E test
make e2e -e E2E_LOG_LEVEL=trace -e E2E_FAIL_FAST=true  # E2E with debug output
```

E2E tests are bash scripts in `tests/` that run inside Docker-in-Docker containers. Unit tests use `testify` for assertions and `go-test/deep` for deep equality. Config test assets are in `pkg/config/test_assets/`.

### E2E Test Framework

**Execution chain:** `make e2e` -> `tests/dind.sh` (builds DIND image, starts privileged container) -> `tests/runner.sh` (discovers and batches tests) -> `test_*.sh` scripts.

**Writing a new E2E test:**

1. Create `tests/test_<name>.sh` (the `test_` prefix is required for auto-discovery)
2. Make it executable (`chmod +x`)
3. Follow the standard boilerplate:
   ```bash
   #!/bin/bash
   CURR_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
   [ -d "$CURR_DIR" ] || { echo "FATAL: no current dir (maybe running in zsh?)"; exit 1; }
   source "$CURR_DIR/common.sh"

   ### Step Setup ###
   LOG_FILE="$TEST_OUTPUT_DIR/$( basename "${BASH_SOURCE[0]}" ).log"
   exec >${LOG_FILE} 2>&1
   export LOG_FILE
   KUBECONFIG="$KUBECONFIG_ROOT/$( basename "${BASH_SOURCE[0]}" ).yaml"
   export KUBECONFIG
   ### Step Setup ###

   export CURRENT_STAGE="Test | <name>"
   # ... test logic using $EXE, info, failed, check_clusters, etc.
   exit 0
   ```
4. Use unique cluster/resource names to avoid collisions with parallel tests
5. Always clean up resources (cluster delete, registry delete) at the end

**Key helpers from `tests/common.sh`:** `info`, `failed`, `passed`, `highlight`, `check_clusters`, `check_multi_node`, `check_registry`, `wait_for_pod_running_by_name`, `wait_for_pod_running_by_label`, `exec_in_node`, `docker_assert_container_label`, `k3s_assert_node_label`.

**E2E environment variables:** `E2E_INCLUDE` (run only named tests), `E2E_EXCLUDE` (skip named tests), `E2E_PARALLEL` (default: 4), `E2E_EXTRA` (run `extra_test_*` files), `E2E_FAIL_FAST`, `E2E_LOG_LEVEL`, `E2E_K3S_VERSION`, `E2E_DIND_VERSION`, `E2E_KEEP` (keep runner container).

## Linting & Formatting

```bash
make fmt        # Fix formatting (gofmt)
make check-fmt  # Check formatting
make lint       # Run golangci-lint (v2.4.0)
make ci-lint    # Same with 5-minute timeout (CI)
make check      # check-fmt + lint
```

## Architecture

Three-layer design:

1. **CLI layer** (`cmd/`) - Cobra commands for cluster, node, registry, kubeconfig, image, config, debug subcommands. Entry point: `cmd/root.go` -> `NewCmdK3d()`.

2. **Client layer** (`pkg/client/`) - Business logic and orchestration. Key files: `cluster.go` (ClusterCreate/Start/Delete/Run), `node.go` (NodeCreate/Start/Delete), `registry.go`, `loadbalancer.go`, `kubeconfig.go`.

3. **Runtime layer** (`pkg/runtimes/`) - Container runtime abstraction via the `Runtime` interface in `runtime.go`. Only Docker is implemented (`pkg/runtimes/docker/`). The Docker runtime translates k3d types to Docker API calls (`translate.go`).

### Key Packages

- **`pkg/types/`** - Core domain types: `Cluster`, `Node`, `Registry`, `Role` (ServerRole, AgentRole, LoadBalancerRole, RegistryRole). Extensive label system (`k3d.cluster`, `k3d.role`, etc.) used to track Docker container metadata.
- **`pkg/config/`** - Versioned config system (current: `v1alpha5`, API version `k3d.io/v1alpha5`). Includes migration (`migrate.go`), JSON schema validation (`schema.json` embedded via `//go:embed`), and config processing/transformation pipeline.
- **`pkg/actions/`** - Node lifecycle hooks (e.g., `WriteFileAction`).
- **`pkg/logger/`** - Logging via logrus.

### Helper Components

- **`proxy/`** - nginx-based load balancer with confd for dynamic upstream config. Built as `ghcr.io/k3d-io/k3d-proxy`.
- **`tools/`** - Separate Go module (`tools/`) for the k3d-tools helper binary. Built as `ghcr.io/k3d-io/k3d-tools`.

## Config System

Config files are YAML with `apiVersion: k3d.io/v1alpha5` and `kind: Simple`. The processing pipeline: read YAML -> validate JSON schema -> migrate from older versions if needed -> transform to internal types -> merge with defaults. Config versions: v1alpha2 (legacy) -> v1alpha3 -> v1alpha4 -> v1alpha5 (current).

## Version & LDFLAGS

Version info is injected at build time via LDFLAGS into `version/version.go`:
- `version.Version` - git tag
- `version.K3sVersion` - latest stable k3s version (fetched from k3s update channel)
- `version.HelperVersionOverride` - optional override for helper image versions

## License & Contributing

This project is **MIT licensed** (Copyright 2019-2023 Thorsten Klein). All new Go source files must include the MIT copyright header present in existing files. See `CONTRIBUTING.md` for guidelines — check existing issues/PRs before opening new ones to avoid duplicates. The project follows the Contributor Covenant Code of Conduct (`CODE_OF_CONDUCT.md`).

## Conventions

- All Go source files have the MIT license copyright header
- Import alias: `k3d "github.com/k3d-io/k3d/v5/pkg/types"` is used throughout as the canonical import for the types package
- Docker is the only runtime, but all container operations go through the `Runtime` interface
- Node types are identified by `Role` and tracked via Docker container labels

## Issue & PR Handling

**Before starting work on any issue:**

1. **Check for duplicates** — search open and closed issues for the same topic (`gh issue list -S "keyword"` / `gh issue list -S "keyword" --state closed`)
2. **Check for existing PRs** — look for open PRs that already address the issue (`gh pr list -S "keyword"`)
3. **Check if it's a k3d issue** — many reports are actually k3s, Docker, or CNI issues. If so, redirect the reporter to the appropriate upstream repo
4. **Read the full thread** — issues often evolve through discussion; the original request may have been refined or scoped down

**When replying to issues:**

- For questions: answer concisely, point to env vars/flags/docs, close if resolved
- For bugs: confirm reproducibility scope, check if already fixed on `main`
- For features: check if there's a workaround, note if a PR would be welcome

**When working on PRs:**

1. **Check the linked issue** — understand the full context and any decisions made in discussion
2. **Check the PR's CI status** — don't start reviewing or building on a PR that's failing CI for unrelated reasons
3. **Check for related/conflicting PRs** — look for other open PRs touching the same files (`gh pr list` + review changed files)
4. **Verify the branch is up to date** with `main` before investing effort

**Scope awareness:**

- k3d only controls the container orchestration layer around k3s. Issues about k3s behavior, CNI plugins, Kubernetes internals, or Docker engine bugs are upstream concerns
- The proxy (`k3d-proxy`) and tools (`k3d-tools`) images are part of this project — issues about those are in scope
- Registry issues may involve the upstream `registry` image vs k3d's registry wiring — distinguish which layer is at fault

## Deep Dive References

For detailed analysis beyond this summary, see `.planning/codebase/`:

| Document | Contents |
|----------|----------|
| `STACK.md` | Full dependency inventory, Go version, build toolchain, Docker SDK usage |
| `ARCHITECTURE.md` | Layer diagrams, data flow through CLI->Client->Runtime, abstraction boundaries |
| `STRUCTURE.md` | Complete directory layout, file naming conventions, package organization |
| `CONVENTIONS.md` | Error handling patterns, logging conventions, code style, naming rules |
| `TESTING.md` | Unit test patterns, E2E test framework details, test helpers, fixture locations |
| `INTEGRATIONS.md` | Docker API interaction, k3s channel server, registry (wharfie), kubeconfig handling |
| `CONCERNS.md` | Technical debt, single-runtime limitation, config migration complexity, known fragile areas |