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