1
16
17 package loader
18
19 import (
20 "archive/tar"
21 "bytes"
22 "compress/gzip"
23 "io"
24 "log"
25 "os"
26 "path/filepath"
27 "runtime"
28 "strings"
29 "testing"
30 "time"
31
32 "helm.sh/helm/v3/pkg/chart"
33 )
34
35 func TestLoadDir(t *testing.T) {
36 l, err := Loader("testdata/frobnitz")
37 if err != nil {
38 t.Fatalf("Failed to load testdata: %s", err)
39 }
40 c, err := l.Load()
41 if err != nil {
42 t.Fatalf("Failed to load testdata: %s", err)
43 }
44 verifyFrobnitz(t, c)
45 verifyChart(t, c)
46 verifyDependencies(t, c)
47 verifyDependenciesLock(t, c)
48 }
49
50 func TestLoadDirWithDevNull(t *testing.T) {
51 if runtime.GOOS == "windows" {
52 t.Skip("test only works on unix systems with /dev/null present")
53 }
54
55 l, err := Loader("testdata/frobnitz_with_dev_null")
56 if err != nil {
57 t.Fatalf("Failed to load testdata: %s", err)
58 }
59 if _, err := l.Load(); err == nil {
60 t.Errorf("packages with an irregular file (/dev/null) should not load")
61 }
62 }
63
64 func TestLoadDirWithSymlink(t *testing.T) {
65 sym := filepath.Join("..", "LICENSE")
66 link := filepath.Join("testdata", "frobnitz_with_symlink", "LICENSE")
67
68 if err := os.Symlink(sym, link); err != nil {
69 t.Fatal(err)
70 }
71
72 defer os.Remove(link)
73
74 l, err := Loader("testdata/frobnitz_with_symlink")
75 if err != nil {
76 t.Fatalf("Failed to load testdata: %s", err)
77 }
78
79 c, err := l.Load()
80 if err != nil {
81 t.Fatalf("Failed to load testdata: %s", err)
82 }
83 verifyFrobnitz(t, c)
84 verifyChart(t, c)
85 verifyDependencies(t, c)
86 verifyDependenciesLock(t, c)
87 }
88
89 func TestBomTestData(t *testing.T) {
90 testFiles := []string{"frobnitz_with_bom/.helmignore", "frobnitz_with_bom/templates/template.tpl", "frobnitz_with_bom/Chart.yaml"}
91 for _, file := range testFiles {
92 data, err := os.ReadFile("testdata/" + file)
93 if err != nil || !bytes.HasPrefix(data, utf8bom) {
94 t.Errorf("Test file has no BOM or is invalid: testdata/%s", file)
95 }
96 }
97
98 archive, err := os.ReadFile("testdata/frobnitz_with_bom.tgz")
99 if err != nil {
100 t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err)
101 }
102 unzipped, err := gzip.NewReader(bytes.NewReader(archive))
103 if err != nil {
104 t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err)
105 }
106 defer unzipped.Close()
107 for _, testFile := range testFiles {
108 data := make([]byte, 3)
109 err := unzipped.Reset(bytes.NewReader(archive))
110 if err != nil {
111 t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err)
112 }
113 tr := tar.NewReader(unzipped)
114 for {
115 file, err := tr.Next()
116 if err == io.EOF {
117 break
118 }
119 if err != nil {
120 t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err)
121 }
122 if file != nil && strings.EqualFold(file.Name, testFile) {
123 _, err := tr.Read(data)
124 if err != nil {
125 t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err)
126 } else {
127 break
128 }
129 }
130 }
131 if !bytes.Equal(data, utf8bom) {
132 t.Fatalf("Test file has no BOM or is invalid: frobnitz_with_bom.tgz/%s", testFile)
133 }
134 }
135 }
136
137 func TestLoadDirWithUTFBOM(t *testing.T) {
138 l, err := Loader("testdata/frobnitz_with_bom")
139 if err != nil {
140 t.Fatalf("Failed to load testdata: %s", err)
141 }
142 c, err := l.Load()
143 if err != nil {
144 t.Fatalf("Failed to load testdata: %s", err)
145 }
146 verifyFrobnitz(t, c)
147 verifyChart(t, c)
148 verifyDependencies(t, c)
149 verifyDependenciesLock(t, c)
150 verifyBomStripped(t, c.Files)
151 }
152
153 func TestLoadArchiveWithUTFBOM(t *testing.T) {
154 l, err := Loader("testdata/frobnitz_with_bom.tgz")
155 if err != nil {
156 t.Fatalf("Failed to load testdata: %s", err)
157 }
158 c, err := l.Load()
159 if err != nil {
160 t.Fatalf("Failed to load testdata: %s", err)
161 }
162 verifyFrobnitz(t, c)
163 verifyChart(t, c)
164 verifyDependencies(t, c)
165 verifyDependenciesLock(t, c)
166 verifyBomStripped(t, c.Files)
167 }
168
169 func TestLoadV1(t *testing.T) {
170 l, err := Loader("testdata/frobnitz.v1")
171 if err != nil {
172 t.Fatalf("Failed to load testdata: %s", err)
173 }
174 c, err := l.Load()
175 if err != nil {
176 t.Fatalf("Failed to load testdata: %s", err)
177 }
178 verifyDependencies(t, c)
179 verifyDependenciesLock(t, c)
180 }
181
182 func TestLoadFileV1(t *testing.T) {
183 l, err := Loader("testdata/frobnitz.v1.tgz")
184 if err != nil {
185 t.Fatalf("Failed to load testdata: %s", err)
186 }
187 c, err := l.Load()
188 if err != nil {
189 t.Fatalf("Failed to load testdata: %s", err)
190 }
191 verifyDependencies(t, c)
192 verifyDependenciesLock(t, c)
193 }
194
195 func TestLoadFile(t *testing.T) {
196 l, err := Loader("testdata/frobnitz-1.2.3.tgz")
197 if err != nil {
198 t.Fatalf("Failed to load testdata: %s", err)
199 }
200 c, err := l.Load()
201 if err != nil {
202 t.Fatalf("Failed to load testdata: %s", err)
203 }
204 verifyFrobnitz(t, c)
205 verifyChart(t, c)
206 verifyDependencies(t, c)
207 }
208
209 func TestLoadFiles_BadCases(t *testing.T) {
210 for _, tt := range []struct {
211 name string
212 bufferedFiles []*BufferedFile
213 expectError string
214 }{
215 {
216 name: "These files contain only requirements.lock",
217 bufferedFiles: []*BufferedFile{
218 {
219 Name: "requirements.lock",
220 Data: []byte(""),
221 },
222 },
223 expectError: "validation: chart.metadata.apiVersion is required"},
224 } {
225 _, err := LoadFiles(tt.bufferedFiles)
226 if err == nil {
227 t.Fatal("expected error when load illegal files")
228 }
229 if !strings.Contains(err.Error(), tt.expectError) {
230 t.Errorf("Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.name)
231 }
232 }
233 }
234
235 func TestLoadFiles(t *testing.T) {
236 goodFiles := []*BufferedFile{
237 {
238 Name: "Chart.yaml",
239 Data: []byte(`apiVersion: v1
240 name: frobnitz
241 description: This is a frobnitz.
242 version: "1.2.3"
243 keywords:
244 - frobnitz
245 - sprocket
246 - dodad
247 maintainers:
248 - name: The Helm Team
249 email: helm@example.com
250 - name: Someone Else
251 email: nobody@example.com
252 sources:
253 - https://example.com/foo/bar
254 home: http://example.com
255 icon: https://example.com/64x64.png
256 `),
257 },
258 {
259 Name: "values.yaml",
260 Data: []byte("var: some values"),
261 },
262 {
263 Name: "values.schema.json",
264 Data: []byte("type: Values"),
265 },
266 {
267 Name: "templates/deployment.yaml",
268 Data: []byte("some deployment"),
269 },
270 {
271 Name: "templates/service.yaml",
272 Data: []byte("some service"),
273 },
274 }
275
276 c, err := LoadFiles(goodFiles)
277 if err != nil {
278 t.Errorf("Expected good files to be loaded, got %v", err)
279 }
280
281 if c.Name() != "frobnitz" {
282 t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Name())
283 }
284
285 if c.Values["var"] != "some values" {
286 t.Error("Expected chart values to be populated with default values")
287 }
288
289 if len(c.Raw) != 5 {
290 t.Errorf("Expected %d files, got %d", 5, len(c.Raw))
291 }
292
293 if !bytes.Equal(c.Schema, []byte("type: Values")) {
294 t.Error("Expected chart schema to be populated with default values")
295 }
296
297 if len(c.Templates) != 2 {
298 t.Errorf("Expected number of templates == 2, got %d", len(c.Templates))
299 }
300
301 if _, err = LoadFiles([]*BufferedFile{}); err == nil {
302 t.Fatal("Expected err to be non-nil")
303 }
304 if err.Error() != "Chart.yaml file is missing" {
305 t.Errorf("Expected chart metadata missing error, got '%s'", err.Error())
306 }
307 }
308
309
310
311 func TestLoadFilesOrder(t *testing.T) {
312 goodFiles := []*BufferedFile{
313 {
314 Name: "requirements.yaml",
315 Data: []byte("dependencies:"),
316 },
317 {
318 Name: "values.yaml",
319 Data: []byte("var: some values"),
320 },
321
322 {
323 Name: "templates/deployment.yaml",
324 Data: []byte("some deployment"),
325 },
326 {
327 Name: "templates/service.yaml",
328 Data: []byte("some service"),
329 },
330 {
331 Name: "Chart.yaml",
332 Data: []byte(`apiVersion: v1
333 name: frobnitz
334 description: This is a frobnitz.
335 version: "1.2.3"
336 keywords:
337 - frobnitz
338 - sprocket
339 - dodad
340 maintainers:
341 - name: The Helm Team
342 email: helm@example.com
343 - name: Someone Else
344 email: nobody@example.com
345 sources:
346 - https://example.com/foo/bar
347 home: http://example.com
348 icon: https://example.com/64x64.png
349 `),
350 },
351 }
352
353
354
355 r, w, err := os.Pipe()
356 if err != nil {
357 t.Fatalf("Unable to create pipe: %s", err)
358 }
359 stderr := log.Writer()
360 log.SetOutput(w)
361 defer func() {
362 log.SetOutput(stderr)
363 }()
364
365 _, err = LoadFiles(goodFiles)
366 if err != nil {
367 t.Errorf("Expected good files to be loaded, got %v", err)
368 }
369 w.Close()
370
371 var text bytes.Buffer
372 io.Copy(&text, r)
373 if text.String() != "" {
374 t.Errorf("Expected no message to Stderr, got %s", text.String())
375 }
376
377 }
378
379
380
381 func TestLoadFileBackslash(t *testing.T) {
382 c, err := Load("testdata/frobnitz_backslash-1.2.3.tgz")
383 if err != nil {
384 t.Fatalf("Failed to load testdata: %s", err)
385 }
386 verifyChartFileAndTemplate(t, c, "frobnitz_backslash")
387 verifyChart(t, c)
388 verifyDependencies(t, c)
389 }
390
391 func TestLoadV2WithReqs(t *testing.T) {
392 l, err := Loader("testdata/frobnitz.v2.reqs")
393 if err != nil {
394 t.Fatalf("Failed to load testdata: %s", err)
395 }
396 c, err := l.Load()
397 if err != nil {
398 t.Fatalf("Failed to load testdata: %s", err)
399 }
400 verifyDependencies(t, c)
401 verifyDependenciesLock(t, c)
402 }
403
404 func TestLoadInvalidArchive(t *testing.T) {
405 tmpdir := t.TempDir()
406
407 writeTar := func(filename, internalPath string, body []byte) {
408 dest, err := os.Create(filename)
409 if err != nil {
410 t.Fatal(err)
411 }
412 zipper := gzip.NewWriter(dest)
413 tw := tar.NewWriter(zipper)
414
415 h := &tar.Header{
416 Name: internalPath,
417 Mode: 0755,
418 Size: int64(len(body)),
419 ModTime: time.Now(),
420 }
421 if err := tw.WriteHeader(h); err != nil {
422 t.Fatal(err)
423 }
424 if _, err := tw.Write(body); err != nil {
425 t.Fatal(err)
426 }
427 tw.Close()
428 zipper.Close()
429 dest.Close()
430 }
431
432 for _, tt := range []struct {
433 chartname string
434 internal string
435 expectError string
436 }{
437 {"illegal-dots.tgz", "../../malformed-helm-test", "chart illegally references parent directory"},
438 {"illegal-dots2.tgz", "/foo/../../malformed-helm-test", "chart illegally references parent directory"},
439 {"illegal-dots3.tgz", "/../../malformed-helm-test", "chart illegally references parent directory"},
440 {"illegal-dots4.tgz", "./../../malformed-helm-test", "chart illegally references parent directory"},
441 {"illegal-name.tgz", "./.", "chart illegally contains content outside the base directory"},
442 {"illegal-name2.tgz", "/./.", "chart illegally contains content outside the base directory"},
443 {"illegal-name3.tgz", "missing-leading-slash", "chart illegally contains content outside the base directory"},
444 {"illegal-name4.tgz", "/missing-leading-slash", "Chart.yaml file is missing"},
445 {"illegal-abspath.tgz", "//foo", "chart illegally contains absolute paths"},
446 {"illegal-abspath2.tgz", "///foo", "chart illegally contains absolute paths"},
447 {"illegal-abspath3.tgz", "\\\\foo", "chart illegally contains absolute paths"},
448 {"illegal-abspath3.tgz", "\\..\\..\\foo", "chart illegally references parent directory"},
449
450
451 {"illegal-abspath4.tgz", "\\.\\c:\\\\foo", "chart contains illegally named files"},
452 {"illegal-abspath5.tgz", "/./c://foo", "chart contains illegally named files"},
453 {"illegal-abspath6.tgz", "\\\\?\\Some\\windows\\magic", "chart illegally contains absolute paths"},
454 } {
455 illegalChart := filepath.Join(tmpdir, tt.chartname)
456 writeTar(illegalChart, tt.internal, []byte("hello: world"))
457 _, err := Load(illegalChart)
458 if err == nil {
459 t.Fatal("expected error when unpacking illegal files")
460 }
461 if !strings.Contains(err.Error(), tt.expectError) {
462 t.Errorf("Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname)
463 }
464 }
465
466
467 illegalChart := filepath.Join(tmpdir, "abs-path.tgz")
468 writeTar(illegalChart, "/Chart.yaml", []byte("hello: world"))
469 _, err := Load(illegalChart)
470 if err.Error() != "validation: chart.metadata.name is required" {
471 t.Error(err)
472 }
473
474
475 illegalChart = filepath.Join(tmpdir, "abs-path2.tgz")
476 writeTar(illegalChart, "files/whatever.yaml", []byte("hello: world"))
477 _, err = Load(illegalChart)
478 if err.Error() != "Chart.yaml file is missing" {
479 t.Errorf("Unexpected error message: %s", err)
480 }
481
482
483 illegalChart = filepath.Join(tmpdir, "abs-winpath.tgz")
484 writeTar(illegalChart, "c:\\Chart.yaml", []byte("hello: world"))
485 _, err = Load(illegalChart)
486 if err.Error() != "validation: chart.metadata.name is required" {
487 t.Error(err)
488 }
489 }
490
491 func verifyChart(t *testing.T, c *chart.Chart) {
492 t.Helper()
493 if c.Name() == "" {
494 t.Fatalf("No chart metadata found on %v", c)
495 }
496 t.Logf("Verifying chart %s", c.Name())
497 if len(c.Templates) != 1 {
498 t.Errorf("Expected 1 template, got %d", len(c.Templates))
499 }
500
501 numfiles := 6
502 if len(c.Files) != numfiles {
503 t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files))
504 for _, n := range c.Files {
505 t.Logf("\t%s", n.Name)
506 }
507 }
508
509 if len(c.Dependencies()) != 2 {
510 t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies()), c.Dependencies())
511 for _, d := range c.Dependencies() {
512 t.Logf("\tSubchart: %s\n", d.Name())
513 }
514 }
515
516 expect := map[string]map[string]string{
517 "alpine": {
518 "version": "0.1.0",
519 },
520 "mariner": {
521 "version": "4.3.2",
522 },
523 }
524
525 for _, dep := range c.Dependencies() {
526 if dep.Metadata == nil {
527 t.Fatalf("expected metadata on dependency: %v", dep)
528 }
529 exp, ok := expect[dep.Name()]
530 if !ok {
531 t.Fatalf("Unknown dependency %s", dep.Name())
532 }
533 if exp["version"] != dep.Metadata.Version {
534 t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version)
535 }
536 }
537
538 }
539
540 func verifyDependencies(t *testing.T, c *chart.Chart) {
541 if len(c.Metadata.Dependencies) != 2 {
542 t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
543 }
544 tests := []*chart.Dependency{
545 {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
546 {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
547 }
548 for i, tt := range tests {
549 d := c.Metadata.Dependencies[i]
550 if d.Name != tt.Name {
551 t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name)
552 }
553 if d.Version != tt.Version {
554 t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version)
555 }
556 if d.Repository != tt.Repository {
557 t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository)
558 }
559 }
560 }
561
562 func verifyDependenciesLock(t *testing.T, c *chart.Chart) {
563 if len(c.Metadata.Dependencies) != 2 {
564 t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies))
565 }
566 tests := []*chart.Dependency{
567 {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
568 {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
569 }
570 for i, tt := range tests {
571 d := c.Metadata.Dependencies[i]
572 if d.Name != tt.Name {
573 t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name)
574 }
575 if d.Version != tt.Version {
576 t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version)
577 }
578 if d.Repository != tt.Repository {
579 t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository)
580 }
581 }
582 }
583
584 func verifyFrobnitz(t *testing.T, c *chart.Chart) {
585 verifyChartFileAndTemplate(t, c, "frobnitz")
586 }
587
588 func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) {
589 if c.Metadata == nil {
590 t.Fatal("Metadata is nil")
591 }
592 if c.Name() != name {
593 t.Errorf("Expected %s, got %s", name, c.Name())
594 }
595 if len(c.Templates) != 1 {
596 t.Fatalf("Expected 1 template, got %d", len(c.Templates))
597 }
598 if c.Templates[0].Name != "templates/template.tpl" {
599 t.Errorf("Unexpected template: %s", c.Templates[0].Name)
600 }
601 if len(c.Templates[0].Data) == 0 {
602 t.Error("No template data.")
603 }
604 if len(c.Files) != 6 {
605 t.Fatalf("Expected 6 Files, got %d", len(c.Files))
606 }
607 if len(c.Dependencies()) != 2 {
608 t.Fatalf("Expected 2 Dependency, got %d", len(c.Dependencies()))
609 }
610 if len(c.Metadata.Dependencies) != 2 {
611 t.Fatalf("Expected 2 Dependencies.Dependency, got %d", len(c.Metadata.Dependencies))
612 }
613 if len(c.Lock.Dependencies) != 2 {
614 t.Fatalf("Expected 2 Lock.Dependency, got %d", len(c.Lock.Dependencies))
615 }
616
617 for _, dep := range c.Dependencies() {
618 switch dep.Name() {
619 case "mariner":
620 case "alpine":
621 if len(dep.Templates) != 1 {
622 t.Fatalf("Expected 1 template, got %d", len(dep.Templates))
623 }
624 if dep.Templates[0].Name != "templates/alpine-pod.yaml" {
625 t.Errorf("Unexpected template: %s", dep.Templates[0].Name)
626 }
627 if len(dep.Templates[0].Data) == 0 {
628 t.Error("No template data.")
629 }
630 if len(dep.Files) != 1 {
631 t.Fatalf("Expected 1 Files, got %d", len(dep.Files))
632 }
633 if len(dep.Dependencies()) != 2 {
634 t.Fatalf("Expected 2 Dependency, got %d", len(dep.Dependencies()))
635 }
636 default:
637 t.Errorf("Unexpected dependency %s", dep.Name())
638 }
639 }
640 }
641
642 func verifyBomStripped(t *testing.T, files []*chart.File) {
643 for _, file := range files {
644 if bytes.HasPrefix(file.Data, utf8bom) {
645 t.Errorf("Byte Order Mark still present in processed file %s", file.Name)
646 }
647 }
648 }
649
View as plain text