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