Instructions for AI Agents when working with the cluster-capi-operator project.
# Build and test
make build # Build all binaries
make test # Run all tests
make unit # Run unit tests with coverage
make lint # Run linting
make fmt # Format code
## Project Overview
The Cluster CAPI Operator manages the installation and lifecycle of Cluster API components on OpenShift clusters. It serves as a bridge between OpenShift's Machine API (MAPI) and the upstream Cluster API (CAPI), providing forward compatibility and migration capabilities.
### Architecture
The operator consists of three main binaries:
1. **capi-operator** (`cmd/capi-operator/`) - Manages installation of Cluster API components and providers
2. **capi-controllers** (`cmd/capi-controllers/`) - Main controllers managing CAPI component lifecycle, cluster resources, and synchronization
3. **machine-api-migration** (`cmd/machine-api-migration/`) - Handles migration between Machine API and Cluster API resources
The repository also includes:
4. **manifests-gen** (`manifests-gen/`) - Standalone tool that transforms upstream Cluster API provider manifests into OpenShift-compatible format, applying OpenShift-specific annotations, replacing cert-manager with service-ca, and generating provider ConfigMaps with compressed components
### Key Controllers
#### capi-operator Controllers
- **CAPI Installer Controller** (`pkg/controllers/capiinstaller/`) - Handles installation of CAPI components and providers
#### capi-controllers Controllers
- **ClusterOperator Controller** (`pkg/controllers/clusteroperator/`) - Manages the operator's status in the cluster
- **Core Cluster Controller** (`pkg/controllers/corecluster/`) - Manages CAPI Cluster resources representing the OpenShift cluster
- **Infra Cluster Controller** (`pkg/controllers/infracluster/`) - Manages infrastructure-specific cluster resources (AWS, Azure, GCP, etc.)
- **Secret Sync Controller** (`pkg/controllers/secretsync/`) - Synchronizes secrets between MAPI and CAPI namespaces
- **Kubeconfig Controller** (`pkg/controllers/kubeconfig/`) - Manages kubeconfig secrets for cluster access
#### machine-api-migration Controllers
- **Machine Migration Controller** (`pkg/controllers/machinemigration/`) - Handles handover of AuthoritativeAPI and object pausing for machine migration
- **MachineSet Migration Controller** (`pkg/controllers/machinesetmigration/`) - Handles handover of AuthoritativeAPI and object pausing for machineset migration
- **Machine Sync Controller** (`pkg/controllers/machinesync/`) - Synchronizes individual machine related resources between APIs
- **MachineSet Sync Controller** (`pkg/controllers/machinesetsync/`) - Synchronizes machineset related objects resources between APIs
#### Conversion Framework
- **MAPI to CAPI Conversion** (`pkg/conversion/mapi2capi/`) - Library implementing Conversion of MAPI resources to CAPI
- **CAPI to MAPI Conversion** (`pkg/conversion/capi2mapi/`) - Library implementing conversion of CAPI resources to MAPI
### File Structure
- `manifests/` - Contains OpenShift manifests for operator deployment
- `manifests-gen/` - Tool for generating customized provider manifests
- `hack/` - Development and testing scripts
- `docs/controllers/` - Detailed controller documentation
- `e2e/` - End-to-end tests for each supported platform
## Development Rules
**- ALWAYS check for existing patterns, and use them if found**
### Coding Style
- Use early returns
- Descriptive names
- Helper functions over inline code
- Minimal comments (only for non-obvious decisions)
- Simple code over complex language features
- For user-facing text like logs and errors, use "Cluster API" and "Machine API". For code and internal identifiers, use "CAPI" and "MAPI".
## Testing
### Running Tests
**Do not use `go test` or `ginkgo` directly.** Tests use `envtest` which requires `KUBEBUILDER_ASSETS`
to point at downloaded API server and etcd binaries. The Makefile handles this: `make unit` depends on
the `.localtestenv` target (which runs `setup-envtest` to download binaries and writes their path to
`.localtestenv`), and `hack/test.sh` sources that file before invoking ginkgo. Running `go test`
directly will fail because the envtest `Environment` cannot locate the binaries.
```bash
make unit # All unit tests
make unit TEST_DIRS="./pkg/controllers/installer/..." # Specific package
make unit TEST_DIRS="./pkg/controllers/machinesync/..." # Another specific packageImportant: Ginkgo functional tests are slow and produce verbose output that will exceed context limits. Always redirect output to a log file and use multi-pass processing:
make unit TEST_DIRS="./pkg/..." 2>&1 | tee /tmp/test-output.log
# Then check results:
tail -20 /tmp/test-output.log # Summary
grep -E 'FAIL|PASSED' /tmp/test-output.log # Pass/fail status
grep 'FAIL' /tmp/test-output.log # Find failuresGINKGO_ARGS="-r -v --randomize-all --randomize-suites --keep-going --race --trace --timeout=10m"Prefer usingGINKGO_EXTRA_ARGSto pass additional arguments to ginkgo. UseGINKGO_ARGSwhen you need to override the default values entirely.
Use Ginkgo/Gomega framework and prefer built-in features over custom implementations:
- Use
DescribeTablewithEntryfor table-driven tests instead of manual loops - Use
HaveField,HaveValue,HaveKeyfor struct/map assertions instead of manual field checks - Use
ConsistOffor unordered slice matching instead of sorting +Equal - Use
MatchErrorfor error checking instead of string contains - Use
BeNumericallyfor numeric comparisons instead of manual range checks
Use Komega for Kubernetes object assertions:
// Use komega.Object for async assertions
Eventually(k.Object(myResource)).Should(HaveField("ObjectMeta.ResourceVersion", Equal(expectedRV)))
// Update resources with komega helpers
Eventually(k.UpdateStatus(myResource, func() {
myResource.Status.SomeField = "value"
})).Should(Succeed())- Nested Contexts: Organize related test scenarios with nested
Context()blocksContext("when migrating from MachineAPI to ClusterAPI", func() { Context("when status is not paused", func() { // Test cases }) })
- Descriptive test names: Use "should..." format:
It("should do nothing", func() {...}) - Use
By()for test steps: Document test phases withBy("Setting up namespaces for the test")
- Resource builders: Use testutils resource builders for creating test objects
mapiMachine = mapiMachineBuilder. WithNamespace(namespace). WithName("foo"). WithAuthoritativeAPI(machinev1beta1.MachineAuthorityMachineAPI). Build()
- Standard cleanup: Use
testutils.CleanupResources()in AfterEachtestutils.CleanupResources(Default, ctx, cfg, k8sClient, namespace, &machinev1beta1.Machine{}, &clusterv1.Machine{}, )
- Complex assertions: Combine matchers with
SatisfyAllEventually(komega.Object(resource)).Should(SatisfyAll( HaveField("Status.AuthoritativeAPI", Equal(expected)), HaveField("Status.SynchronizedGeneration", BeZero()), ))
- Checking absence: Use
ShouldNotwith appropriate matchersEventually(komega.Object(resource)).ShouldNot( HaveField("ObjectMeta.Annotations", ContainElement(HaveKeyWithValue(key, value))))
- Nested field checks: Chain
HaveFieldfor nested assertionsHaveField("Status.Conditions", ContainElement(SatisfyAll( HaveField("Type", Equal("Paused")), HaveField("Status", Equal(corev1.ConditionTrue)), )))
// Focus specific tests (REMOVE before committing!)
FIt("test name", func() { /* test */ })
FContext("context name", func() { /* tests */ })- Each controller has a
suite_test.gothat bootstraps anenvtest.Environment - See "Running Tests" above for why
make unitis required