Pod template merge layer
Per-engine pod-template overrides live onFireboltEngine.spec.template,
parallel to the existing FireboltEngineClass.spec.template.
When an engine sets spec.engineClassRef, the reconciler resolves the
referenced FireboltEngineClass and composes the rendered pod
template from three layers, top wins on conflict for scalar and
struct fields:
- Firebolt Operator defaults: Hardcoded engine container identity
(
name,command,args,ports, probes, reserved env keys), the StatefulSet-bound pod fields (terminationGracePeriodSeconds=60,subdomain,hostname,restartPolicy,activeDeadlineSeconds), and the operator-owned volumes / mounts (nodes-config, data). Always win. FireboltEngineClass.spec.template(whenspec.engineClassRefis set): Fills in user-owned fields the engine template doesn’t set.FireboltEngine.spec.template: Wins over the class on conflict.
FireboltEngineClassPodTemplateRules)
to both class and engine templates, so a field accepted on one side is
also accepted on the other and operator-owned paths are rejected
uniformly on both.
Merge rules (centralized in the effective* helpers in
engine_reconcile.go so buildStatefulSet and stsMatchesSpec agree
on the resolved value):
| Field | Rule |
|---|---|
serviceAccountName | engine template > class > "" |
nodeSelector | map-merge, engine keys win |
tolerations | class slice + engine slice, concatenated |
affinity | engine wins if non-nil, else class (no field-merge) |
| pod-template labels | Reserved keys (firebolt.io/engine, firebolt.io/generation) non-overridable. Engine template labels > class labels |
| pod-template annotations | engine template annotations > class annotations |
pod-level securityContext | engine template > class > Firebolt Operator default (FSGroup, FSGroupChangePolicy always stamped) |
pod-level volumes | Firebolt Operator-owned volumes (nodes-config, data) come first. Class volumes then engine volumes are appended in that order. Names colliding with operator volumes are dropped |
pod-level imagePullSecrets | class slice + engine slice, concatenated |
| init containers | class slice + engine slice, concatenated (mirrors tolerations) |
sidecar containers (anything not named engine) | class sidecars + engine sidecars, appended after the operator-built engine container |
engine container image / imagePullPolicy | engine template > class > Firebolt Operator default |
engine container resources | engine template wins wholesale if it carries any requests/limits/claims, else class fills in (no field-level merge) |
engine container securityContext | engine template wins if non-nil, else class |
engine container env | Firebolt Operator-injected vars (POD_INDEX, FB_AWS_EC2_METADATA_CLIENT_ENABLED, FIREBOLT_CORE_MODE) first. Class env then engine env appended in that order (reserved keys rejected at admission) |
engine container envFrom | class slice + engine slice, concatenated |
engine container volumeMounts | Firebolt Operator-owned mounts first. Class mounts then engine mounts appended in that order. Mounts colliding with operator volume names are dropped |
engine container lifecycle | engine template wins if non-nil, else class |
FireboltEngineClass via
EnqueueRequestsFromMapFunc so a class edit immediately enqueues
every consumer engine. The validating webhooks on FireboltEngine
and FireboltEngineClass reject user input on paths the Firebolt
Operator owns end-to-end. See
fireboltengineclass-crd-reference
and engine-crd-reference. The
merge layer therefore assumes both resolved templates only carry
fields it knows how to handle.
Inherited engine settings
Beyond the pod template, aFireboltEngineClass carries defaults for a subset
of FireboltEngine settings. Each resolves the engine value first, then the
class value, then the operator default, the same precedence the pod template
follows. The effective* helpers in engine_reconcile.go centralize the
resolution so the renderer and the drift comparator agree.
| Field | Rule |
|---|---|
storage | Whole-struct. The engine owns its storage when it names any backend (persistentVolumeClaim, emptyDir, or hostPath). Otherwise the class backend applies, falling through to the default emptyDir. Whole-struct selection keeps the three backends mutually exclusive. |
customEngineConfig | Deep-merged: operator base, then class, then engine, so an engine key wins over the same key on the class. The Firebolt Operator strips its owned config paths from both layers, so neither can override identity, routing, or topology. |
rollout | engine > class > graceful. |
drainCheckEnabled | engine > class > true. |
drainCheckInterval | engine > class > operator default. |
autoStop | Whole-struct. An engine that sets spec.autoStop owns the entire policy. The class policy applies only when the engine omits it. |
uiSidecar | engine > class > false. When it resolves to true, the Firebolt Operator injects a built-in, operator-owned nginx container named engine-web (serving the Engine Web UI, pointed at the local engine) into each engine pod. The engine-web name is reserved: a user-supplied container or init container with that name is rejected. |
rollout, drainCheckEnabled, drainCheckInterval, and uiSidecar carry no
CRD default on FireboltEngine. The Firebolt Operator applies the default in
the resolver instead, so an unset engine value falls through to the class
rather than being filled in at admission.
Deletion
The validating webhook refusesDELETE while any FireboltEngine in
the class’s namespace references the class via spec.engineClassRef.
Only same-namespace engines count. The check lists engines live from the API
server at admission time rather than reading status.boundEngines, so a
class bound between reconciler runs is still protected. Clear
spec.engineClassRef on every referencing engine first, then delete the
class. failurePolicy: Fail on the webhook configuration prevents a webhook
outage from opening a window in which a bound class could be removed.
Class edit rollouts
Editing the class spec can trigger a blue-green rollout on every referencing engine in the same namespace. The engine controller watchesFireboltEngineClass and enqueues every consumer whose
spec.engineClassRef matches. stsMatchesSpec compares the resolved class
template hash against the firebolt.io/engine-class-hash annotation on the
StatefulSet, and detects storage, customEngineConfig, and uiSidecar
changes (from either the engine or the class) through their own resolved-value
comparators.
Any mismatch bumps currentGeneration, whether the class template was edited in
place, the engine flipped to a different class, or the reference was cleared.
Class changes to rollout, drainCheckEnabled, drainCheckInterval, and
autoStop do not reshape the pod, so they take effect on the next reconcile
without forcing a new generation.