Skip to content

ControlPlane CRD API Reference

Reference documentation for the c5c3-operator ControlPlane Custom Resource Definition. The ControlPlane CRD is the top-level aggregate that projects an OpenStack control plane: it owns the shared infrastructure references (database, cache), a curated set of per-service specs (today: Keystone), and the K-ORC (OpenStack Resource Controller) integration that bootstraps and rotates the admin application credential. The reconciler (L2) materializes this aggregate into the individual per-service CRs — see the ControlPlane Reconciler reference for the reconciliation flow.

The c5c3 API group also ships two companion kinds: CredentialRotation (a one-shot credential-rotation request) and SecretAggregate (types-only at this level; the reconciler is deferred). All three are documented here.

The API surface is intentionally smaller than the Keystone CRD: the ControlPlane curates a subset of each service's knobs and derives the rest from operator policy, rather than re-exposing every service field through the aggregate.

API Group and Version

FieldValue
Groupc5c3.io
Versionv1alpha1
ScopeNamespaced
KindList Kind
ControlPlaneControlPlaneList
CredentialRotationCredentialRotationList
SecretAggregateSecretAggregateList

Import path:

go
import c5c3v1alpha1 "github.com/c5c3/forge/operators/c5c3/api/v1alpha1"

Scheme registration:

The init() functions in controlplane_types.go, credentialrotation_types.go, and secretaggregate_types.go each register their kind (and List kind) with the shared SchemeBuilder. The operator entrypoint registers the group with the manager's scheme through internal/common/bootstrap (which calls AddToScheme), so every kind in the group is available to the manager:

go
utilruntime.Must(c5c3v1alpha1.AddToScheme(scheme))

The manager runs with LeaderElectionID c5c3.openstack.c5c3.io.


Resource Shape — ControlPlane

yaml
apiVersion: c5c3.io/v1alpha1
kind: ControlPlane
metadata:
  name: controlplane
  namespace: openstack
spec:
  openStackRelease: "2025.2"
  region: RegionOne
  infrastructure:
    database:
      clusterRef:
        name: mariadb
      database: keystone
      # In managed mode (clusterRef set) database.secretRef is
      # operator-owned — reconcileDBCredentials materialises a per-ControlPlane
      # Secret and the projected Keystone CR's secretRef is overridden to
      # {name}-keystone-db-credentials (key "password"); the value below is not
      # what Keystone consumes. A brownfield CR (database.host) must instead
      # supply its own database.secretRef Secret out-of-band.
      secretRef:
        name: keystone-db-credentials
        key: password
    cache:
      backend: dogpile.cache.pymemcache
      clusterRef:
        name: memcached
  services:
    keystone:
      replicas: 3
      rotationInterval: 168h
      gateway:
        parentRef:
          name: openstack-gw
        hostname: keystone.example.com
        path: /
      publicEndpoint: https://keystone.example.com/v3
  korc:
    adminCredential:
      cloudCredentialsRef:
        cloudName: admin
        secretName: k-orc-clouds-yaml
      passwordSecretRef:
        name: keystone-admin
        key: password
      applicationCredential:
        restricted: true
        rotation:
          mode: PasswordDriven
      bootstrapResources:
        - kind: Project
          name: admin
        - kind: Role
          name: admin
status:
  conditions:
    - type: Ready
      status: "True"
      reason: AllReady
      message: All sub-conditions are ready
      lastTransitionTime: "2026-06-02T00:00:00Z"
  observedGeneration: 4
  updatePhase: Idle
  services:
    - name: keystone
      ready: true
      release: "2025.2"
  adminApplicationCredential:
    id: 6f3c…
    restricted: true
    lastRotation: "2026-06-02T00:00:00Z"

Printer Columns

kubectl get controlplanes displays these columns:

ColumnJSON PathType
Ready.status.conditions[?(@.type=='Ready')].statusstring
Release.spec.openStackReleasestring
Age.metadata.creationTimestampdate

The status subresource is enabled via +kubebuilder:subresource:status.


Resource Shape — CredentialRotation

yaml
apiVersion: c5c3.io/v1alpha1
kind: CredentialRotation
metadata:
  name: rotate-admin
  namespace: openstack
spec:
  target: adminApplicationCredential
  bootstrap: false
  reMint: true
status:
  conditions:
    - type: Ready
      status: "True"
      reason: RotationTriggered
      lastTransitionTime: "2026-06-02T00:00:00Z"
  observedGeneration: 1

Printer Columns

kubectl get credentialrotations displays these columns:

ColumnJSON PathType
Target.spec.targetstring
Ready.status.conditions[?(@.type=='Ready')].statusstring
Age.metadata.creationTimestampdate

ControlPlaneSpec

FieldTypeRequiredDefaultDescription
openStackReleasestringYesOpenStack release the control plane targets (e.g. "2025.2"). The reconciler (L2) projects this into each service CR's image tag. Must match the date-based release pattern ^\d{4}\.\d$, enforced by both the CRD +kubebuilder:validation:Pattern marker and the validating webhook. Upgrades are allowed on update, but downgrades are rejected (Keystone DB migrations are forward-only).
regionstringNo"RegionOne"OpenStack region name applied across the control plane. Projected into the Keystone CR's bootstrap.region. Defaulted to RegionOne by both the +kubebuilder:default marker (normal admission) and the defaulting webhook (callers that bypass the CRD default). Immutable after create (the projected bootstrap.region is itself immutable).
infrastructureInfrastructureSpecNomanaged-mode defaultedShared backing services (database, cache) the control plane's services connect to. Optional — the defaulting webhook materializes a managed-mode database/cache when omitted; see InfrastructureSpec.
servicesServicesSpecYesPer-service configuration projected into the individual service CRs.
global*commonv1.PolicySpecNoniloslo.policy overrides applied across every service in the control plane. Per-service overrides (e.g. services.keystone.policyOverrides) take precedence over these global rules when both are set.
korcKORCSpecNodefaultedK-ORC integration used to bootstrap and rotate the admin application credential and any declared bootstrap resources. Optional — the defaulting webhook fills adminCredential (cloudCredentialsRef, passwordSecretRef, applicationCredential restriction/rotation) from well-known defaults when omitted.

InfrastructureSpec

Declares the shared backing services for the control plane. Both fields reuse the canonical commonv1 shapes so the ControlPlane and the per-service CRs validate the database/cache the same way.

spec.infrastructure (and each of its database / cache blocks) may be omitted entirely on a minimal managed-mode ControlPlane. The defaulting webhook constructs a managed-mode database (clusterRef: openstack-db, database: keystone, secretRef.name: keystone-db) and a managed-mode cache (clusterRef: openstack-memcached, backend: dogpile.cache.pymemcache) before validation runs. The two managed clusterRef names are only invented when the brownfield discriminator (database.host / cache.servers) is unset, so the database/cache XOR rule below still passes for a brownfield CR — the webhook never coerces an explicit brownfield endpoint into managed mode. See the Defaulting Webhook for the exact conditions and mechanism.

The defaulted database.secretRef.name (keystone-db) is a managed-mode convenience name only — in managed mode database.secretRef is operator-owned and the projected Keystone CR's secretRef is overridden to a per-ControlPlane Secret, and a brownfield CR must supply its own. See the database field notes below.

FieldTypeRequiredDefaultDescription
databasecommonv1.DatabaseSpecNomanaged clusterRef: openstack-db, database: keystone, secretRef.name: keystone-dbMariaDB connection parameters shared by the control plane. Supports managed (clusterRef) and brownfield (host) modes; exactly one must hold after defaulting (enforced by the CRD CEL XValidation rule and the validating webhook — see Validation Rules). Optional because the defaulting webhook materializes a managed-mode block when omitted. database.secretRef ownership: in managed mode this reference is operator-ownedreconcileDBCredentials materialises a per-ControlPlane DB-credential Secret and the reconciler overrides the projected Keystone CR's spec.database.secretRef to point at it, so the keystone-db default secretRef.name is only a managed-mode convenience name (it is not what Keystone consumes and no longer resolves to a cluster Secret). A brownfield ControlPlane (database.host set, no clusterRef) MUST supply its own database.secretRef Secret out-of-band — the operator projects no ExternalSecret in brownfield mode. See managed-mode provisioning below.
cachecommonv1.CacheSpecNomanaged clusterRef: openstack-memcached, backend: dogpile.cache.pymemcacheMemcached configuration shared by the control plane. Supports managed (clusterRef) and brownfield (servers) modes; exactly one must hold after defaulting (enforced by the CRD CEL XValidation rule and the validating webhook). Optional because the defaulting webhook materializes a managed-mode block when omitted.

In managed mode the reconciler provisions an owned MariaDB CR (named after database.clusterRef.name) and an owned Memcached CR (named after cache.clusterRef.name) in the ControlPlane's own namespace. The Keystone CR the reconciler projects points at the same DatabaseSpec / CacheSpec verbatim, so the aggregate and the projected service agree on the backing services.

database.secretRef is operator-owned in managed mode. The DatabaseSpec is projected onto the Keystone CR verbatim except for its secretRef. In managed mode the reconcileDBCredentials sub-reconciler create-or-updates a per-ControlPlane DB-credential ExternalSecret named {controlplane.Name}-keystone-db-credentials in the ControlPlane namespace (reading OpenBao path openstack/keystone/{namespace}/{name}/db), and reconcileKeystone then overrides the projected Keystone CR's spec.database.secretRef to {name: "{controlplane.Name}-keystone-db-credentials", key: "password"} — the operator-owned Secret. The source cp.Spec is left untouched; only the projected child's secretRef value is reassigned. The Secret that Keystone actually consumes is therefore the one this reconciler materialises, not a Secret literally named after database.secretRef.name. Consequently the keystone-db default for database.secretRef.name is a managed-mode convenience name only: the production deploy stack ships no keystone-db ExternalSecret (only the kind overlay materialises one, for standalone Keystone instances), and a managed ControlPlane never consumes it either way.

A brownfield ControlPlane (database.host set, clusterRef == nil) MUST supply spec.infrastructure.database.secretRef pointing to a Secret it owns out-of-band. In brownfield mode reconcileDBCredentials is a no-op (it reports DBCredentialsReady=True, reason BrownfieldUserSuppliedCredential) and projects no ExternalSecret, so the operator never materialises the Secret — and the keystone-db default no longer resolves to a cluster Secret. See the ControlPlane Reconciler reference for the reconcileDBCredentials flow.


ServicesSpec

Declares the per-service configuration of the control plane. Today only Keystone is modeled; additional services are added as fields as the operator grows.

FieldTypeRequiredDefaultDescription
keystoneServiceKeystoneSpecYesConfiguration for the Keystone service projected by the reconciler.

ServiceKeystoneSpec

A curated local subset of the knobs the ControlPlane exposes for the Keystone service.

DECISION: This struct is intentionally not an import of keystonev1alpha1.KeystoneSpec. The reconciler (L2) projects this struct into a Keystone CR; the database, cache, and Fernet rotation schedule of that Keystone CR are derived from the ControlPlane (infrastructure.* and operator policy) rather than set by the user here. Keeping a curated subset avoids leaking every Keystone knob through the aggregate and keeps the L1 API package free of a dependency on the keystone module. Fields not present below (replica strategy, uWSGI, network policy, fernet key count, etc.) are governed by the Keystone operator's own defaults on the projected CR, not by the ControlPlane.

FieldTypeRequiredDefaultDescription
replicas*int32Nonil (Keystone operator default, 3)Overrides the number of Keystone API replicas. When nil, the reconciler leaves replicas unset on the projected Keystone CR, so the Keystone operator applies its own default. Minimum: 1.
image*commonv1.ImageSpecNonilOverrides the Keystone container image. When nil, the reconciler derives the image as ghcr.io/c5c3/keystone:{spec.openStackRelease}. When set, the whole image reference is used verbatim.
policyOverrides*commonv1.PolicySpecNonilPer-service oslo.policy overrides for Keystone. When set, these take precedence over spec.global for the Keystone service.
rotationInterval*metav1.DurationNonilOverrides the Fernet / credential-key rotation interval the reconciler derives for the projected Keystone CR. When nil, the reconciler derives a default schedule. When set, the duration is converted to a cron expression and applied to both fernet.rotationSchedule and credentialKeys.rotationSchedule on the projected Keystone CR. An unconvertible interval (not a positive whole number of days) is rejected at admission by the validating webhook; if the webhook is bypassed, the reconciler surfaces KeystoneReady=False with reason InvalidRotationInterval and returns the error so the reconcile chain stops and requeues with backoff.
gateway*commonv1.GatewaySpecNonilExposes the projected Keystone API externally via a Gateway API HTTPRoute. When nil, no HTTPRoute is projected and the Keystone API is reachable in-cluster only (its ClusterIP Service). When set, the reconciler projects it onto the Keystone CR's spec.gateway, so the Keystone operator attaches an HTTPRoute to the referenced Gateway. When a gateway is set its hostname must be non-empty — enforced at admission by the validating webhook (see Validation Rules).
publicEndpointstringNo""Externally routable Keystone identity endpoint URL (e.g. https://keystone.example.com/v3). Projected into the Keystone bootstrap (--bootstrap-public-url) and used for the K-ORC identity catalog Endpoint, so external clients resolve the same URL Keystone advertises. When set, it must be an HTTP(S) URL (+kubebuilder:validation:Pattern=^https?://), so a malformed endpoint fails at admission rather than wedging the projected Keystone CR. When empty and gateway is set, the reconciler derives https://{gateway.hostname}/v3 (the default-443 form); set it explicitly when the externally reachable port differs (e.g. a kind host-port mapping like :8443).

GatewaySpec

The shared commonv1.GatewaySpec (internal/common/types), the single source of truth for the Gateway API HTTPRoute knobs reused by both the ControlPlane and the Keystone CRD — see the Keystone CRD → GatewaySpec for the same shape on the projected child. The reconciler (L2) maps it onto the projected Keystone CR's spec.gateway. As with the other commonv1 shapes, reusing this type still keeps the L1 API package free of a dependency on the keystone module (the formerly hand-curated local copy was consolidated into commonv1; the L1 package imports only commonv1, never the keystone module).

FieldTypeRequiredDefaultDescription
parentRefGatewayParentRefSpecYesThe pre-existing Gateway the HTTPRoute attaches to. The Gateway (and GatewayClass) are platform-team infrastructure managed outside this CR.
hostnamestringYesExternally reachable host (SNI / Host header) the HTTPRoute matches, e.g. keystone.example.com. Minimum length 1.
pathstringNo"/" (Keystone operator default)URL path prefix matched by the HTTPRoute.
annotationsmap[string]stringNonilPassed through to the generated HTTPRoute metadata verbatim (rate limits, timeouts, CORS) without extending the CRD.

GatewayParentRefSpec

References a pre-existing Gateway that the projected Keystone's HTTPRoute attaches to. The shared commonv1.GatewayParentRefSpec (internal/common/types), nested under commonv1.GatewaySpec and reused by both the ControlPlane and the Keystone CRD.

FieldTypeRequiredDefaultDescription
namestringYesGateway resource name. Minimum length 1.
namespacestringNo""Namespace of the referenced Gateway. When empty, the projected Keystone CR's namespace is assumed.
sectionNamestringNo""Targets a specific listener on the Gateway (e.g. https) when it defines multiple listeners. When empty, the HTTPRoute attaches to all compatible listeners.

KORCSpec

Configures the K-ORC (OpenStack Resource Controller) integration of the control plane. It declares how the admin application credential is bootstrapped and rotated and which bootstrap resources are reconciled.

FieldTypeRequiredDefaultDescription
adminCredentialAdminCredentialSpecYesThe admin OpenStack credential K-ORC uses to reconcile resources, plus the application-credential rotation policy.

AdminCredentialSpec

Declares the admin OpenStack credential and the application-credential rotation policy for the control plane.

FieldTypeRequiredDefaultDescription
cloudCredentialsRefCloudCredentialsRefYesReferences the clouds.yaml Secret and cloud entry K-ORC authenticates as.
passwordSecretRefcommonv1.SecretRefSpecNoname "keystone-admin", key "password"References the Secret holding the admin password used to (re-)mint the application credential. The defaulting webhook materializes a missing name to keystone-admin and a missing key to password, so the block may be omitted on a minimal CR. The validating webhook still enforces passwordSecretRef.name non-empty as defense-in-depth (see Validation Rules), but the defaulting webhook always satisfies it before validation runs, so a user may leave it unset. The reconciler's existing "password" key fallback also remains. Mode-dependent use: the keystone-admin default is the brownfield / spec-level default. In brownfield mode (database.clusterRef == nil) this field is used verbatim — the user supplies the admin-password Secret out-of-band and the operator projects no ExternalSecret, so this reference is projected onto the Keystone CR's bootstrap.adminPasswordSecretRef so Keystone and K-ORC agree on the admin password source. In managed mode (database.clusterRef set) the operator instead projects a per-ControlPlane admin ExternalSecret named {controlplane.Name}-keystone-admin-credentials (materialising the admin password from OpenBao) and overrides the projected Keystone CR's bootstrap.adminPasswordSecretRef to point at that operator-owned per-CP Secret's password key — the cp-level passwordSecretRef is not used as the child's ref in managed mode. See the managed-mode admin-password provisioning note below.
applicationCredentialApplicationCredentialSpecYesPolicy for the K-ORC admin application credential (restriction, access rules, rotation mode).
bootstrapResources[]BootstrapResourceSpecNonilOpenStack resources K-ORC bootstraps alongside the admin credential (e.g. the projects/roles a fresh control plane needs). The element shape is intentionally minimal at L1; the reconciler interprets it.

passwordSecretRef is operator-owned in managed mode. The keystone-admin default is the brownfield / spec-level default only. In managed mode (database.clusterRef set) the operator projects a per-ControlPlane admin ExternalSecret named {controlplane.Name}-keystone-admin-credentials in the ControlPlane namespace that materialises the admin password from OpenBao path bootstrap/{namespace}/{controlplane.Name}-keystone/admin (canonical: bootstrap/openstack/controlplane-keystone/admin, property password), and overrides the projected Keystone CR's bootstrap.adminPasswordSecretRef to point at that operator-owned per-CP Secret's password key. The cp-level passwordSecretRef is therefore not used as the child's ref in managed mode — the source cp.Spec is left untouched; only the projected child's ref is reassigned.

In brownfield mode (database.clusterRef == nil, a Host-based DB) the operator projects no admin ExternalSecret: the user supplies the admin-password Secret out-of-band and the cp-level passwordSecretRef (default keystone-admin) is projected onto the Keystone CR's bootstrap.adminPasswordSecretRef verbatim. See the ControlPlane Reconciler reference for the admin-credential flow.


CloudCredentialsRef

References the clouds.yaml Secret and the cloud entry within it that K-ORC authenticates as.

FieldTypeRequiredDefaultDescription
cloudNamestringNo"admin"The entry in clouds.yaml K-ORC authenticates as. Also used by the reconciler as the conventional K-ORC User reference name and projected onto the catalog Service/Endpoint CRs. Defaulted to admin by both the +kubebuilder:default marker (normal admission) and the defaulting webhook (callers that bypass the CRD default).
secretNamestringNo"k-orc-clouds-yaml"Name of the Secret holding the clouds.yaml document. Defaulted to k-orc-clouds-yaml by both the +kubebuilder:default marker and the defaulting webhook. The Secret is namespace-local to the ControlPlane's child namespace; because the operator enforces one ControlPlane per namespace, the shared default name does not collide across control planes. The operator (reconcileKORCensureKORCCloudsYAMLExternalSecret) creates and owns a per-ControlPlane ExternalSecret of this name in the child namespace that materialises the Secret, reading the per-CR OpenBao path — so the shared default name is safe and needs no per-CR manifest.

ApplicationCredentialSpec

Declares the K-ORC admin application-credential policy.

FieldTypeRequiredDefaultDescription
restricted*boolNotrueControls whether the application credential is restricted (least-privilege, unable to create further application credentials). Defaulted to true by both the +kubebuilder:default marker and the defaulting webhook. The pointer distinguishes "unset" (→ default true) from an explicit false, which is preserved. See the restricted → unrestricted inversion note.
accessRules[]AccessRuleNonilOptionally narrows the application credential to a specific set of service/method/path rules. When empty, the credential is not constrained by access rules.
rotationRotationSpecYesHow the application credential is rotated.

restricted → unrestricted inversion

The ControlPlane spec exposes a restricted flag (the safe, least-privilege posture). K-ORC's ApplicationCredentialResourceSpec exposes the inverse field, unrestricted. The reconciler performs the inversion when projecting the K-ORC ApplicationCredential CR:

restricted=true  → K-ORC spec.resource.unrestricted=false
restricted=false → K-ORC spec.resource.unrestricted=true

The same inversion is applied in reverse when reflecting the K-ORC-reported state back into status.adminApplicationCredential.restricted.


AccessRule

Narrows an application credential to a specific service endpoint and method, mirroring the Keystone application-credential access-rule shape.

FieldTypeRequiredDefaultDescription
servicestringYesOpenStack service type the rule applies to (e.g. "compute"). The reconciler uses this verbatim as the name of the referenced K-ORC Service CR (serviceRef).
methodstringNoHTTP method the rule allows (e.g. "GET", "POST"). Projected onto the K-ORC typed HTTPMethod enum; constrained by +kubebuilder:validation:Enum=CONNECT;DELETE;GET;HEAD;OPTIONS;PATCH;POST;PUT;TRACE (mirrors that enum). Optional — omitted from the projected rule when empty.
pathstringNoRequest path the rule allows (e.g. "/v2.1/servers"). When set it must be an absolute path (+kubebuilder:validation:Pattern=^/). Optional — omitted from the projected rule when empty.

RotationSpec

Declares the rotation policy for the admin application credential.

FieldTypeRequiredDefaultDescription
modeRotationModeNoPasswordDrivenSelects the rotation strategy. Defaulted to PasswordDriven by both the +kubebuilder:default marker and the defaulting webhook.

RotationMode

RotationMode is a string enum (+kubebuilder:validation:Enum=PasswordDriven;Scheduled;Manual).

ValueStatusMeaning
PasswordDrivenActive (default)Re-mints the application credential whenever the underlying admin password changes. The reconciler compares the SHA-256 of the admin password against an annotation stamped on the K-ORC ApplicationCredential CR; a mismatch drives a re-mint.
ScheduledReservedRotates the application credential on a schedule. Surfaced in the enum now so the CRD schema is stable, but the scheduled-rotation logic is deferred to a later level.
ManualReservedRotates only when a CredentialRotation CR requests it. The CredentialRotation flow is the mechanism; the Manual mode value itself is reserved at this level.

BootstrapResourceSpec

Declares an OpenStack resource K-ORC bootstraps with the control plane. The shape is intentionally minimal at L1 — the reconciler interprets the kind/name and applies it.

FieldTypeRequiredDefaultDescription
kindstringYesThe K-ORC resource kind to bootstrap. Constrained to the kinds the control plane bootstraps today by +kubebuilder:validation:Enum=Project;Role; widen the enum when the reconciler learns to interpret additional kinds.
namestringYesName of the bootstrapped resource.

ControlPlaneStatus

FieldTypeDescription
conditions[]metav1.ConditionLatest available observations of the control-plane state. Each condition carries an observedGeneration. See Status Conditions.
observedGenerationint64The .metadata.generation the controller last reconciled, so a stale status is distinguishable from a current one.
updatePhaseUpdatePhaseCurrent phase of a control-plane release update. Written on every status update; fixed at Idle in the current implementation because the release-update state machine is reserved (the other UpdatePhase values are not yet set).
services[]ServiceStatusPer-service readiness of the projected service CRs. A listType=map list keyed by name, so per-service entries merge under server-side apply and can grow per-service conditions cleanly. Written on every status update with a keystone entry whose ready mirrors the KeystoneReady condition and whose release is spec.openStackRelease. See ServiceStatus.
adminApplicationCredential*AdminApplicationCredentialStatusObserved state of the K-ORC admin application credential.

ServiceStatus

Reports the observed readiness of a single projected service CR.

FieldTypeRequiredDescription
namestringYesService name (e.g. keystone); keys the listType=map services list.
readyboolYesWhether the projected service CR is Ready.
releasestringNoThe OpenStack release the service currently reports installed.

AdminApplicationCredentialStatus

Reports the observed state of the K-ORC admin application credential.

Multi-instance — per-ControlPlane OpenBao path. The minted admin application credential is mirrored to OpenBao at the per-ControlPlane path openstack/keystone/{namespace}/{name}/admin/app-credential (for the default deployment identity openstack/controlplane, this is openstack/keystone/openstack/controlplane/admin/app-credential), replacing the earlier flat path shared across control planes. Because the validating webhook permits exactly one ControlPlane per namespace, these per-CR paths are disjoint across namespaces by construction. See the ControlPlane Reconciler reference for the full OpenBao layout and the migration from the legacy flat path.

FieldTypeRequiredDescription
idstringNoThe OpenStack application-credential ID currently in use, populated by K-ORC once the credential is minted.
restrictedboolNoWhether the active credential is restricted. Computed as the inverse of the K-ORC-reported unrestricted (falling back to the desired value while K-ORC status is empty).
lastRotation*metav1.TimeNoTimestamp of the last successful rotation. (Re-)stamped to "now" whenever the recorded credential id changes (initial mint or re-mint); preserved once the id is stable.

UpdatePhase

UpdatePhase is a string enum (+kubebuilder:validation:Enum=Idle;Updating;UpdatingServices;Verifying;RollingBack).

DECISION: the enum surfaces the future phases alongside the active ones so the CRD schema is stable across levels and does not need a breaking change when the update state machine is implemented. The reserved values below are never set by the current reconciler; they are documented so consumers (dashboards, kubectl) see the full vocabulary.

ValueStatusMeaning
IdleActiveNo update is in progress.
UpdatingActiveA release update has started.
UpdatingServicesReserved — not yet implementedPer-service CRs are being updated.
VerifyingReserved — not yet implementedThe control plane is verifying an update.
RollingBackReserved — not yet implementedA failed update is being rolled back.

CredentialRotationSpec

Defines the desired state of a CredentialRotation — a one-shot request to rotate a control-plane credential. The reconciler re-mints the target credential and reports progress via status conditions.

FieldTypeRequiredDefaultDescription
targetRotationTargetYesWhich credential to rotate.
bootstrapboolNofalseWhen true, requests an initial mint of the credential rather than a rotation of an existing one. Idempotent: if the credential already exists it is a no-op.
reMintboolNofalseWhen true, forces the reconciler to discard the current credential and mint a fresh one even if the existing credential is still valid. The nudge is one-shot per spec generation (latched on status.lastTriggeredGeneration), so a reMint: true left in the spec does not re-rotate on every resync.
intervalDays*int32NonilDeferred — accepted by the schema but ignored by the L1 reconciler. Rotation cadence in days for scheduled rotation. Minimum: 1.
preRotationDays*int32NonilDeferred — accepted but ignored. Days before expiry a replacement credential is minted (the overlap window). Minimum: 0.
gracePeriodDays*int32NonilDeferred — accepted but ignored. Days the superseded credential remains valid after a rotation before it is revoked. Minimum: 0.

DECISION: the scheduled-rotation fields (intervalDays, preRotationDays, gracePeriodDays) surface in the CRD schema now so the contract is stable, but the L1 reconciler ignores them — scheduled rotation (and the two-credential pre-rotation/grace overlap) is implemented in a later level. They are kept here, rather than introduced via a future breaking schema change, so dashboards and GitOps manifests can be written against the final shape.

RotationTarget

RotationTarget is a string enum (+kubebuilder:validation:Enum=adminApplicationCredential).

ValueMeaning
adminApplicationCredentialRotates the K-ORC admin application credential. The only target supported at this level.

CredentialRotationStatus

FieldTypeDescription
conditions[]metav1.ConditionLatest available observations of the rotation state. Upserted via the shared conditions helper.
observedGenerationint64The .metadata.generation the controller last reconciled.
lastTriggeredGenerationint64The most recent .metadata.generation for which an explicit reMint nudge was performed. Latches reMint to a single spec generation so a reMint: true left in the spec fires once per edit, not on every resync or restart.

SecretAggregate

SecretAggregate aggregates the Secrets produced by a control plane into a single materialized Secret.

DECISION: this is types-only at this level — there is no controller. The reconciler is deferred to a later level, and the operator RBAC for this kind is read-only (get/list/watch) until that reconciler lands, so the operator can observe SecretAggregate CRs without being granted write access to a kind it does not yet manage. The Spec/Status below are intentionally minimal placeholders; a later level will flesh them out.

SecretAggregateSpec

FieldTypeRequiredDefaultDescription
targetSecretNamestringNo""Name of the materialized aggregate Secret the (deferred) reconciler will produce.

SecretAggregateStatus

FieldTypeDescription
conditions[]metav1.ConditionLatest available observations of the aggregate state. Upserted via the shared conditions helper.

SecretAggregate has no printer columns and no defaulting/validating webhook at this level.


Shared Types (from internal/common/types)

The ControlPlane reuses the canonical commonv1 shapes imported from github.com/c5c3/forge/internal/common/types. These are shared across all CobaltCore operator CRDs and are documented in full in the Keystone reference; this section links rather than re-documents them to keep a single source of truth.

TypeUsed byReference
ImageSpecservices.keystone.imageKeystone CRD → ImageSpec
DatabaseSpecinfrastructure.databaseKeystone CRD → DatabaseSpec
CacheSpecinfrastructure.cacheKeystone CRD → CacheSpec
SecretRefSpeckorc.adminCredential.passwordSecretRefKeystone CRD → SecretRefSpec
PolicySpecglobal, services.keystone.policyOverridesKeystone CRD → PolicySpec

Note on DatabaseSpec.tls / CacheSpec: the commonv1 shapes carry the full Keystone field set, including the optional database.tls block. Those fields are part of the reused struct and are validated by the API server, but the ControlPlane reconciler projects the DatabaseSpec/CacheSpec onto the Keystone CR verbatim — TLS behavior is therefore governed by the Keystone DatabaseTLSSpec on the projected child, not re-implemented in the aggregate.


Validation Rules

The c5c3 ControlPlane uses a two-layer validation strategy, mirroring the Keystone discipline:

  1. CRD schema markers (+kubebuilder:validation:*) enforced by the API server before webhooks run — patterns, enums, and minimums.
  2. The validating webhook (validate()), which re-checks the schema-level rules and adds the cross-field invariants that cannot be expressed as simple field markers.

CEL x-kubernetes-validations on this CRD. The ControlPlane CRD carries CEL XValidation rules inherited from the shared commonv1 types: the database clusterRef/host and cache clusterRef/servers mutual-exclusivity (from DatabaseSpec/CacheSpec, applied to spec.infrastructure.database and spec.infrastructure.cache), and the policy-rule name/value constraints (from PolicySpec, applied to spec.global and spec.services.keystone.policyOverrides). The required passwordSecretRef.name remains enforced only by the validating webhook: a cluster that disables or bypasses the webhook (e.g. envtest without the webhook wired up, or a direct etcd write) will not reject a ControlPlane on that one rule, only on the CEL rules and the pattern/enum/minimum markers below. The markers and webhook together are defense-in-depth for the fields that can be expressed at both layers.

CRD schema markers (API-server enforced)

FieldRule
spec.openStackReleasePattern ^\d{4}\.\d$
spec.services.keystone.publicEndpointPattern ^https?://
spec.korc.adminCredential.applicationCredential.accessRules[].methodEnum: CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE
spec.korc.adminCredential.applicationCredential.accessRules[].pathPattern ^/
spec.korc.adminCredential.bootstrapResources[].kindEnum: Project, Role
spec.korc.adminCredential.applicationCredential.rotation.modeEnum: PasswordDriven, Scheduled, Manual
spec.services.keystone.replicasMinimum: 1
CredentialRotation spec.targetEnum: adminApplicationCredential
CredentialRotation spec.intervalDaysMinimum: 1
CredentialRotation spec.preRotationDaysMinimum: 0
CredentialRotation spec.gracePeriodDaysMinimum: 0
status.updatePhaseEnum: Idle, Updating, UpdatingServices, Verifying, RollingBack
spec.infrastructure.database (CEL)has(self.clusterRef) != has(self.host) → "exactly one of clusterRef or host must be set"
spec.infrastructure.cache (CEL)has(self.clusterRef) != (has(self.servers) && size(self.servers) > 0) → "exactly one of clusterRef or servers must be set"
spec.global, spec.services.keystone.policyOverrides (CEL)!has(self.rules) || self.rules.all(k, size(k) > 0) → "policy rule name must not be empty"
spec.global, spec.services.keystone.policyOverrides (CEL)!has(self.rules) || self.rules.all(k, size(self.rules[k]) > 0) → "policy rule value must not be empty"

Validating-webhook rules

The validate() method accumulates all errors in a field.ErrorList and returns a single apierrors.NewInvalid error keyed on GroupKind{Group: "c5c3.io", Kind: "ControlPlane"}. It does not short-circuit on the first error.

RuleField PathError TypeCondition
Release patternspec.openStackReleasefield.InvalidValue does not match ^\d{4}\.\d$. Defense-in-depth alongside the CRD +kubebuilder:validation:Pattern marker.
Database mutual exclusivityspec.infrastructure.databasefield.InvalidBoth clusterRef and host set, or neither ((clusterRef != nil) == (host != "")). Defense-in-depth alongside the CEL XValidation rule on commonv1.DatabaseSpec.
Cache mutual exclusivityspec.infrastructure.cachefield.InvalidBoth clusterRef and servers set, or neither ((clusterRef != nil) == (len(servers) > 0)). Defense-in-depth alongside the CEL XValidation rule on commonv1.CacheSpec.
Admin password Secret requiredspec.korc.adminCredential.passwordSecretRef.namefield.Requiredname is empty — without it the reconciler cannot (re-)mint the admin application credential. Webhook-only.
Gateway hostname requiredspec.services.keystone.gateway.hostnamefield.RequiredA gateway is configured but its hostname is empty. Mirrors the +kubebuilder:validation:MinLength=1 marker on commonv1.GatewaySpec.Hostname; without it the reconciler derives an empty https:///v3 public endpoint.
Empty policy rule namespec.global.rules[<key>], spec.services.keystone.policyOverrides.rules[<key>]field.RequiredA rule name (map key) is the empty string. Enforced via the shared policy.ValidatePolicyRules, mirrored by the CEL rule on commonv1.PolicySpec.
Empty policy rule valuespec.global.rules[<key>], spec.services.keystone.policyOverrides.rules[<key>]field.RequiredA rule value is the empty string. Enforced via the shared policy.ValidatePolicyRules, mirrored by the CEL rule on commonv1.PolicySpec.

Update-only immutability rules

On update the validating webhook additionally rejects changes to the create-only fields below, accumulating them into the same field.ErrorList as the spec checks above. These fields are webhook-only (the affected leaves live in the shared commonv1.DatabaseSpec/CacheSpec, which the keystone operator reuses and which must not carry c5c3-specific CEL immutability markers). Flipping the database/cache mode or renaming a managed clusterRef would leave the previously-projected MariaDB/Memcached child (and its per-ControlPlane credential) orphaned and owned until the ControlPlane is deleted; renaming cloudCredentialsRef.secretName would leak the previously-projected K-ORC clouds.yaml ExternalSecret. The database name and the region are also immutable: both are projected verbatim into the Keystone child's now-immutable spec.database.database / spec.bootstrap.region, so a rename here would make the next reconcile attempt an update the Keystone CEL rule rejects, wedging the loop — rejecting it at the ControlPlane layer surfaces a clean error instead.

The webhook additionally rejects an openStackRelease downgrade: a monotonic upgrade check parses the YYYY.N release into (year, minor) and rejects a lower tuple while allowing upgrades and same-release no-ops. Keystone DB migrations are forward-only, so a downgrade would project an older image against an already-migrated schema — an unrecoverable state.

RuleField PathCondition
Database mode immutablespec.infrastructure.databaseclusterRef nil-ness changed (managed ↔ brownfield)
Database clusterRef.name immutablespec.infrastructure.database.clusterRef.nameBoth managed, but the name changed
Database name immutablespec.infrastructure.database.databaseThe database name changed
Cache mode immutablespec.infrastructure.cacheclusterRef nil-ness changed (managed ↔ brownfield)
Cache clusterRef.name immutablespec.infrastructure.cache.clusterRef.nameBoth managed, but the name changed
Cloud secretName immutablespec.korc.adminCredential.cloudCredentialsRef.secretNameThe value changed
Region immutablespec.regionThe region changed
Release downgrade rejectedspec.openStackReleaseNew release (year, minor) is lower than the old (upgrades and same-release updates allowed)

Webhooks

The ControlPlaneWebhook struct implements both defaulting and validating admission webhooks for the ControlPlane CRD via the typed-generic admission.Defaulter[*ControlPlane] and admission.Validator[*ControlPlane] interfaces from controller-runtime. CredentialRotation and SecretAggregate have no webhook at this level.

The struct carries a Client client.Reader, injected at startup with the manager's uncached API reader (mgr.GetAPIReader()). ValidateCreate uses it to enforce one ControlPlane per namespace; reading the API server directly ensures concurrent or cache-sync-window CREATEs cannot both pass the check against an empty informer cache. The spec-level validate() rules do not touch the client.

Registration

go
func (w *ControlPlaneWebhook) SetupWebhookWithManager(mgr ctrl.Manager) error

Registers both webhooks with the manager using builder.WebhookManagedBy[*ControlPlane]. The generated webhook paths are /mutate-c5c3-io-v1alpha1-controlplane (mutating) and /validate-c5c3-io-v1alpha1-controlplane (validating); both use failurePolicy=fail, sideEffects=None, and admissionReviewVersions=v1. Both webhooks fire on create/update only. delete is deliberately not registered: the webhook is served in-process by the operator, so with failurePolicy=fail a delete rule would let a down operator block CR — and thereby namespace — deletion.

Defaulting Webhook

go
func (w *ControlPlaneWebhook) Default(_ context.Context, obj *ControlPlane) error

Fills only zero-valued fields with their documented defaults, leaving any explicit value untouched. It is idempotent: applying it twice produces the same result. The defaults split across two layers that do not uniformly overlap:

  • Dual-layer defaults — also expressed as a +kubebuilder:default marker on the corresponding spec field, so the marker covers the normal admission path and the webhook covers callers that bypass the CRD default. These are region, cloudCredentialsRef.secretName, applicationCredential.restricted, applicationCredential.rotation.mode, and cloudCredentialsRef.cloudName.
  • Webhook-only defaults — materialized by the webhook with no+kubebuilder:default marker. These are the shared-commonv1-leaf defaults (database.database, database.secretRef.name, cache.backend, passwordSecretRef.name, passwordSecretRef.key) and the two managed clusterRef names (openstack-db, openstack-memcached). They carry no marker because the commonv1 DatabaseSpec / CacheSpec / SecretRefSpec types are reused by the Keystone CRD, and a c5c3-specific +kubebuilder:default on those shared types would leak into Keystone's CRD. The two managed clusterRef names are brownfield-guarded: they are only invented when the brownfield discriminator (database.host / cache.servers) is unset, so the database/cache XOR validation still passes for a brownfield CR.

The defaulting constants in controlplane_webhook.go (e.g. DefaultRegion"RegionOne", DefaultDatabaseName "keystone", DefaultCacheBackend"dogpile.cache.pymemcache") are the single source of truth shared with the markers' documented values where a marker also exists.

FieldConditionDefault ValueMechanism
spec.region== """RegionOne"Marker + webhook
spec.korc.adminCredential.cloudCredentialsRef.secretName== """k-orc-clouds-yaml"Marker + webhook
spec.korc.adminCredential.applicationCredential.restricted== niltrue (pointer set to true; an explicit false is preserved)Marker + webhook
spec.korc.adminCredential.applicationCredential.rotation.mode== ""PasswordDrivenMarker + webhook
spec.korc.adminCredential.cloudCredentialsRef.cloudName== """admin"Marker + webhook
spec.infrastructure.database.database== """keystone"Webhook-only
spec.infrastructure.database.secretRef.name== """keystone-db" Webhook-only
spec.infrastructure.database.clusterRef.namehost == "" (managed mode)"openstack-db"Webhook-only, brownfield-guarded
spec.infrastructure.cache.backend== """dogpile.cache.pymemcache"Webhook-only
spec.infrastructure.cache.clusterRef.namelen(servers) == 0 (managed mode)"openstack-memcached"Webhook-only, brownfield-guarded
spec.korc.adminCredential.passwordSecretRef.name== """keystone-admin"Webhook-only
spec.korc.adminCredential.passwordSecretRef.key== """password"Webhook-only

database.secretRef.name default — managed-mode convenience name only. The webhook still defaults secretRef.name to the keystone-db value (unchanged), but that default is no longer the Secret Keystone consumes. In managed mode database.secretRef is operator-owned: reconcileDBCredentials materialises a per-ControlPlane ExternalSecret and reconcileKeystone overrides the projected Keystone CR's spec.database.secretRef to the operator-owned Secret {controlplane.Name}-keystone-db-credentials (key "password") — see managed-mode provisioning. In production the bare name keystone-db does not resolve to any cluster Secret (only the kind overlay ships a keystone-db ExternalSecret, pinned to the default identity's path for standalone Keystone instances); a managed ControlPlane never consumes it either way. A brownfield ControlPlane (database.host set, clusterRef == nil) MUST supply its own database.secretRef Secret out-of-band; the operator projects no ExternalSecret in brownfield mode.

Operational contract. The webhook only materializes the Secret names and references — it never invents credential material. In managed mode (database.clusterRef set) the operator itself projects the admin password: it creates a per-ControlPlane admin ExternalSecret ({controlplane.Name}-keystone-admin-credentials) materialising the password from OpenBao and overrides the projected Keystone CR's bootstrap.adminPasswordSecretRef onto it, so the cp-level passwordSecretRef (default keystone-admin) is not the Secret the child consumes — see the passwordSecretRef managed-mode note above. In brownfield mode (database.clusterRef == nil) the operator projects no admin ExternalSecret, so a ControlPlane that omits spec.korc (or spec.infrastructure.database.secretRef) relies on the cluster operator having pre-seeded the referenced Secrets in the ControlPlane's namespace before the credential sub-reconcilers can advance: the admin password Secret (keystone-admin, key password) and the K-ORC clouds.yaml ExternalSecret/Secret (k-orc-clouds-yaml). The infrastructure layer seeds these (see the quick-start). A missing admin password Secret degrades to KORCReady=False / WaitingForAdminPassword, and a not-yet-synced clouds.yaml to AdminCredentialReady=False / WaitingForCloudsYaml — never a silent authentication. A clouds.yaml that is synced but stale (a re-mint revoked the old credential while ESO has not yet re-materialised the Secret) degrades to AdminCredentialReady=False / WaitingForCloudsYamlSync: reconcileAdminCredential semantically compares the materialised Secret (by parsed application-credential id+secret) against the freshly assembled credential and forces an ESO re-sync, so the gate never passes against a revoked credential. TestIntegration_MinimalManagedToReady encodes this contract by pre-creating those Secrets at the defaulted names.

Validating Webhook

go
func (w *ControlPlaneWebhook) ValidateCreate(_ context.Context, obj *ControlPlane) (admission.Warnings, error)
func (w *ControlPlaneWebhook) ValidateUpdate(_ context.Context, _, newObj *ControlPlane) (admission.Warnings, error)
func (w *ControlPlaneWebhook) ValidateDelete(_ context.Context, _ *ControlPlane) (admission.Warnings, error)
  • ValidateCreate and ValidateUpdate both delegate to the internal validate() method (see Validating-webhook rules). ValidateCreate additionally enforces the one-ControlPlane-per-namespace contract: it lists existing ControlPlanes in the new object's namespace through the uncached API reader and rejects the CREATE with a Forbidden error naming the incumbent when one already exists. The check runs only on CREATE so an existing CR stays mutable; ValidateUpdate validates the new object only.
  • ValidateDelete always returns nil, nil. It exists only to satisfy the admission.Validator interface and is never invoked — the validating webhook does not register the delete verb, so deletion is unconditionally allowed even while the operator is down.

Status Conditions

The ControlPlane status is driven by five sub-reconcilers, each owning one condition type, plus an aggregate Ready condition. The condition-type constants in controlplane_controller.go are the single source of truth; call sites reference the constants rather than inline literals.

The sub-reconcilers run in dependency order, each gated on the previous one's condition being True:

InfrastructureReady → KeystoneReady → KORCReady → AdminCredentialReady → CatalogReady

Ready is True (reason AllReady) only when all sub-conditions are True (via conditions.AllTrue); otherwise it is False (reason NotAllReady). One exception bypasses the aggregation: when a namespace holds more than one ControlPlane (possible only for CRs that predate the one-per-namespace webhook guard or bypassed it), every CR except the oldest is parked with Ready=False reason DuplicateControlPlane naming the incumbent, and none of its sub-reconcilers run. For the full flow, see the ControlPlane Reconciler reference.

InfrastructureReady

Set by reconcileInfrastructure.

StatusReasonWhen
TrueInfrastructureReadyAll managed backing services are ensured and report Ready (or the control plane uses only brownfield infra, so there is nothing to provision).
FalseWaitingForDatabaseManaged MariaDB is ensured but not yet Ready.
FalseWaitingForCacheManaged Memcached is ensured but not yet Ready.
FalseMariaDBErrorError create-or-updating the MariaDB child.
FalseMemcachedErrorError create-or-updating the Memcached child.

KeystoneReady

Set by reconcileKeystone (gated on InfrastructureReady).

StatusReasonWhen
TrueKeystoneReadyThe projected Keystone CR reports Ready.
FalseWaitingForInfrastructureInfrastructureReady is not True; Keystone projection deferred.
FalseWaitingForKeystoneThe Keystone CR is ensured but not yet Ready.
FalseInvalidRotationIntervalservices.keystone.rotationInterval could not be converted to a cron schedule.
FalseKeystoneErrorError create-or-updating the Keystone CR.

KORCReady

Set by reconcileKORC.

StatusReasonWhen
TrueApplicationCredentialMintedThe K-ORC admin ApplicationCredential is minted and reports Available=True.
FalseWaitingForAdminPasswordThe admin password Secret/key is not yet available; minting deferred.
FalseApplicationCredentialFailedThe ApplicationCredential reports a terminal K-ORC error (GetTerminalError — an unrecoverable/invalid-config reason such as K-ORC being unable to authenticate); the message folds in any stuck admin Domain/User import.
FalseWaitingForApplicationCredentialThe ApplicationCredential CR is ensured but not yet Available; the message folds in any stuck admin Domain/User import.
FalseAdminPasswordErrorNon-missing error reading the admin password.
FalseApplicationCredentialErrorError create-or-updating the ApplicationCredential CR.

AdminCredentialReady

Set by reconcileAdminCredential (gated on KORCReady, the OpenBao-backed ClusterSecretStore being Ready, the K-ORC clouds.yaml ExternalSecret being Ready, and the materialised clouds.yaml Secret semantically matching (parsed application-credential id+secret) the freshly assembled credential).

StatusReasonWhen
TrueAdminCredentialReadyThe admin application credential is committed to the owned Secret, mirrored to OpenBao, and the materialised clouds.yaml Secret matches the assembled credential.
FalseWaitingForKORCKORCReady is not True; credential push deferred.
FalseSecretStoreNotReadyThe OpenBao-backed ClusterSecretStore is not Ready; the secret backend is unreachable.
FalseWaitingForCloudsYamlThe operator-created per-ControlPlane k-orc-clouds-yaml ExternalSecret in the control-plane namespace (co-located with the K-ORC CRs per C1; created and owned by reconcileKORC) is not yet Ready.
FalseWaitingForCloudsYamlSyncThe materialised k-orc-clouds-yaml Secret is absent or still holds a stale credential (a re-mint revoked the old one but ESO has not re-synced yet). reconcileAdminCredential stamps the external-secrets.io/force-sync annotation to force an immediate re-sync and compares the materialised Secret semantically (parsed application-credential id+secret) against the assembled credential before reporting Ready, so the condition never reads True against a revoked credential.
FalseCloudsYamlSyncStuckThe materialised k-orc-clouds-yaml Secret has failed to match the assembled credential for longer than cloudsYamlSyncStuckTimeout (measured from the credential's LastRotation); the ESO ExternalSecret or OpenBao backend may be unable to sync. Escalated from WaitingForCloudsYamlSync so a never-converging sync is alertable and distinguishable from a transient miss.
FalseCloudsYamlErrorError checking the clouds.yaml ExternalSecret, forcing its re-sync, or reading the materialised Secret.
FalseSecretErrorError ensuring the operator-owned application-credential Secret.
FalsePushSecretErrorError ensuring the OpenBao PushSecret.

CatalogReady

Set by reconcileCatalog (gated on AdminCredentialReady, and both the identity Service and Endpoint reporting Available for their current generation — korcAvailableUpToDate, which refuses a stale Available condition whose ObservedGeneration lags the object, so an endpoint/region edit cannot flip CatalogReady True before K-ORC re-reconciles).

StatusReasonWhen
TrueCatalogRegisteredThe Keystone identity Service and public Endpoint are registered as K-ORC CRs and both report Available.
FalseWaitingForAdminCredentialAdminCredentialReady is not True; catalog registration deferred.
FalseWaitingForCatalogThe identity Service/Endpoint are registered but not yet Available for the current generation (the catalog entries have not landed in Keystone, or a stale Available condition whose ObservedGeneration lags the object does not yet count).
FalseCatalogFailedThe identity Service or Endpoint reports a terminal K-ORC error (GetTerminalError — e.g. a wrong clouds.yaml endpoint or an import stuck on "created externally").
FalseServiceErrorError create-or-updating the identity Service CR.
FalseEndpointErrorError create-or-updating the identity Endpoint CR.

Ready (aggregate)

Set by setReadyCondition.

StatusReasonWhen
TrueAllReadyAll five sub-conditions above are True.
FalseNotAllReadyOne or more sub-conditions are not True.

Child Namespace

DECISION: every child the reconciler projects — the MariaDB, Memcached, and Keystone CRs, the K-ORC ApplicationCredential / Service / Endpoint CRs, the owned Secret, and the OpenBao PushSecret — is created in the ControlPlane's own namespace (childNamespace = cp.Namespace), not a hardcoded "openstack" literal.

The rationale is garbage collection: controllerutil.SetControllerReference rejects cross-namespace owner references because Kubernetes GC only cascades within a single namespace. A child in openstack owned by a ControlPlane in default would fail admission and, even if forced, would never be GC'd. Co-locating the children with their owner keeps the owner reference valid and the GC cascade intact. In production the ControlPlane is deployed into the openstack control-plane namespace, so the projected children land in openstack exactly as expected — the namespace is now derived from the owner rather than assumed. Projected child names are deterministic and derived from the ControlPlane name (e.g. {name}-keystone, {name}-admin-app-credential, {name}-identity-service, {name}-identity-endpoint) so a single namespace can host the children of multiple ControlPlanes without clashing.