This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Build everything (operator binary + OTE test binary)
make build
# Run unit tests
make test-unit
# Run a single unit test
go test ./pkg/operator/ -run TestSyncName -count 1
# Run e2e tests (requires cluster access)
make test-e2e
# Verify all generated files
make verify
# Update all generated files
make update
# Update vendored dependencies
go mod tidy && go mod vendorThis is an OpenShift ClusterOperator with a two-process architecture running from a single binary (service-ca-operator):
- Package:
pkg/operator/ - Has two main responsibilities:
- Operand lifecycle: Manages the controller Deployment in the
openshift-service-canamespace, syncing static resources (namespace, service account, RBAC, deployment) from embedded assets - Signing CA management: Creates and rotates the signing CA keypair (Secret) and maintains the CA bundle ConfigMap
- Operand lifecycle: Manages the controller Deployment in the
- Reports the "service-ca" ClusterOperator status conditions
- Package:
pkg/controller/ - Started by the operator as a Deployment; runs three controllers:
- Serving Cert Signer (
pkg/controller/servingcert/): Signs TLS serving certs for Services annotated withservice.beta.openshift.io/serving-cert-secret-name - ConfigMap CA Bundle Injector (
pkg/controller/cabundleinjector/configmap.go): Injects the CA bundle into ConfigMaps annotated withservice.beta.openshift.io/inject-cabundle=true - Generic CA Bundle Injector (
pkg/controller/cabundleinjector/): Injects the CA bundle into APIServices, MutatingWebhookConfigurations, ValidatingWebhookConfigurations, and CRDs with the same annotation
- Serving Cert Signer (
Static resource manifests are embedded using Go's native embed.FS:
- Asset location:
bindata/assets/*.yaml - Embed declaration:
bindata/assets.go(uses//go:embed assets/*.yaml) - Usage:
bindata.MustAsset("assets/<filename>.yaml")to read asset bytes at runtime - No code generation step is required — assets are embedded at compile time
Users interact with the controller by annotating their resources. The annotation constants are defined in pkg/controller/api/api.go.
service.beta.openshift.io/serving-cert-secret-name=<secret-name>— Set on a Service to request a TLS serving cert. The controller creates a Secret with the given name containingtls.crtandtls.keysigned by the service CA.service.beta.openshift.io/inject-cabundle=true— Set on a ConfigMap, APIService, CRD, MutatingWebhookConfiguration, or ValidatingWebhookConfiguration to inject the service CA bundle. For ConfigMaps the bundle is written to theservice-ca.crtdata key; for the others it is set in the appropriatecaBundlespec field.
Legacy service.alpha.openshift.io equivalents of both annotations are also supported.
Vulnerable legacy injection: The annotation service.alpha.openshift.io/inject-vulnerable-legacy-cabundle is a special case added in 4.8 (PR #167) to support clusters upgraded from pre-4.7. On those clusters, the openshift-service-ca.crt ConfigMap in each namespace was published by kube-controller-manager (via the OPENSHIFT_USE_VULNERABLE_LEGACY_SERVICE_CA_CRT env var) and contained more certificates than just the service-serving CA — it included the full trust bundle that was historically embedded in SA tokens. The legacy injector (LegacyVulnerableConfigMapCABundleInjector in pkg/controller/cabundleinjector/configmap.go) is intentionally scoped: it only injects into ConfigMaps named exactly openshift-service-ca.crt, and it yields to the preferred inject-cabundle or alpha inject-cabundle annotations if either is present. Key details:
- Cannot be enabled on new clusters. Validation introduced in 4.8 prevents changing the
KubeControllerManagercluster config'suseMoreSecureServiceCAfrom"true"to"false". The only way to reproduce the legacy annotation is to start with a 4.7 install and upgrade. - Deprecated but not removed. The annotation is fully supported for upgraded clusters that already have it, but it is a known vulnerability and customers are advised to migrate their workloads off of it. It may be removed in a future release.
Feature gates must not be detected at runtime in the controller process. MicroShift does not have the ClusterVersion or FeatureGate CRDs, so creating informers for them causes the controller to crash (see OCPBUGS-82110).
Instead, the operator detects feature gates via the standard FeatureGateAccess mechanism and forwards enabled gates to the controller Deployment as --feature-gates=Key=true CLI args (pkg/operator/sync_common.go). The controller receives them as a map[string]bool via pkg/cmd/controller/cmd.go and threads the map through the call chain. This means adding a new feature gate does not require changing function signatures — just check the map key where needed.
openshift-service-ca-operator: Where the operator runsopenshift-service-ca: Where the controller Deployment runs
- Resource names:
pkg/controller/api/resourcenames.go - Namespace constants:
pkg/operator/operatorclient/interfaces.go
PRs should separate code changes from generated/vendored artifacts:
Code commits — one or more commits with source code changes. Exclude go.mod, go.sum, and vendor/ from these commits.
Vendor commit (if needed) — a single final commit containing all vendored changes (go.mod, go.sum, vendor/). Commit message: vendor: bump(*). If a PR has no dependency changes, this commit is not needed.
Always base commits on upstream/main, not origin/main.
- Unit tests are colocated with source in
pkg/ - E2e tests are in
test/e2e/and use the OpenShift Tests Extension (OTE) framework - New e2e tests must be added to
test/e2e/e2e.go(OTE format), nottest/e2e/e2e_test.go(legacy — being phased out) - E2e tests require a running OpenShift cluster with
KUBECONFIGset - The OTE test binary is
service-ca-operator-tests-ext
- Uses
openshift/library-gocontroller framework (controllercmd), not raw controller-runtime.