Skip to content

Keystone CRD API Reference

Reference documentation for the Keystone Custom Resource Definition (CC-0011). The Keystone CRD is the reference implementation for all CobaltCore service operators — the patterns established here (types, webhooks, generation, scheme registration) will be replicated for Nova, Neutron, Glance, and other OpenStack service operators.

API Group and Version

FieldValue
Groupkeystone.openstack.c5c3.io
Versionv1alpha1
KindKeystone
List KindKeystoneList
ScopeNamespaced

Import path:

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

Scheme registration:

The init() function in keystone_types.go registers Keystone and KeystoneList with the SchemeBuilder. Operator main.go calls AddToScheme to register the types with the manager's scheme.


Resource Shape

yaml
apiVersion: keystone.openstack.c5c3.io/v1alpha1
kind: Keystone
metadata:
  name: keystone
  namespace: openstack
spec:
  replicas: 3
  image:
    repository: c5c3/keystone
    tag: "2025.1"
  database:
    clusterRef:
      name: mariadb
    database: keystone
    secretRef:
      name: keystone-db-credentials
      key: password
  cache:
    backend: dogpile.cache.pymemcache
    clusterRef:
      name: memcached
  fernet:
    rotationSchedule: "0 0 * * 0"
    maxActiveKeys: 3
  bootstrap:
    adminUser: admin
    adminPasswordSecretRef:
      name: keystone-admin
      key: password
    region: RegionOne
status:
  conditions:
    - type: Ready
      status: "True"
      reason: AllSubResourcesReady
      message: All sub-resources are ready
      lastTransitionTime: "2026-03-09T00:00:00Z"
  endpoint: https://keystone.openstack.svc:5000/v3

Printer Columns

kubectl get keystones displays these columns:

ColumnJSON PathType
Ready.status.conditions[?(@.type=='Ready')].statusstring
Endpoint.status.endpointstring
Age.metadata.creationTimestampdate

KeystoneSpec

FieldTypeRequiredDefaultDescription
replicasint32No3Number of Keystone API replicas. Minimum: 1. The webhook provides a secondary default of 3 when zero.
imageImageSpecYesKeystone container image reference.
databaseDatabaseSpecYesMariaDB connection configuration.
cacheCacheSpecYesMemcached cache configuration.
fernetFernetSpecNoSee belowFernet key rotation configuration.
federation*FederationSpecNonilFederation configuration (optional).
bootstrapBootstrapSpecYesInitial Keystone bootstrap parameters.
middleware[]MiddlewareSpecNonilWSGI middleware filters for api-paste.ini.
plugins[]PluginSpecNonilService plugins/drivers to configure.
policyOverrides*PolicySpecNonilCustom oslo.policy rules.
extraConfigmap[string]map[string]stringNonilFree-form INI sections for additional configuration.

CEL Validation Rules

The CRD includes structural validation rules enforced by the API server before webhooks are invoked:

FieldRuleError Message
spec.databasehas(self.clusterRef) != has(self.host)"exactly one of clusterRef or host must be set"
spec.policyOverridesself.rules != null || self.configMapRef != null"at least one of rules or configMapRef must be set"
spec.replicasMinimum: 1
spec.fernet.maxActiveKeysMinimum: 3

FernetSpec

Configures Fernet token key rotation.

FieldTypeRequiredDefaultDescription
rotationSchedulestringNo"0 0 * * 0"Cron expression (5-field standard format) for key rotation. Validated by robfig/cron/v3 ParseStandard.
maxActiveKeysint32No3Maximum number of active Fernet keys. Minimum: 3.

FederationSpec

Configures Keystone federation support. This is a pointer field (*FederationSpec) on KeystoneSpec — when nil, federation is disabled.

FieldTypeRequiredDefaultDescription
enabledboolYesActivates federation support.

BootstrapSpec

Configures the initial Keystone bootstrap.

FieldTypeRequiredDefaultDescription
adminUserstringNo"admin"Admin username for the bootstrap.
adminPasswordSecretRefSecretRefSpecYesSecret containing the admin password.
regionstringNo"RegionOne"Keystone region name.

KeystoneStatus

FieldTypeDescription
conditions[]metav1.ConditionLatest available observations of the Keystone state.
endpointstringKeystone API endpoint URL (set by the controller when ready).

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


Shared Types (from internal/common/types)

The following types are imported as commonv1 from github.com/c5c3/forge/internal/common/types. They are shared across all CobaltCore operator CRDs.

ImageSpec

FieldTypeRequiredDescription
repositorystringYesContainer image repository (e.g., c5c3/keystone).
tagstringYesImage tag (e.g., 2025.1).

DatabaseSpec

FieldTypeRequiredDescription
clusterRef*corev1.LocalObjectReferenceNoReference to a MariaDB CR (managed mode).
hoststringNoDatabase hostname (brownfield mode).
portint32NoDatabase port (brownfield mode, default 3306).
databasestringYesDatabase name.
secretRefSecretRefSpecYesSecret with database credentials.

Exactly one of clusterRef or host must be set (enforced by CEL validation).

CacheSpec

FieldTypeRequiredDescription
clusterRef*corev1.LocalObjectReferenceNoReference to a Memcached CR (managed mode).
backendstringYesCache backend (e.g., dogpile.cache.pymemcache).
servers[]stringNoCache server endpoints (brownfield mode).

SecretRefSpec

FieldTypeRequiredDescription
namestringYesName of the Kubernetes Secret.
keystringNoKey within the Secret's data.

PolicySpec

FieldTypeRequiredDescription
rulesmap[string]stringNoInline policy rule overrides. Keys are oslo.policy rule names; values are rule definitions. Inline rules take precedence over ConfigMap rules.
configMapRef*corev1.LocalObjectReferenceNoReference to a ConfigMap containing a policy.yaml key with rule overrides.

When policyOverrides is set on KeystoneSpec, at least one of rules or configMapRef must be provided (enforced by both CEL validation and the webhook).

PluginSpec

FieldTypeRequiredDescription
namestringYesPlugin name (e.g., keystone-keycloak-backend).
configSectionstringYesINI section name (e.g., keycloak). Must be unique across all plugins.
configmap[string]stringNoKey-value pairs for the plugin's INI section.

MiddlewareSpec

FieldTypeRequiredDescription
namestringYesFilter name (e.g., audit).
filterFactorystringYesPython entry point (e.g., audit_middleware:filter_factory).
positionPipelinePositionYesPipeline insertion point: "before" or "after".
configmap[string]stringNoKey-value pairs for the filter section.

Webhooks

The KeystoneWebhook struct implements both defaulting and validating admission webhooks via the admission.Defaulter[*Keystone] and admission.Validator[*Keystone] interfaces from controller-runtime.

Registration

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

Registers both webhooks with the manager using builder.WebhookManagedBy[*Keystone].

Defaulting Webhook

go
func (w *KeystoneWebhook) Default(_ context.Context, obj *Keystone) error

Sets spec fields to their documented defaults when they carry zero values. Explicit (non-zero) values are never overridden.

FieldConditionDefault Value
spec.replicas== 03
spec.fernet.maxActiveKeys== 03
spec.cache.backend== """dogpile.cache.pymemcache"
spec.bootstrap.adminUser== """admin"
spec.bootstrap.region== """RegionOne"

Design note: spec.fernet.rotationSchedule is NOT defaulted by the webhook — it relies solely on the Kubebuilder +kubebuilder:default="0 0 * * 0" marker (plan decision #3, CC-0011). The webhook uses conditional checks (== 0 / == "") rather than always-set to cooperate with the remaining Kubebuilder +default markers, which also provide schema-level defaults. Both layers are intentional — schema defaults apply at deserialization time, while webhook defaults catch zero values that bypass schema defaults (e.g., explicit replicas: 0).

Validating Webhook

go
func (w *KeystoneWebhook) ValidateCreate(_ context.Context, obj *Keystone) (admission.Warnings, error)
func (w *KeystoneWebhook) ValidateUpdate(_ context.Context, _, newObj *Keystone) (admission.Warnings, error)
func (w *KeystoneWebhook) ValidateDelete(_ context.Context, _ *Keystone) (admission.Warnings, error)
  • ValidateCreate and ValidateUpdate both delegate to the internal validate() method. There are no create-specific or update-specific rules.
  • ValidateDelete always returns nil — deletion is unconditionally allowed.

Validation Rules

The validate() method accumulates all errors in a field.ErrorList and returns a single apierrors.NewInvalid error. It does not short-circuit on the first error.

RuleField PathError TypeCondition
Replicas minimumspec.replicasfield.Invalidreplicas < 1. Defense-in-depth alongside the +kubebuilder:validation:Minimum=1 marker.
Cron expressionspec.fernet.rotationSchedulefield.Invalidcron.ParseStandard() fails. Error message includes the parse failure details.
Duplicate plugin sectionsspec.plugins[i].configSectionfield.DuplicateTwo or more plugins share the same configSection value.
Policy source requiredspec.policyOverridesfield.RequiredpolicyOverrides is set but both rules and configMapRef are nil/empty.
Empty policy rule namespec.policyOverrides.rulesfield.InvalidA key in rules map is the empty string.

Error format: All validation errors are returned as a structured apierrors.StatusError with GroupKind{Group: "keystone.openstack.c5c3.io", Kind: "Keystone"}, providing clear, field-specific error messages to the operator.


Testing

The Keystone CRD has a three-layer test strategy (CC-0012):

  1. Unit tests — fast, in-process tests for webhook logic (existing from CC-0011).
  2. Integration tests — envtest-based tests that run a real API server + etcd to validate CRD schema, CEL rules, and webhooks through the full admission pipeline.
  3. E2E tests — Chainsaw tests that deploy the operator to a real cluster and verify webhook rejection in a production-like environment.

Running the Tests

LayerCommandPrerequisites
Unitgo test ./operators/keystone/api/v1alpha1/None
Integrationgo test -tags=integration ./operators/keystone/api/v1alpha1/KUBEBUILDER_ASSETS set to envtest binaries
E2Echainsaw test --test-dir tests/e2e/keystone/invalid-cr/Operator deployed to a cluster with webhooks active

envtest Integration Helper

The operators/keystone/internal/testutil package provides a Keystone-specific envtest setup helper that configures CRD installation and webhook serving for integration tests.

go
func SetupKeystoneEnvTest(
    t testing.TB,
    addToScheme func(*runtime.Scheme) error,
    registerWebhooks func(ctrl.Manager) error,
) (client.Client, context.Context, context.CancelFunc)

Design decisions (CC-0012):

  • Uses a local schemeSharedScheme() from internal/common is not modified. Only Keystone tests need Keystone types registered.
  • Resolves CRD and webhook manifest paths via runtime.Caller(0) relative navigation, matching the pattern in internal/common/testutil/envtest/setup.go.
  • Starts a controller-runtime manager with a webhook server bound to the envtest-allocated host, port, and certificate directory.
  • Waits for the webhook server TLS endpoint to accept connections before returning.
  • Tears down the environment automatically via t.Cleanup().

Parameters:

NameTypeDescription
addToSchemefunc(*runtime.Scheme) errorRegisters Keystone API types (breaks import cycle between testutil and v1alpha1).
registerWebhooksfunc(ctrl.Manager) errorSets up webhook handlers with the manager.

The SkipIfEnvTestUnavailable guard is re-exported from internal/common/testutil/envtest for convenience.

Integration Test Coverage

All integration tests use the //go:build integration tag and call testutil.SkipIfEnvTestUnavailable(t) as the first statement.

CRD Installation and Valid CR Acceptance

TestRequirementBehavior
TestIntegration_CRDInstalledCRD discoverableLists CRDs via apiextensions API; verifies keystones.keystone.openstack.c5c3.io is present.
TestIntegration_ValidCRAcceptedHappy-path admissionCreates a valid Keystone CR (brownfield database mode), verifies HTTP 201 and successful Get.
TestIntegration_ValidCRWithClusterRefAcceptedClusterRef modeCreates a valid CR using database.clusterRef and cache.clusterRef, verifies acceptance and readback.

CEL Validation Rejection

TestRequirementTriggerExpected Error
TestIntegration_CELRejectsDBBothClusterRefAndHostMutual exclusivityBoth database.clusterRef and database.host setInvalid/Forbidden containing "database"
TestIntegration_CELRejectsCacheBothClusterRefAndServersMutual exclusivityBoth cache.clusterRef and cache.servers setInvalid/Forbidden containing "cache"
TestIntegration_CELRejectsReplicasBelowMinimumMinimum constraintreplicas = -1 (note: 0 is converted to 3 by the defaulting webhook, so -1 is used)Invalid/Forbidden
TestIntegration_CELRejectsMaxActiveKeysBelowMinimumMinimum constraintfernet.maxActiveKeys = 1 (below minimum of 3; 0 is defaulted to 3 by webhook)Invalid/Forbidden
TestIntegration_CELRejectsPolicyOverridesEmptyPolicy source requiredpolicyOverrides set with neither rules nor configMapRefInvalid/Forbidden containing "policyOverrides"

Admission pipeline note: In Kubernetes, the admission order is: mutating webhooks then schema validation (CEL) then validating webhooks. The defaulting webhook converts replicas: 0 to 3 and maxActiveKeys: 0 to 3 before CEL validation runs, so these tests use values that bypass defaulting (negative or non-zero-but-below-minimum) to exercise the CRD schema constraints.

Webhook Defaulting

TestRequirementBehavior
TestIntegration_WebhookDefaultsSetsZeroValuesDefaults appliedCreates a CR with zero-valued defaultable fields; verifies replicas=3, cache.backend="dogpile.cache.pymemcache", bootstrap.adminUser="admin", bootstrap.region="RegionOne", fernet.maxActiveKeys=3 after admission.
TestIntegration_WebhookDefaultsPreservesExplicitExplicit values preservedCreates a CR with replicas=5 and region="EU-West"; verifies these values are not overwritten by the defaulting webhook.

Chainsaw E2E Tests

E2E tests live in tests/e2e/keystone/invalid-cr/ and use the Chainsaw framework (chainsaw.kyverno.io/v1alpha2). They verify webhook rejection in a real cluster with the operator deployed.

StepManifestRequirementExpected Error
invalid-cron-expression-rejected00-invalid-cron.yamlInvalid cronError containing "rotationSchedule" and "invalid cron expression"
duplicate-plugin-config-section-rejected01-duplicate-plugins.yamlDuplicate configSectionError containing "configSection" and "Duplicate value"

Each step uses apply with expect to assert that the $error variable is non-null and contains the expected field-level error message.


CRD Generation

The CRD manifest and DeepCopy methods are generated by controller-gen:

TargetCommandOutput
DeepCopymake generateoperators/keystone/api/v1alpha1/zz_generated.deepcopy.go
CRD YAMLmake manifestsoperators/keystone/config/crd/bases/keystone.openstack.c5c3.io_keystones.yaml

Both targets are parameterized by operator directory in the Makefile. Generated zz_generated.*.go files are excluded from linting via .golangci.yml.

Generated DeepCopy Types

zz_generated.deepcopy.go provides DeepCopyObject() and DeepCopyInto() for:

  • Keystone
  • KeystoneList
  • KeystoneSpec
  • KeystoneStatus
  • FernetSpec
  • FederationSpec
  • BootstrapSpec

File Layout

text
operators/keystone/
├── api/v1alpha1/
│   ├── groupversion_info.go          GroupVersion, SchemeBuilder, AddToScheme
│   ├── keystone_types.go             CRD types + init() scheme registration
│   ├── keystone_webhook.go           Defaulting + validating webhooks
│   ├── keystone_types_test.go        Type and scheme registration tests
│   ├── keystone_webhook_test.go      Webhook unit tests (table-driven)
│   ├── integration_test.go           envtest integration tests (CC-0012)
│   └── zz_generated.deepcopy.go     Generated DeepCopy methods
├── config/crd/bases/
│   └── keystone.openstack.c5c3.io_keystones.yaml  Generated CRD manifest
├── config/webhook/
│   ├── manifests.yaml                Generated webhook configurations
│   └── ...
├── internal/testutil/
│   └── envtest_setup.go              Keystone-specific envtest helper (CC-0012)
└── main.go                           Scheme registration + bootstrap + webhook wiring

tests/e2e/keystone/
└── invalid-cr/
    ├── chainsaw-test.yaml            Chainsaw E2E test definition (CC-0012)
    ├── 00-invalid-cron.yaml          Invalid cron expression CR manifest
    └── 01-duplicate-plugins.yaml     Duplicate plugin configSection CR manifest

This layout is the canonical pattern for all CobaltCore operators. New operators should replicate this directory structure.