Skip to content

Commit ca355cb

Browse files
feat(operator): Add spec.services.initImage for init container image override (#6598)
* 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> * chore(operator): update secrets baseline after initImage API change Shift detect-secrets baseline line numbers for featurestore_types.go and zz_generated.deepcopy.go after adding InitImage. Signed-off-by: Aniket Paluskar <apaluska@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com> --------- Signed-off-by: Aniket Paluskar <apaluska@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 996c6ea commit ca355cb

11 files changed

Lines changed: 152 additions & 10 deletions

File tree

.secrets.baseline

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@
957957
"filename": "infra/feast-operator/api/v1/featurestore_types.go",
958958
"hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
959959
"is_verified": false,
960-
"line_number": 942
960+
"line_number": 946
961961
}
962962
],
963963
"infra/feast-operator/api/v1/zz_generated.deepcopy.go": [
@@ -966,21 +966,21 @@
966966
"filename": "infra/feast-operator/api/v1/zz_generated.deepcopy.go",
967967
"hashed_secret": "f914fc9324de1bec1ad13dec94a8ea2ddb41fc87",
968968
"is_verified": false,
969-
"line_number": 817
969+
"line_number": 822
970970
},
971971
{
972972
"type": "Secret Keyword",
973973
"filename": "infra/feast-operator/api/v1/zz_generated.deepcopy.go",
974974
"hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
975975
"is_verified": false,
976-
"line_number": 878
976+
"line_number": 883
977977
},
978978
{
979979
"type": "Secret Keyword",
980980
"filename": "infra/feast-operator/api/v1/zz_generated.deepcopy.go",
981981
"hashed_secret": "c2028031c154bbe86fd69bef740855c74b927dcf",
982982
"is_verified": false,
983-
"line_number": 1570
983+
"line_number": 1575
984984
}
985985
],
986986
"infra/feast-operator/api/v1alpha1/featurestore_types.go": [
@@ -1564,5 +1564,5 @@
15641564
}
15651565
]
15661566
},
1567-
"generated_at": "2026-07-23T08:52:32Z"
1567+
"generated_at": "2026-07-30T09:40:48Z"
15681568
}

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.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:
@@ -8020,6 +8024,10 @@ spec:
80208024
disableInitContainers:
80218025
description: Disable the 'feast repo initialization' initContainer
80228026
type: boolean
8027+
initImage:
8028+
description: InitImage overrides the image for init containers
8029+
(feast-init, feast-apply).
8030+
type: string
80238031
offlineStore:
80248032
description: OfflineStore configures the offline store service
80258033
properties:

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:
@@ -8025,6 +8029,10 @@ spec:
80258029
disableInitContainers:
80268030
description: Disable the 'feast repo initialization' initContainer
80278031
type: boolean
8032+
initImage:
8033+
description: InitImage overrides the image for init containers
8034+
(feast-init, feast-apply).
8035+
type: string
80288036
offlineStore:
80298037
description: OfflineStore configures the offline store service
80308038
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:
@@ -8033,6 +8037,10 @@ spec:
80338037
disableInitContainers:
80348038
description: Disable the 'feast repo initialization' initContainer
80358039
type: boolean
8040+
initImage:
8041+
description: InitImage overrides the image for init containers
8042+
(feast-init, feast-apply).
8043+
type: string
80368044
offlineStore:
80378045
description: OfflineStore configures the offline store service
80388046
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
@@ -704,9 +704,10 @@ func (feast *FeastServices) setInitContainer(podSpec *corev1.PodSpec, fsYamlB64
704704
feastProjectDir := applied.FeastProjectDir
705705
workingDir := getOfflineMountPath(feast.Handler.FeatureStore)
706706
projectPath := workingDir + "/" + applied.FeastProject
707+
initImage := getInitContainerImage(applied.Services)
707708
container := corev1.Container{
708709
Name: feastInitContainerName,
709-
Image: getFeatureServerImage(),
710+
Image: initImage,
710711
Env: []corev1.EnvVar{
711712
{
712713
Name: TmpFeatureStoreYamlEnvVar,
@@ -759,7 +760,7 @@ func (feast *FeastServices) setInitContainer(podSpec *corev1.PodSpec, fsYamlB64
759760
if applied.Services.RunFeastApplyOnInit != nil && *applied.Services.RunFeastApplyOnInit {
760761
applyContainer := corev1.Container{
761762
Name: feastApplyContainerName,
762-
Image: getFeatureServerImage(),
763+
Image: initImage,
763764
Command: []string{feastCommand, "apply"},
764765
WorkingDir: featureRepoDir,
765766
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,15 @@ func getFeatureServerImage() string {
214214
return DefaultImage
215215
}
216216

217+
// getInitContainerImage resolves the image for feast-init / feast-apply.
218+
// Order: spec.services.initImage → RELATED_IMAGE_FEATURE_SERVER → DefaultImage.
219+
func getInitContainerImage(services *feastdevv1.FeatureStoreServices) string {
220+
if services != nil && services.InitImage != nil && len(*services.InitImage) > 0 {
221+
return *services.InitImage
222+
}
223+
return getFeatureServerImage()
224+
}
225+
217226
func checkOfflineStoreFilePersistenceType(value string) error {
218227
if slices.Contains(feastdevv1.ValidOfflineStoreFilePersistenceTypes, value) {
219228
return nil

0 commit comments

Comments
 (0)