From 168ea3d6900b328b055b654351caf1c79e504194 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 12:29:55 +0000 Subject: [PATCH 1/4] Initial plan From 8eae6844d864f53b5820eedcaad6142dd251e9c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 12:33:56 +0000 Subject: [PATCH 2/4] feat: detect namespace from service account file for in-cluster config - Add detectInClusterNamespace() helper function to read namespace from /var/run/secrets/kubernetes.io/serviceaccount/namespace - Update LoadInClusterConfig to use the detected namespace instead of hardcoded "default" - Add comprehensive test cases for namespace detection logic - Include namespace in in-cluster config loading log message Co-authored-by: basebandit <8973567+basebandit@users.noreply.github.com> --- cluster/manager.go | 33 +++++++++++++++++++++- cluster/manager_test.go | 61 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/cluster/manager.go b/cluster/manager.go index 3f0a572..5f44c3b 100644 --- a/cluster/manager.go +++ b/cluster/manager.go @@ -46,6 +46,33 @@ func New() *Manager { } } +// detectInClusterNamespace reads the namespace from the service account file +// that Kubernetes mounts into pods. Returns "default" if the file cannot be read. +func detectInClusterNamespace() string { + const namespaceFile = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + + // #nosec G304 - This is a well-known Kubernetes file path that is safe to read + data, err := os.ReadFile(namespaceFile) + if err != nil { + // If we can't read the file, fall back to "default" + 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 was empty, using default", + slog.String("file", namespaceFile), + ) + return "default" + } + + return namespace +} + // LoadInClusterConfig loads the in-cluster Kubernetes configuration // This is used when kai is running inside a Kubernetes pod func (cm *Manager) LoadInClusterConfig(name string) error { @@ -78,11 +105,14 @@ func (cm *Manager) LoadInClusterConfig(name string) error { return fmt.Errorf("failed to connect to cluster: %w", err) } + // Detect the actual namespace from the service account 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 +127,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 diff --git a/cluster/manager_test.go b/cluster/manager_test.go index f98daeb..16c3d49 100644 --- a/cluster/manager_test.go +++ b/cluster/manager_test.go @@ -3,6 +3,7 @@ package cluster import ( "os" "path/filepath" + "strings" "testing" "github.com/basebandit/kai" @@ -50,6 +51,7 @@ func TestExtendedClusterManager(t *testing.T) { func TestInClusterConfig(t *testing.T) { t.Run("LoadInClusterConfig", testLoadInClusterConfig) + t.Run("DetectInClusterNamespace", testDetectInClusterNamespace) } func testLoadInClusterConfig(t *testing.T) { @@ -86,6 +88,65 @@ func testLoadInClusterConfig(t *testing.T) { }) } +func testDetectInClusterNamespace(t *testing.T) { + t.Run("FileDoesNotExist", func(t *testing.T) { + // When the file doesn't exist (normal case outside cluster), should return "default" + namespace := detectInClusterNamespace() + assert.Equal(t, "default", namespace) + }) + + t.Run("FileExistsWithNamespace", func(t *testing.T) { + // Create a temporary file that simulates the service account namespace file + tmpDir := t.TempDir() + namespaceFile := filepath.Join(tmpDir, "namespace") + err := os.WriteFile(namespaceFile, []byte("my-custom-namespace\n"), 0644) + require.NoError(t, err) + + // Temporarily replace the namespace file path for testing + // Since the function uses a const, we'll test the logic by reading the temp file directly + data, err := os.ReadFile(namespaceFile) + require.NoError(t, err) + namespace := strings.TrimSpace(string(data)) + + assert.Equal(t, "my-custom-namespace", namespace) + assert.NotEqual(t, "default", namespace) + }) + + t.Run("FileIsEmpty", func(t *testing.T) { + // Create a temporary empty file + tmpDir := t.TempDir() + namespaceFile := filepath.Join(tmpDir, "namespace") + err := os.WriteFile(namespaceFile, []byte(""), 0644) + require.NoError(t, err) + + // Read and test the logic + data, err := os.ReadFile(namespaceFile) + require.NoError(t, err) + namespace := strings.TrimSpace(string(data)) + + if namespace == "" { + namespace = "default" + } + + assert.Equal(t, "default", namespace) + }) + + t.Run("FileWithWhitespace", func(t *testing.T) { + // Create a file with leading/trailing whitespace + tmpDir := t.TempDir() + namespaceFile := filepath.Join(tmpDir, "namespace") + err := os.WriteFile(namespaceFile, []byte(" production \n"), 0644) + require.NoError(t, err) + + // Read and test the logic + data, err := os.ReadFile(namespaceFile) + require.NoError(t, err) + namespace := strings.TrimSpace(string(data)) + + assert.Equal(t, "production", namespace) + }) +} + func testNewClusterManager(t *testing.T) { cm := New() assert.NotNil(t, cm) From 3337c17b8882bd69fbaf63324df7975ae56d3b8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 12:35:04 +0000 Subject: [PATCH 3/4] refactor: make namespace detection testable - Extract readNamespaceFromFile function to enable proper unit testing - Update tests to test the actual function rather than duplicating logic - Add test for detectInClusterNamespace default behavior - Remove unused strings import from test file Co-authored-by: basebandit <8973567+basebandit@users.noreply.github.com> --- cluster/manager.go | 8 ++++++-- cluster/manager_test.go | 34 +++++++++++----------------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/cluster/manager.go b/cluster/manager.go index 5f44c3b..05594a2 100644 --- a/cluster/manager.go +++ b/cluster/manager.go @@ -49,8 +49,12 @@ func New() *Manager { // detectInClusterNamespace reads the namespace from the service account file // that Kubernetes mounts into pods. Returns "default" if the file cannot be read. func detectInClusterNamespace() string { - const namespaceFile = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" - + return readNamespaceFromFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") +} + +// readNamespaceFromFile reads the namespace from a file, with fallback to "default" +// This function is extracted to enable testing without filesystem dependencies +func readNamespaceFromFile(namespaceFile string) string { // #nosec G304 - This is a well-known Kubernetes file path that is safe to read data, err := os.ReadFile(namespaceFile) if err != nil { diff --git a/cluster/manager_test.go b/cluster/manager_test.go index 16c3d49..25d64db 100644 --- a/cluster/manager_test.go +++ b/cluster/manager_test.go @@ -3,7 +3,6 @@ package cluster import ( "os" "path/filepath" - "strings" "testing" "github.com/basebandit/kai" @@ -91,7 +90,7 @@ func testLoadInClusterConfig(t *testing.T) { func testDetectInClusterNamespace(t *testing.T) { t.Run("FileDoesNotExist", func(t *testing.T) { // When the file doesn't exist (normal case outside cluster), should return "default" - namespace := detectInClusterNamespace() + namespace := readNamespaceFromFile("/nonexistent/path/namespace") assert.Equal(t, "default", namespace) }) @@ -102,14 +101,8 @@ func testDetectInClusterNamespace(t *testing.T) { err := os.WriteFile(namespaceFile, []byte("my-custom-namespace\n"), 0644) require.NoError(t, err) - // Temporarily replace the namespace file path for testing - // Since the function uses a const, we'll test the logic by reading the temp file directly - data, err := os.ReadFile(namespaceFile) - require.NoError(t, err) - namespace := strings.TrimSpace(string(data)) - + namespace := readNamespaceFromFile(namespaceFile) assert.Equal(t, "my-custom-namespace", namespace) - assert.NotEqual(t, "default", namespace) }) t.Run("FileIsEmpty", func(t *testing.T) { @@ -119,15 +112,7 @@ func testDetectInClusterNamespace(t *testing.T) { err := os.WriteFile(namespaceFile, []byte(""), 0644) require.NoError(t, err) - // Read and test the logic - data, err := os.ReadFile(namespaceFile) - require.NoError(t, err) - namespace := strings.TrimSpace(string(data)) - - if namespace == "" { - namespace = "default" - } - + namespace := readNamespaceFromFile(namespaceFile) assert.Equal(t, "default", namespace) }) @@ -138,13 +123,16 @@ func testDetectInClusterNamespace(t *testing.T) { err := os.WriteFile(namespaceFile, []byte(" production \n"), 0644) require.NoError(t, err) - // Read and test the logic - data, err := os.ReadFile(namespaceFile) - require.NoError(t, err) - namespace := strings.TrimSpace(string(data)) - + namespace := readNamespaceFromFile(namespaceFile) assert.Equal(t, "production", namespace) }) + + t.Run("DefaultFunctionBehavior", func(t *testing.T) { + // Test that detectInClusterNamespace calls readNamespaceFromFile with correct path + // Since the actual file won't exist in test environment, it should return "default" + namespace := detectInClusterNamespace() + assert.Equal(t, "default", namespace) + }) } func testNewClusterManager(t *testing.T) { From d5ca0bd737cfef09a880953a7ffd0196a51bb2b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 1 Feb 2026 12:37:07 +0000 Subject: [PATCH 4/4] style: remove trailing whitespace Co-authored-by: basebandit <8973567+basebandit@users.noreply.github.com> --- cluster/manager.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/manager.go b/cluster/manager.go index 05594a2..aa1d043 100644 --- a/cluster/manager.go +++ b/cluster/manager.go @@ -65,7 +65,7 @@ func readNamespaceFromFile(namespaceFile string) string { ) return "default" } - + namespace := strings.TrimSpace(string(data)) if namespace == "" { slog.Debug("namespace file was empty, using default", @@ -73,7 +73,7 @@ func readNamespaceFromFile(namespaceFile string) string { ) return "default" } - + return namespace }