Skip to content

Kubernetes-Interacting Packages ​

Reference documentation for the internal/common/ packages that interact with the Kubernetes API server. These packages provide reconciler building blocks for managing external operator CRDs (MariaDB, External Secrets Operator, cert-manager) and core Kubernetes resources (Deployments, Services, Jobs, CronJobs, ConfigMaps).

All packages share these conventions:

  • Idempotent create-or-update via Server-Side Apply — Ensure* functions apply the desired object through the generic apply.EnsureObject helper, which uses Server-Side Apply under the fixed field manager cobaltcore-operator. The field manager owns only the fields the builder sets, so server-defaulted fields the builder omits are never claimed or overwritten and a converged object is applied without a write. The apply is wrapped in retry.RetryOnConflict, so a benign field-manager conflict is absorbed as an internal retry rather than surfacing as condition noise.
  • Owner references — controllerutil.SetControllerReference is called on every apply (not only on create) so the resource is garbage-collected when the owning CR is deleted and the reference is re-enforced if it drifts.
  • Readiness reporting — Functions that create resources return (bool, error), where true means the resource is ready and false means it exists but is not yet ready.
  • Error wrapping — All errors include context via fmt.Errorf with %w for errors.Is/errors.As compatibility.

Import Paths ​

PackageImport Path
applygithub.com/c5c3/cobaltcore/internal/common/apply
configgithub.com/c5c3/cobaltcore/internal/common/config
databasegithub.com/c5c3/cobaltcore/internal/common/database
deploymentgithub.com/c5c3/cobaltcore/internal/common/deployment
jobgithub.com/c5c3/cobaltcore/internal/common/job
policygithub.com/c5c3/cobaltcore/internal/common/policy
satellitegithub.com/c5c3/cobaltcore/internal/common/satellite
secretsgithub.com/c5c3/cobaltcore/internal/common/secrets
tlsgithub.com/c5c3/cobaltcore/internal/common/tls

External CRD Dependencies ​

These packages import typed Go structs from external operator modules:

OperatorGo ModuleAPI VersionTypes Used
mariadb-operatorgithub.com/mariadb-operator/mariadb-operatorv1alpha1Database, User, Grant
External Secrets Operatorgithub.com/external-secrets/external-secretsv1 (ExternalSecret, ClusterSecretStore), v1alpha1 (PushSecret)ExternalSecret, PushSecret
cert-managergithub.com/cert-manager/cert-managerv1Certificate

Note: The PushSecret API is v1alpha1 (unstable). Its schema may change in future ESO releases. Pin the ESO module version in go.mod to avoid breakage.


Package: apply ​

Provides the generic Server-Side Apply create-or-update primitive that backs the Ensure* family.

EnsureObject ​

go
func EnsureObject[T client.Object](
    ctx context.Context,
    c client.Client,
    scheme *runtime.Scheme,
    owner client.Object,
    obj T,
    fieldManager string,
) error

Creates or updates obj via Server-Side Apply under fieldManager and sets owner as the controller reference.

Behavior:

  • Sets the controller owner reference and stamps the object's GVK (objects built in-code carry an empty TypeMeta, which Server-Side Apply requires).
  • Applies the object with client.FieldOwner(fieldManager) and client.ForceOwnership, wrapped in retry.RetryOnConflict so a benign field-manager conflict is retried internally rather than surfaced.
  • The field manager owns only the fields the builder sets, so server-defaulted fields the builder omits are never claimed and a converged object is applied without a write.
  • Decodes the server response back into obj, so callers may read fresh status (e.g. readiness) without an extra Get.

Field manager: the package exports apply.FieldManager ("cobaltcore-operator"), the stable field-manager name shared by all Ensure* helpers.


Package: config ​

Implements the INI configuration rendering pipeline for CobaltCore operators. The CreateImmutableConfigMap function provides Kubernetes-interacting config management.

CreateImmutableConfigMap ​

go
func CreateImmutableConfigMap(
    ctx context.Context,
    c client.Client,
    scheme *runtime.Scheme,
    owner client.Object,
    baseName, namespace string,
    data map[string]string,
) (string, error)

Creates an immutable ConfigMap with a content-hash suffix appended to the base name. The hash ensures that configuration changes result in new ConfigMap names, triggering pod restarts when the ConfigMap is referenced in a Deployment's volume spec.

Parameters:

NameTypeDescription
ctxcontext.ContextRequest context
cclient.ClientKubernetes API client
scheme*runtime.SchemeScheme for owner reference resolution
ownerclient.ObjectOwning CR for garbage collection
baseNamestringBase name for the ConfigMap (hash is appended as -<hash>)
namespacestringNamespace for the ConfigMap
datamap[string]stringConfigMap data entries

Returns:

ValueDescription
stringActual ConfigMap name including the 8-character SHA256 hash suffix
errorNon-nil on owner reference or API server failure

Behavior:

  • Computes a deterministic SHA256 hash from sorted data keys and values.
  • Truncates the hash to 8 hex characters and appends it as baseName-<hash>.
  • Sets Immutable: true on the ConfigMap.
  • Sets a controller owner reference on the ConfigMap.
  • If a ConfigMap with the same name already exists (AlreadyExists error), returns the name without error (idempotent).
  • Same data always produces the same hash (deterministic).
  • Different data always produces a different hash.

Example:

go
name, err := config.CreateImmutableConfigMap(ctx, client, scheme, owner,
    "keystone-config", "openstack",
    map[string]string{"keystone.conf": renderedINI},
)
// name == "keystone-config-a1b2c3d4"

PruneImmutableConfigMaps ​

go
func PruneImmutableConfigMaps(
    ctx context.Context,
    c client.Client,
    owner client.Object,
    baseName, namespace, currentName string,
    retain int,
) error

Deletes stale immutable ConfigMaps that were previously created by CreateImmutableConfigMap, retaining the newest retain historical ConfigMaps (by CreationTimestamp) plus the currently active one identified by currentName. This prevents unbounded accumulation of immutable ConfigMaps across reconcile cycles.

Parameters:

NameTypeDescription
ctxcontext.ContextRequest context
cclient.ClientKubernetes API client
ownerclient.ObjectOwning CR — only ConfigMaps with a controller owner reference matching this object's UID are considered
baseNamestringBase name prefix for candidate ConfigMaps (matches baseName-*)
namespacestringNamespace to list ConfigMaps in
currentNamestringName of the currently active ConfigMap (never deleted, even with retain=0)
retainintNumber of historical ConfigMaps to keep beyond the current one

Returns:

ValueDescription
errorNon-nil on list or delete failure; nil on success or when no pruning is needed

Algorithm:

  1. Lists ConfigMaps matching the cobaltcore.c5c3.io/config-base label in the namespace.
  2. Filters to ConfigMaps matching the baseName + "-" prefix.
  3. Excludes the ConfigMap named currentName (the active one).
  4. Excludes ConfigMaps without a controller owner reference matching owner.GetUID().
  5. Sorts remaining candidates by CreationTimestamp descending (newest first).
  6. If the number of candidates is less than or equal to retain, returns nil (no-op).
  7. Deletes candidates from index retain onwards (oldest first).
  8. Logs each deletion at info level for auditability.

Idempotency and concurrency safety:

  • Uses client.IgnoreNotFound() on delete operations, so a ConfigMap deleted between the list and delete calls does not cause an error.
  • Calling the function twice with the same state produces the same result.
  • Does not use optimistic locking — concurrent reconcile goroutines may both attempt to delete the same ConfigMap, but IgnoreNotFound makes this safe.

Filtering rules:

ConfigMap StateIncluded in Candidates?
Name matches baseName-* prefix, owned by ownerYes
Name equals currentNameNo (always excluded)
Name does not match baseName-* prefixNo
No owner referenceNo
Owner reference UID does not match ownerNo

Edge cases:

ScenarioResult
No historical ConfigMaps existNo-op, returns nil
Fewer historical ConfigMaps than retainNo-op, returns nil
retain=0All historical ConfigMaps deleted, only currentName survives
ConfigMap deleted between list and deleteNotFound silently ignored
Overlapping prefix (e.g., test-config- vs test-config-extra-)Strict baseName + "-" prefix prevents false matches
Pre-existing ConfigMaps without cobaltcore.c5c3.io/config-base labelNot pruned — invisible to server-side selector. Bounded in number and GC'd on CR deletion via owner reference.

Example:

go
// After creating a new ConfigMap, prune old ones keeping 3 historical:
err := config.PruneImmutableConfigMaps(ctx, client, keystoneCR,
    "keystone-config", "openstack", "keystone-config-a1b2c3d4", 3,
)
// With 5 historical ConfigMaps, the 2 oldest are deleted, 3 newest + current remain.

Package: database ​

Manages MariaDB database resources for CobaltCore operators. Uses typed structs from github.com/mariadb-operator/mariadb-operator/api/v1alpha1.

EnsureDatabase ​

go
func EnsureDatabase(
    ctx context.Context,
    c client.Client,
    scheme *runtime.Scheme,
    owner client.Object,
    db *mariadbv1alpha1.Database,
) (bool, error)

Creates or updates a MariaDB Database CR.

Returns: (true, nil) when the Database has a Ready condition with status True; (false, nil) when it exists but is not yet ready; (false, error) on failure.

Behavior:

  • Applies the desired Database via apply.EnsureObject (Server-Side Apply); the field manager owns only the fields the builder sets, so server defaults on omitted fields (for example maxUserConnections on a User whose provision params leave it unsized) are preserved and a converged Database is not rewritten.
  • Readiness is determined by the package-internal isDatabaseReady helper on the server-fresh apply response.

EnsureDatabaseUser ​

go
func EnsureDatabaseUser(
    ctx context.Context,
    c client.Client,
    scheme *runtime.Scheme,
    owner client.Object,
    user *mariadbv1alpha1.User,
    grant *mariadbv1alpha1.Grant,
) (bool, error)

Creates or updates a MariaDB User CR and a Grant CR in a single call.

Returns: (true, nil) when both User and Grant have Ready conditions with status True; (false, nil) when either is not yet ready; (false, error) on failure.

Behavior:

  • Processes User first, then Grant. If User creation/update fails, Grant is not attempted.
  • Both resources receive controller owner references.
  • Readiness of the User and Grant is determined by the package-internal isUserReady and isGrantReady helpers.

Package: deployment ​

Manages Kubernetes Deployments and Services for CobaltCore operators.

EnsureDeployment ​

go
func EnsureDeployment(
    ctx context.Context,
    c client.Client,
    scheme *runtime.Scheme,
    owner client.Object,
    deploy *appsv1.Deployment,
) (bool, error)

Creates or updates a Deployment.

Returns: (true, nil) when all replicas are available; (false, nil) when the Deployment exists but is not yet ready; (false, error) on failure.

Behavior:

  • Applies the desired Deployment via apply.EnsureObject (Server-Side Apply); a converged Deployment is not rewritten on every reconcile.
  • Retains a pre-apply Get only to delete-and-recreate on an immutable-selector change and to preserve the HPA-owned replica count when spec.deployment.replicas is left nil.
  • Readiness is determined by IsDeploymentReady on the server-fresh apply response.

EnsureService ​

go
func EnsureService(
    ctx context.Context,
    c client.Client,
    scheme *runtime.Scheme,
    owner client.Object,
    svc *corev1.Service,
) error

Creates or updates a Service. Does not report readiness (Services are ready immediately).

Behavior:

  • Applies the desired Service via apply.EnsureObject (Server-Side Apply). The builder leaves server-assigned fields (ClusterIP, ClusterIPs, IPFamilies, NodePort) unset, so the field manager never owns them and the API server keeps the values it assigned — no hand-rolled preservation is needed.
  • Retains a pre-apply Get only to fail fast when the caller explicitly sets one of those immutable fields to a conflicting value.

IsDeploymentReady ​

go
func IsDeploymentReady(deploy *appsv1.Deployment) bool

Pure function. Returns true when both conditions are met:

  1. deploy.Status.ReadyReplicas >= *deploy.Spec.Replicas (defaults to 1 if Spec.Replicas is nil).
  2. The Deployment has an Available condition with status True.

Edge cases:

ScenarioResult
Spec.Replicas is nilDefaults to 1
No Available conditionfalse
ReadyReplicas < desiredfalse
ReadyReplicas >= desired and Available=Truetrue

Package: job ​

Manages Kubernetes Jobs and CronJobs for CobaltCore operators.

RunJob ​

go
func RunJob(
    ctx context.Context,
    c client.Client,
    scheme *runtime.Scheme,
    owner client.Object,
    job *batchv1.Job,
) (bool, error)

Creates a Job if it does not already exist and reports completion status.

Returns: (true, nil) when the Job has a Complete condition with status True and its re-run key is unchanged; (false, nil) when the Job exists but is still running, or was deleted and re-created because its re-run key changed; (false, error) wrapping ErrJobFailed when the Job has permanently failed (e.g. exceeded backoffLimit) and its re-run key is unchanged; (false, error) on unexpected API failures.

Behavior:

  • If the Job does not exist: creates it with a controller owner reference, returns (false, nil) (newly created Jobs are never immediately complete).
  • If the Job already exists: checks completion via the package-internal isJobComplete helper and permanent failure via isJobFailed. A completed or permanently failed Job whose stored re-run key (the cobaltcore.c5c3.io/pod-spec-hash annotation) no longer matches the desired pod template is deleted (background propagation) and re-created — so a Job that failed under a since-fixed spec (new container image, corrected ConfigMap, rotated password) re-runs instead of wedging. A permanently failed Job whose re-run key is unchanged returns an error wrapping ErrJobFailed to prevent infinite requeue loops. Jobs are never updated in place.
  • Reconcilers should call RunJob on each reconciliation loop. The function is idempotent: calling it when the Job already exists and is complete returns (true, nil) without side effects.

EnsureCronJob ​

go
func EnsureCronJob(
    ctx context.Context,
    c client.Client,
    scheme *runtime.Scheme,
    owner client.Object,
    cronJob *batchv1.CronJob,
) error

Creates or updates a CronJob with a controller owner reference.

Behavior:

  • Applies the desired CronJob via apply.EnsureObject (Server-Side Apply); a converged CronJob is not rewritten, and spec changes take effect on the next scheduled run.

The Job completion and permanent-failure checks (isJobComplete / isJobFailed) are package-internal helpers consumed by RunJob.


Package: policy ​

Provides pure functions for OpenStack oslo.policy rule rendering, merging, and validation. The LoadPolicyFromConfigMap function reads policy from Kubernetes ConfigMaps.

LoadPolicyFromConfigMap ​

go
func LoadPolicyFromConfigMap(
    ctx context.Context,
    c client.Client,
    key client.ObjectKey,
) (map[string]string, error)

Reads a ConfigMap by namespace/name and extracts the policy.yaml key as a map[string]string of oslo.policy rules.

Parameters:

NameTypeDescription
ctxcontext.ContextRequest context
cclient.ClientKubernetes API client
keyclient.ObjectKeyNamespace and name of the ConfigMap

Returns:

ValueDescription
map[string]stringParsed policy rules (action → rule expression)
errorNon-nil when ConfigMap is missing, key is absent, or YAML is invalid

Error conditions:

ConditionError
ConfigMap does not existWrapped API server error (compatible with apierrors.IsNotFound)
policy.yaml key absentConfigMap <key> does not contain key "policy.yaml"
Invalid YAML contentparsing policy.yaml from ConfigMap <key>: <parse error>

Example:

go
rules, err := policy.LoadPolicyFromConfigMap(ctx, client,
    types.NamespacedName{Namespace: "openstack", Name: "keystone-policy"},
)
// rules == map[string]string{"identity:get_user": "role:admin", ...}

Package: satellite ​

Holds the mechanics shared by the satellite-CRD controllers and by the parent reconcilers that aggregate them. A satellite CR is a namespaced configuration CR attached to a parent service CR by name, and it observes its own effect through the Deployment the parent runs. GlanceBackend, CinderBackend and BarbicanSecretStore consume resolve, observe and collect. CinderBackupBackend consumes resolve, collect and the volume lookup, and KeystoneIdentityBackend the volume lookup alone.

The condition vocabulary, the messages, the events and the requeue intervals stay in the operator. Each helper either hands a decision back for the operator to stamp, or takes the condition texts as parameters.

The handshake ​

A satellite and its parent settle in five steps. The figure draws them, and its numbers are the steps below.

The handshake between a satellite resource and the service it attaches to, in five numbered steps across two controllers. 1: the satellite controller checks the credentials and sets CredentialsReady on the satellite. 2: the aggregation step of the service controller reads only that condition. 3: it renders one section per satellite that passed into a Secret whose name carries a hash of its content. 4: the pod template of the service's Deployment mounts that Secret, and a new name rolls the pods. 5: the satellite controller finds its section in the mounted Secret and sets ConfigProjected. Ready turns True once both conditions are. An arrow marked never runs from Ready to the aggregation step: reading Ready there would deadlock, because Ready needs ConfigProjected, which needs that step. On a KeystoneIdentityBackend the gate is DomainReady, and ConfigProjected also waits until the rollout has finished.

  1. The satellite controller resolves the parent the satellite names, checks what the satellite needs, and sets the gate condition on the satellite: CredentialsReady.
  2. The aggregation step in the parent's pipeline lists the satellites that name the parent and reads that one condition of each. A satellite whose gate is not True is skipped, and the pipeline goes on.
  3. The step renders one section per satellite that passed into a Secret whose name carries a hash of its content.
  4. The pod template of the parent's Deployment mounts that Secret. A new Secret name is a new pod template, so the pods roll.
  5. The satellite controller reads the pod template, follows the volume to the Secret, and finds its own section there. It sets ConfigProjected, and the aggregate Ready follows once both conditions are True.

The step never reads a satellite's Ready. Ready needs ConfigProjected, and ConfigProjected turns True only after the step has projected the satellite, so a step that waited for Ready would wait for itself. Another controller may gate on Ready: the c5c3-operator does, for the websso choices it projects onto Horizon.

No status field of the parent carries the projection. The Deployment and the Secret are the evidence, and while ConfigProjected is False the satellite controller polls for it every 15 seconds, every 30 on a BarbicanSecretStore.

KindGate the step readsWhat the gate needsRendered intoWorkloadEvidence for ConfigProjected
GlanceBackendCredentialsReadyThe Secret of spec.s3.credentialsSecretRef with both S3 keysKey backends.conf of the Secret {glance}-backends-{hash}Deployment {glance}The header [{name}] in the mounted Secret
CinderBackendCredentialsReadyNothing for NFS, where it is True/CredentialsNotRequired once the parent resolves; the Secret of spec.rbd.keySecretRef carrying a non-empty base64 userKey for RBDThe Secret {cinder}-backend-{name}-{hash}, one per backendDeployment {cinder}-volume-{name}, one per backendThe header [{name}] in the key backend.conf
CinderBackupBackendCredentialsReadyNothing for NFS, where it is True/CredentialsNotRequired once the parent resolves; the Secret of spec.rbd.keySecretRef carrying a non-empty base64 userKey for RBDKey backup.conf of a Secret named after the backendDeployment {cinder}-backupThe name of the Secret its backup volume references. The content is not read
BarbicanSecretStoreCredentialsReadyThe credentials Secret and a login the OpenBao server accepts. A store the operator provisions sets ProvisioningReady first, and the step does not read thatThe section [secretstore:{name}] of barbican.conf in the Secret {barbican}-config-{hash}Deployment {barbican}The section header in the mounted Secret
KeystoneIdentityBackendDomainReadyThe Keystone API answering, the admin password Secret, and the domain created or adoptedkeystone.{domain}.conf in the Secret {keystone}-domains-{hash}; the OIDC and SAML documents in {keystone}-federation-{hash}Deployment {keystone}The data key in the mounted Secret, and the rollout finished

KeystoneIdentityBackend is the exception by design. Its gate needs the Keystone API, so the parent's Deployment already runs when step 1 passes, and its ConfigProjected has a third state, WaitingForRollout.

ResolveParentChildren ​

go
func ResolveParentChildren(
    ctx context.Context,
    p ResolveParams,
) (children client.Client, result ctrl.Result, err error)

Returns the client a satellite's observations belong on, after reading its parent and resolving the cluster that parent targets.

Behavior:

  • ResolveParams.Client reads the parent on the management cluster into Parent at ParentKey; ParentKind names the kind in the error text. TargetClusterRef then reads the ref off Parent, and Resolver looks the cluster up. A nil resolver keeps everything local. Conditions land in the satellite's own Conditions slice at Generation.
  • A non-NotFound Get on the parent returns a nil client and fetching parent <kind> <key>: <cause>.
  • A parent that does not exist sets GateConditionType to False with WaitingForParentReason and WaitingForParentMessage, and requeues after RequeueAfter. There is no fallback to the local client: the parent's targetClusterRef is what decides which cluster the satellite's credentials and observations belong on, so a local guess would read the wrong cluster for the whole window between a GitOps apply landing the satellite and the same apply landing its parent.
  • The projection demotion is opt-in. With ProjectionConditionType set, a missing parent also flips that condition to False under ProjectionDemotionReason and ProjectionDemotionMessage, but only where it currently stands True. An empty type demotes nothing, and an absent or already False condition is left untouched, so a satellite that has not converged yet keeps the condition absent.
  • A target cluster that does not resolve sets GateConditionType to False under the shared multicluster.TargetClusterUnavailable reason with the resolver's error text, and requeues after RequeueAfter.
  • On success the children client is non-nil and the result is the zero ctrl.Result. A nil children client means the caller returns result and err as they are.

SecretNameForVolume ​

go
func SecretNameForVolume(spec *corev1.PodSpec, volumeName string) string

Pure function. Returns the name of the Secret a pod-spec volume mounts.

Behavior:

  • Returns "" for a nil spec and for a volume name absent from spec.Volumes.
  • The scan stops at the first name match. A ConfigMap- or emptyDir-backed volume of the searched name yields "" and the walk does not continue to the next volume that happens to carry a Secret.

SectionPresent ​

go
func SectionPresent(rendered []byte, header string) bool

Pure function. Reports whether rendered carries header as a whole line.

Behavior:

  • The token is bounded on the left by the start of the data or a newline, and on the right by a newline (LF or CR) or the end of the data.
  • The boundary check rules out substring collisions: a section [name2] does not satisfy a lookup for [name], and a header appearing inside an option value does not either.
  • The scan resumes past a hit the boundary check rejected, so a later whole-line occurrence still matches.

SectionProjected ​

go
func SectionProjected(
    ctx context.Context,
    p ObserveParams,
) (bool, error)

Reports whether a satellite's section reached the config file the parent's Deployment mounts.

Behavior:

  • ObserveParams.Children reads the Deployment at DeploymentKey, then the Secret that the pod volume named VolumeName mounts, in the Deployment's namespace. SectionHeader (for example [store]) is looked up in that Secret's DataKey.
  • A NotFound anywhere on the chain is (false, nil): a missing Deployment, a missing or non-Secret volume, a missing Secret and an absent data key all read as an authoritative "not projected yet".
  • A non-NotFound Get failure is wrapped as fetching Deployment <key>: <cause> or fetching projection Secret <key>: <cause>.
  • Consumers keep their own parent re-read ahead of this call, and their observeConfigProjected wrappers own the condition reasons, the messages and the requeue intervals.

Collect ​

go
func Collect[T any, PT interface {
    *T
    client.Object
}](p CollectParams[T, PT]) Collection[T, PT]

Turns a parent's list of attached satellites into one pass's view of them.

Behavior:

  • CollectParams.Items is the caller's indexed List result, in any order. A List type holding values, as GlanceBackendList.Items does, becomes a pointer slice by appending &list.Items[i] over the index.
  • Gate decides which items may be projected; GlanceBackend passes CredentialsReady == True. A nil gate passes everything. IsDefault marks the parent's default, and a nil function means the parent has no default rule.
  • Collection.Attached holds the items carrying no deletion timestamp, sorted by name. Gated is the subset that passes the gate and DefaultCandidates the gated items IsDefault marks, both in that same order.
  • Collection.Valid reports whether the default rule holds: no rule at all, or one gated candidate. It is a decision, and the parent stamps its own condition type with its own reason and message from it.
  • A deleting satellite drops out here, so the pass de-projects it without waiting for the object to go.
  • Sorting by name is what keeps the rendered config, and with it its content hash, stable across passes.
  • The function returns no error and never touches the API. Everything it needs is on the objects the caller listed, and the pointers it hands back alias them.

Package: secrets ​

Manages External Secrets Operator resources and Kubernetes Secrets for CobaltCore operators. Uses typed structs from github.com/external-secrets/external-secrets.

WaitForExternalSecret ​

go
func WaitForExternalSecret(
    ctx context.Context,
    c client.Client,
    key client.ObjectKey,
) (exists, ready bool, err error)

Checks whether the ExternalSecret identified by key exists and has a Ready condition with status True (the ESO ExternalSecretReady condition type).

Returns (tri-state):

  • (false, false, nil) — the ExternalSecret was not found.
  • (true, false, nil) — it exists but has not synced yet (no Ready=True).
  • (true, true, nil) — it exists and is Ready.
  • (false, false, err) — an unexpected client failure.

Behavior:

  • This is a point-in-time check, not a blocking wait. Reconcilers should call it on each reconciliation loop and requeue while ready is false.
  • The separate exists return lets callers surface a clearer status — "ExternalSecret not found yet" versus "waiting for ESO to sync" — instead of collapsing both into a single not-ready signal.
  • Uses the ESO ExternalSecretReady condition type constant, not a raw string.

IsSecretReady ​

go
func IsSecretReady(
    ctx context.Context,
    c client.Client,
    key client.ObjectKey,
    expectedKeys ...string,
) (bool, error)

Checks whether a Kubernetes Secret exists at the given key and, when expectedKeys are provided, verifies that all specified keys are present in the Secret's .Data field.

Returns: (true, nil) if the Secret exists and contains all expected keys; (false, nil) if not found or missing expected keys; (false, error) on unexpected API failures.

Behavior:

  • A NotFound error is treated as a normal condition ((false, nil)), not a failure.
  • When no expectedKeys are provided, only checks for Secret existence.
  • When expectedKeys are provided, returns (false, nil) if any key is absent from Secret.Data.

GetSecretValue ​

go
func GetSecretValue(
    ctx context.Context,
    c client.Client,
    key client.ObjectKey,
    dataKey string,
) (string, error)

Retrieves and decodes the value of a specific data key from a Secret.

Parameters:

NameTypeDescription
keyclient.ObjectKeyNamespace and name of the Secret
dataKeystringKey within Secret.Data to retrieve

Returns: The decoded string value, or an error if the Secret or key is not found.

Error conditions:

ConditionError
Secret does not existWrapped API server error
Key not in Secret.DataWraps ErrKeyNotFound: key not found in Secret: key "<dataKey>" in Secret <namespace>/<name> — test with errors.Is(err, secrets.ErrKeyNotFound)

EnsurePushSecret ​

go
func EnsurePushSecret(
    ctx context.Context,
    c client.Client,
    scheme *runtime.Scheme,
    owner client.Object,
    ps *esov1alpha1.PushSecret,
) error

Creates or updates a PushSecret CR with a controller owner reference.

Behavior:

  • Applies the desired PushSecret via apply.EnsureObject (Server-Side Apply). The field manager owns only the fields the builder sets, so the server defaults on updatePolicy and refreshInterval are preserved and a converged PushSecret is not rewritten — ESO is not woken to re-push an unchanged credential.
  • Uses the ESO v1alpha1 PushSecret API (unstable — see External CRD Dependencies).

Per-CR secret store selection ​

These helpers back the optional per-CR spec.secretStoreRef: each CR routes its ExternalSecrets and PushSecrets through the store it selects (a cluster-scoped ClusterSecretStore, default openbao-cluster-store, or a namespaced SecretStore), while the IsClusterSecretStoreReady / OpenBaoClusterStoreName primitives above remain.

  • EffectiveStoreRef(ref *commonv1.SecretStoreRefSpec) commonv1.SecretStoreRefSpec — resolves a nil or empty-kind ref to the shared cluster store {ClusterSecretStore, openbao-cluster-store}, so a CR that omits the field behaves as before.
  • IsStoreRefReady(ctx, c, ref commonv1.SecretStoreRefSpec, namespace string) (bool, error) — reports store readiness, dispatching on kind: a cluster store is looked up by name, a namespaced store in namespace; an unknown kind returns an error.
  • IsSecretStoreReady(ctx, c, name, namespace string) (bool, error) — the namespaced twin of IsClusterSecretStoreReady, checking a SecretStore's Ready condition.
  • GateStoreReady(ctx, c, ref, namespace string, namespaceScoped bool, conds *[]metav1.Condition, generation int64, conditionType string) (bool, error) — store-ref-aware readiness gate; on a not-ready store it sets a SecretStoreNotReady condition whose message names the store kind and name ("%s %q is not ready; upstream secret backend unreachable") and returns (false, nil). With namespaceScoped set, a ref of kind ClusterSecretStore is refused without any client call: the condition gets reason ReasonClusterSecretStoreUnsupported (ClusterSecretStoreUnsupported) and the message "ClusterSecretStore %q cannot be read by a namespace-scoped operator; set spec.secretStoreRef to a SecretStore in namespace %q", and the gate returns (false, nil).
  • AddStoreWatches(b *mcbuilder.Builder, scheme *runtime.Scheme, targets multicluster.TargetClusterFunc, namespaceScoped bool, c client.Reader, mapper func(client.Reader, commonv1.SecretStoreRefKind) handler.MapFunc) (*mcbuilder.Builder, error) — adds the multicluster.AddInputWatch legs for the stores a CR can select: ClusterSecretStore unless namespaceScoped, then SecretStore, each mapped through mapper(c, kind). A nil mapper returns the error secrets: store watch mapper must not be nil; an AddInputWatch error (resolving input kind for remote watch: …) is returned unchanged. Both return a nil builder.
  • ESOSecretStoreRef(ref commonv1.SecretStoreRefSpec) esov1.SecretStoreRef — builds the ESO ExternalSecret store ref from a resolved store reference.
  • PushSecretStoreRefs(ref commonv1.SecretStoreRefSpec) []esov1alpha1.PushSecretStoreRef — builds the single-element ESO PushSecret store-ref slice from a resolved store reference.

Package: tls ​

Manages TLS certificates and secrets for CobaltCore operators. Uses typed structs from github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.

EnsureCertificate ​

go
func EnsureCertificate(
    ctx context.Context,
    c client.Client,
    scheme *runtime.Scheme,
    owner client.Object,
    cert *certmanagerv1.Certificate,
) (bool, error)

Creates or updates a cert-manager Certificate CR.

Returns: (true, nil) when the Certificate has a Ready condition with status True; (false, nil) when it exists but is not yet ready; (false, error) on failure.

Behavior:

  • On create: sets controller owner reference, creates the resource, returns (false, nil).
  • On update: overwrites existing.Spec with the provided spec.
  • Readiness is determined by the package-internal isCertificateReady helper on the existing resource.
  • cert-manager creates a Secret with the TLS certificate once the Certificate is ready.

Cross-Package Dependencies ​

These internal/common packages are independent of each other; reconcilers compose them directly (for example a Keystone sub-reconciler calls job.RunJob and database.EnsureDatabase side by side).

Reconciler Integration Pattern ​

A reconciler calls these packages from the steps of its pipeline. The figure shows the pipeline of the Keystone operator, and the table names the functions of this page each of its steps calls.

The sub-reconciler pipeline of the Keystone operator. A pass runs its entries one after another and ends at the first that returns a requeue or an error: Secrets, DatabaseTLS, DBConnectionSecret, IdentityBackends, Config, a parallel group of FernetKeys, CredentialKeys and NetworkPolicy, then Database, PolicyValidation, Deployment, an unnamed prune step, a second parallel group of HTTPRoute, HealthCheck, HPA, VPA, Bootstrap and TrustFlush, and PasswordRotation. Every member of a group starts, and the group returns the shortest requeue. Each step sets one condition; DBConnectionSecret and Config report through SecretsReady, and the prune step has none of its own. DBConnectionSecret hands the hash of the database connection to Deployment, IdentityBackends hands on the name of the domains Secret and the federation projection, Config hands on the name of the ConfigMap, and Deployment sets status.endpoint, which HealthCheck waits for. The early exit and the full pass both end in updateStatus, which aggregates Ready from the sub-conditions and writes the status only when it changed.

StepFunctions of this page it calls
DatabaseTLStls.EnsureCertificate
IdentityBackendssecrets.GetSecretValue, satellite.SecretNameForVolume
Configpolicy.LoadPolicyFromConfigMap, config.CreateImmutableConfigMap
FernetKeys, CredentialKeysconfig.CreateImmutableConfigMap, job.EnsureCronJob, secrets.EnsurePushSecret
PolicyValidationjob.RunJob
Deploymentdeployment.EnsureDeployment, deployment.EnsureService
(prune)config.PruneImmutableConfigMaps
Bootstrapsecrets.GetSecretValue
TrustFlushjob.EnsureCronJob
PasswordRotationconfig.CreateImmutableConfigMap, config.PruneImmutableConfigMaps, job.EnsureCronJob, secrets.EnsurePushSecret

The steps the table leaves out call shared flows this page does not describe, such as database.ReconcileProvision, database.ReconcileSyncJobs and deployment.ReconcileHPA. A step returns a ctrl.Result and an error. The pipeline moves on when the result is zero and the error is nil; otherwise the pass ends, and the status is still written.