github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/downloader/manager_test.go (about) 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 downloader 17 18 import ( 19 "bytes" 20 "os" 21 "path/filepath" 22 "reflect" 23 "testing" 24 25 "github.com/stefanmcshane/helm/pkg/chart" 26 "github.com/stefanmcshane/helm/pkg/chart/loader" 27 "github.com/stefanmcshane/helm/pkg/chartutil" 28 "github.com/stefanmcshane/helm/pkg/getter" 29 "github.com/stefanmcshane/helm/pkg/repo/repotest" 30 ) 31 32 func TestVersionEquals(t *testing.T) { 33 tests := []struct { 34 name, v1, v2 string 35 expect bool 36 }{ 37 {name: "semver match", v1: "1.2.3-beta.11", v2: "1.2.3-beta.11", expect: true}, 38 {name: "semver match, build info", v1: "1.2.3-beta.11+a", v2: "1.2.3-beta.11+b", expect: true}, 39 {name: "string match", v1: "abcdef123", v2: "abcdef123", expect: true}, 40 {name: "semver mismatch", v1: "1.2.3-beta.11", v2: "1.2.3-beta.22", expect: false}, 41 {name: "semver mismatch, invalid semver", v1: "1.2.3-beta.11", v2: "stinkycheese", expect: false}, 42 } 43 44 for _, tt := range tests { 45 if versionEquals(tt.v1, tt.v2) != tt.expect { 46 t.Errorf("%s: failed comparison of %q and %q (expect equal: %t)", tt.name, tt.v1, tt.v2, tt.expect) 47 } 48 } 49 } 50 51 func TestNormalizeURL(t *testing.T) { 52 tests := []struct { 53 name, base, path, expect string 54 }{ 55 {name: "basic URL", base: "https://example.com", path: "http://helm.sh/foo", expect: "http://helm.sh/foo"}, 56 {name: "relative path", base: "https://helm.sh/charts", path: "foo", expect: "https://helm.sh/charts/foo"}, 57 {name: "Encoded path", base: "https://helm.sh/a%2Fb/charts", path: "foo", expect: "https://helm.sh/a%2Fb/charts/foo"}, 58 } 59 60 for _, tt := range tests { 61 got, err := normalizeURL(tt.base, tt.path) 62 if err != nil { 63 t.Errorf("%s: error %s", tt.name, err) 64 continue 65 } else if got != tt.expect { 66 t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got) 67 } 68 } 69 } 70 71 func TestFindChartURL(t *testing.T) { 72 var b bytes.Buffer 73 m := &Manager{ 74 Out: &b, 75 RepositoryConfig: repoConfig, 76 RepositoryCache: repoCache, 77 } 78 repos, err := m.loadChartRepositories() 79 if err != nil { 80 t.Fatal(err) 81 } 82 83 name := "alpine" 84 version := "0.1.0" 85 repoURL := "http://example.com/charts" 86 87 churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err := m.findChartURL(name, version, repoURL, repos) 88 if err != nil { 89 t.Fatal(err) 90 } 91 92 if churl != "https://charts.helm.sh/stable/alpine-0.1.0.tgz" { 93 t.Errorf("Unexpected URL %q", churl) 94 } 95 if username != "" { 96 t.Errorf("Unexpected username %q", username) 97 } 98 if password != "" { 99 t.Errorf("Unexpected password %q", password) 100 } 101 if passcredentialsall != false { 102 t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) 103 } 104 if insecureSkipTLSVerify { 105 t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) 106 } 107 108 name = "tlsfoo" 109 version = "1.2.3" 110 repoURL = "https://example-https-insecureskiptlsverify.com" 111 112 churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err = m.findChartURL(name, version, repoURL, repos) 113 if err != nil { 114 t.Fatal(err) 115 } 116 117 if !insecureSkipTLSVerify { 118 t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) 119 } 120 if churl != "https://example.com/tlsfoo-1.2.3.tgz" { 121 t.Errorf("Unexpected URL %q", churl) 122 } 123 if username != "" { 124 t.Errorf("Unexpected username %q", username) 125 } 126 if password != "" { 127 t.Errorf("Unexpected password %q", password) 128 } 129 if passcredentialsall != false { 130 t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) 131 } 132 } 133 134 func TestGetRepoNames(t *testing.T) { 135 b := bytes.NewBuffer(nil) 136 m := &Manager{ 137 Out: b, 138 RepositoryConfig: repoConfig, 139 RepositoryCache: repoCache, 140 } 141 tests := []struct { 142 name string 143 req []*chart.Dependency 144 expect map[string]string 145 err bool 146 }{ 147 { 148 name: "no repo definition, but references a url", 149 req: []*chart.Dependency{ 150 {Name: "oedipus-rex", Repository: "http://example.com/test"}, 151 }, 152 expect: map[string]string{"http://example.com/test": "http://example.com/test"}, 153 }, 154 { 155 name: "no repo definition failure -- stable repo", 156 req: []*chart.Dependency{ 157 {Name: "oedipus-rex", Repository: "stable"}, 158 }, 159 err: true, 160 }, 161 { 162 name: "no repo definition failure", 163 req: []*chart.Dependency{ 164 {Name: "oedipus-rex", Repository: "http://example.com"}, 165 }, 166 expect: map[string]string{"oedipus-rex": "testing"}, 167 }, 168 { 169 name: "repo from local path", 170 req: []*chart.Dependency{ 171 {Name: "local-dep", Repository: "file://./testdata/signtest"}, 172 }, 173 expect: map[string]string{"local-dep": "file://./testdata/signtest"}, 174 }, 175 { 176 name: "repo alias (alias:)", 177 req: []*chart.Dependency{ 178 {Name: "oedipus-rex", Repository: "alias:testing"}, 179 }, 180 expect: map[string]string{"oedipus-rex": "testing"}, 181 }, 182 { 183 name: "repo alias (@)", 184 req: []*chart.Dependency{ 185 {Name: "oedipus-rex", Repository: "@testing"}, 186 }, 187 expect: map[string]string{"oedipus-rex": "testing"}, 188 }, 189 { 190 name: "repo from local chart under charts path", 191 req: []*chart.Dependency{ 192 {Name: "local-subchart", Repository: ""}, 193 }, 194 expect: map[string]string{}, 195 }, 196 } 197 198 for _, tt := range tests { 199 l, err := m.resolveRepoNames(tt.req) 200 if err != nil { 201 if tt.err { 202 continue 203 } 204 t.Fatal(err) 205 } 206 207 if tt.err { 208 t.Fatalf("Expected error in test %q", tt.name) 209 } 210 211 // m1 and m2 are the maps we want to compare 212 eq := reflect.DeepEqual(l, tt.expect) 213 if !eq { 214 t.Errorf("%s: expected map %v, got %v", tt.name, l, tt.name) 215 } 216 } 217 } 218 219 func TestDownloadAll(t *testing.T) { 220 chartPath := t.TempDir() 221 m := &Manager{ 222 Out: new(bytes.Buffer), 223 RepositoryConfig: repoConfig, 224 RepositoryCache: repoCache, 225 ChartPath: chartPath, 226 } 227 signtest, err := loader.LoadDir(filepath.Join("testdata", "signtest")) 228 if err != nil { 229 t.Fatal(err) 230 } 231 if err := chartutil.SaveDir(signtest, filepath.Join(chartPath, "testdata")); err != nil { 232 t.Fatal(err) 233 } 234 235 local, err := loader.LoadDir(filepath.Join("testdata", "local-subchart")) 236 if err != nil { 237 t.Fatal(err) 238 } 239 if err := chartutil.SaveDir(local, filepath.Join(chartPath, "charts")); err != nil { 240 t.Fatal(err) 241 } 242 243 signDep := &chart.Dependency{ 244 Name: signtest.Name(), 245 Repository: "file://./testdata/signtest", 246 Version: signtest.Metadata.Version, 247 } 248 localDep := &chart.Dependency{ 249 Name: local.Name(), 250 Repository: "", 251 Version: local.Metadata.Version, 252 } 253 254 // create a 'tmpcharts' directory to test #5567 255 if err := os.MkdirAll(filepath.Join(chartPath, "tmpcharts"), 0755); err != nil { 256 t.Fatal(err) 257 } 258 if err := m.downloadAll([]*chart.Dependency{signDep, localDep}); err != nil { 259 t.Error(err) 260 } 261 262 if _, err := os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")); os.IsNotExist(err) { 263 t.Error(err) 264 } 265 } 266 267 func TestUpdateBeforeBuild(t *testing.T) { 268 // Set up a fake repo 269 srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") 270 if err != nil { 271 t.Fatal(err) 272 } 273 defer srv.Stop() 274 if err := srv.LinkIndices(); err != nil { 275 t.Fatal(err) 276 } 277 dir := func(p ...string) string { 278 return filepath.Join(append([]string{srv.Root()}, p...)...) 279 } 280 281 // Save dep 282 d := &chart.Chart{ 283 Metadata: &chart.Metadata{ 284 Name: "dep-chart", 285 Version: "0.1.0", 286 APIVersion: "v1", 287 }, 288 } 289 if err := chartutil.SaveDir(d, dir()); err != nil { 290 t.Fatal(err) 291 } 292 // Save a chart 293 c := &chart.Chart{ 294 Metadata: &chart.Metadata{ 295 Name: "with-dependency", 296 Version: "0.1.0", 297 APIVersion: "v2", 298 Dependencies: []*chart.Dependency{{ 299 Name: d.Metadata.Name, 300 Version: ">=0.1.0", 301 Repository: "file://../dep-chart", 302 }}, 303 }, 304 } 305 if err := chartutil.SaveDir(c, dir()); err != nil { 306 t.Fatal(err) 307 } 308 309 // Set-up a manager 310 b := bytes.NewBuffer(nil) 311 g := getter.Providers{getter.Provider{ 312 Schemes: []string{"http", "https"}, 313 New: getter.NewHTTPGetter, 314 }} 315 m := &Manager{ 316 ChartPath: dir(c.Metadata.Name), 317 Out: b, 318 Getters: g, 319 RepositoryConfig: dir("repositories.yaml"), 320 RepositoryCache: dir(), 321 } 322 323 // Update before Build. see issue: https://github.com/helm/helm/issues/7101 324 err = m.Update() 325 if err != nil { 326 t.Fatal(err) 327 } 328 329 err = m.Build() 330 if err != nil { 331 t.Fatal(err) 332 } 333 } 334 335 // TestUpdateWithNoRepo is for the case of a dependency that has no repo listed. 336 // This happens when the dependency is in the charts directory and does not need 337 // to be fetched. 338 func TestUpdateWithNoRepo(t *testing.T) { 339 // Set up a fake repo 340 srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") 341 if err != nil { 342 t.Fatal(err) 343 } 344 defer srv.Stop() 345 if err := srv.LinkIndices(); err != nil { 346 t.Fatal(err) 347 } 348 dir := func(p ...string) string { 349 return filepath.Join(append([]string{srv.Root()}, p...)...) 350 } 351 352 // Setup the dependent chart 353 d := &chart.Chart{ 354 Metadata: &chart.Metadata{ 355 Name: "dep-chart", 356 Version: "0.1.0", 357 APIVersion: "v1", 358 }, 359 } 360 361 // Save a chart with the dependency 362 c := &chart.Chart{ 363 Metadata: &chart.Metadata{ 364 Name: "with-dependency", 365 Version: "0.1.0", 366 APIVersion: "v2", 367 Dependencies: []*chart.Dependency{{ 368 Name: d.Metadata.Name, 369 Version: "0.1.0", 370 }}, 371 }, 372 } 373 if err := chartutil.SaveDir(c, dir()); err != nil { 374 t.Fatal(err) 375 } 376 377 // Save dependent chart into the parents charts directory. If the chart is 378 // not in the charts directory Helm will return an error that it is not 379 // found. 380 if err := chartutil.SaveDir(d, dir(c.Metadata.Name, "charts")); err != nil { 381 t.Fatal(err) 382 } 383 384 // Set-up a manager 385 b := bytes.NewBuffer(nil) 386 g := getter.Providers{getter.Provider{ 387 Schemes: []string{"http", "https"}, 388 New: getter.NewHTTPGetter, 389 }} 390 m := &Manager{ 391 ChartPath: dir(c.Metadata.Name), 392 Out: b, 393 Getters: g, 394 RepositoryConfig: dir("repositories.yaml"), 395 RepositoryCache: dir(), 396 } 397 398 // Test the update 399 err = m.Update() 400 if err != nil { 401 t.Fatal(err) 402 } 403 } 404 405 // This function is the skeleton test code of failing tests for #6416 and #6871 and bugs due to #5874. 406 // 407 // This function is used by below tests that ensures success of build operation 408 // with optional fields, alias, condition, tags, and even with ranged version. 409 // Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default. 410 // If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used. 411 func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) { 412 // Set up a fake repo 413 srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") 414 if err != nil { 415 t.Fatal(err) 416 } 417 defer srv.Stop() 418 if err := srv.LinkIndices(); err != nil { 419 t.Fatal(err) 420 } 421 dir := func(p ...string) string { 422 return filepath.Join(append([]string{srv.Root()}, p...)...) 423 } 424 425 // Set main fields if not exist 426 if dep.Name == "" { 427 dep.Name = "local-subchart" 428 } 429 if dep.Version == "" { 430 dep.Version = "0.1.0" 431 } 432 if dep.Repository == "" { 433 dep.Repository = srv.URL() 434 } 435 436 // Save a chart 437 c := &chart.Chart{ 438 Metadata: &chart.Metadata{ 439 Name: chartName, 440 Version: "0.1.0", 441 APIVersion: "v2", 442 Dependencies: []*chart.Dependency{&dep}, 443 }, 444 } 445 if err := chartutil.SaveDir(c, dir()); err != nil { 446 t.Fatal(err) 447 } 448 449 // Set-up a manager 450 b := bytes.NewBuffer(nil) 451 g := getter.Providers{getter.Provider{ 452 Schemes: []string{"http", "https"}, 453 New: getter.NewHTTPGetter, 454 }} 455 m := &Manager{ 456 ChartPath: dir(chartName), 457 Out: b, 458 Getters: g, 459 RepositoryConfig: dir("repositories.yaml"), 460 RepositoryCache: dir(), 461 } 462 463 // First build will update dependencies and create Chart.lock file. 464 err = m.Build() 465 if err != nil { 466 t.Fatal(err) 467 } 468 469 // Second build should be passed. See PR #6655. 470 err = m.Build() 471 if err != nil { 472 t.Fatal(err) 473 } 474 } 475 476 func TestBuild_WithoutOptionalFields(t *testing.T) { 477 // Dependency has main fields only (name/version/repository) 478 checkBuildWithOptionalFields(t, "without-optional-fields", chart.Dependency{}) 479 } 480 481 func TestBuild_WithSemVerRange(t *testing.T) { 482 // Dependency version is the form of SemVer range 483 checkBuildWithOptionalFields(t, "with-semver-range", chart.Dependency{ 484 Version: ">=0.1.0", 485 }) 486 } 487 488 func TestBuild_WithAlias(t *testing.T) { 489 // Dependency has an alias 490 checkBuildWithOptionalFields(t, "with-alias", chart.Dependency{ 491 Alias: "local-subchart-alias", 492 }) 493 } 494 495 func TestBuild_WithCondition(t *testing.T) { 496 // Dependency has a condition 497 checkBuildWithOptionalFields(t, "with-condition", chart.Dependency{ 498 Condition: "some.condition", 499 }) 500 } 501 502 func TestBuild_WithTags(t *testing.T) { 503 // Dependency has several tags 504 checkBuildWithOptionalFields(t, "with-tags", chart.Dependency{ 505 Tags: []string{"tag1", "tag2"}, 506 }) 507 } 508 509 // Failing test for #6871 510 func TestBuild_WithRepositoryAlias(t *testing.T) { 511 // Dependency repository is aliased in Chart.yaml 512 checkBuildWithOptionalFields(t, "with-repository-alias", chart.Dependency{ 513 Repository: "@test", 514 }) 515 } 516 517 func TestErrRepoNotFound_Error(t *testing.T) { 518 type fields struct { 519 Repos []string 520 } 521 tests := []struct { 522 name string 523 fields fields 524 want string 525 }{ 526 { 527 name: "OK", 528 fields: fields{ 529 Repos: []string{"https://charts1.example.com", "https://charts2.example.com"}, 530 }, 531 want: "no repository definition for https://charts1.example.com, https://charts2.example.com", 532 }, 533 } 534 for _, tt := range tests { 535 t.Run(tt.name, func(t *testing.T) { 536 e := ErrRepoNotFound{ 537 Repos: tt.fields.Repos, 538 } 539 if got := e.Error(); got != tt.want { 540 t.Errorf("Error() = %v, want %v", got, tt.want) 541 } 542 }) 543 } 544 } 545 546 func TestKey(t *testing.T) { 547 tests := []struct { 548 name string 549 expect string 550 }{ 551 { 552 name: "file:////tmp", 553 expect: "afeed3459e92a874f6373aca264ce1459bfa91f9c1d6612f10ae3dc2ee955df3", 554 }, 555 { 556 name: "https://example.com/charts", 557 expect: "7065c57c94b2411ad774638d76823c7ccb56415441f5ab2f5ece2f3845728e5d", 558 }, 559 { 560 name: "foo/bar/baz", 561 expect: "15c46a4f8a189ae22f36f201048881d6c090c93583bedcf71f5443fdef224c82", 562 }, 563 } 564 565 for _, tt := range tests { 566 o, err := key(tt.name) 567 if err != nil { 568 t.Fatalf("unable to generate key for %q with error: %s", tt.name, err) 569 } 570 if o != tt.expect { 571 t.Errorf("wrong key name generated for %q, expected %q but got %q", tt.name, tt.expect, o) 572 } 573 } 574 }