github.com/uhthomas/helm@v3.0.0-beta.3+incompatible/pkg/action/install_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 action
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"log"
    23  	"os"
    24  	"path/filepath"
    25  	"regexp"
    26  	"testing"
    27  
    28  	"github.com/stretchr/testify/assert"
    29  
    30  	"helm.sh/helm/internal/test"
    31  	"helm.sh/helm/pkg/chartutil"
    32  	kubefake "helm.sh/helm/pkg/kube/fake"
    33  	"helm.sh/helm/pkg/release"
    34  	"helm.sh/helm/pkg/storage/driver"
    35  )
    36  
    37  type nameTemplateTestCase struct {
    38  	tpl              string
    39  	expected         string
    40  	expectedErrorStr string
    41  }
    42  
    43  func installAction(t *testing.T) *Install {
    44  	config := actionConfigFixture(t)
    45  	instAction := NewInstall(config)
    46  	instAction.Namespace = "spaced"
    47  	instAction.ReleaseName = "test-install-release"
    48  
    49  	return instAction
    50  }
    51  
    52  func TestInstallRelease(t *testing.T) {
    53  	is := assert.New(t)
    54  	instAction := installAction(t)
    55  	vals := map[string]interface{}{}
    56  	res, err := instAction.Run(buildChart(), vals)
    57  	if err != nil {
    58  		t.Fatalf("Failed install: %s", err)
    59  	}
    60  	is.Equal(res.Name, "test-install-release", "Expected release name.")
    61  	is.Equal(res.Namespace, "spaced")
    62  
    63  	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
    64  	is.NoError(err)
    65  
    66  	is.Len(rel.Hooks, 1)
    67  	is.Equal(rel.Hooks[0].Manifest, manifestWithHook)
    68  	is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall)
    69  	is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete")
    70  
    71  	is.NotEqual(len(res.Manifest), 0)
    72  	is.NotEqual(len(rel.Manifest), 0)
    73  	is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
    74  	is.Equal(rel.Info.Description, "Install complete")
    75  }
    76  
    77  func TestInstallReleaseClientOnly(t *testing.T) {
    78  	is := assert.New(t)
    79  	instAction := installAction(t)
    80  	instAction.ClientOnly = true
    81  	instAction.Run(buildChart(), nil) // disregard output
    82  
    83  	is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities)
    84  	is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard})
    85  }
    86  
    87  func TestInstallRelease_NoName(t *testing.T) {
    88  	instAction := installAction(t)
    89  	instAction.ReleaseName = ""
    90  	vals := map[string]interface{}{}
    91  	_, err := instAction.Run(buildChart(), vals)
    92  	if err == nil {
    93  		t.Fatal("expected failure when no name is specified")
    94  	}
    95  	assert.Contains(t, err.Error(), "name is required")
    96  }
    97  
    98  func TestInstallRelease_WithNotes(t *testing.T) {
    99  	is := assert.New(t)
   100  	instAction := installAction(t)
   101  	instAction.ReleaseName = "with-notes"
   102  	vals := map[string]interface{}{}
   103  	res, err := instAction.Run(buildChart(withNotes("note here")), vals)
   104  	if err != nil {
   105  		t.Fatalf("Failed install: %s", err)
   106  	}
   107  
   108  	is.Equal(res.Name, "with-notes")
   109  	is.Equal(res.Namespace, "spaced")
   110  
   111  	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
   112  	is.NoError(err)
   113  	is.Len(rel.Hooks, 1)
   114  	is.Equal(rel.Hooks[0].Manifest, manifestWithHook)
   115  	is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall)
   116  	is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete")
   117  	is.NotEqual(len(res.Manifest), 0)
   118  	is.NotEqual(len(rel.Manifest), 0)
   119  	is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
   120  	is.Equal(rel.Info.Description, "Install complete")
   121  
   122  	is.Equal(rel.Info.Notes, "note here")
   123  }
   124  
   125  func TestInstallRelease_WithNotesRendered(t *testing.T) {
   126  	is := assert.New(t)
   127  	instAction := installAction(t)
   128  	instAction.ReleaseName = "with-notes"
   129  	vals := map[string]interface{}{}
   130  	res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals)
   131  	if err != nil {
   132  		t.Fatalf("Failed install: %s", err)
   133  	}
   134  
   135  	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
   136  	is.NoError(err)
   137  
   138  	expectedNotes := fmt.Sprintf("got-%s", res.Name)
   139  	is.Equal(expectedNotes, rel.Info.Notes)
   140  	is.Equal(rel.Info.Description, "Install complete")
   141  }
   142  
   143  func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) {
   144  	// Regression: Make sure that the child's notes don't override the parent's
   145  	is := assert.New(t)
   146  	instAction := installAction(t)
   147  	instAction.ReleaseName = "with-notes"
   148  	vals := map[string]interface{}{}
   149  	res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals)
   150  	if err != nil {
   151  		t.Fatalf("Failed install: %s", err)
   152  	}
   153  
   154  	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
   155  	is.Equal("with-notes", rel.Name)
   156  	is.NoError(err)
   157  	is.Equal("parent", rel.Info.Notes)
   158  	is.Equal(rel.Info.Description, "Install complete")
   159  }
   160  
   161  func TestInstallRelease_DryRun(t *testing.T) {
   162  	is := assert.New(t)
   163  	instAction := installAction(t)
   164  	instAction.DryRun = true
   165  	vals := map[string]interface{}{}
   166  	res, err := instAction.Run(buildChart(withSampleTemplates()), vals)
   167  	if err != nil {
   168  		t.Fatalf("Failed install: %s", err)
   169  	}
   170  
   171  	is.Contains(res.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
   172  	is.Contains(res.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world")
   173  	is.Contains(res.Manifest, "hello: Earth")
   174  	is.NotContains(res.Manifest, "hello: {{ template \"_planet\" . }}")
   175  	is.NotContains(res.Manifest, "empty")
   176  
   177  	_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
   178  	is.Error(err)
   179  	is.Len(res.Hooks, 1)
   180  	is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "expect hook to not be marked as run")
   181  	is.Equal(res.Info.Description, "Dry run complete")
   182  }
   183  
   184  func TestInstallRelease_NoHooks(t *testing.T) {
   185  	is := assert.New(t)
   186  	instAction := installAction(t)
   187  	instAction.DisableHooks = true
   188  	instAction.ReleaseName = "no-hooks"
   189  	instAction.cfg.Releases.Create(releaseStub())
   190  
   191  	vals := map[string]interface{}{}
   192  	res, err := instAction.Run(buildChart(), vals)
   193  	if err != nil {
   194  		t.Fatalf("Failed install: %s", err)
   195  	}
   196  
   197  	is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "hooks should not run with no-hooks")
   198  }
   199  
   200  func TestInstallRelease_FailedHooks(t *testing.T) {
   201  	is := assert.New(t)
   202  	instAction := installAction(t)
   203  	instAction.ReleaseName = "failed-hooks"
   204  	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   205  	failer.WatchUntilReadyError = fmt.Errorf("Failed watch")
   206  	instAction.cfg.KubeClient = failer
   207  
   208  	vals := map[string]interface{}{}
   209  	res, err := instAction.Run(buildChart(), vals)
   210  	is.Error(err)
   211  	is.Contains(res.Info.Description, "failed post-install")
   212  	is.Equal(release.StatusFailed, res.Info.Status)
   213  }
   214  
   215  func TestInstallRelease_ReplaceRelease(t *testing.T) {
   216  	is := assert.New(t)
   217  	instAction := installAction(t)
   218  	instAction.Replace = true
   219  
   220  	rel := releaseStub()
   221  	rel.Info.Status = release.StatusUninstalled
   222  	instAction.cfg.Releases.Create(rel)
   223  	instAction.ReleaseName = rel.Name
   224  
   225  	vals := map[string]interface{}{}
   226  	res, err := instAction.Run(buildChart(), vals)
   227  	is.NoError(err)
   228  
   229  	// This should have been auto-incremented
   230  	is.Equal(2, res.Version)
   231  	is.Equal(res.Name, rel.Name)
   232  
   233  	getres, err := instAction.cfg.Releases.Get(rel.Name, res.Version)
   234  	is.NoError(err)
   235  	is.Equal(getres.Info.Status, release.StatusDeployed)
   236  }
   237  
   238  func TestInstallRelease_KubeVersion(t *testing.T) {
   239  	is := assert.New(t)
   240  	instAction := installAction(t)
   241  	vals := map[string]interface{}{}
   242  	_, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals)
   243  	is.NoError(err)
   244  
   245  	// This should fail for a few hundred years
   246  	instAction.ReleaseName = "should-fail"
   247  	vals = map[string]interface{}{}
   248  	_, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
   249  	is.Error(err)
   250  	is.Contains(err.Error(), "chart requires kubernetesVersion")
   251  }
   252  
   253  func TestInstallRelease_Wait(t *testing.T) {
   254  	is := assert.New(t)
   255  	instAction := installAction(t)
   256  	instAction.ReleaseName = "come-fail-away"
   257  	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   258  	failer.WaitError = fmt.Errorf("I timed out")
   259  	instAction.cfg.KubeClient = failer
   260  	instAction.Wait = true
   261  	vals := map[string]interface{}{}
   262  
   263  	res, err := instAction.Run(buildChart(), vals)
   264  	is.Error(err)
   265  	is.Contains(res.Info.Description, "I timed out")
   266  	is.Equal(res.Info.Status, release.StatusFailed)
   267  }
   268  
   269  func TestInstallRelease_Atomic(t *testing.T) {
   270  	is := assert.New(t)
   271  
   272  	t.Run("atomic uninstall succeeds", func(t *testing.T) {
   273  		instAction := installAction(t)
   274  		instAction.ReleaseName = "come-fail-away"
   275  		failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   276  		failer.WaitError = fmt.Errorf("I timed out")
   277  		instAction.cfg.KubeClient = failer
   278  		instAction.Atomic = true
   279  		vals := map[string]interface{}{}
   280  
   281  		res, err := instAction.Run(buildChart(), vals)
   282  		is.Error(err)
   283  		is.Contains(err.Error(), "I timed out")
   284  		is.Contains(err.Error(), "atomic")
   285  
   286  		// Now make sure it isn't in storage any more
   287  		_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
   288  		is.Error(err)
   289  		is.Equal(err, driver.ErrReleaseNotFound)
   290  	})
   291  
   292  	t.Run("atomic uninstall fails", func(t *testing.T) {
   293  		instAction := installAction(t)
   294  		instAction.ReleaseName = "come-fail-away-with-me"
   295  		failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   296  		failer.WaitError = fmt.Errorf("I timed out")
   297  		failer.DeleteError = fmt.Errorf("uninstall fail")
   298  		instAction.cfg.KubeClient = failer
   299  		instAction.Atomic = true
   300  		vals := map[string]interface{}{}
   301  
   302  		_, err := instAction.Run(buildChart(), vals)
   303  		is.Error(err)
   304  		is.Contains(err.Error(), "I timed out")
   305  		is.Contains(err.Error(), "uninstall fail")
   306  		is.Contains(err.Error(), "an error occurred while uninstalling the release")
   307  	})
   308  }
   309  
   310  func TestNameTemplate(t *testing.T) {
   311  	testCases := []nameTemplateTestCase{
   312  		// Just a straight up nop please
   313  		{
   314  			tpl:              "foobar",
   315  			expected:         "foobar",
   316  			expectedErrorStr: "",
   317  		},
   318  		// Random numbers at the end for fun & profit
   319  		{
   320  			tpl:              "foobar-{{randNumeric 6}}",
   321  			expected:         "foobar-[0-9]{6}$",
   322  			expectedErrorStr: "",
   323  		},
   324  		// Random numbers in the middle for fun & profit
   325  		{
   326  			tpl:              "foobar-{{randNumeric 4}}-baz",
   327  			expected:         "foobar-[0-9]{4}-baz$",
   328  			expectedErrorStr: "",
   329  		},
   330  		// No such function
   331  		{
   332  			tpl:              "foobar-{{randInt}}",
   333  			expected:         "",
   334  			expectedErrorStr: "function \"randInt\" not defined",
   335  		},
   336  		// Invalid template
   337  		{
   338  			tpl:              "foobar-{{",
   339  			expected:         "",
   340  			expectedErrorStr: "unexpected unclosed action",
   341  		},
   342  	}
   343  
   344  	for _, tc := range testCases {
   345  
   346  		n, err := TemplateName(tc.tpl)
   347  		if err != nil {
   348  			if tc.expectedErrorStr == "" {
   349  				t.Errorf("Was not expecting error, but got: %v", err)
   350  				continue
   351  			}
   352  			re, compErr := regexp.Compile(tc.expectedErrorStr)
   353  			if compErr != nil {
   354  				t.Errorf("Expected error string failed to compile: %v", compErr)
   355  				continue
   356  			}
   357  			if !re.MatchString(err.Error()) {
   358  				t.Errorf("Error didn't match for %s expected %s but got %v", tc.tpl, tc.expectedErrorStr, err)
   359  				continue
   360  			}
   361  		}
   362  		if err == nil && tc.expectedErrorStr != "" {
   363  			t.Errorf("Was expecting error %s but didn't get an error back", tc.expectedErrorStr)
   364  		}
   365  
   366  		if tc.expected != "" {
   367  			re, err := regexp.Compile(tc.expected)
   368  			if err != nil {
   369  				t.Errorf("Expected string failed to compile: %v", err)
   370  				continue
   371  			}
   372  			if !re.MatchString(n) {
   373  				t.Errorf("Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n)
   374  			}
   375  		}
   376  	}
   377  }
   378  
   379  func TestInstallReleaseOutputDir(t *testing.T) {
   380  	is := assert.New(t)
   381  	instAction := installAction(t)
   382  	vals := map[string]interface{}{}
   383  
   384  	dir, err := ioutil.TempDir("", "output-dir")
   385  	if err != nil {
   386  		log.Fatal(err)
   387  	}
   388  	defer os.RemoveAll(dir)
   389  
   390  	instAction.OutputDir = dir
   391  
   392  	_, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
   393  	if err != nil {
   394  		t.Fatalf("Failed install: %s", err)
   395  	}
   396  
   397  	_, err = os.Stat(filepath.Join(dir, "hello/templates/goodbye"))
   398  	is.NoError(err)
   399  
   400  	_, err = os.Stat(filepath.Join(dir, "hello/templates/hello"))
   401  	is.NoError(err)
   402  
   403  	_, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials"))
   404  	is.NoError(err)
   405  
   406  	_, err = os.Stat(filepath.Join(dir, "hello/templates/rbac"))
   407  	is.NoError(err)
   408  
   409  	test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt")
   410  
   411  	_, err = os.Stat(filepath.Join(dir, "hello/templates/empty"))
   412  	is.True(os.IsNotExist(err))
   413  }