This document serves as a comprehensive briefing for AI coding assistants working with the operator-controller repository. It covers the WHAT, WHY, and HOW of contributing to this codebase.
operator-controller is the central component of Operator Lifecycle Manager (OLM) v1, extending Kubernetes with APIs to install and manage cluster extensions. The project follows a microservices architecture with two main binaries:
operator-controller
- manages
ClusterExtensionandClusterObjectSetCRDs - resolves bundles from configured source
- unpacks bundles and renders manifests from them
- applies manifests with phase-based rollouts
- monitors extension lifecycle
catalogd
- manages user-defined
ClusterCatalogresources to make references catalog metadata available to the cluster. - unpacks and serves operator catalog content via HTTP.
- serves catalog metadata to clients in the cluster that need to use or present information about the contents of the catalog. For example, operator-controller queries catalogd for available bundles.
Languages:
- Go: The Go version used by the project often lags the latest upstream available version in order to give integrators the ability to consume the latest versions of OLMv1 without being required to also consume the latest versions of Go.
- Runtime Platform: Linux containers (multi-arch: amd64, arm64, ppc64le, s390x)
- Developer Platform: Generally macOS and Linux. It is important that all shell commands used in Makefiles and other helper scripts work on both Linux and macOS.
Core Frameworks:
- Kubernetes: client-go, api, apimachinery
- controller-runtime
- operator-framework/api: For OLMv0 API types that are relevant to OLMv1
- operator-registry For file-based catalog (FBC) processing
- Helm: helm-operator-plugins (which depends on helm itself)
Key Dependencies:
- cert-manager
- boxcutter (package-operator.run)
Container Base:
- Base image:
gcr.io/distroless/static:nonroot - User:
65532:65532(non-root)
Build Tags:
containers_image_openpgp- required for image handling
Tools (managed via .bingo/):
- controller-gen, golangci-lint, goreleaser, helm, kind, kustomize, mockgen, setup-envtest, operator-sdk
Test Mocking:
- gomock (
go.uber.org/mock): Used for generated mock implementations.//go:generate mockgendirectives live ininternal/testutil/mock/generate.go(external and internal exported interfaces) and at interface definitions for internal unexported interfaces (source mode). Runmake generate-mocksto regenerate. All interface mocks should be gomock-generated. Test fakes that return preconfigured values without interaction verification (e.g.,FakePuller,FakeCacheinimage/fakes.go) are not mocks and stay hand-written.
# Build for local platform
make build
# Build for Linux (required for docker)
make build-linux
# Build docker images
make docker-build
# Full release build
make release# Unit tests (uses ENVTEST)
make test-unit
# E2E tests
make test-e2e # Standard features
make test-experimental-e2e # Experimental features
make test-extension-developer-e2e # Extension developer workflow
# E2E tests in parallel (runs standard and experimental on separate KIND clusters)
# Recommended when machine has enough resources (4+ CPU cores, 8+ GB RAM) for faster feedback
make -j2 test-e2e test-experimental-e2e
# Linux prerequisite: sudo sysctl fs.inotify.max_user_instances=512
# Regression tests
make test-regression
# All (non-upgrade, non-experimental) tests
make test# Set up a persistent e2e cluster (does not tear down after tests)
make e2e-setup # Standard features
make experimental-e2e-setup # Experimental features
# Run e2e scenarios against the running cluster
make e2e/install # All scenarios in install.feature
make e2e/install/Install # Scenarios starting with "Install"
make "e2e/install/Install latest" # Exact prefix with spaces
make e2e/install E2E_TIMEOUT=30m # Override timeout
make e2e/install KUBECONFIG=~/.kube/config # Override kubeconfig
# Run against experimental cluster (override KUBECONFIG)
make e2e/install/Install KUBECONFIG=.kubeconfig/operator-controller-experimental-e2e.kubeconfig
# Tear down the e2e cluster when done
make e2e-teardown # Standard cluster
make experimental-e2e-teardown # Experimental cluster# Run golangci-lint
make lint
# Run helm lint
make lint-helm
# Verify all generated code is up-to-date
make verify
# Format code
make fmt
# Fix lint issues automatically
make fix-lint# Create kind cluster and deploy
make run # Standard manifest
make run-experimental # Experimental manifest
# OR step by step:
make kind-cluster # Create cluster
make docker-build # Build images
make kind-load # Load into kind
make kind-deploy # Deploy manifests
make wait # Wait for ready
# Clean up
make kind-clean# Generate CRDs and manifests
make manifests
# Update CRDs and reference docs (when Go-based API definitions change)
make update-crds crd-ref-docs
# Generate code (DeepCopy methods and mock implementations)
make generate
# Regenerate mock implementations only
make generate-mocks/
├── api/v1/ # API definitions (CRD types)
├── cmd/ # Main entry points
│ ├── operator-controller/ # Operator controller binary
│ └── catalogd/ # Catalogd binary
├── internal/ # Private implementation
│ ├── operator-controller/ # Operator controller internals
│ ├── catalogd/ # Catalogd internals
│ └── shared/ # Shared utilities
├── helm/ # Helm charts
│ ├── olmv1/ # Main OLM v1 chart
│ │ ├── base/ # Base manifests & CRDs
│ │ ├── templates/ # Helm templates
│ │ └── values.yaml # Default values
├── testdata/
│ └── prometheus/ # kube-prometheus-stack values for e2e monitoring
├── test/ # Test suites
│ ├── e2e/ # End-to-end tests (see test/e2e/README.md)
│ ├── extension-developer-e2e/ # Extension developer tests
│ ├── upgrade-e2e/ # Upgrade tests
│ └── regression/ # Regression tests
├── docs/ # Documentation (mkdocs)
├── hack/ # Scripts and tools
├── config/samples # Example manifests for ClusterCatalog and ClusterExtension
├── manifests/ # Generated manifests
├── .github/workflows/ # CI/CD workflows
├── OWNERS # Defines approver and reviewer groups
└── OWNERS_ALIASES # Defined group membership
- Controllers:
{resource}_controller.go - Tests:
{name}_test.go - Internal packages: lowercase, no underscores
- Generated files:
zz_generated.*.go - CRDs:
{group}.{domain}_{resources}.yaml
- Primary CRDs:
ClusterExtension- declares desired extension installationsClusterObjectSet- revision management (experimental)ClusterCatalog- catalog source definitions
- API domain:
olm.operatorframework.io- This is the API group of our user-facing CRDs
- This is also the domain that should be used in ALL label and annotation prefixes that are generated by OLMv1)
- API version:
v1
Two manifest variants exist:
- Standard: Production-ready features
- Experimental: Features under development/testing (includes
ClusterObjectSetAPI)
API changes in api/v1/*_types.go must follow the OpenShift API conventions in addition to upstream Kubernetes API conventions.
After modifying API types, run make generate manifests crd-ref-docs to regenerate DeepCopy/ApplyConfiguration code, CRDs, reference docs, and manifests. Run make lint-api-diff to validate changes against kube-api-linter.
Deprecating fields: Follow Go and OpenShift conventions (see openshift/api for examples):
type ExampleConfig struct {
// value holds some value.
Value string `json:"value"`
}
// Deprecated: structField is no longer used and will be removed in a future release.
// Explanation of why and what replaces it.
//
// +optional
StructField ExampleConfig `json:"structField,omitzero"`
// Deprecated: scalarField is no longer used and will be removed in a future release.
// Explanation of why and what replaces it.
//
// +optional
ScalarField string `json:"scalarField,omitempty"`- Use
Deprecated:with capital D and colon (Go convention, recognized by godoc and staticcheck) - Use the lowercase JSON field name (camelCase) in doc comments, not the Go PascalCase identifier — doc comments are surfaced to end users via
oc explainand generated API docs - Deprecated fields must be
+optional, never+required - Do not use pointers for optional struct fields (OpenShift convention); use value types with
omitzero - Use
omitzeroin the JSON tag for struct types,omitemptyfor scalar types (string, int, bool) - For intentional reads of deprecated fields (e.g. deprecation warnings), add
//nolint:staticcheckwith a reason
- Main branch:
main(default, protected) - Release branches:
release-v{MAJOR}.{MINOR}(e.g.,release-v1.2) - Feature branches: Created from
main, usually in forks, merged via PR
<High-level description>
<Detailed description>
- Must pass all CI checks (unit-test, e2e, sanity, lint)
- Must have both
approvedandlgtmlabels (from repository approvers and reviewers) - DCO sign-off required (Developer Certificate of Origin)
- Reasonable title and description
- Draft PRs: prefix with "WIP:" or use GitHub draft feature
- PR title must use specific prefix based on the type of the PR:
- ⚠ (:warning:, major/breaking change)
- ✨ (:sparkles:, minor/compatible change)
- 🐛 (:bug:, patch/bug fix)
- 📖 (:book:, docs)
- 🌱 (:seedling:, other)
unit-test.yaml- Unit tests with coveragee2e.yaml- Multiple e2e test suites (7 variants)sanity.yaml- Verification, linting, helm lintingtest-regression.yaml- Regression testsgo-apidiff.yaml- API compatibility checkscrd-diff.yaml- CRD compatibility verificationrelease.yaml- Automated releases on tags
- Semantic versioning:
vMAJOR.MINOR.PATCH - Tags trigger automated release via goreleaser
- Patch releases from
release-v*branches - Major/minor releases from
mainbranch - Creates multi-arch container images
- Generates release manifests and install scripts
Schema Files:
/api/v1/zz_generated.deepcopy.go- Generated by controller-gen/helm/olmv1/base/*/crd/standard/*.yaml- Standard CRDs (generated)/helm/olmv1/base/*/crd/experimental/*.yaml- Experimental CRDs (generated)- Process: Modify types in
/api/v1/*_types.go, then runmake manifests
Generated Manifests:
/manifests/standard.yaml- Generated bymake manifests/manifests/experimental.yaml- Generated bymake manifests/manifests/standard-e2e.yaml- Generated bymake manifests/manifests/experimental-e2e.yaml- Generated bymake manifests- Process: Modify Helm charts in
/helm/olmv1/, then run `make manifests
Generated Docs:
/docs/api-reference/olmv1-api-reference.md- Generated bymake crd-ref-docs- Process: Requires regeneration whenever API definitions change (
api/*/*_types.go)
Never modify without explicit permission:
/.github/workflows/*.yaml- CI pipelines (consult team)/.goreleaser.yml- Release configuration/Makefile- Core build logic (discuss changes first)/go.mod- Dependencies (usemake tidy, avoid Go version bumps without discussion)/PROJECT- Kubebuilder project config/OWNERS&/OWNERS_ALIASES- Maintainer lists/mkdocs.yml- Documentation site config/.bingo/*.mod- Tool dependencies (managed by bingo)/.golangci.yaml- Linter configuration/kind-config.yaml- Kind cluster config
/helm/olmv1/Chart.yaml- Chart metadata/helm/olmv1/values.yaml- Default values/helm/olmv1/templates/*.yaml- Chart templates
/DCO- Developer Certificate of Origin/LICENSE- Apache 2.0 license/codecov.yml- Code coverage config
- Never commit to
maindirectly - always use PRs - CRD changes require running
make update-crdsand may break compatibility - API changes in
/api/v1/trigger CRD and CRD reference docs regeneration - Generated files must be committed after running generators
- Helm changes require
make manifeststo update manifests - Go version changes need community discussion (see CONTRIBUTING.md)
- Dependencies: 2-week cooldown policy for non-critical updates
- Make changes to source code
- Run
make verifyto ensure generated code is updated - Run
make lintandmake test-unit - Commit both source and generated files
- CI will verify everything is in sync
operator-controller:
ClusterExtensioncontroller - manages extension installationsClusterObjectSetcontroller - manages revision lifecycle- Resolver - bundle version selection
- Applier - applies manifests to cluster
- Content Manager - manages extension content
catalogd:
- Catalog controllers - manage catalog unpacking
- Storage - catalog storage backend
- Server utilities - HTTP server for catalog content
Last Updated: 2025-12-10