...

Source file src/helm.sh/helm/v3/pkg/plugin/plugin_test.go

Documentation: helm.sh/helm/v3/pkg/plugin

     1  /*
     2  Copyright The Helm Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package plugin // import "helm.sh/helm/v3/pkg/plugin"
    17  
    18  import (
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"reflect"
    23  	"runtime"
    24  	"testing"
    25  
    26  	"helm.sh/helm/v3/pkg/cli"
    27  )
    28  
    29  func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) {
    30  	cmd, args, err := p.PrepareCommand(extraArgs)
    31  	if err != nil {
    32  		t.Fatal(err)
    33  	}
    34  	if cmd != "echo" {
    35  		t.Fatalf("Expected echo, got %q", cmd)
    36  	}
    37  
    38  	if l := len(args); l != 5 {
    39  		t.Fatalf("expected 5 args, got %d", l)
    40  	}
    41  
    42  	expect := []string{"-n", osStrCmp, "--debug", "--foo", "bar"}
    43  	for i := 0; i < len(args); i++ {
    44  		if expect[i] != args[i] {
    45  			t.Errorf("Expected arg=%q, got %q", expect[i], args[i])
    46  		}
    47  	}
    48  
    49  	// Test with IgnoreFlags. This should omit --debug, --foo, bar
    50  	p.Metadata.IgnoreFlags = true
    51  	cmd, args, err = p.PrepareCommand(extraArgs)
    52  	if err != nil {
    53  		t.Fatal(err)
    54  	}
    55  	if cmd != "echo" {
    56  		t.Fatalf("Expected echo, got %q", cmd)
    57  	}
    58  	if l := len(args); l != 2 {
    59  		t.Fatalf("expected 2 args, got %d", l)
    60  	}
    61  	expect = []string{"-n", osStrCmp}
    62  	for i := 0; i < len(args); i++ {
    63  		if expect[i] != args[i] {
    64  			t.Errorf("Expected arg=%q, got %q", expect[i], args[i])
    65  		}
    66  	}
    67  }
    68  
    69  func TestPrepareCommand(t *testing.T) {
    70  	p := &Plugin{
    71  		Dir: "/tmp", // Unused
    72  		Metadata: &Metadata{
    73  			Name:    "test",
    74  			Command: "echo -n foo",
    75  		},
    76  	}
    77  	argv := []string{"--debug", "--foo", "bar"}
    78  
    79  	checkCommand(p, argv, "foo", t)
    80  }
    81  
    82  func TestPlatformPrepareCommand(t *testing.T) {
    83  	p := &Plugin{
    84  		Dir: "/tmp", // Unused
    85  		Metadata: &Metadata{
    86  			Name:    "test",
    87  			Command: "echo -n os-arch",
    88  			PlatformCommand: []PlatformCommand{
    89  				{OperatingSystem: "linux", Architecture: "386", Command: "echo -n linux-386"},
    90  				{OperatingSystem: "linux", Architecture: "amd64", Command: "echo -n linux-amd64"},
    91  				{OperatingSystem: "linux", Architecture: "arm64", Command: "echo -n linux-arm64"},
    92  				{OperatingSystem: "linux", Architecture: "ppc64le", Command: "echo -n linux-ppc64le"},
    93  				{OperatingSystem: "linux", Architecture: "s390x", Command: "echo -n linux-s390x"},
    94  				{OperatingSystem: "linux", Architecture: "riscv64", Command: "echo -n linux-riscv64"},
    95  				{OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"},
    96  			},
    97  		},
    98  	}
    99  	var osStrCmp string
   100  	os := runtime.GOOS
   101  	arch := runtime.GOARCH
   102  	if os == "linux" && arch == "386" {
   103  		osStrCmp = "linux-386"
   104  	} else if os == "linux" && arch == "amd64" {
   105  		osStrCmp = "linux-amd64"
   106  	} else if os == "linux" && arch == "arm64" {
   107  		osStrCmp = "linux-arm64"
   108  	} else if os == "linux" && arch == "ppc64le" {
   109  		osStrCmp = "linux-ppc64le"
   110  	} else if os == "linux" && arch == "s390x" {
   111  		osStrCmp = "linux-s390x"
   112  	} else if os == "linux" && arch == "riscv64" {
   113  		osStrCmp = "linux-riscv64"
   114  	} else if os == "windows" && arch == "amd64" {
   115  		osStrCmp = "win-64"
   116  	} else {
   117  		osStrCmp = "os-arch"
   118  	}
   119  
   120  	argv := []string{"--debug", "--foo", "bar"}
   121  	checkCommand(p, argv, osStrCmp, t)
   122  }
   123  
   124  func TestPartialPlatformPrepareCommand(t *testing.T) {
   125  	p := &Plugin{
   126  		Dir: "/tmp", // Unused
   127  		Metadata: &Metadata{
   128  			Name:    "test",
   129  			Command: "echo -n os-arch",
   130  			PlatformCommand: []PlatformCommand{
   131  				{OperatingSystem: "linux", Architecture: "386", Command: "echo -n linux-386"},
   132  				{OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"},
   133  			},
   134  		},
   135  	}
   136  	var osStrCmp string
   137  	os := runtime.GOOS
   138  	arch := runtime.GOARCH
   139  	if os == "linux" {
   140  		osStrCmp = "linux-386"
   141  	} else if os == "windows" && arch == "amd64" {
   142  		osStrCmp = "win-64"
   143  	} else {
   144  		osStrCmp = "os-arch"
   145  	}
   146  
   147  	argv := []string{"--debug", "--foo", "bar"}
   148  	checkCommand(p, argv, osStrCmp, t)
   149  }
   150  
   151  func TestNoPrepareCommand(t *testing.T) {
   152  	p := &Plugin{
   153  		Dir: "/tmp", // Unused
   154  		Metadata: &Metadata{
   155  			Name: "test",
   156  		},
   157  	}
   158  	argv := []string{"--debug", "--foo", "bar"}
   159  
   160  	_, _, err := p.PrepareCommand(argv)
   161  	if err == nil {
   162  		t.Fatalf("Expected error to be returned")
   163  	}
   164  }
   165  
   166  func TestNoMatchPrepareCommand(t *testing.T) {
   167  	p := &Plugin{
   168  		Dir: "/tmp", // Unused
   169  		Metadata: &Metadata{
   170  			Name: "test",
   171  			PlatformCommand: []PlatformCommand{
   172  				{OperatingSystem: "no-os", Architecture: "amd64", Command: "echo -n linux-386"},
   173  			},
   174  		},
   175  	}
   176  	argv := []string{"--debug", "--foo", "bar"}
   177  
   178  	if _, _, err := p.PrepareCommand(argv); err == nil {
   179  		t.Fatalf("Expected error to be returned")
   180  	}
   181  }
   182  
   183  func TestLoadDir(t *testing.T) {
   184  	dirname := "testdata/plugdir/good/hello"
   185  	plug, err := LoadDir(dirname)
   186  	if err != nil {
   187  		t.Fatalf("error loading Hello plugin: %s", err)
   188  	}
   189  
   190  	if plug.Dir != dirname {
   191  		t.Fatalf("Expected dir %q, got %q", dirname, plug.Dir)
   192  	}
   193  
   194  	expect := &Metadata{
   195  		Name:        "hello",
   196  		Version:     "0.1.0",
   197  		Usage:       "usage",
   198  		Description: "description",
   199  		Command:     "$HELM_PLUGIN_DIR/hello.sh",
   200  		IgnoreFlags: true,
   201  		Hooks: map[string]string{
   202  			Install: "echo installing...",
   203  		},
   204  	}
   205  
   206  	if !reflect.DeepEqual(expect, plug.Metadata) {
   207  		t.Fatalf("Expected plugin metadata %v, got %v", expect, plug.Metadata)
   208  	}
   209  }
   210  
   211  func TestLoadDirDuplicateEntries(t *testing.T) {
   212  	dirname := "testdata/plugdir/bad/duplicate-entries"
   213  	if _, err := LoadDir(dirname); err == nil {
   214  		t.Errorf("successfully loaded plugin with duplicate entries when it should've failed")
   215  	}
   216  }
   217  
   218  func TestDownloader(t *testing.T) {
   219  	dirname := "testdata/plugdir/good/downloader"
   220  	plug, err := LoadDir(dirname)
   221  	if err != nil {
   222  		t.Fatalf("error loading Hello plugin: %s", err)
   223  	}
   224  
   225  	if plug.Dir != dirname {
   226  		t.Fatalf("Expected dir %q, got %q", dirname, plug.Dir)
   227  	}
   228  
   229  	expect := &Metadata{
   230  		Name:        "downloader",
   231  		Version:     "1.2.3",
   232  		Usage:       "usage",
   233  		Description: "download something",
   234  		Command:     "echo Hello",
   235  		Downloaders: []Downloaders{
   236  			{
   237  				Protocols: []string{"myprotocol", "myprotocols"},
   238  				Command:   "echo Download",
   239  			},
   240  		},
   241  	}
   242  
   243  	if !reflect.DeepEqual(expect, plug.Metadata) {
   244  		t.Fatalf("Expected metadata %v, got %v", expect, plug.Metadata)
   245  	}
   246  }
   247  
   248  func TestLoadAll(t *testing.T) {
   249  
   250  	// Verify that empty dir loads:
   251  	if plugs, err := LoadAll("testdata"); err != nil {
   252  		t.Fatalf("error loading dir with no plugins: %s", err)
   253  	} else if len(plugs) > 0 {
   254  		t.Fatalf("expected empty dir to have 0 plugins")
   255  	}
   256  
   257  	basedir := "testdata/plugdir/good"
   258  	plugs, err := LoadAll(basedir)
   259  	if err != nil {
   260  		t.Fatalf("Could not load %q: %s", basedir, err)
   261  	}
   262  
   263  	if l := len(plugs); l != 3 {
   264  		t.Fatalf("expected 3 plugins, found %d", l)
   265  	}
   266  
   267  	if plugs[0].Metadata.Name != "downloader" {
   268  		t.Errorf("Expected first plugin to be echo, got %q", plugs[0].Metadata.Name)
   269  	}
   270  	if plugs[1].Metadata.Name != "echo" {
   271  		t.Errorf("Expected first plugin to be echo, got %q", plugs[0].Metadata.Name)
   272  	}
   273  	if plugs[2].Metadata.Name != "hello" {
   274  		t.Errorf("Expected second plugin to be hello, got %q", plugs[1].Metadata.Name)
   275  	}
   276  }
   277  
   278  func TestFindPlugins(t *testing.T) {
   279  	cases := []struct {
   280  		name     string
   281  		plugdirs string
   282  		expected int
   283  	}{
   284  		{
   285  			name:     "plugdirs is empty",
   286  			plugdirs: "",
   287  			expected: 0,
   288  		},
   289  		{
   290  			name:     "plugdirs isn't dir",
   291  			plugdirs: "./plugin_test.go",
   292  			expected: 0,
   293  		},
   294  		{
   295  			name:     "plugdirs doesn't have plugin",
   296  			plugdirs: ".",
   297  			expected: 0,
   298  		},
   299  		{
   300  			name:     "normal",
   301  			plugdirs: "./testdata/plugdir/good",
   302  			expected: 3,
   303  		},
   304  	}
   305  	for _, c := range cases {
   306  		t.Run(t.Name(), func(t *testing.T) {
   307  			plugin, _ := FindPlugins(c.plugdirs)
   308  			if len(plugin) != c.expected {
   309  				t.Errorf("expected: %v, got: %v", c.expected, len(plugin))
   310  			}
   311  		})
   312  	}
   313  }
   314  
   315  func TestSetupEnv(t *testing.T) {
   316  	name := "pequod"
   317  	base := filepath.Join("testdata/helmhome/helm/plugins", name)
   318  
   319  	s := cli.New()
   320  	s.PluginsDirectory = "testdata/helmhome/helm/plugins"
   321  
   322  	SetupPluginEnv(s, name, base)
   323  	for _, tt := range []struct {
   324  		name, expect string
   325  	}{
   326  		{"HELM_PLUGIN_NAME", name},
   327  		{"HELM_PLUGIN_DIR", base},
   328  	} {
   329  		if got := os.Getenv(tt.name); got != tt.expect {
   330  			t.Errorf("Expected $%s=%q, got %q", tt.name, tt.expect, got)
   331  		}
   332  	}
   333  }
   334  
   335  func TestSetupEnvWithSpace(t *testing.T) {
   336  	name := "sureshdsk"
   337  	base := filepath.Join("testdata/helm home/helm/plugins", name)
   338  
   339  	s := cli.New()
   340  	s.PluginsDirectory = "testdata/helm home/helm/plugins"
   341  
   342  	SetupPluginEnv(s, name, base)
   343  	for _, tt := range []struct {
   344  		name, expect string
   345  	}{
   346  		{"HELM_PLUGIN_NAME", name},
   347  		{"HELM_PLUGIN_DIR", base},
   348  	} {
   349  		if got := os.Getenv(tt.name); got != tt.expect {
   350  			t.Errorf("Expected $%s=%q, got %q", tt.name, tt.expect, got)
   351  		}
   352  	}
   353  }
   354  
   355  func TestValidatePluginData(t *testing.T) {
   356  	// A mock plugin missing any metadata.
   357  	mockMissingMeta := &Plugin{
   358  		Dir: "no-such-dir",
   359  	}
   360  
   361  	for i, item := range []struct {
   362  		pass bool
   363  		plug *Plugin
   364  	}{
   365  		{true, mockPlugin("abcdefghijklmnopqrstuvwxyz0123456789_-ABC")},
   366  		{true, mockPlugin("foo-bar-FOO-BAR_1234")},
   367  		{false, mockPlugin("foo -bar")},
   368  		{false, mockPlugin("$foo -bar")}, // Test leading chars
   369  		{false, mockPlugin("foo -bar ")}, // Test trailing chars
   370  		{false, mockPlugin("foo\nbar")},  // Test newline
   371  		{false, mockMissingMeta},         // Test if the metadata section missing
   372  	} {
   373  		err := validatePluginData(item.plug, fmt.Sprintf("test-%d", i))
   374  		if item.pass && err != nil {
   375  			t.Errorf("failed to validate case %d: %s", i, err)
   376  		} else if !item.pass && err == nil {
   377  			t.Errorf("expected case %d to fail", i)
   378  		}
   379  	}
   380  }
   381  
   382  func TestDetectDuplicates(t *testing.T) {
   383  	plugs := []*Plugin{
   384  		mockPlugin("foo"),
   385  		mockPlugin("bar"),
   386  	}
   387  	if err := detectDuplicates(plugs); err != nil {
   388  		t.Error("no duplicates in the first set")
   389  	}
   390  	plugs = append(plugs, mockPlugin("foo"))
   391  	if err := detectDuplicates(plugs); err == nil {
   392  		t.Error("duplicates in the second set")
   393  	}
   394  }
   395  
   396  func mockPlugin(name string) *Plugin {
   397  	return &Plugin{
   398  		Metadata: &Metadata{
   399  			Name:        name,
   400  			Version:     "v0.1.2",
   401  			Usage:       "Mock plugin",
   402  			Description: "Mock plugin for testing",
   403  			Command:     "echo mock plugin",
   404  		},
   405  		Dir: "no-such-dir",
   406  	}
   407  }
   408  

View as plain text