1/* 2Copyright The Helm Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package loader 18 19import ( 20 "archive/tar" 21 "bytes" 22 "compress/gzip" 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "runtime" 27 "strings" 28 "testing" 29 "time" 30 31 "helm.sh/helm/v3/pkg/chart" 32) 33 34func TestLoadDir(t *testing.T) { 35 l, err := Loader("testdata/frobnitz") 36 if err != nil { 37 t.Fatalf("Failed to load testdata: %s", err) 38 } 39 c, err := l.Load() 40 if err != nil { 41 t.Fatalf("Failed to load testdata: %s", err) 42 } 43 verifyFrobnitz(t, c) 44 verifyChart(t, c) 45 verifyDependencies(t, c) 46 verifyDependenciesLock(t, c) 47} 48 49func TestLoadDirWithDevNull(t *testing.T) { 50 if runtime.GOOS == "windows" { 51 t.Skip("test only works on unix systems with /dev/null present") 52 } 53 54 l, err := Loader("testdata/frobnitz_with_dev_null") 55 if err != nil { 56 t.Fatalf("Failed to load testdata: %s", err) 57 } 58 if _, err := l.Load(); err == nil { 59 t.Errorf("packages with an irregular file (/dev/null) should not load") 60 } 61} 62 63func TestLoadDirWithSymlink(t *testing.T) { 64 sym := filepath.Join("..", "LICENSE") 65 link := filepath.Join("testdata", "frobnitz_with_symlink", "LICENSE") 66 67 if err := os.Symlink(sym, link); err != nil { 68 t.Fatal(err) 69 } 70 71 defer os.Remove(link) 72 73 l, err := Loader("testdata/frobnitz_with_symlink") 74 if err != nil { 75 t.Fatalf("Failed to load testdata: %s", err) 76 } 77 78 c, err := l.Load() 79 if err != nil { 80 t.Fatalf("Failed to load testdata: %s", err) 81 } 82 verifyFrobnitz(t, c) 83 verifyChart(t, c) 84 verifyDependencies(t, c) 85 verifyDependenciesLock(t, c) 86} 87 88func TestLoadV1(t *testing.T) { 89 l, err := Loader("testdata/frobnitz.v1") 90 if err != nil { 91 t.Fatalf("Failed to load testdata: %s", err) 92 } 93 c, err := l.Load() 94 if err != nil { 95 t.Fatalf("Failed to load testdata: %s", err) 96 } 97 verifyDependencies(t, c) 98 verifyDependenciesLock(t, c) 99} 100 101func TestLoadFileV1(t *testing.T) { 102 l, err := Loader("testdata/frobnitz.v1.tgz") 103 if err != nil { 104 t.Fatalf("Failed to load testdata: %s", err) 105 } 106 c, err := l.Load() 107 if err != nil { 108 t.Fatalf("Failed to load testdata: %s", err) 109 } 110 verifyDependencies(t, c) 111 verifyDependenciesLock(t, c) 112} 113 114func TestLoadFile(t *testing.T) { 115 l, err := Loader("testdata/frobnitz-1.2.3.tgz") 116 if err != nil { 117 t.Fatalf("Failed to load testdata: %s", err) 118 } 119 c, err := l.Load() 120 if err != nil { 121 t.Fatalf("Failed to load testdata: %s", err) 122 } 123 verifyFrobnitz(t, c) 124 verifyChart(t, c) 125 verifyDependencies(t, c) 126} 127 128func TestLoadFiles(t *testing.T) { 129 goodFiles := []*BufferedFile{ 130 { 131 Name: "Chart.yaml", 132 Data: []byte(`apiVersion: v1 133name: frobnitz 134description: This is a frobnitz. 135version: "1.2.3" 136keywords: 137 - frobnitz 138 - sprocket 139 - dodad 140maintainers: 141 - name: The Helm Team 142 email: helm@example.com 143 - name: Someone Else 144 email: nobody@example.com 145sources: 146 - https://example.com/foo/bar 147home: http://example.com 148icon: https://example.com/64x64.png 149`), 150 }, 151 { 152 Name: "values.yaml", 153 Data: []byte("var: some values"), 154 }, 155 { 156 Name: "values.schema.json", 157 Data: []byte("type: Values"), 158 }, 159 { 160 Name: "templates/deployment.yaml", 161 Data: []byte("some deployment"), 162 }, 163 { 164 Name: "templates/service.yaml", 165 Data: []byte("some service"), 166 }, 167 } 168 169 c, err := LoadFiles(goodFiles) 170 if err != nil { 171 t.Errorf("Expected good files to be loaded, got %v", err) 172 } 173 174 if c.Name() != "frobnitz" { 175 t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Name()) 176 } 177 178 if c.Values["var"] != "some values" { 179 t.Error("Expected chart values to be populated with default values") 180 } 181 182 if len(c.Raw) != 5 { 183 t.Errorf("Expected %d files, got %d", 5, len(c.Raw)) 184 } 185 186 if !bytes.Equal(c.Schema, []byte("type: Values")) { 187 t.Error("Expected chart schema to be populated with default values") 188 } 189 190 if len(c.Templates) != 2 { 191 t.Errorf("Expected number of templates == 2, got %d", len(c.Templates)) 192 } 193 194 if _, err = LoadFiles([]*BufferedFile{}); err == nil { 195 t.Fatal("Expected err to be non-nil") 196 } 197 if err.Error() != "validation: chart.metadata is required" { 198 t.Errorf("Expected chart metadata missing error, got '%s'", err.Error()) 199 } 200} 201 202// Packaging the chart on a Windows machine will produce an 203// archive that has \\ as delimiters. Test that we support these archives 204func TestLoadFileBackslash(t *testing.T) { 205 c, err := Load("testdata/frobnitz_backslash-1.2.3.tgz") 206 if err != nil { 207 t.Fatalf("Failed to load testdata: %s", err) 208 } 209 verifyChartFileAndTemplate(t, c, "frobnitz_backslash") 210 verifyChart(t, c) 211 verifyDependencies(t, c) 212} 213 214func TestLoadV2WithReqs(t *testing.T) { 215 l, err := Loader("testdata/frobnitz.v2.reqs") 216 if err != nil { 217 t.Fatalf("Failed to load testdata: %s", err) 218 } 219 c, err := l.Load() 220 if err != nil { 221 t.Fatalf("Failed to load testdata: %s", err) 222 } 223 verifyDependencies(t, c) 224 verifyDependenciesLock(t, c) 225} 226 227func TestLoadInvalidArchive(t *testing.T) { 228 tmpdir, err := ioutil.TempDir("", "helm-test-") 229 if err != nil { 230 t.Fatal(err) 231 } 232 defer os.Remove(tmpdir) 233 234 writeTar := func(filename, internalPath string, body []byte) { 235 dest, err := os.Create(filename) 236 if err != nil { 237 t.Fatal(err) 238 } 239 zipper := gzip.NewWriter(dest) 240 tw := tar.NewWriter(zipper) 241 242 h := &tar.Header{ 243 Name: internalPath, 244 Mode: 0755, 245 Size: int64(len(body)), 246 ModTime: time.Now(), 247 } 248 if err := tw.WriteHeader(h); err != nil { 249 t.Fatal(err) 250 } 251 if _, err := tw.Write(body); err != nil { 252 t.Fatal(err) 253 } 254 tw.Close() 255 zipper.Close() 256 dest.Close() 257 } 258 259 for _, tt := range []struct { 260 chartname string 261 internal string 262 expectError string 263 }{ 264 {"illegal-dots.tgz", "../../malformed-helm-test", "chart illegally references parent directory"}, 265 {"illegal-dots2.tgz", "/foo/../../malformed-helm-test", "chart illegally references parent directory"}, 266 {"illegal-dots3.tgz", "/../../malformed-helm-test", "chart illegally references parent directory"}, 267 {"illegal-dots4.tgz", "./../../malformed-helm-test", "chart illegally references parent directory"}, 268 {"illegal-name.tgz", "./.", "chart illegally contains content outside the base directory"}, 269 {"illegal-name2.tgz", "/./.", "chart illegally contains content outside the base directory"}, 270 {"illegal-name3.tgz", "missing-leading-slash", "chart illegally contains content outside the base directory"}, 271 {"illegal-name4.tgz", "/missing-leading-slash", "validation: chart.metadata is required"}, 272 {"illegal-abspath.tgz", "//foo", "chart illegally contains absolute paths"}, 273 {"illegal-abspath2.tgz", "///foo", "chart illegally contains absolute paths"}, 274 {"illegal-abspath3.tgz", "\\\\foo", "chart illegally contains absolute paths"}, 275 {"illegal-abspath3.tgz", "\\..\\..\\foo", "chart illegally references parent directory"}, 276 277 // Under special circumstances, this can get normalized to things that look like absolute Windows paths 278 {"illegal-abspath4.tgz", "\\.\\c:\\\\foo", "chart contains illegally named files"}, 279 {"illegal-abspath5.tgz", "/./c://foo", "chart contains illegally named files"}, 280 {"illegal-abspath6.tgz", "\\\\?\\Some\\windows\\magic", "chart illegally contains absolute paths"}, 281 } { 282 illegalChart := filepath.Join(tmpdir, tt.chartname) 283 writeTar(illegalChart, tt.internal, []byte("hello: world")) 284 _, err = Load(illegalChart) 285 if err == nil { 286 t.Fatal("expected error when unpacking illegal files") 287 } 288 if !strings.Contains(err.Error(), tt.expectError) { 289 t.Errorf("Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname) 290 } 291 } 292 293 // Make sure that absolute path gets interpreted as relative 294 illegalChart := filepath.Join(tmpdir, "abs-path.tgz") 295 writeTar(illegalChart, "/Chart.yaml", []byte("hello: world")) 296 _, err = Load(illegalChart) 297 if err.Error() != "validation: chart.metadata.name is required" { 298 t.Error(err) 299 } 300 301 // And just to validate that the above was not spurious 302 illegalChart = filepath.Join(tmpdir, "abs-path2.tgz") 303 writeTar(illegalChart, "files/whatever.yaml", []byte("hello: world")) 304 _, err = Load(illegalChart) 305 if err.Error() != "validation: chart.metadata is required" { 306 t.Error(err) 307 } 308 309 // Finally, test that drive letter gets stripped off on Windows 310 illegalChart = filepath.Join(tmpdir, "abs-winpath.tgz") 311 writeTar(illegalChart, "c:\\Chart.yaml", []byte("hello: world")) 312 _, err = Load(illegalChart) 313 if err.Error() != "validation: chart.metadata.name is required" { 314 t.Error(err) 315 } 316} 317 318func verifyChart(t *testing.T, c *chart.Chart) { 319 t.Helper() 320 if c.Name() == "" { 321 t.Fatalf("No chart metadata found on %v", c) 322 } 323 t.Logf("Verifying chart %s", c.Name()) 324 if len(c.Templates) != 1 { 325 t.Errorf("Expected 1 template, got %d", len(c.Templates)) 326 } 327 328 numfiles := 6 329 if len(c.Files) != numfiles { 330 t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files)) 331 for _, n := range c.Files { 332 t.Logf("\t%s", n.Name) 333 } 334 } 335 336 if len(c.Dependencies()) != 2 { 337 t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies()), c.Dependencies()) 338 for _, d := range c.Dependencies() { 339 t.Logf("\tSubchart: %s\n", d.Name()) 340 } 341 } 342 343 expect := map[string]map[string]string{ 344 "alpine": { 345 "version": "0.1.0", 346 }, 347 "mariner": { 348 "version": "4.3.2", 349 }, 350 } 351 352 for _, dep := range c.Dependencies() { 353 if dep.Metadata == nil { 354 t.Fatalf("expected metadata on dependency: %v", dep) 355 } 356 exp, ok := expect[dep.Name()] 357 if !ok { 358 t.Fatalf("Unknown dependency %s", dep.Name()) 359 } 360 if exp["version"] != dep.Metadata.Version { 361 t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) 362 } 363 } 364 365} 366 367func verifyDependencies(t *testing.T, c *chart.Chart) { 368 if len(c.Metadata.Dependencies) != 2 { 369 t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) 370 } 371 tests := []*chart.Dependency{ 372 {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, 373 {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, 374 } 375 for i, tt := range tests { 376 d := c.Metadata.Dependencies[i] 377 if d.Name != tt.Name { 378 t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) 379 } 380 if d.Version != tt.Version { 381 t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) 382 } 383 if d.Repository != tt.Repository { 384 t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) 385 } 386 } 387} 388 389func verifyDependenciesLock(t *testing.T, c *chart.Chart) { 390 if len(c.Metadata.Dependencies) != 2 { 391 t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) 392 } 393 tests := []*chart.Dependency{ 394 {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, 395 {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, 396 } 397 for i, tt := range tests { 398 d := c.Metadata.Dependencies[i] 399 if d.Name != tt.Name { 400 t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) 401 } 402 if d.Version != tt.Version { 403 t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) 404 } 405 if d.Repository != tt.Repository { 406 t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) 407 } 408 } 409} 410 411func verifyFrobnitz(t *testing.T, c *chart.Chart) { 412 verifyChartFileAndTemplate(t, c, "frobnitz") 413} 414 415func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { 416 if c.Metadata == nil { 417 t.Fatal("Metadata is nil") 418 } 419 if c.Name() != name { 420 t.Errorf("Expected %s, got %s", name, c.Name()) 421 } 422 if len(c.Templates) != 1 { 423 t.Fatalf("Expected 1 template, got %d", len(c.Templates)) 424 } 425 if c.Templates[0].Name != "templates/template.tpl" { 426 t.Errorf("Unexpected template: %s", c.Templates[0].Name) 427 } 428 if len(c.Templates[0].Data) == 0 { 429 t.Error("No template data.") 430 } 431 if len(c.Files) != 6 { 432 t.Fatalf("Expected 6 Files, got %d", len(c.Files)) 433 } 434 if len(c.Dependencies()) != 2 { 435 t.Fatalf("Expected 2 Dependency, got %d", len(c.Dependencies())) 436 } 437 if len(c.Metadata.Dependencies) != 2 { 438 t.Fatalf("Expected 2 Dependencies.Dependency, got %d", len(c.Metadata.Dependencies)) 439 } 440 if len(c.Lock.Dependencies) != 2 { 441 t.Fatalf("Expected 2 Lock.Dependency, got %d", len(c.Lock.Dependencies)) 442 } 443 444 for _, dep := range c.Dependencies() { 445 switch dep.Name() { 446 case "mariner": 447 case "alpine": 448 if len(dep.Templates) != 1 { 449 t.Fatalf("Expected 1 template, got %d", len(dep.Templates)) 450 } 451 if dep.Templates[0].Name != "templates/alpine-pod.yaml" { 452 t.Errorf("Unexpected template: %s", dep.Templates[0].Name) 453 } 454 if len(dep.Templates[0].Data) == 0 { 455 t.Error("No template data.") 456 } 457 if len(dep.Files) != 1 { 458 t.Fatalf("Expected 1 Files, got %d", len(dep.Files)) 459 } 460 if len(dep.Dependencies()) != 2 { 461 t.Fatalf("Expected 2 Dependency, got %d", len(dep.Dependencies())) 462 } 463 default: 464 t.Errorf("Unexpected dependency %s", dep.Name()) 465 } 466 } 467} 468