This file provides guidance to coding agents when working with code in this repository.
Kubernetes operator for managing ClickHouse database clusters and ClickHouse Keeper clusters. Built with Kubebuilder and controller-runtime. Manages two CRDs: ClickHouseCluster and KeeperCluster (both v1alpha1 in the clickhouse.com API group).
make build # Build binary (bin/clickhouse-manager). Runs manifests, generate, fmt, vet first.
make build-linux-manager # Build binary (bin/clickhouse-manager) for linux platform. Used to build Docker image.
make docker-build # Build Docker imagemake test # Unit tests (excludes e2e and deploy tests). Uses `envtest` with Ginkgo.
make test-ci # CI tests with coverage (cover.out), race detection, JUnit report
make fuzz # Fuzz tests for keeper and clickhouse spec validation
make test-e2e # Full e2e tests (requires Kind cluster, 30m timeout)
make test-keeper-e2e # Keeper-only e2e tests (--ginkgo.label-filter keeper)
make test-clickhouse-e2e # ClickHouse-only e2e tests (--ginkgo.label-filter clickhouse)
make test-compat-e2e # Compatibility smoke tests across ClickHouse versionsRun a single test file or spec:
# Single package
go test ./internal/controller/keeper/ -v --ginkgo.v
# Single spec by name
go test ./internal/controller/keeper/ -v --ginkgo.v --ginkgo.focus="spec name pattern"- Run
make lint-fixbefore tests - Run
make testor specific unit tests - Do NOT run
make test-e2e,test-keeper-e2e,test-clickhouse-e2e,test-compat-e2e - If changes complex and require e2e testing run target with
--ginkgo.focusto limit scope to a single test at a time
- Functional tests (
controller_test.go): Usetestutil.SetupEnvironment()which startsenvtest(real API server +etcd). Use for full reconciliation flow testing. - Unit tests (
sync_test.go,commands_test.go): Usefake.NewClientBuilder()for faster, focused tests on individual methods. - E2E tests (
test/e2e/): Real Kind cluster. Label tests withLabel("clickhouse")orLabel("keeper")for filtered runs.
make generate # DeepCopy methods (zz_generated.deepcopy.go)
make manifests # CRDs, RBAC roles, webhooks → config/crd/bases/
make generate-helmchart-ci # Generate Helm chart templates and reset manually maintained files (dist/chart/templates/)
make docs-generate-api-ref # Generate API reference docs (docs/reference/api-reference.mdx)The Helm chart in dist/chart/ is partially generated from Kustomize configs — do not edit templates directly. Regenerate with make generate-helmchart-ci. Except you are directly asked.
make lint # golangci-lint + codespell + actionlint
make lint-fix # golangci-lint with --fix
make golangci-fmt # gofmt + goimports formatting
make docs-lint # markdownlint for docsKind cluster configuration at ci/kind-cluster.config creates 1 control-plane + 3 workers with zone topology labels:
kind create cluster --config ci/kind-cluster.configmake check-crd-compat # Check CRD backward compatibility against origin/main- Do not introduce new dependencies without justification
- Follow existing error handling patterns
- Try to minimize diff
- Avoid API changes unless necessary
- Always ensure
make lintpasses, usemake lint-fixto automatically fix issues before writing code
- Lint:
go mod tidycheck,make manifests/make generatefreshness, CRD backward compatibility, golangci-lint, codespell, actionlint - Build + unit tests (
make test-ci) - Fuzz tests
- Helm chart generation + lint
- OLM bundle + scorecard validation
- Compatibility e2e (multiple K8s + ClickHouse version matrices)
- Full e2e (Keeper + ClickHouse, on self-hosted runners)
clickhousecluster_types.go— ClickHouseCluster spec: shards, replicas per shard, keeper reference, storage, settingskeepercluster_types.go— KeeperCluster spec: replicas (odd, 0-15), storage, settingscommon.go— Shared types: ContainerImage, LoggerConfig, PodDisruptionBudgetSpec, ClickHouseSettings, KeeperSettingsdefaults.go— Default value logic applied by webhooksconditions.go— Status condition helpers
After modifying types, always run make generate manifests generate-helmchart-ci docs-generate-api-ref.
Both controllers follow the same pattern, extending a shared base:
reconcilerbase.go— Base reconciliation logic shared by both controllersclickhouse/controller.go— ClickHouseCluster reconciler (manages StatefulSets, ConfigMaps, Services)keeper/controller.go— KeeperCluster reconcilerclickhouse/sync.go,keeper/sync.go— Sync desired state to Kubernetesclickhouse/templates.go,keeper/templates.go— Generate Kubernetes resource specsclickhouse/config.go— Generate ClickHouse YAML configurationclickhouse/commands.go,keeper/commands.go— Interfaces to communicate to running containersoverrides.go— Pod/container spec overrides helpers via strategic merge patchversionprobe.go— Creates Jobs to detect actual ClickHouse/Keeper versionsupgradecheck.go— Checks for newer version availabilityresources.go— Shared Kubernetes resource creation helpersstatus.go— Status update logic
Controllers execute reconcile as a sequence of step functions (func(ctx, log) (*Result, error)). Steps are defined in sync() and executed sequentially.
Resources are tracked via annotation hashes (checksum/spec, checksum/configuration). Before updating a K8s resource, compare util.DeepHashResource() output against the stored annotation. Skip updates when hashes match. Always call util.AddSpecHashToObject() on reconciled resources.
Validation and defaulting webhooks for both CRDs. ENABLE_WEBHOOKS Env var controls whether they are registered.
internal/upgrade/— Version update checking (fetcher + compatibility checker)internal/version/— Build version info injected vialdflagsinternal/environment/— Environment variable processing (ENABLE_WEBHOOKS,WATCH_NAMESPACE)internal/controllerutil/— Shared utilities (annotations, dialer, logger)
Kustomize-based configuration: config/default/ is the main overlay composing CRDs, RBAC, manager deployment, webhooks, and cert-manager integration.
cmd/main.go — Sets up controller-runtime manager, registers both controllers and webhooks, configures metrics and health probes.
- Import ordering:
stdlib, third-party, thengithub.com/ClickHouse/clickhouse-operator(enforced by goimports with local-prefixes) interface{}→any: gofmt rewritesinterface{}toanyautomatically- Testing: Ginkgo v2 BDD style with Gomega matchers. Dot imports for
ginkgo/v2andgomegaare allowed. - Linting: 80+ linters enabled in
.golangci.yml. Notable:wsl_v5(whitespace),mnd(magic numbers,2is exempted),godot(comment periods),ireturn(interface return restrictions with whitelisted types) - Generated files: Never edit
zz_generated.deepcopy.goor files inconfig/crd/bases/,config/rbac/directly