diff --git a/cluster/manager.go b/cluster/manager.go index 3f0a572..69176c2 100644 --- a/cluster/manager.go +++ b/cluster/manager.go @@ -78,11 +78,14 @@ func (cm *Manager) LoadInClusterConfig(name string) error { return fmt.Errorf("failed to connect to cluster: %w", err) } + // Detect the namespace from the service account namespace file + namespace := detectInClusterNamespace("") + contextInfo := &kai.ContextInfo{ Name: name, Cluster: "in-cluster", User: "service-account", - Namespace: "default", + Namespace: namespace, ServerURL: config.Host, ConfigPath: "", IsActive: true, @@ -97,6 +100,7 @@ func (cm *Manager) LoadInClusterConfig(name string) error { slog.Info("in-cluster config loaded", slog.String("context", name), slog.String("server", config.Host), + slog.String("namespace", namespace), ) return nil @@ -511,6 +515,36 @@ func validateFile(path string) error { return nil } +// detectInClusterNamespace reads the namespace from the service account namespace file +// when running inside a Kubernetes pod. Falls back to "default" if the file cannot be read. +// If customPath is provided and not empty, it will be used instead of the default Kubernetes path. +func detectInClusterNamespace(customPath string) string { + namespaceFile := "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + if customPath != "" { + namespaceFile = customPath + } + + // #nosec G304 - This is a well-known Kubernetes service account file path + data, err := os.ReadFile(namespaceFile) + if err != nil { + slog.Debug("failed to read namespace from service account file, using default", + slog.String("file", namespaceFile), + slog.String("error", err.Error()), + ) + return "default" + } + + namespace := strings.TrimSpace(string(data)) + if namespace == "" { + slog.Debug("namespace file is empty, using default", + slog.String("file", namespaceFile), + ) + return "default" + } + + return namespace +} + func ptr[T any](v T) *T { return &v } diff --git a/cluster/manager_test.go b/cluster/manager_test.go index f98daeb..96c9fea 100644 --- a/cluster/manager_test.go +++ b/cluster/manager_test.go @@ -50,6 +50,64 @@ func TestExtendedClusterManager(t *testing.T) { func TestInClusterConfig(t *testing.T) { t.Run("LoadInClusterConfig", testLoadInClusterConfig) + t.Run("DetectInClusterNamespace", testDetectInClusterNamespace) +} + +func testDetectInClusterNamespace(t *testing.T) { + t.Run("NamespaceFileDoesNotExist", func(t *testing.T) { + // When the namespace file doesn't exist, should return "default" + namespace := detectInClusterNamespace("/nonexistent/path/namespace") + assert.Equal(t, "default", namespace) + }) + + t.Run("NamespaceFileExists", func(t *testing.T) { + // Create a temporary directory and file to simulate the service account namespace file + tmpDir := t.TempDir() + namespaceFile := filepath.Join(tmpDir, "namespace") + + // Write a test namespace to the file + testNs := "my-custom-namespace" + err := os.WriteFile(namespaceFile, []byte(testNs), 0600) + require.NoError(t, err) + + // Test the actual function + namespace := detectInClusterNamespace(namespaceFile) + assert.Equal(t, testNs, namespace) + }) + + t.Run("NamespaceFileIsEmpty", func(t *testing.T) { + // Create a temporary empty file + tmpDir := t.TempDir() + namespaceFile := filepath.Join(tmpDir, "namespace") + + err := os.WriteFile(namespaceFile, []byte(""), 0600) + require.NoError(t, err) + + // Test that empty file returns "default" + namespace := detectInClusterNamespace(namespaceFile) + assert.Equal(t, "default", namespace) + }) + + t.Run("NamespaceFileWithWhitespace", func(t *testing.T) { + // Create a temporary file with whitespace + tmpDir := t.TempDir() + namespaceFile := filepath.Join(tmpDir, "namespace") + + testNs := "my-namespace" + err := os.WriteFile(namespaceFile, []byte(" "+testNs+" \n"), 0600) + require.NoError(t, err) + + // Test that whitespace is trimmed + namespace := detectInClusterNamespace(namespaceFile) + assert.Equal(t, testNs, namespace) + }) + + t.Run("DefaultPath", func(t *testing.T) { + // When no custom path is provided, should use default path + // Since the default path won't exist in test environment, it should return "default" + namespace := detectInClusterNamespace("") + assert.Equal(t, "default", namespace) + }) } func testLoadInClusterConfig(t *testing.T) {