This guide provides instructions for contributing to the OpenShift Lightspeed Operator.
For architecture details, see ARCHITECTURE.md
For coding conventions, see AGENTS.md
The operator uses a modular, component-based architecture:
- Each component (appserver, lcore, postgres, console) is a self-contained package
- Components use the
reconciler.Reconcilerinterface (no circular dependencies) - Task-based reconciliation pattern (list of tasks executed sequentially)
- Two resource approaches: Owned (operator-created, ResourceVersion tracking) vs External (user-provided, data comparison)
Best way to learn: Read existing component code (appserver/, postgres/, console/, lcore/)
Follow these steps to add a new component. Use existing components as reference implementations.
mkdir -p internal/controller/mycomponent
cd internal/controller/mycomponent
touch reconciler.go assets.go suite_test.go reconciler_test.go assets_test.goFile: reconciler.go
Structure:
- Package doc comment explaining what the component does
- Main function:
ReconcileMyComponent(reconciler.Reconciler, context, *OLSConfig) error - Task-based pattern: list of
ReconcileTaskstructs - Individual
reconcile<Resource>functions for each Kubernetes resource
Pattern:
func ReconcileMyComponent(r reconciler.Reconciler, ctx context.Context, cr *olsv1alpha1.OLSConfig) error {
tasks := []utils.ReconcileTask{
{Name: "reconcile ConfigMap", Task: reconcileConfigMap},
{Name: "reconcile Deployment", Task: reconcileDeployment},
{Name: "reconcile Service", Task: reconcileService},
}
for _, task := range tasks {
if err := task.Task(r, ctx, cr); err != nil {
return fmt.Errorf("%s: %w", task.Name, err)
}
}
return nil
}Reference: See appserver/reconciler.go, postgres/reconciler.go, or lcore/reconciler.go
File: assets.go
Structure:
- One
generate<Resource>()function per Kubernetes resource - Each function returns the resource and error
- Always set owner references with
controllerutil.SetControllerReference() - Use
utils.DefaultLabels()for consistent labeling
Reference: See appserver/assets.go, postgres/assets.go, or lcore/assets.go
File: internal/controller/utils/constants.go
Add:
- Resource names:
MyComponentDeploymentName,MyComponentServiceName, etc. - Error constants:
ErrGenerateMyComponent,ErrCreateMyComponent, etc.
Reference: See existing constants in utils/constants.go
File: suite_test.go
Setup:
- Use
envtestfor test environment - Create test namespace
- Use
utils.NewTestReconciler()helper - BeforeSuite/AfterSuite for setup/teardown
Reference: Copy structure from appserver/suite_test.go or postgres/suite_test.go
Files: reconciler_test.go, assets_test.go
Pattern:
- Use Ginkgo/Gomega BDD style
- Test resource generation (assets_test.go)
- Test reconciliation (reconciler_test.go)
- Use
utils.GetDefaultOLSConfigCR()for test fixtures
Reference: See appserver/reconciler_test.go, postgres/assets_test.go
File: internal/controller/olsconfig_controller.go
- Import your component package
- Add to reconciliation steps in
Reconcile()method - Add status condition type to
utils/constants.go
Reference: See how appserver, postgres, console are integrated
File: internal/controller/reconciler/interface.go
Add getter methods if your component needs specific configuration (e.g., GetMyComponentImage()).
Implement in internal/controller/olsconfig_controller.go.
make test # ALWAYS use make test, NEVER go test directlyThe OLM bundle needs regeneration when changes affect how the operator is deployed or what permissions it needs.
Bundle Update Required:
- RBAC Changes: Modified
//+kubebuilder:rbacmarkers in Go code OR changed files inconfig/rbac/ - CRD Changes: Modified API types in
api/v1alpha1/olsconfig_types.go - Image Changes: New operator image, new operand images (appserver, lcore, postgres, console), or image version changes
- CSV Metadata: Changed operator description, keywords, maintainers, links, or other metadata
Bundle Update NOT Required:
- Reconciliation logic changes (internal/controller/)
- Test changes (*_test.go files)
- Documentation updates (*.md files)
- Internal utilities (utils/ package)
- Bug fixes that don't affect RBAC, CRD, or images
1. For RBAC or CRD Changes:
# Regenerate manifests (updates config/rbac/ and config/crd/)
make manifests
# Regenerate bundle (updates bundle/ directory)
make bundle
# Validate bundle
operator-sdk bundle validate ./bundle2. For Image Changes:
# Update related_images.json with new image references
# (This file defines all images used by the operator and operands)
# Regenerate bundle with image updates
make bundle RELATED_IMAGES_FILE=related_images.json
# Validate bundle
operator-sdk bundle validate ./bundle3. For CSV Metadata Changes:
# Edit bundle/manifests/lightspeed-operator.clusterserviceversion.yaml directly
# OR edit the CSV template if one exists
# Regenerate bundle to sync changes
make bundle
# Validate bundle
operator-sdk bundle validate ./bundleWhen you run make bundle, it updates:
bundle/manifests/lightspeed-operator.clusterserviceversion.yaml- Main operator metadatabundle/manifests/ols.openshift.io_olsconfigs.yaml- CRD definitionbundle/manifests/*_rbac.authorization.k8s.io_*.yaml- RBAC resourcesbundle/metadata/annotations.yaml- Bundle metadata
# Validate bundle structure and metadata
operator-sdk bundle validate ./bundle
# Test deployment with updated bundle (optional)
make deploy # Deploys using manifests
# Test functionality
make undeploy- "invalid bundle" errors: Run
make manifestsbeforemake bundle - Missing RBAC permissions: Ensure
//+kubebuilder:rbacmarkers are in reconciler code - Image references not updated: Check
related_images.jsonhas correct image URLs - CSV validation fails: Check CSV syntax with
operator-sdk bundle validate
π For detailed bundle workflows, version management, and catalog publishing, see docs/olm-bundle-management.md
Update ARCHITECTURE.md Component Overview section with brief description of your component.
- Add generation function in
assets.go:generate<Resource>() - Add reconciliation function in
reconciler.go:reconcile<Resource>() - Add to task list in main
Reconcile<Component>()function - Add constants in
utils/constants.go - Add tests in
assets_test.goandreconciler_test.go
Reference: Search existing code for similar resources (e.g., generateConfigMap or reconcileService)
User-provided secrets/configmaps (not owned by operator):
Use iterator helpers:
err := utils.ForEachExternalSecret(cr, func(name string, source string) error {
// Process secret
return nil
})
err = utils.ForEachExternalConfigMap(cr, func(name string, source string) error {
// Process configmap
return nil
})Benefits: Centralizes traversal, handles annotation, prevents duplication
If your component needs to watch system resources (not from CR), update cmd/main.go:
watcherConfig := &utils.WatcherConfig{
Secrets: utils.SecretWatcherConfig{
SystemResources: []utils.SystemSecret{
{
Name: "my-system-secret",
Namespace: namespace,
Description: "Description for debugging",
AffectedDeployments: []string{"ACTIVE_BACKEND"},
},
},
},
}Reference: See existing WatcherConfig in cmd/main.go
π See ARCHITECTURE.md for owned vs external resources
CRITICAL: ALWAYS use make test - NEVER use go test directly!
The Makefile handles essential setup (envtest, CRDs, build flags, coverage) that go test doesn't.
make test # Run all unit tests
go tool cover -html=cover.out # View coverage reportmake lint # Check linting
make lint-fix # Fix lint issuesexport KUBECONFIG=/path/to/kubeconfig
export LLM_TOKEN=your-token
make test-e2emake run # Run controller locally (auto-setup RBAC, CRDs, namespace)make docker-build
make deploy
oc apply -f config/samples/ols_v1alpha1_olsconfig.yaml
oc logs -n openshift-lightspeed deployment/lightspeed-operator-controller-manager -fIf you modified RBAC, CRD, images, or CSV:
operator-sdk bundle validate ./bundleπ For comprehensive OLM testing, see docs/olm-testing-validation.md
- Functions:
reconcile<Resource>,generate<Resource> - Constants:
<Component><Resource>Name,Err<Action><Resource> - Files:
reconciler.go,assets.go,suite_test.go - Tests:
reconciler_test.go,assets_test.go
return fmt.Errorf("%s: %w", utils.ErrConstant, err)r.GetLogger().Info("action", "key", value)
r.GetLogger().Error(err, "description", "key", value)controllerutil.SetControllerReference(cr, resource, r.GetScheme())It("should do something", func() {
// Arrange
cr := utils.GetDefaultOLSConfigCR()
// Act
err := ReconcileMyComponent(testReconcilerInstance, ctx, cr)
// Assert
Expect(err).NotTo(HaveOccurred())
})When writing tests that create/delete OLSConfig CRs, always use the cleanupOLSConfig() helper from suite_test.go:
AfterEach(func() {
cleanupOLSConfig(ctx, cr) // Removes finalizers and waits for deletion
})Why: The operator adds a finalizer to OLSConfig CRs. Without proper cleanup:
- Tests will fail with "already exists" errors
- CRs remain stuck in Terminating state
- Race conditions between test suites
Don't:
_ = k8sClient.Delete(ctx, cr) // β Won't complete due to finalizerDo:
cleanupOLSConfig(ctx, cr) // β
Removes finalizer, waits for deletionSee: olsconfig_finalizer_test.go for examples of testing finalizer behavior
Before implementing new functionality:
- Check
utils/for existing helpers - Use iterator patterns:
ForEachExternalSecret(),ForEachExternalConfigMap() - Use constants:
VolumeDefaultMode,VolumeRestrictedMode - Use helpers:
GetResourcesOrDefault()
- Package docs explaining purpose
- Function docs for public functions
- Inline comments for complex logic
Reference: Read existing component code for patterns
For operators deployed via OLM, see comprehensive guides in docs/:
- OLM Bundle Management
- OLM Catalog Management
- OLM Integration & Lifecycle
- OLM Testing & Validation
- OLM RBAC & Security
- ARCHITECTURE.md - Architecture and design decisions
- AGENTS.md - Coding conventions and patterns
- Operator SDK Documentation
- Kubebuilder Book
- Ginkgo Testing Framework
- Check existing components as reference implementations
- Review test files for testing patterns
- Ask questions in pull requests or issues
- Run tests:
make test - Check linting:
make lint - Update documentation if needed:
- ARCHITECTURE.md: If you changed component structure or design decisions
- AGENTS.md: If you changed architectural patterns, conventions, or critical rules (e.g., new naming patterns, error handling approaches, testing requirements)
- CONTRIBUTING.md: If you changed development workflows or added new processes
- Create pull request with clear description
- Ensure CI passes
Thank you for contributing to OpenShift Lightspeed Operator! π