Contributing
This page is for anyone changing CAPTF itself: the repository layout, the prerequisites, the build/lint/test/verify loop, the conventions the checks enforce, and running the manager under Tilt.
Repository layout
| Path | Holds |
|---|---|
api/ | The v1alpha1 Go types: its own module in go.work, so the CRD types carry no dependency on controller-runtime or the manager. |
cmd/manager, cmd/runner, cmd/tfcapi-lint | The three binaries. Each has an app package with its wiring and an app/options or equivalent for flags, so main.go stays a thin entry point. |
internal/ | Everything the binaries share, one package per concern: controllers (one subpackage per reconciled kind — TerraformClusterTemplate and TerraformMachinePoolTemplate have no controller of their own — plus shared for the common reconcile flow and sweep for the orphan RBAC sweep), jobs, runner, state, identity, rbac, runlease, inputs, render, outputs, locks, conditions, contract, hash, ownership, webhooks, lint, docsgen, feature, imageinspect, manager, metrics. |
config/ | Kustomize bases: crd, rbac, webhook, manager, certmanager, assembled by default; network-policy and prometheus are separate optional overlays, and samples holds example custom resources. |
templates/ | The clusterctl generate templates and flavors. |
modules/ | Reference Terraform/OpenTofu modules: noop (with its variants) and libvirt. |
hack/ | Build and verify tooling: pinned tool installers under hack/tools, the verify-*.sh/check-*.sh scripts make verify runs, hack/godoccheck, and the vale house style under hack/vale. |
test/fixtures | Frozen fixtures unit tests read, such as real captured Terraform/OpenTofu state. |
The book itself lives in a separate repository, captf-io/docs, published at https://captf.io/docs/; see Writing Documentation.
Each Go module (. and api) is listed in go.work. hack/verify-modules.sh
(make verify) checks that neither carries a replace directive and that
both agree on the Kubernetes and controller-runtime versions they share.
Because api only exists inside the workspace, go mod tidy run from the
repository root does not update its go.mod; add or bump one of its
dependencies by editing api/go.mod’s require block directly, then run
go mod tidy inside api/ with GOWORK=off.
Before you begin
- Go, matching the version
go.workdeclares,make,git,jqandcurl. podman(the defaultCONTAINER_TOOL) ordocker, to build images.- Node.js 22 or later with
npm, formake lint-docs-mdand so formake verify: it installsmarkdownlint-cli2from the lockfile underhack/tools/markdownlint. python3, with thejsonschemaandPyYAMLpackages, formake verify-schemas,make verify-metadata,make promtool-check,make promtool-testandmake release-assets.
Everything else — controller-gen, kustomize, golangci-lint (plus its
kube-api-linter build), clusterctl, promtool, goreleaser, goimports,
crd-ref-docs, mdbook, mdbook-mermaid, lychee and vale — is a pinned
binary make downloads for you.
The build, lint, test and verify loop
- Install the pinned tools once:
make tools. Each lands inhack/tools/binas<name>-<version>, plus an unversioned symlink; bumping a version in theMakefilere-downloads it. - After changing
api/v1alpha1or a controller’s markers, regenerate the deepcopy code and the CRD/RBAC/webhook manifests:make generate manifests.make verify-gen(part ofmake verify) fails if either is stale. - Format and lint:
make fmt lint.fmtrunsgofmt -sandgoimports -local github.com/captf-io/cluster-api-provider-terraform;lintrunsgolangci-lint(.golangci.yml) in every module, then kube-api-linter (.golangci-kal.yml) onapi/andtfcapi-lint module --stricton the reference modules undermodules/.make lint-fixreruns both linters with their auto-fixers. - Run the unit tests:
make test. See Testing for what it covers and how to run less than everything. - Before sending a change, run
make verify: every check listed in Make Targets, including that generated code and the API reference are current and thatmake lint-docs-mdandmake lint-docs-proseare clean. A change that touches a generated page needs acaptf-io/docscheckout too: see Writing Documentation. make buildcompiles everycmd/*binary tobin/; never to the repository root.make runbuilds and runs the manager out of cluster against your currentkubeconfig, for a quick check against a real API server.
The full target list, grouped the same way, is in Make Targets.
Conventions
- Commits: an imperative subject in
<subsystem>: <summary>form, such asdocsgen: render event reasons as proseorrunner: name the exit codes— checkgit logfor the subsystem names already in use. - License header: every hand-written Go file starts with the Apache 2.0
header the other files in its package carry;
hack/boilerplate.go.txtis the headercontroller-genwrites onapi/v1alpha1/zz_generated.deepcopy.go. - Documentation comments:
hack/godoccheck(make verify-godoc, part ofmake verify) enforces one rule per declaration and one per package, everywhere except generated files andhack/tools:- Every function, method and type has a doc comment starting with its
name; every interface method and top-level
constorvargroup needs only a doc comment, not one starting with its name. - Every named parameter is mentioned by name in that comment. Receivers,
parameters named
_, and aTest/Benchmark/Fuzzfunction’s conventional*testing.T/*testing.B/*testing.Fparameter are exempt. - A function or method that returns anything says what it returns, using “return”, “returns”, “returned” or “reports”.
- Every non-generated, non-external-test package has a
doc.gowhose package comment is a real overview of at least 400 characters.
- Every function, method and type has a doc comment starting with its
name; every interface method and top-level
- Generated code:
zz_generated.deepcopy.goand the CRD/RBAC/webhook manifests come frommake generate manifests; the book’s reference pages come frommake docs-gen DOCS_DIR=<path to a captf-io/docs checkout>(see Writing Documentation). Never hand-edit a generated file:make verify-genfails when the deepcopy code or the manifests no longer match their source, andmake verify-docs DOCS_DIR=<path>fails when a generated reference page does.
Common changes
Each make docs-gen below needs DOCS_DIR=<path to a captf-io/docs checkout> (see Writing Documentation).
-
Adding a manager flag: also add it to the
managerFlagGroupsmap ininternal/docsgen/manager.go, naming the reference-page heading it belongs under.TestManagerFlagGroupsfails when a flag the manager registers has no entry, or an entry names a flag that no longer exists. Runmake docs-gento regeneratereference/manager-flags.md. -
Adding a condition reason: condition types, their reasons and each reason’s doc comment live in
api/v1alpha1/conditions_consts.go;ConditionReasonsreturns the full type-to-reason table, and a reason’s doc comment becomes its Meaning in the generated reference page. A reason is set frominternal/conditionswhen it applies across controllers, or frominternal/controllers/sharedwhen it belongs to one reconcile flow. Afterward, runmake generate manifests docs-gento regenerate the CRD schema, the deepcopy code andreference/conditions.md. Until you do,internal/docsgen’sTestConditionKindsComplete,TestConditionTypeOrderComplete,TestReasonMeaningandTestGoldenConditions, plusmake verify-genandmake test, fail.An event reason follows the same shape: it lives in
internal/controllers/shared/events.go, orinternal/runner’s equivalent for a runner event, with a doc comment that becomes its Meaning;TestManagerEventsCompleteorTestRunnerEventsComplete, andTestGoldenEvents, fail untilmake docs-genregeneratesreference/events.md. Atfcapi-lintcheck ID ininternal/lintis the same again: its doc comment becomes its description, andTestCheckDescriptionsCompletefails untilmake docs-genregeneratesreference/tfcapi-lint-cli.md.
Tilt
tilt-provider.yaml wires this repository into a
Cluster API checkout’s Tilt setup: add
../cluster-api-provider-terraform to that checkout’s tilt-settings.yaml
provider_repos, and terraform to enable_providers. Tilt then builds and
live-reloads only the manager binary (from cmd, api, internal,
go.mod and go.sum) and substitutes its image for
ghcr.io/captf-io/cluster-api-provider-terraform in config/default.
Tilt never rebuilds the runner image. A Job always takes its runner from
CAPTF_MANAGER_IMAGE, which Tilt does not rewrite, so it stays whatever
:dev image is already loaded in the Tilt cluster. After a change under
internal/runner or cmd/runner, run make docker-build yourself and load
the result into the cluster before a Job needs it. make verify-tilt-provider
checks tilt-provider.yaml against the tree and config/default.