diff --git a/backend/pkg/kubeconfig/kubeconfig.go b/backend/pkg/kubeconfig/kubeconfig.go index d652c3abedc..a080d7459a3 100644 --- a/backend/pkg/kubeconfig/kubeconfig.go +++ b/backend/pkg/kubeconfig/kubeconfig.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "errors" "fmt" + "io/fs" "net/http" "net/http/httputil" "net/url" @@ -460,9 +461,23 @@ type ContextLoadError struct { // It returns an error if the file cannot be read. // It will return valid (contexts, ContextLoadErrors,nil) and errors if there are any errors in the file. func LoadContextsFromFile(kubeConfigPath string, source int) ([]Context, []ContextLoadError, error) { + // An empty path or a path that does not exist on disk is not an error + // condition for this loader: there is simply nothing to read. The + // in-cluster mode leaves KubeConfigPath empty, and the dynamic-cluster + // persistence file is absent on a fresh deployment until the user adds + // the first dynamic cluster. Treating both as ERRORs produced log + // spam reported in issues #4724 and #4401. + if kubeConfigPath == "" { + return nil, nil, nil + } + data, err := os.ReadFile(kubeConfigPath) //nolint:gosec if err != nil { - return nil, nil, fmt.Errorf("error reading kubeconfig file: %v", err) + if errors.Is(err, fs.ErrNotExist) { + return nil, nil, nil + } + + return nil, nil, fmt.Errorf("error reading kubeconfig file: %w", err) } skipProxySetup := source != KubeConfig diff --git a/backend/pkg/kubeconfig/kubeconfig_test.go b/backend/pkg/kubeconfig/kubeconfig_test.go index f54cc4b03ab..56619b63609 100644 --- a/backend/pkg/kubeconfig/kubeconfig_test.go +++ b/backend/pkg/kubeconfig/kubeconfig_test.go @@ -77,11 +77,28 @@ func TestLoadAndStoreKubeConfigs(t *testing.T) { require.Equal(t, "minikube", ctx.Name) }) - t.Run("invalid_file", func(t *testing.T) { - kubeConfigFile := "invalid_kubeconfig" + t.Run("missing_file", func(t *testing.T) { + // Regression for issues #4724 and #4401: a kubeconfig path that does + // not exist on disk (in-cluster dynamic-cluster persistence file, + // fresh deployments) used to surface as an ERROR. It should now be + // treated as "no contexts to load". + err := kubeconfig.LoadAndStoreKubeConfigs( + contextStore, "missing_kubeconfig", kubeconfig.KubeConfig, nil, + ) + require.NoError(t, err) + }) - err := kubeconfig.LoadAndStoreKubeConfigs(contextStore, kubeConfigFile, kubeconfig.KubeConfig, nil) - require.Error(t, err) + t.Run("empty_path", func(t *testing.T) { + // Regression for issues #4724 and #4401: in-cluster mode leaves + // KubeConfigPath empty; passing "" should not error. + store := kubeconfig.NewContextStore() + + err := kubeconfig.LoadAndStoreKubeConfigs(store, "", kubeconfig.KubeConfig, nil) + require.NoError(t, err) + + contexts, err := store.GetContexts() + require.NoError(t, err) + require.Empty(t, contexts) }) } @@ -95,13 +112,22 @@ func TestLoadContextsFromKubeConfigFile(t *testing.T) { require.Equal(t, 2, len(contexts), "Expected 2 contexts from valid file") }) - t.Run("invalid_file", func(t *testing.T) { - kubeConfigFile := "invalid_kubeconfig" + t.Run("missing_file", func(t *testing.T) { + // Regression for issues #4724 and #4401. See TestLoadAndStoreKubeConfigs. + contexts, contextErrors, err := kubeconfig.LoadContextsFromFile( + "missing_kubeconfig", kubeconfig.KubeConfig, + ) + require.NoError(t, err, "missing file is not an error condition") + require.Empty(t, contextErrors) + require.Empty(t, contexts) + }) - contexts, contextErrors, err := kubeconfig.LoadContextsFromFile(kubeConfigFile, kubeconfig.KubeConfig) - require.Error(t, err, "Expected error for invalid file") - require.Empty(t, contextErrors, "Expected no context errors for invalid file") - require.Empty(t, contexts, "Expected no contexts from invalid file") + t.Run("empty_path", func(t *testing.T) { + // Regression for issues #4724 and #4401: in-cluster mode passes "". + contexts, contextErrors, err := kubeconfig.LoadContextsFromFile("", kubeconfig.KubeConfig) + require.NoError(t, err) + require.Empty(t, contextErrors) + require.Empty(t, contexts) }) t.Run("autherror", func(t *testing.T) {