Release Service is a Kubernetes operator that orchestrates software release pipelines in Konflux CI. It watches Release CRs, validates them, creates Tekton PipelineRuns, tracks their progress, and records results.
- Language: Go
- Framework: controller-runtime
- CRDs: Release, ReleasePlan, ReleasePlanAdmission, ReleaseServiceConfig
- Pipeline engine: Tekton PipelineRuns
- Testing: Ginkgo/Gomega + envtest (local K8s API server)
- Build:
make test(unit),make manifests generate(codegen)
api/v1alpha1/ # CRD types and webhooks
controllers/ # Reconcilers: release/, releaseplan/, releaseplanadmission/
loader/ # ObjectLoader interface — abstracts K8s resource fetching
syncer/ # Cross-namespace Snapshot syncing
tekton/ # PipelineRun builders, status helpers, watch predicates
metadata/ # Label/annotation/finalizer constants and utilities
metrics/ # Prometheus gauges, histograms, counters for release lifecycle
config/ # Kustomize manifests (CRDs, RBAC, webhooks, samples)
main.go # Entry point — registers controllers and webhooks
Each controller delegates to an adapter (adapter.go) that holds the K8s client, resource under reconciliation, an ObjectLoader, and a Syncer. All domain logic lives in adapter methods, not in the controller.
The Release controller runs ~20 sequential operations via controller.ReconcileHandler() from operator-toolkit. Each returns (OperationResult, error) — failure requeues, success continues. The pipeline stages are:
- Finalizer management and config loading
- Validation (author, application, pipeline source, Enterprise Contract)
- Pipeline processing in order: tenant collectors, managed collectors, tenant, managed, final
- Completion and cleanup
PipelineRuns are watched via EnqueueRequestForAnnotation — when a PipelineRun updates, the owning Release is re-reconciled.
loader.ObjectLoader centralizes all K8s Gets with error classification (retriable vs permanent) and KubeArchive fallback for deleted resources. A mock implementation exists for tests.
- Git: conventional commits with Jira ticket as scope —
type(RELEASE-NNNN): description(e.g.feat(RELEASE-2119): add failure inspection) - Follow Kubernetes coding conventions
- Log via
ctrl.LoggerFrom(ctx), wrap errors withfmt.Errorf("context: %w", err) - API changes: edit types in
api/v1alpha1/, thenmake generate manifests - Controller changes: implement in
controllers/<resource>/adapter.go - Webhooks: add to
api/v1alpha1/webhooks/<resource>/ - Tests: unit tests alongside code using Ginkgo + envtest; E2E in dedicated repos
- Quick package verification:
go vet ./path/to/package/(lint),go build ./path/to/package/(type-check) — faster than fullmake testfor iterating on a single package
- Idempotent operations: every
Ensure*function starts with gate conditions that skip work already done (e.g.HasTenantPipelineProcessingFinished()) or wait for prerequisites (e.g.HasManagedCollectorsPipelineProcessingFinished()), making reconciliation safe to re-run at any point - Patching discipline: status is updated via
client.Status().Patch()withclient.MergeFrom(a.release.DeepCopy())only in state-changing operations (marking phases, registering pipeline data) — read-only and tracking operations must not patch - Error handling:
handlePipelineCreationErrorsplits errors into retriable (requeue) vs permanent (mark phase failed + continue, not stop) — continuing is required so downstream operations can mark their phases as skipped via theIsFailed()gate; stopping would hang the release since status-only patches don't triggerGenerationChangedPredicate - PipelineRunBuilder (
tekton/utils/): fluent API —.WithParams(),.WithWorkspace(),.WithPipelineRef(),.WithTimeouts() - Metadata utilities (
metadata/): prefix-based label filtering, safe-copy helpers that don't clobber existing keys - Metrics: registered during reconciliation —
RegisterNewRelease(),RegisterCompletedRelease(),RegisterValidatedRelease()with start/completion times - Syncer: copies Snapshot metadata to target namespace idempotently (ignores AlreadyExists)