Skip to main content

Pod template merge layer

Per-engine pod-template overrides live on FireboltEngine.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:
  1. 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.
  2. FireboltEngineClass.spec.template (when spec.engineClassRef is set): Fills in user-owned fields the engine template doesn’t set.
  3. FireboltEngine.spec.template: Wins over the class on conflict.
The validating webhook applies the same allowlist (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):
FieldRule
serviceAccountNameengine template > class > ""
nodeSelectormap-merge, engine keys win
tolerationsclass slice + engine slice, concatenated
affinityengine wins if non-nil, else class (no field-merge)
pod-template labelsReserved keys (firebolt.io/engine, firebolt.io/generation) non-overridable. Engine template labels > class labels
pod-template annotationsengine template annotations > class annotations
pod-level securityContextengine template > class > Firebolt Operator default (FSGroup, FSGroupChangePolicy always stamped)
pod-level volumesFirebolt 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 imagePullSecretsclass slice + engine slice, concatenated
init containersclass 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 / imagePullPolicyengine template > class > Firebolt Operator default
engine container resourcesengine template wins wholesale if it carries any requests/limits/claims, else class fills in (no field-level merge)
engine container securityContextengine template wins if non-nil, else class
engine container envFirebolt 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 envFromclass slice + engine slice, concatenated
engine container volumeMountsFirebolt Operator-owned mounts first. Class mounts then engine mounts appended in that order. Mounts colliding with operator volume names are dropped
engine container lifecycleengine template wins if non-nil, else class
The engine controller watches 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, a FireboltEngineClass 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.
FieldRule
storageWhole-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.
customEngineConfigDeep-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.
rolloutengine > class > graceful.
drainCheckEnabledengine > class > true.
drainCheckIntervalengine > class > operator default.
autoStopWhole-struct. An engine that sets spec.autoStop owns the entire policy. The class policy applies only when the engine omits it.
uiSidecarengine > 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 refuses DELETE 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 watches FireboltEngineClass 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.