Skip to content

Commit 2bd80e2

Browse files
committed
feat(operator): add spec.services.initImage for init container image override
Init containers (feast-init, feast-apply) previously only used RELATED_IMAGE_FEATURE_SERVER or the compiled-in DefaultImage, ignoring per-FeatureStore custom images. This blocked users with custom Feast images (e.g. Spark-capable) unless an admin patched the operator. Add spec.services.initImage with resolution order: initImage → RELATED_IMAGE_FEATURE_SERVER → DefaultImage Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
1 parent 1669661 commit 2bd80e2

12 files changed

Lines changed: 1286 additions & 102 deletions

File tree

infra/feast-operator/api/v1/featurestore_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,10 @@ type FeatureStoreServices struct {
407407
PodAnnotations map[string]string `json:"podAnnotations,omitempty"`
408408
// Disable the 'feast repo initialization' initContainer
409409
DisableInitContainers bool `json:"disableInitContainers,omitempty"`
410+
// InitImage overrides the image for init containers (feast-init, feast-apply).
411+
// Resolution order: InitImage → RELATED_IMAGE_FEATURE_SERVER → DefaultImage.
412+
// +optional
413+
InitImage *string `json:"initImage,omitempty"`
410414
// Runs feast apply on pod start to populate the registry. Defaults to true. Ignored when DisableInitContainers is true.
411415
RunFeastApplyOnInit *bool `json:"runFeastApplyOnInit,omitempty"`
412416
// Volumes specifies the volumes to mount in the FeatureStore deployment. A corresponding `VolumeMount` should be added to whichever feast service(s) require access to said volume(s).

infra/feast-operator/api/v1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ metadata:
147147
}
148148
]
149149
capabilities: Basic Install
150-
createdAt: "2026-06-13T11:22:06Z"
150+
createdAt: "2026-07-09T21:22:32Z"
151151
operators.operatorframework.io/builder: operator-sdk-v1.41.0
152152
operators.operatorframework.io/project_layout: go.kubebuilder.io/v4
153153
name: feast-operator.v0.64.0
@@ -246,6 +246,14 @@ spec:
246246
- patch
247247
- update
248248
- watch
249+
- apiGroups:
250+
- config.openshift.io
251+
resources:
252+
- apiservers
253+
verbs:
254+
- get
255+
- list
256+
- watch
249257
- apiGroups:
250258
- feast.dev
251259
resources:

infra/feast-operator/bundle/manifests/feast.dev_featurestores.yaml

Lines changed: 1118 additions & 98 deletions
Large diffs are not rendered by default.

infra/feast-operator/bundle/manifests/openlineage-secret_v1_secret.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ kind: Secret
33
metadata:
44
name: openlineage-secret
55
stringData:
6-
api_key: your-marquez-api-key # pragma: allowlist secret
6+
api_key: your-marquez-api-key

infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,10 @@ spec:
17231723
disableInitContainers:
17241724
description: Disable the 'feast repo initialization' initContainer
17251725
type: boolean
1726+
initImage:
1727+
description: InitImage overrides the image for init containers
1728+
(feast-init, feast-apply).
1729+
type: string
17261730
offlineStore:
17271731
description: OfflineStore configures the offline store service
17281732
properties:
@@ -8019,6 +8023,10 @@ spec:
80198023
disableInitContainers:
80208024
description: Disable the 'feast repo initialization' initContainer
80218025
type: boolean
8026+
initImage:
8027+
description: InitImage overrides the image for init containers
8028+
(feast-init, feast-apply).
8029+
type: string
80228030
offlineStore:
80238031
description: OfflineStore configures the offline store service
80248032
properties:

infra/feast-operator/dist/install.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,10 @@ spec:
17311731
disableInitContainers:
17321732
description: Disable the 'feast repo initialization' initContainer
17331733
type: boolean
1734+
initImage:
1735+
description: InitImage overrides the image for init containers
1736+
(feast-init, feast-apply).
1737+
type: string
17341738
offlineStore:
17351739
description: OfflineStore configures the offline store service
17361740
properties:
@@ -8027,6 +8031,10 @@ spec:
80278031
disableInitContainers:
80288032
description: Disable the 'feast repo initialization' initContainer
80298033
type: boolean
8034+
initImage:
8035+
description: InitImage overrides the image for init containers
8036+
(feast-init, feast-apply).
8037+
type: string
80308038
offlineStore:
80318039
description: OfflineStore configures the offline store service
80328040
properties:

infra/feast-operator/docs/api/markdown/ref.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ _Appears in:_
256256
This enables annotation-driven integrations like OpenTelemetry auto-instrumentation,
257257
Istio sidecar injection, Vault agent injection, etc. |
258258
| `disableInitContainers` _boolean_ | Disable the 'feast repo initialization' initContainer |
259+
| `initImage` _string_ | InitImage overrides the image for init containers (feast-init, feast-apply).
260+
Resolution order: InitImage → RELATED_IMAGE_FEATURE_SERVER → DefaultImage. |
259261
| `runFeastApplyOnInit` _boolean_ | Runs feast apply on pod start to populate the registry. Defaults to true. Ignored when DisableInitContainers is true. |
260262
| `volumes` _[Volume](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#volume-v1-core) array_ | Volumes specifies the volumes to mount in the FeatureStore deployment. A corresponding `VolumeMount` should be added to whichever feast service(s) require access to said volume(s). |
261263
| `scaling` _[ScalingConfig](#scalingconfig)_ | Scaling configures horizontal scaling for the FeatureStore deployment (e.g. HPA autoscaling).

infra/feast-operator/internal/controller/featurestore_controller_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,41 @@ var _ = Describe("FeatureStore Controller", func() {
299299
Expect(err).NotTo(HaveOccurred())
300300
Expect(deploy.Spec.Template.Spec.InitContainers).To(HaveLen(2))
301301
Expect(deploy.Spec.Template.Spec.InitContainers[0].Args[0]).To(ContainSubstring("feast init -t spark"))
302+
303+
// initImage is independent of server images: init containers use initImage,
304+
// main containers keep their own server.image.
305+
initImage := "quay.io/org/feast-init:custom"
306+
serverImage := "quay.io/org/feast-online:server"
307+
if resource.Spec.Services == nil {
308+
resource.Spec.Services = &feastdevv1.FeatureStoreServices{}
309+
}
310+
resource.Spec.Services.InitImage = &initImage
311+
if resource.Spec.Services.OnlineStore == nil {
312+
resource.Spec.Services.OnlineStore = &feastdevv1.OnlineStore{}
313+
}
314+
if resource.Spec.Services.OnlineStore.Server == nil {
315+
resource.Spec.Services.OnlineStore.Server = &feastdevv1.ServerConfigs{}
316+
}
317+
resource.Spec.Services.OnlineStore.Server.Image = &serverImage
318+
err = k8sClient.Update(ctx, resource)
319+
Expect(err).NotTo(HaveOccurred())
320+
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
321+
NamespacedName: typeNamespacedName,
322+
})
323+
Expect(err).NotTo(HaveOccurred())
324+
325+
err = k8sClient.Get(ctx, types.NamespacedName{
326+
Name: objMeta.Name,
327+
Namespace: objMeta.Namespace,
328+
}, deploy)
329+
Expect(err).NotTo(HaveOccurred())
330+
Expect(deploy.Spec.Template.Spec.InitContainers).To(HaveLen(2))
331+
Expect(deploy.Spec.Template.Spec.InitContainers[0].Image).To(Equal(initImage))
332+
Expect(deploy.Spec.Template.Spec.InitContainers[1].Image).To(Equal(initImage))
333+
online = services.GetOnlineContainer(*deploy)
334+
Expect(online).NotTo(BeNil())
335+
Expect(online.Image).To(Equal(serverImage))
336+
Expect(online.Image).NotTo(Equal(initImage))
302337
})
303338

304339
It("should properly encode a feature_store.yaml config", func() {

infra/feast-operator/internal/controller/services/services.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -687,9 +687,10 @@ func (feast *FeastServices) setInitContainer(podSpec *corev1.PodSpec, fsYamlB64
687687
feastProjectDir := applied.FeastProjectDir
688688
workingDir := getOfflineMountPath(feast.Handler.FeatureStore)
689689
projectPath := workingDir + "/" + applied.FeastProject
690+
initImage := getInitContainerImage(applied.Services)
690691
container := corev1.Container{
691692
Name: feastInitContainerName,
692-
Image: getFeatureServerImage(),
693+
Image: initImage,
693694
Env: []corev1.EnvVar{
694695
{
695696
Name: TmpFeatureStoreYamlEnvVar,
@@ -742,7 +743,7 @@ func (feast *FeastServices) setInitContainer(podSpec *corev1.PodSpec, fsYamlB64
742743
if applied.Services.RunFeastApplyOnInit != nil && *applied.Services.RunFeastApplyOnInit {
743744
applyContainer := corev1.Container{
744745
Name: feastApplyContainerName,
745-
Image: getFeatureServerImage(),
746+
Image: initImage,
746747
Command: []string{feastCommand, "apply"},
747748
WorkingDir: featureRepoDir,
748749
}

0 commit comments

Comments
 (0)