Helm Values Schema
Reference documentation for the values.schema.json JSON Schema that validates the Helm chart values of every CobaltCore operator chart — keystone-operator, c5c3-operator, glance-operator, horizon-operator, placement-operator, barbican-operator, ovn-operator and neutron-operator. Helm enforces this schema automatically during helm install, helm upgrade, helm lint, and helm template.
All charts share one schema source, so the value tables below apply to every operator unchanged. The few keys that exist on only some charts are called out in the per-operator applicability matrix. Chart values themselves — the keystone-operator and c5c3-operator defaults, resource recommendations, and ready-to-use override snippets — are in Values by operator at the end.
File Location
Each chart ships its own generated copy of the schema:
| Chart | Schema path |
|---|---|
| keystone-operator | operators/keystone/helm/keystone-operator/values.schema.json |
| c5c3-operator | operators/c5c3/helm/c5c3-operator/values.schema.json |
| glance-operator | operators/glance/helm/glance-operator/values.schema.json |
| horizon-operator | operators/horizon/helm/horizon-operator/values.schema.json |
| placement-operator | operators/placement/helm/placement-operator/values.schema.json |
| barbican-operator | operators/barbican/helm/barbican-operator/values.schema.json |
| ovn-operator | operators/ovn/helm/ovn-operator/values.schema.json |
| neutron-operator | operators/neutron/helm/neutron-operator/values.schema.json |
| cinder-operator | operators/cinder/helm/cinder-operator/values.schema.json |
| nova-operator | operators/nova/helm/nova-operator/values.schema.json |
| operator-library-testbed | operators/shared/helm/operator-library-testbed/values.schema.json (the library's test consumer, never published) |
Generated file
This schema is generated from the shared source in hack/gen-helm-values-schema.py, which discovers every application chart under operators/*/helm/ and emits each chart's schema from the same definitions so they cannot drift. The generator names no operator: the image repository default comes from the chart's values.yaml, the webhook.enabled description lists the CR kinds found in the operator's config/webhook manifests, and networkPolicy is included when the chart ships templates/networkpolicy.yaml. What a single chart adds on top lives next to that chart in values.schema.extras.json, a partial schema that is deep-merged into the shared one (objects merge recursively, lists concatenate). Edit the generator or a chart's extras file and run make gen-helm-schema; do not hand-edit values.schema.json — make verify-helm-schema (run in CI) fails on drift.
Schema Overview
The schema uses JSON Schema Draft-07 and defines constraints for every configurable parameter in values.yaml. No additional properties are allowed at any object level, ensuring typos and unsupported keys are caught at deploy time rather than silently ignored.
| Property | Value |
|---|---|
| JSON Schema Draft | draft-07 |
additionalProperties | false at all object levels |
Each chart carries its own version in its Chart.yaml; the schema is not tied to a chart version, so it is not repeated here.
Per-Operator Applicability
Every chart exposes the same core value keys — image, replicas, resources, nodeSelector, tolerations, priorityClassName, rbac, leaderElection, controller, webhook, metrics, logging, monitoring, serviceAccount, extraArgs, extraEnv, nameOverride, fullnameOverride. A few keys are conditional on what a chart ships, so they are present only where they apply:
| Key | Charts | Source |
|---|---|---|
networkPolicy | every chart that ships templates/networkpolicy.yaml — all but the c5c3-operator | shared generator |
networkPolicy.openBao | barbican-operator | values.schema.extras.json |
rbac.secretStoreNamespaces | barbican-operator | values.schema.extras.json |
federation | keystone-operator | values.schema.extras.json |
barbicanOperator | c5c3-operator | values.schema.extras.json |
- A key a chart does not carry is rejected there (
additionalProperties: false). controller.maxConcurrentReconcilesis accepted by every chart, but only the controllers that opt in consume it (see controller).controller.defaultImagePullPolicyis accepted by every chart; the c5c3-operator renders no container and does not consume it.
Validated Properties
image
| Field | Type | Constraint | Default |
|---|---|---|---|
image.repository | string | — | ghcr.io/c5c3/keystone-operator |
image.tag | string | — | "" |
image.pullPolicy | string | enum: Always, IfNotPresent, Never | IfNotPresent |
replicas
| Field | Type | Constraint | Default |
|---|---|---|---|
replicas | integer | minimum: 1 | 2 |
resources
Resource fields (cpu, memory) use a shared resourceQuantity definition that accepts either a Kubernetes quantity string matching the pattern ^(\.[0-9]+|[0-9]+(\.[0-9]*)?)((e[0-9]+)|(m|k|M|G|T|P|E|Ki|Mi|Gi|Ti|Pi|Ei))?$ or a non-negative number. The pattern enforces mutual exclusion between exponent notation (e[0-9]+) and SI/binary suffixes — values like 1e3m or 1e3Ki are rejected because the Kubernetes quantity grammar does not allow combining both.
| Field | Type | Constraint | Default |
|---|---|---|---|
resources.limits.cpu | resourceQuantity | pattern or number >= 0 | 500m |
resources.limits.memory | resourceQuantity | pattern or number >= 0 | 128Mi |
resources.requests.cpu | resourceQuantity | pattern or number >= 0 | 10m |
resources.requests.memory | resourceQuantity | pattern or number >= 0 | 64Mi |
nodeSelector, tolerations and priorityClassName
| Field | Type | Constraint | Default |
|---|---|---|---|
nodeSelector | object | string values only (stringMap) | {} |
tolerations | array of object | item keys key, operator, value, effect, tolerationSeconds and no others; operator enum: Equal, Exists, Lt, Gt; effect enum: "", NoSchedule, PreferNoSchedule, NoExecute; tolerationSeconds integer | [] |
priorityClassName | string | maxLength: 253 | "" |
Each key renders on the operator pod spec (spec.template.spec) only when it is non-empty, so the defaults render the same pod template as a chart without the keys. The fixed soft hostname spread (topologySpreadConstraints) renders beside them, and the charts expose no affinity key. A toleration without operator means Equal. Lt and Gt need the API server's TaintTolerationComparisonOperators feature gate. The PriorityClass must exist before the pods are created, or the API server rejects them.
The keys need operator-library 0.9.0, which the chart versions keystone-operator 0.11.0, c5c3-operator 0.14.0, horizon-operator 0.4.0, cinder-operator and nova-operator 0.2.0, and barbican-, glance-, neutron-, ovn- and placement-operator 0.3.0 carry. An older chart rejects them.
rbac
| Field | Type | Constraint | Default |
|---|---|---|---|
rbac.namespaceScoped | boolean | conditional: requires webhook.enabled=false | false |
Conditional constraint: When rbac.namespaceScoped is true, the schema requires webhook.enabled to be false. This is enforced via a top-level if/then rule. Helm renders the ValidatingWebhookConfiguration and MutatingWebhookConfiguration of a release, and both are cluster-scoped: each intercepts the CR kind in every namespace and routes to its own release. Several namespace-scoped releases of one chart would each intercept every tenant's CRs, and the webhooks read cluster-scoped kinds a namespaced Role grants nothing for. A namespace-scoped install is admitted by a standalone webhook release instead (see webhook).
Production recommendation: For a control plane confined to a single namespace, set rbac.namespaceScoped: true to bound a compromised operator pod to one namespace's Secrets instead of the cluster-wide Secret access the default ClusterRole grants — see Multi-Tenant Deployment → Security trade-off for the privilege-escalation path this closes. The default stays false because some capabilities still need cluster scope.
leaderElection
| Field | Type | Constraint | Default |
|---|---|---|---|
leaderElection.enabled | boolean | — | true |
controller
| Field | Type | Constraint | Default |
|---|---|---|---|
controller.maxConcurrentReconciles | integer | minimum: 1 | unset |
controller.defaultImagePullPolicy | string | enum: Always, IfNotPresent, Never | unset |
The maximum number of CRs that may reconcile concurrently (controller-runtime MaxConcurrentReconciles), rendered as --max-concurrent-reconciles. Left unset in the c5c3-operator chart; the keystone-operator chart defaults it to 2 (the upstream default of 1 serialises reconciles across CRs, so one slow or flapping CR delays every other). Raise to 5–10 for fleets with many CRs. The key is accepted by every chart, but only controllers that opt in consume it — the c5c3-operator accepts the flag without acting on it yet.
controller.defaultImagePullPolicy is the imagePullPolicy of every container the operator renders from an image reference that names no pullPolicy, rendered as --default-image-pull-policy. Unset, the operator applies the rule of the ImageSpec: IfNotPresent for a digest, Always for a tag. Set it to IfNotPresent where the nodes hold preloaded images under the published tag, as CI does. No chart sets it in its values.yaml; the nine service operator charts carry it as a commented key. The c5c3-operator chart accepts it, and the operator does not consume it, because it renders no container.
webhook
| Field | Type | Constraint | Default |
|---|---|---|---|
webhook.enabled | boolean | — | true |
webhook.standalone | boolean | conditional: requires webhook.enabled=true and rbac.namespaceScoped=false | false |
webhook.standalone: true runs the release as a standalone admission webhook: the manager is started with --enable-controllers=false and without --leader-elect, so it registers no controller and elects no leader, and the ClusterRole renders the chart's webhook rule set (templates/_webhook-rbac-rules.tpl) instead of the manager rules. One such release per cluster serves the CRs of every namespace, for the namespace-scoped installs of the chart, which set webhook.enabled=false. See Multi-Tenant Deployment → Admission webhooks.
Conditional constraint: When webhook.standalone is true, a second top-level if/then rule requires webhook.enabled to be true and rbac.namespaceScoped to be false: the release serves the webhooks of every namespace under a ClusterRole.
The key needs operator-library 0.11.0, which the chart versions keystone-operator 0.13.0, c5c3-operator 0.17.0, horizon-operator 0.6.0, cinder-operator and nova-operator 0.4.0, and barbican-, glance-, neutron-, ovn- and placement-operator 0.5.0 carry. An older chart rejects it.
metrics
| Field | Type | Constraint | Default |
|---|---|---|---|
metrics.port | integer | minimum: 1, maximum: 65535 | 8080 |
monitoring
| Field | Type | Constraint | Default |
|---|---|---|---|
monitoring.serviceMonitor.enabled | boolean | requires the monitoring.coreos.com CRDs in-cluster when enabled | false |
monitoring.serviceMonitor.interval | string | pattern: Go duration (15s, 30s, 1m) or 0 for the global default | 30s |
See How to enable the Keystone operator metrics endpoint.
networkPolicy
The networkPolicy block (default-off operator pod hardening with fail-closed render guards) is validated by the schema on every chart that ships a templates/networkpolicy.yaml — every operator chart but the c5c3-operator, which does not ship the template, so the key is rejected there. The template itself is the shared operator-library.networkPolicy; every service operator chart adds an egress rule for its operator's API health check through the operator-library.chart.networkPolicyEgress hook, and the barbican-operator one for OpenBao (networkPolicy.openBao, the only one with a value). Its fields are documented in Keystone Operator NetworkPolicy.
federation
| Field | Type | Constraint | Default |
|---|---|---|---|
federation.metadataAllowCidrs | array | each item matches the shared cidr pattern definition | [] |
The federation block is keystone-only — it is layered onto the keystone-operator schema by that chart's values.schema.extras.json (unlike networkPolicy, which the shared generator emits for every chart that ships the template). Each metadataAllowCidrs entry is validated against the same shared cidr pattern definition the networkPolicy block reuses. The rendered --federation-metadata-allow-cidrs flag and its runtime semantics are covered in the keystone-operator packaging reference.
extraArgs and extraEnv
| Field | Type | Constraint | Default |
|---|---|---|---|
extraArgs | array | non-empty strings | [] |
extraEnv | array | objects with a non-empty name (Kubernetes EnvVar) | [] |
Both are appended verbatim to the manager container — extraArgs after the flags the chart renders itself, extraEnv after the variables the chart derives from its own values — for a flag or variable the operator binary accepts but the chart exposes no value for.
operator-library
A reserved, empty values namespace for the operator-library library subchart. The library carries no configurable values; Helm injects this key during values coalescing, so the root-level additionalProperties: false must permit it.
logging
The operator runs the controller-runtime zap logger in the production profile by default (development: false): JSON encoder, info-level verbosity, and stack traces only at error level. Override these for human-readable console output during local development. Each value maps to a controller-runtime --zap-* flag and is omitted from the operator args when left at its default, so the production profile is the behaviour unless a field is set explicitly.
| Field | Type | Constraint | Default |
|---|---|---|---|
logging.development | boolean | — | false |
logging.level | string | pattern: debug, info, error, panic, or a positive integer (empty allowed) | "" |
logging.encoder | string | enum: json, console (empty allowed) | "" |
serviceAccount
| Field | Type | Constraint | Default |
|---|---|---|---|
serviceAccount.create | boolean | — | true |
serviceAccount.name | string | — | "" |
Name Overrides
| Field | Type | Constraint | Default |
|---|---|---|---|
nameOverride | string | — | "" |
fullnameOverride | string | — | "" |
Validation Behavior
Helm validates the merged values object against the schema before template rendering. Validation failures produce errors with the JSON path of the offending value:
Error: values don't meet the specifications of the schema(s) in the following chart(s):
keystone-operator:
- at '/image/pullPolicy': value must be one of 'Always', 'IfNotPresent', 'Never'Commands That Trigger Validation
| Command | Validates |
|---|---|
helm install | Yes |
helm upgrade | Yes |
helm lint | Yes |
helm template | Yes |
Resource Quantity Definition
The resourceQuantity definition uses anyOf to accept two formats:
- String format — matches the Kubernetes resource quantity pattern (e.g.,
500m,128Mi,1Gi,0.5). - Numeric format — any non-negative number (e.g.,
0,0.5,1).
This allows both cpu: "500m" (string) and cpu: 0.5 (number) as valid inputs while rejecting malformed strings like cpu: "not-valid" and negative numbers like cpu: -1.
Test Coverage
Schema validation is tested with helm-unittest once for the shared schema, in operators/shared/helm/operator-library-testbed/tests/schema_validation_test.yaml; each operator chart's own tests/schema_validation_test.yaml proves the chart ships an enforced schema and covers the keys its extras file adds.
Negative Tests (rejection)
| Category | Example |
|---|---|
| Type violations | replicas: "abc" (string instead of integer) |
| Enum violations | image.pullPolicy: "InvalidPolicy", controller.defaultImagePullPolicy: "Sometimes" |
| Range violations | replicas: 0, metrics.port: 65536 |
| Unknown properties | image.digest: "sha256:abc" |
| Invalid quantities | resources.limits.cpu: "not-valid" |
| Exponent+suffix | cpu: "1e3m", memory: "1e3Ki" |
| Conditional constraint | rbac.namespaceScoped=true with webhook.enabled=true; webhook.standalone=true with webhook.enabled=false or with rbac.namespaceScoped=true |
| Standalone webhook type | webhook.standalone: "yes" |
| Logging constraints | logging.development: "yes", logging.encoder: "xml", logging.level: "verbose" |
| Placement constraints | nodeSelector: {role: 1}, a toleration with operator: Sometimes, effect: NoSchedul, tolerationSeconds: "300" or the unknown key taint, priorityClassName: 5, a priorityClassName of 254 characters |
Positive Tests (acceptance)
| Category | Example |
|---|---|
| Custom replicas | replicas: 5 |
| Custom metrics port | metrics.port: 9090 |
| String resource quantities | cpu: "2", memory: "1Gi" |
| Numeric resource quantities | cpu: 0.5 |
| Exponent-only quantities | cpu: "1e3" |
| Conditional constraint | rbac.namespaceScoped=true with webhook.enabled=false |
| Standalone webhook | webhook.standalone=true |
| Logging overrides | development: true, level: debug, encoder: console |
| Operator image pull policy | controller.defaultImagePullPolicy: Never |
| Placement keys | priorityClassName, a nodeSelector label and two tolerations, one with tolerationSeconds; a toleration with effect: "", which matches every effect |
Values by Operator
The schema above is shared; the shipped defaults differ only where a chart does not carry a key. The table below is the practical values summary for the two operators this reference centres on. The glance-operator, horizon-operator, placement-operator, barbican-operator, ovn-operator and neutron-operator follow the keystone-operator defaults — including controller.maxConcurrentReconciles: 2 — and differ only by their own image.repository, their webhook.enabled description, and by carrying no federation key (see the applicability matrix).
| Key | keystone-operator | c5c3-operator |
|---|---|---|
image.repository | ghcr.io/c5c3/keystone-operator | ghcr.io/c5c3/c5c3-operator |
image.tag | "" (falls back to .Chart.AppVersion) | "" (falls back to .Chart.AppVersion) |
image.pullPolicy | IfNotPresent | IfNotPresent |
replicas | 2 | 2 |
resources.requests | cpu: 10m, memory: 64Mi | cpu: 10m, memory: 64Mi |
resources.limits | cpu: 500m, memory: 128Mi | cpu: 500m, memory: 128Mi |
nodeSelector | {} | {} |
tolerations | [] | [] |
priorityClassName | "" | "" |
rbac.namespaceScoped | false | false |
leaderElection.enabled | true | true |
controller.maxConcurrentReconciles | 2 | unset (accepted, not consumed) |
controller.defaultImagePullPolicy | unset | unset (accepted, not consumed) |
webhook.enabled | true | true |
webhook.standalone | false | false |
metrics.port | 8080 | 8080 |
monitoring.serviceMonitor.enabled | false | false |
networkPolicy.enabled | false | key not present |
federation.metadataAllowCidrs | [] | key not present |
replicas: 2 runs an active/standby pair: leader election keeps exactly one replica reconciling, and the standby takes over on pod loss. The two operators never reconcile the same CRs, so they are sized independently.
Resource Recommendations
The shipped defaults (requests: 10m/64Mi, limits: 500m/128Mi) are tuned for a laptop-sized kind cluster and a handful of CRs. They are a floor, not a production target: the requests are deliberately tiny so a dev cluster schedules the pod anywhere, and the 128Mi memory limit is close to the working set once an operator reconciles many CRs concurrently.
| Profile | requests | limits | Notes |
|---|---|---|---|
| kind / dev (default) | cpu: 10m, memory: 64Mi | cpu: 500m, memory: 128Mi | Ships as-is; fits a single-node kind. |
| Production baseline | cpu: 100m, memory: 128Mi | cpu: "1", memory: 256Mi | Reserve real headroom; raise the memory limit so a reconcile burst does not OOM-kill the pod. |
| Large fleet | cpu: 250m, memory: 256Mi | cpu: "2", memory: 512Mi | Pair with a higher controller.maxConcurrentReconciles (5–10) so the extra CPU is usable. |
Treat these as starting points and confirm against your own load — the reconcile-performance benchmark measures per-reconcile cost, and a VerticalPodAutoscaler in recommendation mode is a reliable way to right-size the requests before pinning them.
Example Overrides
Both charts install with helm install <release> <chart> -f overrides.yaml. Only the keys you change need to appear; everything else keeps its default.
keystone-operator — production hardening. Raise the resources, publish a ServiceMonitor, widen concurrency, allow federation-metadata fetches against the in-cluster identity provider, and turn on the operator NetworkPolicy:
# keystone-overrides.yaml
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
memory: 256Mi
controller:
maxConcurrentReconciles: 5
monitoring:
serviceMonitor:
enabled: true # requires the prometheus-operator CRDs in-cluster
federation:
metadataAllowCidrs:
- 10.96.0.0/12 # cluster Service CIDR — reach a trusted in-cluster IdP
networkPolicy:
enabled: true
kubeApiServer:
# cluster-specific — discover with: kubectl get endpoints kubernetes -o json
cidrs: ["10.96.0.1/32"]
ports: [6443]c5c3-operator — resources and monitoring. The ControlPlane operator has no federation or networkPolicy key, and its chart refuses rbac.namespaceScoped: true: the ControlPlane controller watches cluster-scoped kinds and creates ClusterRoleBindings, which a namespaced Role cannot grant.
# c5c3-overrides.yaml
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
memory: 256Mi
monitoring:
serviceMonitor:
enabled: trueSee the Multi-Tenant Deployment guide for the charts that refuse the mode and the other capabilities that still need cluster scope.
Any operator chart: dedicated nodes and a priority class. Run the operator pods on nodes labelled and tainted for the platform, and rank them above tenant workloads. The PriorityClass must exist first:
# placement-overrides.yaml
nodeSelector:
node.c5c3.io/role: platform
tolerations:
- key: node.c5c3.io/role
operator: Equal
value: platform
effect: NoSchedule
priorityClassName: cobaltcore-platformOn the Flux install the same keys go into each operator HelmRelease's spec.values through a kustomize overlay; see Sizing and placement overrides.