Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"os"
"strings"

corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
Expand All @@ -38,13 +39,7 @@ type NamespaceRegistryData struct {

// deployNamespaceRegistry creates and manages the namespace registry ConfigMap
func (feast *FeastServices) deployNamespaceRegistry() error {
// Check if we can determine the target namespace before creating any resources
targetNamespace, err := feast.getNamespaceRegistryNamespace()
if err != nil {
logger := log.FromContext(feast.Handler.Context)
logger.V(1).Info("Skipping namespace registry deployment: unable to determine target namespace", "error", err)
return nil // Return nil to avoid failing the entire deployment
}
targetNamespace := feast.getNamespaceRegistryNamespace()

logger := log.FromContext(feast.Handler.Context)
logger.V(1).Info("Deploying namespace registry", "targetNamespace", targetNamespace)
Expand Down Expand Up @@ -209,37 +204,42 @@ func (feast *FeastServices) setNamespaceRegistryRoleBinding(rb *rbacv1.RoleBindi
}

// getNamespaceRegistryNamespace determines the target namespace for the namespace registry ConfigMap
func (feast *FeastServices) getNamespaceRegistryNamespace() (string, error) {
// Check if we're running on OpenShift
func (feast *FeastServices) getNamespaceRegistryNamespace() string {
logger := log.FromContext(feast.Handler.Context)
if isOpenShift {
// TODO: Add support for reading DSCi configuration
if data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
if ns := string(data); len(ns) > 0 {
logger.V(1).Info("Using OpenShift namespace", "namespace", ns)
return ns, nil
}

// 1) Explicit override via environment variable (useful in tests or custom setups)
if envNs := os.Getenv("FEAST_OPERATOR_NAMESPACE"); envNs != "" {
logger.V(1).Info("Using operator namespace from FEAST_OPERATOR_NAMESPACE env var", "namespace", envNs)
return envNs
}

// 2) Namespace from ServiceAccount token file (standard in Kubernetes/OpenShift)
if data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
if ns := strings.TrimSpace(string(data)); ns != "" {
logger.V(1).Info("Using operator namespace from serviceaccount namespace file", "namespace", ns)
return ns
}
// This is what notebook controller team is doing, we are following them
// They are not defaulting to redhat-ods-applications namespace
return "", fmt.Errorf("unable to determine the namespace")
}

return DefaultKubernetesNamespace, nil
// 3) Common Downward API env var, if configured
if podNs := os.Getenv("POD_NAMESPACE"); podNs != "" {
logger.V(1).Info("Using operator namespace from POD_NAMESPACE env var", "namespace", podNs)
return podNs
}

// 4) Fallback for environments without SA mounting (e.g., unit tests)
logger.V(1).Info("Falling back to DefaultKubernetesNamespace", "namespace", DefaultKubernetesNamespace)
return DefaultKubernetesNamespace
}

// AddToNamespaceRegistry adds a feature store instance to the namespace registry
func (feast *FeastServices) AddToNamespaceRegistry() error {
logger := log.FromContext(feast.Handler.Context)
targetNamespace, err := feast.getNamespaceRegistryNamespace()
if err != nil {
logger.V(1).Info("Skipping namespace registry addition: unable to determine target namespace", "error", err)
return nil // Return nil to avoid failing the entire operation
}
targetNamespace := feast.getNamespaceRegistryNamespace()

// Get the existing ConfigMap
cm := &corev1.ConfigMap{}
err = feast.Handler.Client.Get(feast.Handler.Context, types.NamespacedName{
err := feast.Handler.Client.Get(feast.Handler.Context, types.NamespacedName{
Name: NamespaceRegistryConfigMapName,
Namespace: targetNamespace,
}, cm)
Expand Down Expand Up @@ -319,15 +319,11 @@ func (feast *FeastServices) RemoveFromNamespaceRegistry() error {
logger := log.FromContext(feast.Handler.Context)

// Determine the target namespace based on platform
targetNamespace, err := feast.getNamespaceRegistryNamespace()
if err != nil {
logger.V(1).Info("Skipping namespace registry removal: unable to determine target namespace", "error", err)
return nil // Return nil to avoid failing the entire operation
}
targetNamespace := feast.getNamespaceRegistryNamespace()

// Get the existing ConfigMap
cm := &corev1.ConfigMap{}
err = feast.Handler.Client.Get(feast.Handler.Context, client.ObjectKey{
err := feast.Handler.Client.Get(feast.Handler.Context, client.ObjectKey{
Name: NamespaceRegistryConfigMapName,
Namespace: targetNamespace,
}, cm)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
Copyright 2024 Feast Community.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package services

import (
"context"
"os"

feastdevv1 "github.com/feast-dev/feast/infra/feast-operator/api/v1"
"github.com/feast-dev/feast/infra/feast-operator/internal/controller/handler"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = Describe("getNamespaceRegistryNamespace", func() {
var feast *FeastServices

BeforeEach(func() {
feast = &FeastServices{
Handler: handler.FeastHandler{
Context: context.Background(),
FeatureStore: &feastdevv1.FeatureStore{
ObjectMeta: metav1.ObjectMeta{
Name: "test-fs",
Namespace: "default",
},
},
},
}
os.Unsetenv("FEAST_OPERATOR_NAMESPACE")
os.Unsetenv("POD_NAMESPACE")
})

AfterEach(func() {
os.Unsetenv("FEAST_OPERATOR_NAMESPACE")
os.Unsetenv("POD_NAMESPACE")
})

It("should return FEAST_OPERATOR_NAMESPACE when set", func() {
os.Setenv("FEAST_OPERATOR_NAMESPACE", "custom-ns")
Expect(feast.getNamespaceRegistryNamespace()).To(Equal("custom-ns"))
})

It("should prefer FEAST_OPERATOR_NAMESPACE over POD_NAMESPACE", func() {
os.Setenv("FEAST_OPERATOR_NAMESPACE", "feast-ns")
os.Setenv("POD_NAMESPACE", "pod-ns")
Expect(feast.getNamespaceRegistryNamespace()).To(Equal("feast-ns"))
})

It("should return POD_NAMESPACE when FEAST_OPERATOR_NAMESPACE is not set and SA file is missing", func() {
os.Setenv("POD_NAMESPACE", "my-pod-ns")
Expect(feast.getNamespaceRegistryNamespace()).To(Equal("my-pod-ns"))
})

It("should fall back to DefaultKubernetesNamespace when no env vars are set and SA file is missing", func() {
Expect(feast.getNamespaceRegistryNamespace()).To(Equal(DefaultKubernetesNamespace))
})

It("should ignore empty FEAST_OPERATOR_NAMESPACE", func() {
os.Setenv("FEAST_OPERATOR_NAMESPACE", "")
os.Setenv("POD_NAMESPACE", "pod-ns")
Expect(feast.getNamespaceRegistryNamespace()).To(Equal("pod-ns"))
})

It("should ignore empty POD_NAMESPACE and fall back to default", func() {
os.Setenv("POD_NAMESPACE", "")
Expect(feast.getNamespaceRegistryNamespace()).To(Equal(DefaultKubernetesNamespace))
})
})