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