1
15
16 package installer
17
18 import (
19 "archive/tar"
20 "bytes"
21 "compress/gzip"
22 "encoding/base64"
23 "fmt"
24 "net/http"
25 "net/http/httptest"
26 "os"
27 "path/filepath"
28 "strings"
29 "syscall"
30 "testing"
31
32 "github.com/pkg/errors"
33
34 "helm.sh/helm/v3/internal/test/ensure"
35 "helm.sh/helm/v3/pkg/getter"
36 "helm.sh/helm/v3/pkg/helmpath"
37 )
38
39 var _ Installer = new(HTTPInstaller)
40
41
42 type TestHTTPGetter struct {
43 MockResponse *bytes.Buffer
44 MockError error
45 }
46
47 func (t *TestHTTPGetter) Get(_ string, _ ...getter.Option) (*bytes.Buffer, error) {
48 return t.MockResponse, t.MockError
49 }
50
51
52 var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA="
53
54 func TestStripName(t *testing.T) {
55 if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" {
56 t.Errorf("name does not match expected value")
57 }
58 if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" {
59 t.Errorf("name does not match expected value")
60 }
61 if stripPluginName("fake-plugin.tgz") != "fake-plugin" {
62 t.Errorf("name does not match expected value")
63 }
64 if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" {
65 t.Errorf("name does not match expected value")
66 }
67 }
68
69 func mockArchiveServer() *httptest.Server {
70 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
71 if !strings.HasSuffix(r.URL.Path, ".tar.gz") {
72 w.Header().Add("Content-Type", "text/html")
73 fmt.Fprintln(w, "broken")
74 return
75 }
76 w.Header().Add("Content-Type", "application/gzip")
77 fmt.Fprintln(w, "test")
78 }))
79 }
80
81 func TestHTTPInstaller(t *testing.T) {
82 ensure.HelmHome(t)
83
84 srv := mockArchiveServer()
85 defer srv.Close()
86 source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
87
88 if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
89 t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
90 }
91
92 i, err := NewForSource(source, "0.0.1")
93 if err != nil {
94 t.Fatalf("unexpected error: %s", err)
95 }
96
97
98 httpInstaller, ok := i.(*HTTPInstaller)
99 if !ok {
100 t.Fatal("expected a HTTPInstaller")
101 }
102
103
104 mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64)
105 if err != nil {
106 t.Fatalf("Could not decode fake tgz plugin: %s", err)
107 }
108
109 httpInstaller.getter = &TestHTTPGetter{
110 MockResponse: bytes.NewBuffer(mockTgz),
111 }
112
113
114 if err := Install(i); err != nil {
115 t.Fatal(err)
116 }
117 if i.Path() != helmpath.DataPath("plugins", "fake-plugin") {
118 t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
119 }
120
121
122 if err := Install(i); err == nil {
123 t.Fatal("expected error for plugin exists, got none")
124 } else if err.Error() != "plugin already exists" {
125 t.Fatalf("expected error for plugin exists, got (%v)", err)
126 }
127
128 }
129
130 func TestHTTPInstallerNonExistentVersion(t *testing.T) {
131 ensure.HelmHome(t)
132 srv := mockArchiveServer()
133 defer srv.Close()
134 source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
135
136 if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
137 t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
138 }
139
140 i, err := NewForSource(source, "0.0.2")
141 if err != nil {
142 t.Fatalf("unexpected error: %s", err)
143 }
144
145
146 httpInstaller, ok := i.(*HTTPInstaller)
147 if !ok {
148 t.Fatal("expected a HTTPInstaller")
149 }
150
151
152 httpInstaller.getter = &TestHTTPGetter{
153 MockError: errors.Errorf("failed to download plugin for some reason"),
154 }
155
156
157 if err := Install(i); err == nil {
158 t.Fatal("expected error from http client")
159 }
160
161 }
162
163 func TestHTTPInstallerUpdate(t *testing.T) {
164 srv := mockArchiveServer()
165 defer srv.Close()
166 source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz"
167 ensure.HelmHome(t)
168
169 if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil {
170 t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err)
171 }
172
173 i, err := NewForSource(source, "0.0.1")
174 if err != nil {
175 t.Fatalf("unexpected error: %s", err)
176 }
177
178
179 httpInstaller, ok := i.(*HTTPInstaller)
180 if !ok {
181 t.Fatal("expected a HTTPInstaller")
182 }
183
184
185 mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64)
186 if err != nil {
187 t.Fatalf("Could not decode fake tgz plugin: %s", err)
188 }
189
190 httpInstaller.getter = &TestHTTPGetter{
191 MockResponse: bytes.NewBuffer(mockTgz),
192 }
193
194
195 if err := Install(i); err != nil {
196 t.Fatal(err)
197 }
198 if i.Path() != helmpath.DataPath("plugins", "fake-plugin") {
199 t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path())
200 }
201
202
203 if err := Update(i); err == nil {
204 t.Fatal("update method not implemented for http installer")
205 }
206 }
207
208 func TestExtract(t *testing.T) {
209 source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz"
210
211 tempDir := t.TempDir()
212
213
214 oldmask := syscall.Umask(0000)
215 defer func() {
216 syscall.Umask(oldmask)
217 }()
218
219
220 var tarbuf bytes.Buffer
221 tw := tar.NewWriter(&tarbuf)
222 var files = []struct {
223 Name, Body string
224 Mode int64
225 }{
226 {"plugin.yaml", "plugin metadata", 0600},
227 {"README.md", "some text", 0777},
228 }
229 for _, file := range files {
230 hdr := &tar.Header{
231 Name: file.Name,
232 Typeflag: tar.TypeReg,
233 Mode: file.Mode,
234 Size: int64(len(file.Body)),
235 }
236 if err := tw.WriteHeader(hdr); err != nil {
237 t.Fatal(err)
238 }
239 if _, err := tw.Write([]byte(file.Body)); err != nil {
240 t.Fatal(err)
241 }
242 }
243
244
245
246
247
248
249 if err := tw.WriteHeader(&tar.Header{
250 Name: "pax_global_header",
251 Typeflag: tar.TypeXGlobalHeader,
252 }); err != nil {
253 t.Fatal(err)
254 }
255
256 if err := tw.Close(); err != nil {
257 t.Fatal(err)
258 }
259
260 var buf bytes.Buffer
261 gz := gzip.NewWriter(&buf)
262 if _, err := gz.Write(tarbuf.Bytes()); err != nil {
263 t.Fatal(err)
264 }
265 gz.Close()
266
267
268 extractor, err := NewExtractor(source)
269 if err != nil {
270 t.Fatal(err)
271 }
272
273 if err = extractor.Extract(&buf, tempDir); err != nil {
274 t.Fatalf("Did not expect error but got error: %v", err)
275 }
276
277 pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml")
278 if info, err := os.Stat(pluginYAMLFullPath); err != nil {
279 if os.IsNotExist(err) {
280 t.Fatalf("Expected %s to exist but doesn't", pluginYAMLFullPath)
281 }
282 t.Fatal(err)
283 } else if info.Mode().Perm() != 0600 {
284 t.Fatalf("Expected %s to have 0600 mode it but has %o", pluginYAMLFullPath, info.Mode().Perm())
285 }
286
287 readmeFullPath := filepath.Join(tempDir, "README.md")
288 if info, err := os.Stat(readmeFullPath); err != nil {
289 if os.IsNotExist(err) {
290 t.Fatalf("Expected %s to exist but doesn't", readmeFullPath)
291 }
292 t.Fatal(err)
293 } else if info.Mode().Perm() != 0777 {
294 t.Fatalf("Expected %s to have 0777 mode it but has %o", readmeFullPath, info.Mode().Perm())
295 }
296
297 }
298
299 func TestCleanJoin(t *testing.T) {
300 for i, fixture := range []struct {
301 path string
302 expect string
303 expectError bool
304 }{
305 {"foo/bar.txt", "/tmp/foo/bar.txt", false},
306 {"/foo/bar.txt", "", true},
307 {"./foo/bar.txt", "/tmp/foo/bar.txt", false},
308 {"./././././foo/bar.txt", "/tmp/foo/bar.txt", false},
309 {"../../../../foo/bar.txt", "", true},
310 {"foo/../../../../bar.txt", "", true},
311 {"c:/foo/bar.txt", "/tmp/c:/foo/bar.txt", true},
312 {"foo\\bar.txt", "/tmp/foo/bar.txt", false},
313 {"c:\\foo\\bar.txt", "", true},
314 } {
315 out, err := cleanJoin("/tmp", fixture.path)
316 if err != nil {
317 if !fixture.expectError {
318 t.Errorf("Test %d: Path was not cleaned: %s", i, err)
319 }
320 continue
321 }
322 if fixture.expect != out {
323 t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out)
324 }
325 }
326
327 }
328
329 func TestMediaTypeToExtension(t *testing.T) {
330
331 for mt, shouldPass := range map[string]bool{
332 "": false,
333 "application/gzip": true,
334 "application/x-gzip": true,
335 "application/x-tgz": true,
336 "application/x-gtar": true,
337 "application/json": false,
338 } {
339 ext, ok := mediaTypeToExtension(mt)
340 if ok != shouldPass {
341 t.Errorf("Media type %q failed test", mt)
342 }
343 if shouldPass && ext == "" {
344 t.Errorf("Expected an extension but got empty string")
345 }
346 if !shouldPass && len(ext) != 0 {
347 t.Error("Expected extension to be empty for unrecognized type")
348 }
349 }
350 }
351
View as plain text