github.com/koderover/helm@v2.17.0+incompatible/pkg/tiller/release_server_test.go (about) 1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tiller 18 19 import ( 20 "errors" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "os" 25 "regexp" 26 "testing" 27 "time" 28 29 "github.com/ghodss/yaml" 30 "github.com/golang/protobuf/ptypes/timestamp" 31 "github.com/technosophos/moniker" 32 "golang.org/x/net/context" 33 "google.golang.org/grpc/metadata" 34 v1 "k8s.io/api/core/v1" 35 "k8s.io/cli-runtime/pkg/resource" 36 "k8s.io/client-go/kubernetes/fake" 37 38 "k8s.io/helm/pkg/helm" 39 "k8s.io/helm/pkg/hooks" 40 "k8s.io/helm/pkg/kube" 41 "k8s.io/helm/pkg/proto/hapi/chart" 42 "k8s.io/helm/pkg/proto/hapi/release" 43 "k8s.io/helm/pkg/proto/hapi/services" 44 "k8s.io/helm/pkg/storage" 45 "k8s.io/helm/pkg/storage/driver" 46 "k8s.io/helm/pkg/tiller/environment" 47 ) 48 49 const notesText = "my notes here" 50 51 var manifestWithHook = `kind: ConfigMap 52 metadata: 53 name: test-cm 54 annotations: 55 "helm.sh/hook": post-install,pre-delete 56 data: 57 name: value` 58 59 var manifestWithCRDHook = ` 60 apiVersion: apiextensions.k8s.io/v1beta1 61 kind: CustomResourceDefinition 62 metadata: 63 name: crontabs.stable.example.com 64 annotations: 65 "helm.sh/hook": crd-install 66 spec: 67 group: stable.example.com 68 version: v1 69 scope: Namespaced 70 names: 71 plural: crontabs 72 singular: crontab 73 kind: CronTab 74 shortNames: 75 - ct 76 ` 77 78 var manifestWithTestHook = `kind: Pod 79 metadata: 80 name: finding-nemo, 81 annotations: 82 "helm.sh/hook": test-success 83 spec: 84 containers: 85 - name: nemo-test 86 image: fake-image 87 cmd: fake-command 88 ` 89 90 var manifestWithKeep = `kind: ConfigMap 91 metadata: 92 name: test-cm-keep-a 93 annotations: 94 "helm.sh/resource-policy": keep 95 data: 96 name: value 97 ` 98 99 var manifestWithKeepEmpty = `kind: ConfigMap 100 metadata: 101 name: test-cm-keep-b 102 annotations: 103 "helm.sh/resource-policy": "" 104 data: 105 name: value 106 ` 107 108 var manifestWithUpgradeHooks = `kind: ConfigMap 109 metadata: 110 name: test-cm 111 annotations: 112 "helm.sh/hook": post-upgrade,pre-upgrade 113 data: 114 name: value` 115 116 var manifestWithRollbackHooks = `kind: ConfigMap 117 metadata: 118 name: test-cm 119 annotations: 120 "helm.sh/hook": post-rollback,pre-rollback 121 data: 122 name: value 123 ` 124 125 type chartOptions struct { 126 *chart.Chart 127 } 128 129 type chartOption func(*chartOptions) 130 131 func rsFixture() *ReleaseServer { 132 return NewReleaseServer(MockEnvironment(), fake.NewSimpleClientset(), false) 133 } 134 135 func buildChart(opts ...chartOption) *chart.Chart { 136 c := &chartOptions{ 137 Chart: &chart.Chart{ 138 // TODO: This should be more complete. 139 Metadata: &chart.Metadata{ 140 Name: "hello", 141 }, 142 // This adds a basic template and hooks. 143 Templates: []*chart.Template{ 144 {Name: "templates/hello", Data: []byte("hello: world")}, 145 {Name: "templates/hooks", Data: []byte(manifestWithHook)}, 146 }, 147 }, 148 } 149 150 for _, opt := range opts { 151 opt(c) 152 } 153 154 return c.Chart 155 } 156 157 func withKube(version string) chartOption { 158 return func(opts *chartOptions) { 159 opts.Metadata.KubeVersion = version 160 } 161 } 162 163 func withTiller(version string) chartOption { 164 return func(opts *chartOptions) { 165 opts.Metadata.TillerVersion = version 166 } 167 } 168 169 func withDependency(dependencyOpts ...chartOption) chartOption { 170 return func(opts *chartOptions) { 171 opts.Dependencies = append(opts.Dependencies, buildChart(dependencyOpts...)) 172 } 173 } 174 175 func withNotes(notes string) chartOption { 176 return func(opts *chartOptions) { 177 opts.Templates = append(opts.Templates, &chart.Template{ 178 Name: "templates/NOTES.txt", 179 Data: []byte(notes), 180 }) 181 } 182 } 183 184 func withSampleTemplates() chartOption { 185 return func(opts *chartOptions) { 186 sampleTemplates := []*chart.Template{ 187 // This adds basic templates and partials. 188 {Name: "templates/goodbye", Data: []byte("goodbye: world")}, 189 {Name: "templates/empty", Data: []byte("")}, 190 {Name: "templates/with-partials", Data: []byte(`hello: {{ template "_planet" . }}`)}, 191 {Name: "templates/partials/_planet", Data: []byte(`{{define "_planet"}}Earth{{end}}`)}, 192 } 193 opts.Templates = append(opts.Templates, sampleTemplates...) 194 } 195 } 196 197 type installOptions struct { 198 *services.InstallReleaseRequest 199 } 200 201 type installOption func(*installOptions) 202 203 func withName(name string) installOption { 204 return func(opts *installOptions) { 205 opts.Name = name 206 } 207 } 208 209 func withDryRun() installOption { 210 return func(opts *installOptions) { 211 opts.DryRun = true 212 } 213 } 214 215 func withDisabledHooks() installOption { 216 return func(opts *installOptions) { 217 opts.DisableHooks = true 218 } 219 } 220 221 func withReuseName() installOption { 222 return func(opts *installOptions) { 223 opts.ReuseName = true 224 } 225 } 226 227 func withChart(chartOpts ...chartOption) installOption { 228 return func(opts *installOptions) { 229 opts.Chart = buildChart(chartOpts...) 230 } 231 } 232 233 func withSubNotes() installOption { 234 return func(opts *installOptions) { 235 opts.SubNotes = true 236 } 237 } 238 239 func installRequest(opts ...installOption) *services.InstallReleaseRequest { 240 reqOpts := &installOptions{ 241 &services.InstallReleaseRequest{ 242 Namespace: "spaced", 243 Chart: buildChart(), 244 }, 245 } 246 247 for _, opt := range opts { 248 opt(reqOpts) 249 } 250 251 return reqOpts.InstallReleaseRequest 252 } 253 254 // chartStub creates a fully stubbed out chart. 255 func chartStub() *chart.Chart { 256 return buildChart(withSampleTemplates()) 257 } 258 259 // releaseStub creates a release stub, complete with the chartStub as its chart. 260 func releaseStub() *release.Release { 261 return namedReleaseStub("angry-panda", release.Status_DEPLOYED) 262 } 263 264 func namedReleaseStub(name string, status release.Status_Code) *release.Release { 265 date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} 266 return &release.Release{ 267 Name: name, 268 Info: &release.Info{ 269 FirstDeployed: &date, 270 LastDeployed: &date, 271 Status: &release.Status{Code: status}, 272 Description: "Named Release Stub", 273 }, 274 Chart: chartStub(), 275 Config: &chart.Config{Raw: `name: value`}, 276 Version: 1, 277 Hooks: []*release.Hook{ 278 { 279 Name: "test-cm", 280 Kind: "ConfigMap", 281 Path: "test-cm", 282 Manifest: manifestWithHook, 283 Events: []release.Hook_Event{ 284 release.Hook_POST_INSTALL, 285 release.Hook_PRE_DELETE, 286 }, 287 }, 288 { 289 Name: "finding-nemo", 290 Kind: "Pod", 291 Path: "finding-nemo", 292 Manifest: manifestWithTestHook, 293 Events: []release.Hook_Event{ 294 release.Hook_RELEASE_TEST_SUCCESS, 295 }, 296 }, 297 }, 298 } 299 } 300 301 func upgradeReleaseVersion(rel *release.Release) *release.Release { 302 date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} 303 304 rel.Info.Status.Code = release.Status_SUPERSEDED 305 return &release.Release{ 306 Name: rel.Name, 307 Info: &release.Info{ 308 FirstDeployed: rel.Info.FirstDeployed, 309 LastDeployed: &date, 310 Status: &release.Status{Code: release.Status_DEPLOYED}, 311 }, 312 Chart: rel.Chart, 313 Config: rel.Config, 314 Version: rel.Version + 1, 315 } 316 } 317 318 func TestValidName(t *testing.T) { 319 for name, valid := range map[string]error{ 320 "nina pinta santa-maria": errInvalidName, 321 "nina-pinta-santa-maria": nil, 322 "-nina": errInvalidName, 323 "pinta-": errInvalidName, 324 "santa-maria": nil, 325 "niƱa": errInvalidName, 326 "...": errInvalidName, 327 "pinta...": errInvalidName, 328 "santa...maria": nil, 329 "": errMissingRelease, 330 " ": errInvalidName, 331 ".nina.": errInvalidName, 332 "nina.pinta": nil, 333 "abcdefghi-abcdefghi-abcdefghi-abcdefghi-abcdefghi-abcd": errInvalidName, 334 } { 335 if valid != validateReleaseName(name) { 336 t.Errorf("Expected %q to be %t", name, valid) 337 } 338 } 339 } 340 341 func TestGetAllVersionSet(t *testing.T) { 342 rs := rsFixture() 343 vs, err := GetAllVersionSet(rs.clientset.Discovery()) 344 if err != nil { 345 t.Error(err) 346 } 347 if !vs.Has("v1") { 348 t.Errorf("Expected supported versions to at least include v1.") 349 } 350 if vs.Has("nosuchversion/v1") { 351 t.Error("Non-existent version is reported found.") 352 } 353 } 354 355 func TestGetVersionSet(t *testing.T) { 356 rs := rsFixture() 357 vs, err := GetVersionSet(rs.clientset.Discovery()) 358 if err != nil { 359 t.Error(err) 360 } 361 if !vs.Has("v1") { 362 t.Errorf("Expected supported versions to at least include v1.") 363 } 364 if vs.Has("nosuchversion/v1") { 365 t.Error("Non-existent version is reported found.") 366 } 367 } 368 369 func TestUniqName(t *testing.T) { 370 rs := rsFixture() 371 372 rel1 := releaseStub() 373 rel2 := releaseStub() 374 rel2.Name = "happy-panda" 375 rel2.Info.Status.Code = release.Status_DELETED 376 377 rs.env.Releases.Create(rel1) 378 rs.env.Releases.Create(rel2) 379 380 tests := []struct { 381 name string 382 expect string 383 reuse bool 384 err bool 385 }{ 386 {"first", "first", false, false}, 387 {"", "[a-z]+-[a-z]+", false, false}, 388 {"angry-panda", "", false, true}, 389 {"happy-panda", "", false, true}, 390 {"happy-panda", "happy-panda", true, false}, 391 {"hungry-hungry-hungry-hungry-hungry-hungry-hungry-hungry-hippos", "", true, true}, // Exceeds max name length 392 } 393 394 for _, tt := range tests { 395 u, err := rs.uniqName(tt.name, tt.reuse) 396 if err != nil { 397 if tt.err { 398 continue 399 } 400 t.Fatal(err) 401 } 402 if tt.err { 403 t.Errorf("Expected an error for %q", tt.name) 404 } 405 if match, err := regexp.MatchString(tt.expect, u); err != nil { 406 t.Fatal(err) 407 } else if !match { 408 t.Errorf("Expected %q to match %q", u, tt.expect) 409 } 410 } 411 } 412 413 type fakeNamer struct { 414 name string 415 } 416 417 func NewFakeNamer(nam string) moniker.Namer { 418 return &fakeNamer{ 419 name: nam, 420 } 421 } 422 423 func (f *fakeNamer) Name() string { 424 return f.NameSep(" ") 425 } 426 427 func (f *fakeNamer) NameSep(sep string) string { 428 return f.name 429 } 430 431 func TestCreateUniqueName(t *testing.T) { 432 rs := rsFixture() 433 434 rel1 := releaseStub() 435 rel1.Name = "happy-panda" 436 437 rs.env.Releases.Create(rel1) 438 439 tests := []struct { 440 name string 441 expect string 442 err bool 443 }{ 444 {"happy-panda", "ERROR", true}, 445 {"wobbly-octopus", "[a-z]+-[a-z]+", false}, 446 } 447 448 for _, tt := range tests { 449 m := NewFakeNamer(tt.name) 450 u, err := rs.createUniqName(m) 451 if err != nil { 452 if tt.err { 453 continue 454 } 455 t.Fatal(err) 456 } 457 if tt.err { 458 t.Errorf("Expected an error for %q", tt.name) 459 } 460 if match, err := regexp.MatchString(tt.expect, u); err != nil { 461 t.Fatal(err) 462 } else if !match { 463 t.Errorf("Expected %q to match %q", u, tt.expect) 464 } 465 } 466 467 } 468 469 func releaseWithKeepStub(rlsName string) *release.Release { 470 ch := &chart.Chart{ 471 Metadata: &chart.Metadata{ 472 Name: "bunnychart", 473 }, 474 Templates: []*chart.Template{ 475 {Name: "templates/configmap-keep-a", Data: []byte(manifestWithKeep)}, 476 {Name: "templates/configmap-keep-b", Data: []byte(manifestWithKeepEmpty)}, 477 }, 478 } 479 480 date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} 481 rl := &release.Release{ 482 Name: rlsName, 483 Info: &release.Info{ 484 FirstDeployed: &date, 485 LastDeployed: &date, 486 Status: &release.Status{Code: release.Status_DEPLOYED}, 487 }, 488 Chart: ch, 489 Config: &chart.Config{Raw: `name: value`}, 490 Version: 1, 491 } 492 493 helm.RenderReleaseMock(rl, false) 494 495 return rl 496 } 497 498 func MockEnvironment() *environment.Environment { 499 e := environment.New() 500 e.Releases = storage.Init(driver.NewMemory()) 501 e.KubeClient = &environment.PrintingKubeClient{Out: ioutil.Discard} 502 return e 503 } 504 505 func newUpdateFailingKubeClient() *updateFailingKubeClient { 506 return &updateFailingKubeClient{ 507 PrintingKubeClient: environment.PrintingKubeClient{Out: os.Stdout}, 508 } 509 510 } 511 512 type updateFailingKubeClient struct { 513 environment.PrintingKubeClient 514 } 515 516 func (u *updateFailingKubeClient) Update(namespace string, originalReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { 517 return u.UpdateWithOptions(namespace, originalReader, modifiedReader, kube.UpdateOptions{ 518 Force: force, 519 Recreate: recreate, 520 Timeout: timeout, 521 ShouldWait: shouldWait, 522 }) 523 } 524 525 func (u *updateFailingKubeClient) UpdateWithOptions(namespace string, originalReader, modifiedReader io.Reader, opts kube.UpdateOptions) error { 526 return errors.New("Failed update in kube client") 527 } 528 529 func newHookFailingKubeClient() *hookFailingKubeClient { 530 return &hookFailingKubeClient{ 531 PrintingKubeClient: environment.PrintingKubeClient{Out: ioutil.Discard}, 532 } 533 } 534 535 type hookFailingKubeClient struct { 536 environment.PrintingKubeClient 537 } 538 539 func (h *hookFailingKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error { 540 return errors.New("Failed watch") 541 } 542 543 func newDeleteFailingKubeClient() *deleteFailingKubeClient { 544 return &deleteFailingKubeClient{ 545 PrintingKubeClient: environment.PrintingKubeClient{Out: ioutil.Discard}, 546 } 547 } 548 549 type deleteFailingKubeClient struct { 550 environment.PrintingKubeClient 551 } 552 553 func (d *deleteFailingKubeClient) Delete(ns string, r io.Reader) error { 554 return kube.ErrNoObjectsVisited 555 } 556 557 func (d *deleteFailingKubeClient) DeleteWithTimeout(ns string, r io.Reader, timeout int64, shouldWait bool) error { 558 return kube.ErrNoObjectsVisited 559 } 560 561 type mockListServer struct { 562 val *services.ListReleasesResponse 563 } 564 565 func (l *mockListServer) Send(res *services.ListReleasesResponse) error { 566 l.val = res 567 return nil 568 } 569 570 func (l *mockListServer) Context() context.Context { return helm.NewContext() } 571 func (l *mockListServer) SendMsg(v interface{}) error { return nil } 572 func (l *mockListServer) RecvMsg(v interface{}) error { return nil } 573 func (l *mockListServer) SendHeader(m metadata.MD) error { return nil } 574 func (l *mockListServer) SetTrailer(m metadata.MD) {} 575 func (l *mockListServer) SetHeader(m metadata.MD) error { return nil } 576 577 type mockRunReleaseTestServer struct{} 578 579 func (rs mockRunReleaseTestServer) Send(m *services.TestReleaseResponse) error { 580 return nil 581 } 582 func (rs mockRunReleaseTestServer) SetHeader(m metadata.MD) error { return nil } 583 func (rs mockRunReleaseTestServer) SendHeader(m metadata.MD) error { return nil } 584 func (rs mockRunReleaseTestServer) SetTrailer(m metadata.MD) {} 585 func (rs mockRunReleaseTestServer) SendMsg(v interface{}) error { return nil } 586 func (rs mockRunReleaseTestServer) RecvMsg(v interface{}) error { return nil } 587 func (rs mockRunReleaseTestServer) Context() context.Context { 588 return helm.NewContext() 589 } 590 591 type mockHooksManifest struct { 592 Metadata struct { 593 Name string 594 Annotations map[string]string 595 } 596 } 597 type mockHooksKubeClient struct { 598 Resources map[string]*mockHooksManifest 599 } 600 601 var errResourceExists = errors.New("resource already exists") 602 603 func (kc *mockHooksKubeClient) makeManifest(r io.Reader) (*mockHooksManifest, error) { 604 b, err := ioutil.ReadAll(r) 605 if err != nil { 606 return nil, err 607 } 608 609 manifest := &mockHooksManifest{} 610 err = yaml.Unmarshal(b, manifest) 611 if err != nil { 612 return nil, err 613 } 614 615 return manifest, nil 616 } 617 func (kc *mockHooksKubeClient) Create(ns string, r io.Reader, timeout int64, shouldWait bool) error { 618 manifest, err := kc.makeManifest(r) 619 if err != nil { 620 return err 621 } 622 623 if _, hasKey := kc.Resources[manifest.Metadata.Name]; hasKey { 624 return errResourceExists 625 } 626 627 kc.Resources[manifest.Metadata.Name] = manifest 628 629 return nil 630 } 631 func (kc *mockHooksKubeClient) Get(ns string, r io.Reader) (string, error) { 632 return "", nil 633 } 634 func (kc *mockHooksKubeClient) Delete(ns string, r io.Reader) error { 635 return kc.DeleteWithTimeout(ns, r, 0, false) 636 } 637 func (kc *mockHooksKubeClient) DeleteWithTimeout(ns string, r io.Reader, timeout int64, shouldWait bool) error { 638 manifest, err := kc.makeManifest(r) 639 if err != nil { 640 return err 641 } 642 643 delete(kc.Resources, manifest.Metadata.Name) 644 645 return nil 646 } 647 func (kc *mockHooksKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error { 648 paramManifest, err := kc.makeManifest(r) 649 if err != nil { 650 return err 651 } 652 653 manifest, hasManifest := kc.Resources[paramManifest.Metadata.Name] 654 if !hasManifest { 655 return fmt.Errorf("mockHooksKubeClient.WatchUntilReady: no such resource %s found", paramManifest.Metadata.Name) 656 } 657 658 if manifest.Metadata.Annotations["mockHooksKubeClient/Emulate"] == "hook-failed" { 659 return fmt.Errorf("mockHooksKubeClient.WatchUntilReady: hook-failed") 660 } 661 662 return nil 663 } 664 func (kc *mockHooksKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { 665 return nil 666 } 667 func (kc *mockHooksKubeClient) UpdateWithOptions(ns string, currentReader, modifiedReader io.Reader, opts kube.UpdateOptions) error { 668 return nil 669 } 670 func (kc *mockHooksKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { 671 return []*resource.Info{}, nil 672 } 673 func (kc *mockHooksKubeClient) BuildUnstructured(ns string, reader io.Reader) (kube.Result, error) { 674 return []*resource.Info{}, nil 675 } 676 func (kc *mockHooksKubeClient) Validate(ns string, reader io.Reader) error { 677 return nil 678 } 679 func (kc *mockHooksKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (v1.PodPhase, error) { 680 return v1.PodUnknown, nil 681 } 682 func (kc *mockHooksKubeClient) GetPodLogs(name, namespace string) (io.ReadCloser, error) { 683 return nil, nil 684 } 685 686 func (kc *mockHooksKubeClient) WaitUntilCRDEstablished(reader io.Reader, timeout time.Duration) error { 687 return nil 688 } 689 690 func deletePolicyStub(kubeClient *mockHooksKubeClient) *ReleaseServer { 691 e := environment.New() 692 e.Releases = storage.Init(driver.NewMemory()) 693 e.KubeClient = kubeClient 694 695 clientset := fake.NewSimpleClientset() 696 return &ReleaseServer{ 697 ReleaseModule: &LocalReleaseModule{ 698 clientset: clientset, 699 }, 700 env: e, 701 clientset: clientset, 702 Log: func(_ string, _ ...interface{}) {}, 703 } 704 } 705 706 func deletePolicyHookStub(hookName string, extraAnnotations map[string]string, DeletePolicies []release.Hook_DeletePolicy) *release.Hook { 707 extraAnnotationsStr := "" 708 for k, v := range extraAnnotations { 709 extraAnnotationsStr += fmt.Sprintf(" \"%s\": \"%s\"\n", k, v) 710 } 711 712 return &release.Hook{ 713 Name: hookName, 714 Kind: "Job", 715 Path: hookName, 716 Manifest: fmt.Sprintf(`kind: Job 717 metadata: 718 name: %s 719 annotations: 720 "helm.sh/hook": pre-install,pre-upgrade 721 %sdata: 722 name: value`, hookName, extraAnnotationsStr), 723 Events: []release.Hook_Event{ 724 release.Hook_PRE_INSTALL, 725 release.Hook_PRE_UPGRADE, 726 }, 727 DeletePolicies: DeletePolicies, 728 } 729 } 730 731 func execHookShouldSucceed(rs *ReleaseServer, hook *release.Hook, releaseName string, namespace string, hookType string) error { 732 err := rs.execHook([]*release.Hook{hook}, releaseName, namespace, hookType, 600) 733 if err != nil { 734 return fmt.Errorf("expected hook %s to be successful: %s", hook.Name, err) 735 } 736 return nil 737 } 738 739 func execHookShouldFail(rs *ReleaseServer, hook *release.Hook, releaseName string, namespace string, hookType string) error { 740 err := rs.execHook([]*release.Hook{hook}, releaseName, namespace, hookType, 600) 741 if err == nil { 742 return fmt.Errorf("expected hook %s to be failed", hook.Name) 743 } 744 return nil 745 } 746 747 func execHookShouldFailWithError(rs *ReleaseServer, hook *release.Hook, releaseName string, namespace string, hookType string, expectedError error) error { 748 err := rs.execHook([]*release.Hook{hook}, releaseName, namespace, hookType, 600) 749 if err != expectedError { 750 return fmt.Errorf("expected hook %s to fail with error %v, got %v", hook.Name, expectedError, err) 751 } 752 return nil 753 } 754 755 type deletePolicyContext struct { 756 ReleaseServer *ReleaseServer 757 ReleaseName string 758 Namespace string 759 HookName string 760 KubeClient *mockHooksKubeClient 761 } 762 763 func newDeletePolicyContext() *deletePolicyContext { 764 kubeClient := &mockHooksKubeClient{ 765 Resources: make(map[string]*mockHooksManifest), 766 } 767 768 return &deletePolicyContext{ 769 KubeClient: kubeClient, 770 ReleaseServer: deletePolicyStub(kubeClient), 771 ReleaseName: "flying-carp", 772 Namespace: "river", 773 HookName: "migration-job", 774 } 775 } 776 777 func TestSuccessfulHookWithoutDeletePolicy(t *testing.T) { 778 ctx := newDeletePolicyContext() 779 hook := deletePolicyHookStub(ctx.HookName, nil, nil) 780 781 err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 782 if err != nil { 783 t.Error(err) 784 } 785 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 786 t.Errorf("expected resource %s to be created by kube client", hook.Name) 787 } 788 } 789 790 func TestFailedHookWithoutDeletePolicy(t *testing.T) { 791 ctx := newDeletePolicyContext() 792 hook := deletePolicyHookStub(ctx.HookName, 793 map[string]string{"mockHooksKubeClient/Emulate": "hook-failed"}, 794 nil, 795 ) 796 797 err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 798 if err != nil { 799 t.Error(err) 800 } 801 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 802 t.Errorf("expected resource %s to be created by kube client", hook.Name) 803 } 804 } 805 806 func TestSuccessfulHookWithSucceededDeletePolicy(t *testing.T) { 807 ctx := newDeletePolicyContext() 808 hook := deletePolicyHookStub(ctx.HookName, 809 map[string]string{"helm.sh/hook-delete-policy": "hook-succeeded"}, 810 []release.Hook_DeletePolicy{release.Hook_SUCCEEDED}, 811 ) 812 813 err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 814 if err != nil { 815 t.Error(err) 816 } 817 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { 818 t.Errorf("expected resource %s to be unexisting after hook succeeded", hook.Name) 819 } 820 } 821 822 func TestSuccessfulHookWithFailedDeletePolicy(t *testing.T) { 823 ctx := newDeletePolicyContext() 824 hook := deletePolicyHookStub(ctx.HookName, 825 map[string]string{"helm.sh/hook-delete-policy": "hook-failed"}, 826 []release.Hook_DeletePolicy{release.Hook_FAILED}, 827 ) 828 829 err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 830 if err != nil { 831 t.Error(err) 832 } 833 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 834 t.Errorf("expected resource %s to be existing after hook succeeded", hook.Name) 835 } 836 } 837 838 func TestFailedHookWithSucceededDeletePolicy(t *testing.T) { 839 ctx := newDeletePolicyContext() 840 841 hook := deletePolicyHookStub(ctx.HookName, 842 map[string]string{ 843 "mockHooksKubeClient/Emulate": "hook-failed", 844 "helm.sh/hook-delete-policy": "hook-succeeded", 845 }, 846 []release.Hook_DeletePolicy{release.Hook_SUCCEEDED}, 847 ) 848 849 err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 850 if err != nil { 851 t.Error(err) 852 } 853 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 854 t.Errorf("expected resource %s to be existing after hook failed", hook.Name) 855 } 856 } 857 858 func TestFailedHookWithFailedDeletePolicy(t *testing.T) { 859 ctx := newDeletePolicyContext() 860 861 hook := deletePolicyHookStub(ctx.HookName, 862 map[string]string{ 863 "mockHooksKubeClient/Emulate": "hook-failed", 864 "helm.sh/hook-delete-policy": "hook-failed", 865 }, 866 []release.Hook_DeletePolicy{release.Hook_FAILED}, 867 ) 868 869 err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 870 if err != nil { 871 t.Error(err) 872 } 873 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { 874 t.Errorf("expected resource %s to be unexisting after hook failed", hook.Name) 875 } 876 } 877 878 func TestSuccessfulHookWithSuccededOrFailedDeletePolicy(t *testing.T) { 879 ctx := newDeletePolicyContext() 880 881 hook := deletePolicyHookStub(ctx.HookName, 882 map[string]string{ 883 "helm.sh/hook-delete-policy": "hook-succeeded,hook-failed", 884 }, 885 []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_FAILED}, 886 ) 887 888 err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 889 if err != nil { 890 t.Error(err) 891 } 892 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { 893 t.Errorf("expected resource %s to be unexisting after hook succeeded", hook.Name) 894 } 895 } 896 897 func TestFailedHookWithSuccededOrFailedDeletePolicy(t *testing.T) { 898 ctx := newDeletePolicyContext() 899 900 hook := deletePolicyHookStub(ctx.HookName, 901 map[string]string{ 902 "mockHooksKubeClient/Emulate": "hook-failed", 903 "helm.sh/hook-delete-policy": "hook-succeeded,hook-failed", 904 }, 905 []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_FAILED}, 906 ) 907 908 err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 909 if err != nil { 910 t.Error(err) 911 } 912 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { 913 t.Errorf("expected resource %s to be unexisting after hook failed", hook.Name) 914 } 915 } 916 917 func TestHookAlreadyExists(t *testing.T) { 918 ctx := newDeletePolicyContext() 919 920 hook := deletePolicyHookStub(ctx.HookName, nil, nil) 921 922 err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 923 if err != nil { 924 t.Error(err) 925 } 926 927 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 928 t.Errorf("expected resource %s to be existing after hook succeeded", hook.Name) 929 } 930 931 err = execHookShouldFailWithError(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreUpgrade, errResourceExists) 932 if err != nil { 933 t.Error(err) 934 } 935 936 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 937 t.Errorf("expected resource %s to be existing after already exists error", hook.Name) 938 } 939 } 940 941 func TestHookDeletingWithBeforeHookCreationDeletePolicy(t *testing.T) { 942 ctx := newDeletePolicyContext() 943 944 hook := deletePolicyHookStub(ctx.HookName, 945 map[string]string{"helm.sh/hook-delete-policy": "before-hook-creation"}, 946 []release.Hook_DeletePolicy{release.Hook_BEFORE_HOOK_CREATION}, 947 ) 948 949 err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 950 if err != nil { 951 t.Error(err) 952 } 953 954 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 955 t.Errorf("expected resource %s to be existing after hook succeeded", hook.Name) 956 } 957 958 err = execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreUpgrade) 959 if err != nil { 960 t.Error(err) 961 } 962 963 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 964 t.Errorf("expected resource %s to be existing after hook succeeded", hook.Name) 965 } 966 } 967 968 func TestSuccessfulHookWithMixedDeletePolicies(t *testing.T) { 969 ctx := newDeletePolicyContext() 970 971 hook := deletePolicyHookStub(ctx.HookName, 972 map[string]string{ 973 "helm.sh/hook-delete-policy": "hook-succeeded,before-hook-creation", 974 }, 975 []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_BEFORE_HOOK_CREATION}, 976 ) 977 978 err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 979 if err != nil { 980 t.Error(err) 981 } 982 983 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { 984 t.Errorf("expected resource %s to be unexisting after hook succeeded", hook.Name) 985 } 986 987 err = execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreUpgrade) 988 if err != nil { 989 t.Error(err) 990 } 991 992 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { 993 t.Errorf("expected resource %s to be unexisting after hook succeeded", hook.Name) 994 } 995 } 996 997 func TestFailedHookWithMixedDeletePolicies(t *testing.T) { 998 ctx := newDeletePolicyContext() 999 1000 hook := deletePolicyHookStub(ctx.HookName, 1001 map[string]string{ 1002 "mockHooksKubeClient/Emulate": "hook-failed", 1003 "helm.sh/hook-delete-policy": "hook-succeeded,before-hook-creation", 1004 }, 1005 []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_BEFORE_HOOK_CREATION}, 1006 ) 1007 1008 err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 1009 if err != nil { 1010 t.Error(err) 1011 } 1012 1013 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 1014 t.Errorf("expected resource %s to be existing after hook failed", hook.Name) 1015 } 1016 1017 err = execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreUpgrade) 1018 if err != nil { 1019 t.Error(err) 1020 } 1021 1022 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 1023 t.Errorf("expected resource %s to be existing after hook failed", hook.Name) 1024 } 1025 } 1026 1027 func TestFailedThenSuccessfulHookWithMixedDeletePolicies(t *testing.T) { 1028 ctx := newDeletePolicyContext() 1029 1030 hook := deletePolicyHookStub(ctx.HookName, 1031 map[string]string{ 1032 "mockHooksKubeClient/Emulate": "hook-failed", 1033 "helm.sh/hook-delete-policy": "hook-succeeded,before-hook-creation", 1034 }, 1035 []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_BEFORE_HOOK_CREATION}, 1036 ) 1037 1038 err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) 1039 if err != nil { 1040 t.Error(err) 1041 } 1042 1043 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { 1044 t.Errorf("expected resource %s to be existing after hook failed", hook.Name) 1045 } 1046 1047 hook = deletePolicyHookStub(ctx.HookName, 1048 map[string]string{ 1049 "helm.sh/hook-delete-policy": "hook-succeeded,before-hook-creation", 1050 }, 1051 []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_BEFORE_HOOK_CREATION}, 1052 ) 1053 1054 err = execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreUpgrade) 1055 if err != nil { 1056 t.Error(err) 1057 } 1058 1059 if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { 1060 t.Errorf("expected resource %s to be unexisting after hook succeeded", hook.Name) 1061 } 1062 }