...

Source file src/k8s.io/kubectl/pkg/cmd/plugin/plugin_test.go

Documentation: k8s.io/kubectl/pkg/cmd/plugin

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package plugin
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"reflect"
    24  	"strings"
    25  	"testing"
    26  
    27  	"k8s.io/cli-runtime/pkg/genericiooptions"
    28  )
    29  
    30  func TestPluginPathsAreUnaltered(t *testing.T) {
    31  	tempDir, err := os.MkdirTemp(os.TempDir(), "test-cmd-plugins")
    32  	if err != nil {
    33  		t.Fatalf("unexpected error: %v", err)
    34  	}
    35  	tempDir2, err := os.MkdirTemp(os.TempDir(), "test-cmd-plugins2")
    36  	if err != nil {
    37  		t.Fatalf("unexpected error: %v", err)
    38  	}
    39  	// cleanup
    40  	defer func() {
    41  		if err := os.RemoveAll(tempDir); err != nil {
    42  			panic(fmt.Errorf("unexpected cleanup error: %v", err))
    43  		}
    44  		if err := os.RemoveAll(tempDir2); err != nil {
    45  			panic(fmt.Errorf("unexpected cleanup error: %v", err))
    46  		}
    47  	}()
    48  
    49  	ioStreams, _, _, errOut := genericiooptions.NewTestIOStreams()
    50  	verifier := newFakePluginPathVerifier()
    51  	pluginPaths := []string{tempDir, tempDir2}
    52  	o := &PluginListOptions{
    53  		Verifier:  verifier,
    54  		IOStreams: ioStreams,
    55  
    56  		PluginPaths: pluginPaths,
    57  	}
    58  
    59  	// write at least one valid plugin file
    60  	if _, err := os.CreateTemp(tempDir, "kubectl-"); err != nil {
    61  		t.Fatalf("unexpected error %v", err)
    62  	}
    63  	if _, err := os.CreateTemp(tempDir2, "kubectl-"); err != nil {
    64  		t.Fatalf("unexpected error %v", err)
    65  	}
    66  
    67  	if err := o.Run(); err != nil {
    68  		t.Fatalf("unexpected error %v - %v", err, errOut.String())
    69  	}
    70  
    71  	// ensure original paths remain unaltered
    72  	if len(verifier.seenUnsorted) != len(pluginPaths) {
    73  		t.Fatalf("saw unexpected plugin paths. Expecting %v, got %v", pluginPaths, verifier.seenUnsorted)
    74  	}
    75  	for actual := range verifier.seenUnsorted {
    76  		if !strings.HasPrefix(verifier.seenUnsorted[actual], pluginPaths[actual]) {
    77  			t.Fatalf("expected PATH slice to be unaltered. Expecting %v, but got %v", pluginPaths[actual], verifier.seenUnsorted[actual])
    78  		}
    79  	}
    80  }
    81  
    82  func TestPluginPathsAreValid(t *testing.T) {
    83  	tempDir, err := os.MkdirTemp(os.TempDir(), "test-cmd-plugins")
    84  	if err != nil {
    85  		t.Fatalf("unexpected error: %v", err)
    86  	}
    87  	// cleanup
    88  	defer func() {
    89  		if err := os.RemoveAll(tempDir); err != nil {
    90  			panic(fmt.Errorf("unexpected cleanup error: %v", err))
    91  		}
    92  	}()
    93  
    94  	tc := []struct {
    95  		name               string
    96  		pluginPaths        []string
    97  		pluginFile         func() (*os.File, error)
    98  		verifier           *fakePluginPathVerifier
    99  		expectVerifyErrors []error
   100  		expectErr          string
   101  		expectErrOut       string
   102  		expectOut          string
   103  	}{
   104  		{
   105  			name:        "ensure no plugins found if no files begin with kubectl- prefix",
   106  			pluginPaths: []string{tempDir},
   107  			verifier:    newFakePluginPathVerifier(),
   108  			pluginFile: func() (*os.File, error) {
   109  				return os.CreateTemp(tempDir, "notkubectl-")
   110  			},
   111  			expectErr: "error: unable to find any kubectl plugins in your PATH\n",
   112  		},
   113  		{
   114  			name:        "ensure de-duplicated plugin-paths slice",
   115  			pluginPaths: []string{tempDir, tempDir},
   116  			verifier:    newFakePluginPathVerifier(),
   117  			pluginFile: func() (*os.File, error) {
   118  				return os.CreateTemp(tempDir, "kubectl-")
   119  			},
   120  			expectOut: "The following compatible plugins are available:",
   121  		},
   122  		{
   123  			name:        "ensure no errors when empty string or blank path are specified",
   124  			pluginPaths: []string{tempDir, "", " "},
   125  			verifier:    newFakePluginPathVerifier(),
   126  			pluginFile: func() (*os.File, error) {
   127  				return os.CreateTemp(tempDir, "kubectl-")
   128  			},
   129  			expectOut: "The following compatible plugins are available:",
   130  		},
   131  	}
   132  
   133  	for _, test := range tc {
   134  		t.Run(test.name, func(t *testing.T) {
   135  			ioStreams, _, out, errOut := genericiooptions.NewTestIOStreams()
   136  			o := &PluginListOptions{
   137  				Verifier:  test.verifier,
   138  				IOStreams: ioStreams,
   139  
   140  				PluginPaths: test.pluginPaths,
   141  			}
   142  
   143  			// create files
   144  			if test.pluginFile != nil {
   145  				if _, err := test.pluginFile(); err != nil {
   146  					t.Fatalf("unexpected error creating plugin file: %v", err)
   147  				}
   148  			}
   149  
   150  			for _, expected := range test.expectVerifyErrors {
   151  				for _, actual := range test.verifier.errors {
   152  					if expected != actual {
   153  						t.Fatalf("unexpected error: expected %v, but got %v", expected, actual)
   154  					}
   155  				}
   156  			}
   157  
   158  			err := o.Run()
   159  			if err == nil && len(test.expectErr) > 0 {
   160  				t.Fatalf("unexpected non-error: expected %v, but got nothing", test.expectErr)
   161  			} else if err != nil && len(test.expectErr) == 0 {
   162  				t.Fatalf("unexpected error: expected nothing, but got %v", err.Error())
   163  			} else if err != nil && err.Error() != test.expectErr {
   164  				t.Fatalf("unexpected error: expected %v, but got %v", test.expectErr, err.Error())
   165  			}
   166  
   167  			if len(test.expectErrOut) == 0 && errOut.Len() > 0 {
   168  				t.Fatalf("unexpected error output: expected nothing, but got %v", errOut.String())
   169  			} else if len(test.expectErrOut) > 0 && !strings.Contains(errOut.String(), test.expectErrOut) {
   170  				t.Fatalf("unexpected error output: expected to contain %v, but got %v", test.expectErrOut, errOut.String())
   171  			}
   172  
   173  			if len(test.expectOut) == 0 && out.Len() > 0 {
   174  				t.Fatalf("unexpected output: expected nothing, but got %v", out.String())
   175  			} else if len(test.expectOut) > 0 && !strings.Contains(out.String(), test.expectOut) {
   176  				t.Fatalf("unexpected output: expected to contain %v, but got %v", test.expectOut, out.String())
   177  			}
   178  		})
   179  	}
   180  }
   181  
   182  func TestListPlugins(t *testing.T) {
   183  	pluginPath, _ := filepath.Abs("./testdata")
   184  	expectPlugins := []string{
   185  		filepath.Join(pluginPath, "kubectl-create-foo"),
   186  		filepath.Join(pluginPath, "kubectl-foo"),
   187  		filepath.Join(pluginPath, "kubectl-version"),
   188  	}
   189  
   190  	verifier := newFakePluginPathVerifier()
   191  	ioStreams, _, _, _ := genericiooptions.NewTestIOStreams()
   192  	pluginPaths := []string{pluginPath}
   193  
   194  	o := &PluginListOptions{
   195  		Verifier:  verifier,
   196  		IOStreams: ioStreams,
   197  
   198  		PluginPaths: pluginPaths,
   199  	}
   200  
   201  	plugins, errs := o.ListPlugins()
   202  	if len(errs) > 0 {
   203  		t.Fatalf("unexpected errors: %v", errs)
   204  	}
   205  
   206  	if !reflect.DeepEqual(expectPlugins, plugins) {
   207  		t.Fatalf("saw unexpected plugins. Expecting %v, got %v", expectPlugins, plugins)
   208  	}
   209  }
   210  
   211  type duplicatePathError struct {
   212  	path string
   213  }
   214  
   215  func (d *duplicatePathError) Error() string {
   216  	return fmt.Sprintf("path %q already visited", d.path)
   217  }
   218  
   219  type fakePluginPathVerifier struct {
   220  	errors       []error
   221  	seen         map[string]bool
   222  	seenUnsorted []string
   223  }
   224  
   225  func (f *fakePluginPathVerifier) Verify(path string) []error {
   226  	if f.seen[path] {
   227  		err := &duplicatePathError{path}
   228  		f.errors = append(f.errors, err)
   229  		return []error{err}
   230  	}
   231  	f.seen[path] = true
   232  	f.seenUnsorted = append(f.seenUnsorted, path)
   233  	return nil
   234  }
   235  
   236  func newFakePluginPathVerifier() *fakePluginPathVerifier {
   237  	return &fakePluginPathVerifier{seen: make(map[string]bool)}
   238  }
   239  

View as plain text