Skip to content

Commit 080a9b5

Browse files
committed
fix: Mount TLS volumes for init container
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
1 parent 73805d3 commit 080a9b5

5 files changed

Lines changed: 210 additions & 26 deletions

File tree

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

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package controller
1919
import (
2020
"context"
2121
"encoding/base64"
22+
"encoding/json"
2223
"fmt"
2324
"reflect"
2425
"strings"
@@ -1233,6 +1234,127 @@ var _ = Describe("FeatureStore Controller", func() {
12331234
Expect(cond.Message).To(Equal("Error: Remote feast registry of referenced FeatureStore '" + referencedRegistry.Name + "' is not ready"))
12341235
})
12351236

1237+
It("should allow cross-project registry references with different feastProject names", func() {
1238+
By("Reconciling the primary local registry FeatureStore")
1239+
controllerReconciler := &FeatureStoreReconciler{
1240+
Client: k8sClient,
1241+
Scheme: k8sClient.Scheme(),
1242+
}
1243+
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
1244+
NamespacedName: typeNamespacedName,
1245+
})
1246+
Expect(err).NotTo(HaveOccurred())
1247+
1248+
primaryStore := &feastdevv1.FeatureStore{}
1249+
err = k8sClient.Get(ctx, typeNamespacedName, primaryStore)
1250+
Expect(err).NotTo(HaveOccurred())
1251+
Expect(primaryStore.Status.Applied.FeastProject).To(Equal(feastProject))
1252+
1253+
By("Creating a second FeatureStore with a DIFFERENT feastProject name referencing the first")
1254+
crossProjectName := "cross-project-ref"
1255+
crossProjectFeastName := "different_project"
1256+
crossProjectResource := &feastdevv1.FeatureStore{
1257+
ObjectMeta: metav1.ObjectMeta{
1258+
Name: crossProjectName,
1259+
Namespace: primaryStore.Namespace,
1260+
},
1261+
Spec: feastdevv1.FeatureStoreSpec{
1262+
FeastProject: crossProjectFeastName,
1263+
Services: &feastdevv1.FeatureStoreServices{
1264+
OnlineStore: &feastdevv1.OnlineStore{
1265+
Server: &feastdevv1.ServerConfigs{},
1266+
},
1267+
Registry: &feastdevv1.Registry{
1268+
Remote: &feastdevv1.RemoteRegistryConfig{
1269+
FeastRef: &feastdevv1.FeatureStoreRef{
1270+
Name: primaryStore.Name,
1271+
},
1272+
},
1273+
},
1274+
},
1275+
},
1276+
}
1277+
crossProjectResource.SetGroupVersionKind(feastdevv1.GroupVersion.WithKind("FeatureStore"))
1278+
crossProjectNsName := client.ObjectKeyFromObject(crossProjectResource)
1279+
err = k8sClient.Create(ctx, crossProjectResource)
1280+
Expect(err).NotTo(HaveOccurred())
1281+
1282+
By("Reconciling the cross-project FeatureStore — should succeed without error")
1283+
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
1284+
NamespacedName: crossProjectNsName,
1285+
})
1286+
Expect(err).NotTo(HaveOccurred())
1287+
1288+
err = k8sClient.Get(ctx, crossProjectNsName, crossProjectResource)
1289+
Expect(err).NotTo(HaveOccurred())
1290+
1291+
By("Verifying the cross-project FeatureStore is ready and uses its own project name")
1292+
Expect(crossProjectResource.Status.Applied.FeastProject).To(Equal(crossProjectFeastName))
1293+
Expect(crossProjectResource.Status.ServiceHostnames.Registry).To(Equal(primaryStore.Status.ServiceHostnames.Registry))
1294+
Expect(apimeta.IsStatusConditionTrue(crossProjectResource.Status.Conditions, feastdevv1.OnlineStoreReadyType)).To(BeTrue())
1295+
1296+
By("Verifying the cross-project client ConfigMap uses the correct project name and shared registry")
1297+
crossFeast := services.FeastServices{
1298+
Handler: handler.FeastHandler{
1299+
Client: controllerReconciler.Client,
1300+
Context: ctx,
1301+
Scheme: controllerReconciler.Scheme,
1302+
FeatureStore: crossProjectResource,
1303+
},
1304+
}
1305+
crossCm := &corev1.ConfigMap{}
1306+
err = k8sClient.Get(ctx, types.NamespacedName{
1307+
Name: crossFeast.GetFeastServiceName(services.ClientFeastType),
1308+
Namespace: crossProjectResource.Namespace,
1309+
}, crossCm)
1310+
Expect(err).NotTo(HaveOccurred())
1311+
crossRepoConfig := &services.RepoConfig{}
1312+
err = yaml.Unmarshal([]byte(crossCm.Data[services.FeatureStoreYamlCmKey]), crossRepoConfig)
1313+
Expect(err).NotTo(HaveOccurred())
1314+
Expect(crossRepoConfig.Project).To(Equal(crossProjectFeastName))
1315+
Expect(crossRepoConfig.Registry.Path).To(ContainSubstring(primaryStore.Name))
1316+
1317+
By("Verifying the primary store client ConfigMap still uses its own project name")
1318+
primaryFeast := services.FeastServices{
1319+
Handler: handler.FeastHandler{
1320+
Client: controllerReconciler.Client,
1321+
Context: ctx,
1322+
Scheme: controllerReconciler.Scheme,
1323+
FeatureStore: primaryStore,
1324+
},
1325+
}
1326+
primaryCm := &corev1.ConfigMap{}
1327+
err = k8sClient.Get(ctx, types.NamespacedName{
1328+
Name: primaryFeast.GetFeastServiceName(services.ClientFeastType),
1329+
Namespace: primaryStore.Namespace,
1330+
}, primaryCm)
1331+
Expect(err).NotTo(HaveOccurred())
1332+
primaryRepoConfig := &services.RepoConfig{}
1333+
err = yaml.Unmarshal([]byte(primaryCm.Data[services.FeatureStoreYamlCmKey]), primaryRepoConfig)
1334+
Expect(err).NotTo(HaveOccurred())
1335+
Expect(primaryRepoConfig.Project).To(Equal(feastProject))
1336+
1337+
By("Verifying both stores share the same registry path")
1338+
Expect(crossRepoConfig.Registry.Path).To(Equal(primaryRepoConfig.Registry.Path))
1339+
1340+
By("Verifying the namespace registry ConfigMap lists both client configs")
1341+
registryCm := &corev1.ConfigMap{}
1342+
err = k8sClient.Get(ctx, types.NamespacedName{
1343+
Name: services.NamespaceRegistryConfigMapName,
1344+
Namespace: services.DefaultKubernetesNamespace,
1345+
}, registryCm)
1346+
Expect(err).NotTo(HaveOccurred())
1347+
var registryData services.NamespaceRegistryData
1348+
err = json.Unmarshal([]byte(registryCm.Data[services.NamespaceRegistryDataKey]), &registryData)
1349+
Expect(err).NotTo(HaveOccurred())
1350+
ns := primaryStore.Namespace
1351+
Expect(registryData.Namespaces[ns]).To(ContainElement(primaryFeast.GetFeastServiceName(services.ClientFeastType)))
1352+
Expect(registryData.Namespaces[ns]).To(ContainElement(crossFeast.GetFeastServiceName(services.ClientFeastType)))
1353+
1354+
By("Cleaning up the cross-project FeatureStore")
1355+
Expect(k8sClient.Delete(ctx, crossProjectResource)).To(Succeed())
1356+
})
1357+
12361358
It("should correctly set container command args for grpc/rest modes", func() {
12371359
controllerReconciler := &FeatureStoreReconciler{
12381360
Client: k8sClient,

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,16 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() {
193193
Expect(deploy.Spec.Replicas).To(Equal(int32Ptr(1)))
194194
Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue())
195195
Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4))
196+
197+
// verify init containers have TLS volume mounts after reconciliation
198+
Expect(deploy.Spec.Template.Spec.InitContainers).NotTo(BeEmpty())
199+
for _, initContainer := range deploy.Spec.Template.Spec.InitContainers {
200+
Expect(initContainer.VolumeMounts).To(ContainElement(SatisfyAll(
201+
HaveField("MountPath", services.GetTlsPath(services.RegistryFeastType)),
202+
HaveField("ReadOnly", true),
203+
)), "init container %s should have registry TLS volume mount", initContainer.Name)
204+
}
205+
196206
svc := &corev1.Service{}
197207
err = k8sClient.Get(ctx, types.NamespacedName{
198208
Name: feast.GetFeastServiceName(services.RegistryFeastType),
@@ -401,6 +411,14 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() {
401411
Expect(err).NotTo(HaveOccurred())
402412
Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(2))
403413

414+
// verify init containers have remote registry TLS volume mounts
415+
for _, initContainer := range deploy.Spec.Template.Spec.InitContainers {
416+
Expect(initContainer.VolumeMounts).To(ContainElement(SatisfyAll(
417+
HaveField("MountPath", services.GetTlsPath(services.RegistryFeastType)),
418+
HaveField("ReadOnly", true),
419+
)), "init container %s should have remote registry TLS volume mount", initContainer.Name)
420+
}
421+
404422
// check offline config
405423
offlineContainer = services.GetOfflineContainer(*deploy)
406424
env = getFeatureStoreYamlEnvVar(offlineContainer.Env)
@@ -530,6 +548,12 @@ var _ = Describe("Test mountCustomCABundle functionality", func() {
530548
HaveField("MountPath", tlsPathCustomCABundle),
531549
)))
532550
}
551+
for _, initContainer := range deploy.Spec.Template.Spec.InitContainers {
552+
Expect(initContainer.VolumeMounts).To(ContainElement(SatisfyAll(
553+
HaveField("Name", configMapName),
554+
HaveField("MountPath", tlsPathCustomCABundle),
555+
)), "init container %s should have CA bundle volume mount", initContainer.Name)
556+
}
533557
})
534558

535559
It("should not mount CA bundle volume or container mounts when ConfigMap is absent", func() {
@@ -570,5 +594,10 @@ var _ = Describe("Test mountCustomCABundle functionality", func() {
570594
for _, container := range deploy.Spec.Template.Spec.Containers {
571595
Expect(container.VolumeMounts).NotTo(ContainElement(HaveField("Name", configMapName)))
572596
}
597+
for _, initContainer := range deploy.Spec.Template.Spec.InitContainers {
598+
Expect(initContainer.VolumeMounts).NotTo(ContainElement(
599+
HaveField("Name", configMapName),
600+
), "init container %s should not have CA bundle mount when ConfigMap is absent", initContainer.Name)
601+
}
573602
})
574603
})

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

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,9 +1154,6 @@ func (feast *FeastServices) getRemoteRegistryFeastHandler() (*FeastServices, err
11541154
}
11551155
return nil, err
11561156
}
1157-
if feast.Handler.FeatureStore.Status.Applied.FeastProject != remoteFeastObj.Status.Applied.FeastProject {
1158-
return nil, errors.New("FeatureStore '" + remoteFeastObj.Name + "' is using a different feast project than '" + feast.Handler.FeatureStore.Status.Applied.FeastProject + "'. Project names must match.")
1159-
}
11601157
return &FeastServices{
11611158
Handler: handler.FeastHandler{
11621159
Client: feast.Handler.Client,
@@ -1314,13 +1311,11 @@ func (feast *FeastServices) mountPvcConfig(podSpec *corev1.PodSpec, pvcConfig *f
13141311
},
13151312
},
13161313
})
1317-
if feastType == OfflineFeastType {
1318-
for i := range podSpec.InitContainers {
1319-
podSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, corev1.VolumeMount{
1320-
Name: volName,
1321-
MountPath: pvcConfig.MountPath,
1322-
})
1323-
}
1314+
for i := range podSpec.InitContainers {
1315+
podSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, corev1.VolumeMount{
1316+
Name: volName,
1317+
MountPath: pvcConfig.MountPath,
1318+
})
13241319
}
13251320
for i := range podSpec.Containers {
13261321
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, corev1.VolumeMount{

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

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -224,12 +224,16 @@ func (feast *FeastServices) mountTlsConfig(feastType FeastServiceType, podSpec *
224224
},
225225
},
226226
})
227+
tlsMount := corev1.VolumeMount{
228+
Name: volName,
229+
MountPath: GetTlsPath(feastType),
230+
ReadOnly: true,
231+
}
227232
if i, container := getContainerByType(feastType, *podSpec); container != nil {
228-
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, corev1.VolumeMount{
229-
Name: volName,
230-
MountPath: GetTlsPath(feastType),
231-
ReadOnly: true,
232-
})
233+
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, tlsMount)
234+
}
235+
for i := range podSpec.InitContainers {
236+
podSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, tlsMount)
233237
}
234238
}
235239
}
@@ -245,12 +249,16 @@ func mountTlsRemoteRegistryConfig(podSpec *corev1.PodSpec, tls *feastdevv1.TlsRe
245249
},
246250
},
247251
})
252+
tlsMount := corev1.VolumeMount{
253+
Name: volName,
254+
MountPath: GetTlsPath(RegistryFeastType),
255+
ReadOnly: true,
256+
}
248257
for i := range podSpec.Containers {
249-
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, corev1.VolumeMount{
250-
Name: volName,
251-
MountPath: GetTlsPath(RegistryFeastType),
252-
ReadOnly: true,
253-
})
258+
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, tlsMount)
259+
}
260+
for i := range podSpec.InitContainers {
261+
podSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, tlsMount)
254262
}
255263
}
256264
}
@@ -267,13 +275,17 @@ func (feast *FeastServices) mountCustomCABundle(podSpec *corev1.PodSpec) {
267275
},
268276
})
269277

278+
caMount := corev1.VolumeMount{
279+
Name: customCaBundle.VolumeName,
280+
MountPath: tlsPathCustomCABundle,
281+
ReadOnly: true,
282+
SubPath: "ca-bundle.crt",
283+
}
270284
for i := range podSpec.Containers {
271-
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, corev1.VolumeMount{
272-
Name: customCaBundle.VolumeName,
273-
MountPath: tlsPathCustomCABundle,
274-
ReadOnly: true,
275-
SubPath: "ca-bundle.crt",
276-
})
285+
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, caMount)
286+
}
287+
for i := range podSpec.InitContainers {
288+
podSpec.InitContainers[i].VolumeMounts = append(podSpec.InitContainers[i].VolumeMounts, caMount)
277289
}
278290

279291
log.FromContext(feast.Handler.Context).Info("Mounted custom CA bundle ConfigMap to Feast pods.")

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,19 @@ var _ = Describe("TLS Config", func() {
181181
Expect(feastDeploy.Spec.Template.Spec.Containers[3].Command).To(ContainElements(ContainSubstring("--key")))
182182
Expect(feastDeploy.Spec.Template.Spec.Volumes).To(HaveLen(5))
183183

184+
// verify init containers receive TLS volume mounts when all services have TLS
185+
for _, initContainer := range feastDeploy.Spec.Template.Spec.InitContainers {
186+
Expect(initContainer.VolumeMounts).To(ContainElement(
187+
HaveField("MountPath", GetTlsPath(RegistryFeastType)),
188+
), "init container %s should have registry TLS mount", initContainer.Name)
189+
Expect(initContainer.VolumeMounts).To(ContainElement(
190+
HaveField("MountPath", GetTlsPath(OnlineFeastType)),
191+
), "init container %s should have online TLS mount", initContainer.Name)
192+
Expect(initContainer.VolumeMounts).To(ContainElement(
193+
HaveField("MountPath", GetTlsPath(OfflineFeastType)),
194+
), "init container %s should have offline TLS mount", initContainer.Name)
195+
}
196+
184197
// registry service w/ tls and in an openshift cluster
185198
feast.Handler.FeatureStore = minimalFeatureStore()
186199
feast.Handler.FeatureStore.Spec.Services = &feastdevv1.FeatureStoreServices{
@@ -336,6 +349,19 @@ var _ = Describe("TLS Config", func() {
336349
Expect(GetUIContainer(*feastDeploy).Command).NotTo(ContainElements(ContainSubstring("--key")))
337350
Expect(GetUIContainer(*feastDeploy).VolumeMounts).To(HaveLen(1))
338351

352+
// verify init containers receive only the offline TLS mount when only offline has TLS
353+
for _, initContainer := range feastDeploy.Spec.Template.Spec.InitContainers {
354+
Expect(initContainer.VolumeMounts).To(ContainElement(
355+
HaveField("MountPath", GetTlsPath(OfflineFeastType)),
356+
), "init container %s should have offline TLS mount", initContainer.Name)
357+
Expect(initContainer.VolumeMounts).NotTo(ContainElement(
358+
HaveField("MountPath", GetTlsPath(RegistryFeastType)),
359+
), "init container %s should not have registry TLS mount when registry TLS is disabled", initContainer.Name)
360+
Expect(initContainer.VolumeMounts).NotTo(ContainElement(
361+
HaveField("MountPath", GetTlsPath(OnlineFeastType)),
362+
), "init container %s should not have online TLS mount when online TLS is disabled", initContainer.Name)
363+
}
364+
339365
// Test REST registry server TLS configuration
340366
feast.Handler.FeatureStore = minimalFeatureStore()
341367
restEnabled := true

0 commit comments

Comments
 (0)