// Copyright © 2019 The Knative Authors // // 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 commands import ( "os" "path/filepath" "testing" "github.com/spf13/cobra" "gotest.tools/v3/assert" "k8s.io/client-go/tools/clientcmd" "knative.dev/client/pkg/util/test" ) // testCommandGenerator generates a test cobra command func testCommandGenerator(allNamespaceFlag bool) *cobra.Command { var testCmd = &cobra.Command{ Use: "kn", Short: "Namespace test kn command", Run: func(cmd *cobra.Command, args []string) {}, } AddNamespaceFlags(testCmd.Flags(), allNamespaceFlag) return testCmd } // test by setting some namespace func TestGetNamespaceSample(t *testing.T) { testCmd := testCommandGenerator(true) expectedNamespace := "test1" testCmd.SetArgs([]string{"--namespace", expectedNamespace}) testCmd.Execute() kp := &KnParams{fixedCurrentNamespace: FakeNamespace} actualNamespace, err := kp.GetNamespace(testCmd) if err != nil { t.Fatal(err) } if actualNamespace != expectedNamespace { t.Fatalf("Incorrect namespace retrieved: %v, expected: %v", actualNamespace, expectedNamespace) } kp = &KnParams{} // Mock ClientConfig to avoid clash with real kubeconfig on host OS kp.ClientConfig, _ = clientcmd.NewClientConfigFromBytes([]byte(BASIC_KUBECONFIG)) testCmd = testCommandGenerator(false) actualNamespace, err = kp.GetNamespace(testCmd) assert.NilError(t, err) assert.Equal(t, "default", actualNamespace) } // test current namespace without setting any namespace func TestGetNamespaceDefault(t *testing.T) { testCmd := testCommandGenerator(true) expectedNamespace := "current" testCmd.Execute() kp := &KnParams{fixedCurrentNamespace: FakeNamespace} actualNamespace, err := kp.GetNamespace(testCmd) if err != nil { t.Fatal(err) } if actualNamespace != expectedNamespace { t.Fatalf("Incorrect namespace retrieved: %v, expected: %v", actualNamespace, expectedNamespace) } } // test with all-namespaces flag set with sample namespace // all-namespaces flag takes the precedence func TestGetNamespaceAllNamespacesSet(t *testing.T) { testCmd := testCommandGenerator(true) expectedNamespace := "" sampleNamespace := "test1" // Test both variants of the "all namespaces" flag for _, arg := range []string{"--all-namespaces", "-A"} { testCmd.SetArgs([]string{"--namespace", sampleNamespace, arg}) testCmd.Execute() kp := &KnParams{fixedCurrentNamespace: FakeNamespace} actualNamespace, err := kp.GetNamespace(testCmd) if err != nil { t.Fatal(err) } if actualNamespace != expectedNamespace { t.Fatalf("Incorrect namespace retrieved: %v, expected: %v", actualNamespace, expectedNamespace) } } } // test with all-namespace flag set without any namespace flag set // all-namespace flag takes precedence func TestGetNamespaceDefaultAllNamespacesUnset(t *testing.T) { testCmd := testCommandGenerator(true) expectedNamespace := "" // Test both variants of the "all namespaces" flag for _, arg := range []string{"--all-namespaces", "-A"} { testCmd.SetArgs([]string{arg}) testCmd.Execute() kp := &KnParams{fixedCurrentNamespace: FakeNamespace} actualNamespace, err := kp.GetNamespace(testCmd) if err != nil { t.Fatal(err) } if actualNamespace != expectedNamespace { t.Fatalf("Incorrect namespace retrieved: %v, expected: %v", actualNamespace, expectedNamespace) } } } // test with all-namespaces flag not defined for command func TestGetNamespaceAllNamespacesNotDefined(t *testing.T) { testCmd := testCommandGenerator(false) expectedNamespace := "test1" testCmd.SetArgs([]string{"--namespace", expectedNamespace}) testCmd.Execute() kp := &KnParams{fixedCurrentNamespace: FakeNamespace} actualNamespace, err := kp.GetNamespace(testCmd) if err != nil { t.Fatal(err) } if actualNamespace != expectedNamespace { t.Fatalf("Incorrect namespace retrieved: %v, expected: %v", actualNamespace, expectedNamespace) } } func TestGetNamespaceFallback(t *testing.T) { tempDir := t.TempDir() t.Run("MockConfig", func(t *testing.T) { tempFile := filepath.Join(tempDir, "mock") err := os.WriteFile(tempFile, []byte(BASIC_KUBECONFIG), test.FileModeReadWrite) assert.NilError(t, err) kp := &KnParams{KubeCfgPath: tempFile} testCmd := testCommandGenerator(true) testCmd.Execute() actual, err := kp.GetNamespace(testCmd) assert.NilError(t, err) if isInCluster() { // In-cluster config overrides the mocked one in OpenShift CI assertNamespaceInCluster(t, actual, "default") } else { assert.Equal(t, actual, "default") } }) t.Run("EmptyConfig", func(t *testing.T) { tempFile := filepath.Join(tempDir, "empty") err := os.WriteFile(tempFile, []byte(""), test.FileModeReadWrite) assert.NilError(t, err) kp := &KnParams{KubeCfgPath: tempFile} testCmd := testCommandGenerator(true) testCmd.Execute() actual, err := kp.GetNamespace(testCmd) assert.NilError(t, err) if isInCluster() { // In-cluster config overrides the mocked one in OpenShift CI assertNamespaceInCluster(t, actual, "default") } else { assert.Equal(t, actual, "default") } }) t.Run("MissingConfig", func(t *testing.T) { kp := &KnParams{KubeCfgPath: filepath.Join(tempDir, "missing")} testCmd := testCommandGenerator(true) testCmd.Execute() actual, err := kp.GetNamespace(testCmd) assert.ErrorContains(t, err, "can not be found") assert.Equal(t, actual, "") }) } func TestCurrentNamespace(t *testing.T) { tempDir := t.TempDir() t.Run("EmptyConfig", func(t *testing.T) { // Invalid kubeconfig tempFile := filepath.Join(tempDir, "empty") err := os.WriteFile(tempFile, []byte(""), test.FileModeReadWrite) assert.NilError(t, err) kp := &KnParams{KubeCfgPath: tempFile} actual, err := kp.CurrentNamespace() if isInCluster() { // In-cluster config overrides the mocked one in OpenShift CI assert.NilError(t, err) assertNamespaceInCluster(t, actual, "default") } else { assert.Assert(t, err != nil) assert.Assert(t, clientcmd.IsConfigurationInvalid(err)) assert.Assert(t, actual == "") } }) t.Run("MissingConfig", func(t *testing.T) { // Missing kubeconfig kp := &KnParams{KubeCfgPath: filepath.Join(tempDir, "missing")} actual, err := kp.CurrentNamespace() assert.Assert(t, err != nil) assert.ErrorContains(t, err, "can not be found") assert.Assert(t, actual == "") }) t.Run("MissingConfig", func(t *testing.T) { // Variable namespace override kp := &KnParams{fixedCurrentNamespace: FakeNamespace} actual, err := kp.CurrentNamespace() assert.NilError(t, err) assert.Equal(t, actual, FakeNamespace) }) t.Run("MockConfig", func(t *testing.T) { // Fallback to "default" namespace from mock kubeconfig tempFile := filepath.Join(tempDir, "mock") err := os.WriteFile(tempFile, []byte(BASIC_KUBECONFIG), test.FileModeReadWrite) assert.NilError(t, err) kp := &KnParams{KubeCfgPath: tempFile} actual, err := kp.CurrentNamespace() assert.NilError(t, err) if isInCluster() { assertNamespaceInCluster(t, actual, "default") } else { assert.Equal(t, actual, "default") } }) } func assertNamespaceInCluster(t *testing.T, actual, expected string) { // Fallback to Prow CI "test-pods" namespace inCluster := actual == "test-pods" // In-cluster config overrides the mocked one in OpenShift CI if os.Getenv("NAMESPACE") != "" { inCluster = actual == os.Getenv("NAMESPACE") } assert.Check(t, inCluster || (actual == expected)) } // Inspired by client-go function // https://github.com/kubernetes/client-go/blob/master/tools/clientcmd/client_config.go#L600-L606 func isInCluster() bool { fi, err := os.Stat("/var/run/secrets/kubernetes.io/serviceaccount/token") return os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" && err == nil && !fi.IsDir() }