Build and test¶
Status: Alpha — Makefile targets match the Phase 1 scaffold. Last Updated: 2026-05-30
This document is the cookbook for building, testing, and running the operator during development. The Makefile is the canonical interface; if a command does something other than what this document says, the Makefile is right and this document is stale. Open an issue (or a PR) when that happens.
The commands below reflect the current repository. Tool versions are pinned in the Makefile (controller-gen, kustomize, envtest, golangci-lint).
Make targets¶
| Target | What it does |
|---|---|
make manifests |
Regenerates CRD YAML, RBAC YAML, and webhook configurations from kubebuilder markers. Runs controller-gen under the hood. |
make generate |
Regenerates zz_generated.deepcopy.go files for the API types. |
make fmt |
Runs gofmt -w (and goimports -w if available) on the tree. |
make vet |
Runs go vet ./.... |
make lint |
Runs golangci-lint run with the configuration in .golangci.yaml. |
make test |
Runs unit tests and envtest. Downloads envtest binaries on first run. |
make e2e |
Runs end-to-end tests against a fresh kind cluster. |
make docker-build |
Builds the operator container image. Accepts IMG=<registry>/<repo>:<tag>. |
make docker-push |
Pushes the image built by docker-build. Accepts the same IMG=. |
make install |
Applies config/crd to the current kubectl context. Useful for make run. |
make uninstall |
Removes the CRDs applied by install. |
make deploy |
Renders config/default with kustomize, applies it to the current context. Accepts IMG=. |
make undeploy |
Reverses deploy. |
make run |
Runs the operator binary locally against the current kubectl context. Implies make install. |
make controller-gen / make kustomize / make envtest |
Downloads the pinned version of the tool into ./bin/. Run automatically by other targets. |
make help |
Lists the available targets with descriptions. |
A typical workflow¶
The shortest feedback loop for changes to a reconciler:
# 1. Make your code change in internal/controller/operation_controller.go
# 2. Regenerate manifests if you changed an RBAC marker
make manifests
# 3. Run the unit tests
make test
# 4. Spin up the operator locally against kind
make run
# 5. In another terminal, apply a CR and watch it reconcile
kubectl apply -f config/samples/operations/backup-velero.yaml
kubectl get operation -A -w
When you change the API types under api/apps/v1alpha1/ or api/ops/v1alpha1/, also run make generate to refresh the deep-copy methods.
Running unit tests and envtest¶
make test
The Makefile sets KUBEBUILDER_ASSETS to the downloaded envtest binary suite and runs go test ./... with race detection enabled. Some tests are pure Go (no envtest); some use envtest to bring up an in-process kube-apiserver. Both run as part of the same target.
To run a single package:
go test ./internal/engines/velero/... -v
To run a single test:
go test ./internal/engines/velero/... -v -run TestVeleroBackupMaterialize
To run with verbose envtest logging (useful when a test is failing because the apiserver behaves differently than you expected):
KUBEBUILDER_ASSETS="$(./bin/setup-envtest use 1.30 -p path)" \
go test ./internal/controllers/... -v -count=1 -run TestApplicationInstanceReconciler
The -count=1 flag disables Go's test caching so a re-run actually re-runs.
Running e2e tests against kind¶
make e2e
This target:
- Creates a kind cluster named
vworkspace-e2e(idempotent — if it exists, the target reuses it). - Builds the operator image as
local/vworkspace-app-operator:e2e. - Loads the image into kind via
kind load docker-image. - Deploys the operator via
make deploy IMG=local/vworkspace-app-operator:e2e. - Runs the e2e suite under
test/e2e/. - Tears down the cluster on success (set
E2E_KEEP_CLUSTER=1to keep it for debugging).
Expected runtime: 2 to 5 minutes depending on hardware and how many tests are in the suite.
The e2e suite uses Ginkgo as the test driver and assumes the operator can complete a full ApplicationInstance reconcile against a tiny "no-op" chart that the suite ships. It does not pull real charts from the internet; everything the e2e needs is local.
Building the container image¶
make docker-build IMG=ghcr.io/local/vworkspace-app-operator:dev
The image is built from Dockerfile at the repository root. A multi-stage build: Go compile in a golang: builder, copy the binary into a small distroless base image. The result is a single-layer image that runs as nonroot (UID 65532) with readOnlyRootFilesystem: true.
The image's labels carry the version (org.opencontainers.image.version), the source commit, and the build date. These are also embedded in the binary so vworkspace-app-operator version reports them at runtime.
Pushing the image¶
make docker-push IMG=ghcr.io/local/vworkspace-app-operator:dev
The Makefile does not log in to the registry for you; run docker login first. For multi-arch images:
make docker-buildx IMG=ghcr.io/local/vworkspace-app-operator:dev PLATFORMS=linux/amd64,linux/arm64
docker-buildx requires Docker buildx; the target installs the QEMU bootstrap into a buildx builder named vworkspace-builder on first run.
Deploying to a cluster¶
For a kind cluster (or any cluster the current kubectl context points at):
make docker-build IMG=ghcr.io/local/vworkspace-app-operator:dev
kind load docker-image ghcr.io/local/vworkspace-app-operator:dev --name vworkspace-dev
make deploy IMG=ghcr.io/local/vworkspace-app-operator:dev
make deploy runs kustomize build config/default | kubectl apply -f -. The default kustomization includes the manager Deployment, ServiceAccount, ClusterRole/ClusterRoleBinding, Role/RoleBinding bases, the CRDs, and the webhook configurations. It does not install the bundled controllers (Flux, Velero, cert-manager, external-secrets); for those, install the operator's Helm bundle instead (see ../install/quickstart.md).
make undeploy reverses the apply.
CI parity¶
The same make targets run in CI. To reproduce a CI failure locally:
make fmt vet lint
make manifests generate
./hack/verify-generated.sh
make test
make test-e2e # requires kind; optional locally
Running them in order matches the CI's gating set. If git diff --exit-code reports a diff, you forgot to commit a generated file; commit it.
Bumping dependencies¶
go get -u <module>@<version>
go mod tidy
make manifests generate
make test
The Makefile pins the versions of controller-gen, kustomize, and envtest in hack/tools/. To bump those:
# edit hack/tools/go.mod or the relevant version variable in the top-level Makefile
make controller-gen kustomize envtest
make manifests generate test
CI runs against the pinned versions; if the bump changes a generator's output, regenerate and commit.
Troubleshooting¶
make testfails withkube-apiserver not found. Runmake envtestto download the binaries.make runpanics on startup with "no cert files". The webhook expects TLS material; running outside a cluster, you need either--enable-webhook=false(the Makefile default formake run) or to provide certs via env vars.make deployfails with "no matches for kind HelmRelease". You ranmake deploy(which deploys only the operator) before installing Flux's CRDs. Either install the operator's Helm bundle (which brings Flux) or apply Flux's CRDs first.make e2eis slow. Most of the time is Docker pulling base images. SetE2E_KEEP_CLUSTER=1to keep the kind cluster between runs.
Related material¶
- contributing.md — Workflow that uses these targets.
- project-layout.md — Where the source the targets compile lives.
- release-process.md — What happens to the artifacts these targets produce.