...

Source file src/helm.sh/helm/v3/pkg/action/install_test.go

Documentation: helm.sh/helm/v3/pkg/action

     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  	"context"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"path/filepath"
    25  	"regexp"
    26  	"runtime"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  
    34  	"helm.sh/helm/v3/internal/test"
    35  	"helm.sh/helm/v3/pkg/chart"
    36  	"helm.sh/helm/v3/pkg/chartutil"
    37  	kubefake "helm.sh/helm/v3/pkg/kube/fake"
    38  	"helm.sh/helm/v3/pkg/release"
    39  	"helm.sh/helm/v3/pkg/storage/driver"
    40  	helmtime "helm.sh/helm/v3/pkg/time"
    41  )
    42  
    43  type nameTemplateTestCase struct {
    44  	tpl              string
    45  	expected         string
    46  	expectedErrorStr string
    47  }
    48  
    49  func installAction(t *testing.T) *Install {
    50  	config := actionConfigFixture(t)
    51  	instAction := NewInstall(config)
    52  	instAction.Namespace = "spaced"
    53  	instAction.ReleaseName = "test-install-release"
    54  
    55  	return instAction
    56  }
    57  
    58  func TestInstallRelease(t *testing.T) {
    59  	is := assert.New(t)
    60  	req := require.New(t)
    61  
    62  	instAction := installAction(t)
    63  	vals := map[string]interface{}{}
    64  	ctx, done := context.WithCancel(context.Background())
    65  	res, err := instAction.RunWithContext(ctx, buildChart(), vals)
    66  	if err != nil {
    67  		t.Fatalf("Failed install: %s", err)
    68  	}
    69  	is.Equal(res.Name, "test-install-release", "Expected release name.")
    70  	is.Equal(res.Namespace, "spaced")
    71  
    72  	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
    73  	is.NoError(err)
    74  
    75  	is.Len(rel.Hooks, 1)
    76  	is.Equal(rel.Hooks[0].Manifest, manifestWithHook)
    77  	is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall)
    78  	is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete")
    79  
    80  	is.NotEqual(len(res.Manifest), 0)
    81  	is.NotEqual(len(rel.Manifest), 0)
    82  	is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
    83  	is.Equal(rel.Info.Description, "Install complete")
    84  
    85  	// Detecting previous bug where context termination after successful release
    86  	// caused release to fail.
    87  	done()
    88  	time.Sleep(time.Millisecond * 100)
    89  	lastRelease, err := instAction.cfg.Releases.Last(rel.Name)
    90  	req.NoError(err)
    91  	is.Equal(lastRelease.Info.Status, release.StatusDeployed)
    92  }
    93  
    94  func TestInstallReleaseWithValues(t *testing.T) {
    95  	is := assert.New(t)
    96  	instAction := installAction(t)
    97  	userVals := map[string]interface{}{
    98  		"nestedKey": map[string]interface{}{
    99  			"simpleKey": "simpleValue",
   100  		},
   101  	}
   102  	expectedUserValues := map[string]interface{}{
   103  		"nestedKey": map[string]interface{}{
   104  			"simpleKey": "simpleValue",
   105  		},
   106  	}
   107  	res, err := instAction.Run(buildChart(withSampleValues()), userVals)
   108  	if err != nil {
   109  		t.Fatalf("Failed install: %s", err)
   110  	}
   111  	is.Equal(res.Name, "test-install-release", "Expected release name.")
   112  	is.Equal(res.Namespace, "spaced")
   113  
   114  	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
   115  	is.NoError(err)
   116  
   117  	is.Len(rel.Hooks, 1)
   118  	is.Equal(rel.Hooks[0].Manifest, manifestWithHook)
   119  	is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall)
   120  	is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete")
   121  
   122  	is.NotEqual(len(res.Manifest), 0)
   123  	is.NotEqual(len(rel.Manifest), 0)
   124  	is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
   125  	is.Equal("Install complete", rel.Info.Description)
   126  	is.Equal(expectedUserValues, rel.Config)
   127  }
   128  
   129  func TestInstallReleaseClientOnly(t *testing.T) {
   130  	is := assert.New(t)
   131  	instAction := installAction(t)
   132  	instAction.ClientOnly = true
   133  	instAction.Run(buildChart(), nil) // disregard output
   134  
   135  	is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities)
   136  	is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: io.Discard})
   137  }
   138  
   139  func TestInstallRelease_NoName(t *testing.T) {
   140  	instAction := installAction(t)
   141  	instAction.ReleaseName = ""
   142  	vals := map[string]interface{}{}
   143  	_, err := instAction.Run(buildChart(), vals)
   144  	if err == nil {
   145  		t.Fatal("expected failure when no name is specified")
   146  	}
   147  	assert.Contains(t, err.Error(), "no name provided")
   148  }
   149  
   150  func TestInstallRelease_WithNotes(t *testing.T) {
   151  	is := assert.New(t)
   152  	instAction := installAction(t)
   153  	instAction.ReleaseName = "with-notes"
   154  	vals := map[string]interface{}{}
   155  	res, err := instAction.Run(buildChart(withNotes("note here")), vals)
   156  	if err != nil {
   157  		t.Fatalf("Failed install: %s", err)
   158  	}
   159  
   160  	is.Equal(res.Name, "with-notes")
   161  	is.Equal(res.Namespace, "spaced")
   162  
   163  	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
   164  	is.NoError(err)
   165  	is.Len(rel.Hooks, 1)
   166  	is.Equal(rel.Hooks[0].Manifest, manifestWithHook)
   167  	is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall)
   168  	is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete")
   169  	is.NotEqual(len(res.Manifest), 0)
   170  	is.NotEqual(len(rel.Manifest), 0)
   171  	is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
   172  	is.Equal(rel.Info.Description, "Install complete")
   173  
   174  	is.Equal(rel.Info.Notes, "note here")
   175  }
   176  
   177  func TestInstallRelease_WithNotesRendered(t *testing.T) {
   178  	is := assert.New(t)
   179  	instAction := installAction(t)
   180  	instAction.ReleaseName = "with-notes"
   181  	vals := map[string]interface{}{}
   182  	res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals)
   183  	if err != nil {
   184  		t.Fatalf("Failed install: %s", err)
   185  	}
   186  
   187  	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
   188  	is.NoError(err)
   189  
   190  	expectedNotes := fmt.Sprintf("got-%s", res.Name)
   191  	is.Equal(expectedNotes, rel.Info.Notes)
   192  	is.Equal(rel.Info.Description, "Install complete")
   193  }
   194  
   195  func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) {
   196  	// Regression: Make sure that the child's notes don't override the parent's
   197  	is := assert.New(t)
   198  	instAction := installAction(t)
   199  	instAction.ReleaseName = "with-notes"
   200  	vals := map[string]interface{}{}
   201  	res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals)
   202  	if err != nil {
   203  		t.Fatalf("Failed install: %s", err)
   204  	}
   205  
   206  	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
   207  	is.Equal("with-notes", rel.Name)
   208  	is.NoError(err)
   209  	is.Equal("parent", rel.Info.Notes)
   210  	is.Equal(rel.Info.Description, "Install complete")
   211  }
   212  
   213  func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) {
   214  	// Regression: Make sure that the child's notes don't override the parent's
   215  	is := assert.New(t)
   216  	instAction := installAction(t)
   217  	instAction.ReleaseName = "with-notes"
   218  	instAction.SubNotes = true
   219  	vals := map[string]interface{}{}
   220  	res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals)
   221  	if err != nil {
   222  		t.Fatalf("Failed install: %s", err)
   223  	}
   224  
   225  	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
   226  	is.Equal("with-notes", rel.Name)
   227  	is.NoError(err)
   228  	// test run can return as either 'parent\nchild' or 'child\nparent'
   229  	if !strings.Contains(rel.Info.Notes, "parent") && !strings.Contains(rel.Info.Notes, "child") {
   230  		t.Fatalf("Expected 'parent\nchild' or 'child\nparent', got '%s'", rel.Info.Notes)
   231  	}
   232  	is.Equal(rel.Info.Description, "Install complete")
   233  }
   234  
   235  func TestInstallRelease_DryRun(t *testing.T) {
   236  	is := assert.New(t)
   237  	instAction := installAction(t)
   238  	instAction.DryRun = true
   239  	vals := map[string]interface{}{}
   240  	res, err := instAction.Run(buildChart(withSampleTemplates()), vals)
   241  	if err != nil {
   242  		t.Fatalf("Failed install: %s", err)
   243  	}
   244  
   245  	is.Contains(res.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
   246  	is.Contains(res.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world")
   247  	is.Contains(res.Manifest, "hello: Earth")
   248  	is.NotContains(res.Manifest, "hello: {{ template \"_planet\" . }}")
   249  	is.NotContains(res.Manifest, "empty")
   250  
   251  	_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
   252  	is.Error(err)
   253  	is.Len(res.Hooks, 1)
   254  	is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "expect hook to not be marked as run")
   255  	is.Equal(res.Info.Description, "Dry run complete")
   256  }
   257  
   258  func TestInstallRelease_DryRunHiddenSecret(t *testing.T) {
   259  	is := assert.New(t)
   260  	instAction := installAction(t)
   261  
   262  	// First perform a normal dry-run with the secret and confirm its presence.
   263  	instAction.DryRun = true
   264  	vals := map[string]interface{}{}
   265  	res, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
   266  	if err != nil {
   267  		t.Fatalf("Failed install: %s", err)
   268  	}
   269  	is.Contains(res.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret")
   270  
   271  	_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
   272  	is.Error(err)
   273  	is.Equal(res.Info.Description, "Dry run complete")
   274  
   275  	// Perform a dry-run where the secret should not be present
   276  	instAction.HideSecret = true
   277  	vals = map[string]interface{}{}
   278  	res2, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
   279  	if err != nil {
   280  		t.Fatalf("Failed install: %s", err)
   281  	}
   282  
   283  	is.NotContains(res2.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret")
   284  
   285  	_, err = instAction.cfg.Releases.Get(res2.Name, res2.Version)
   286  	is.Error(err)
   287  	is.Equal(res2.Info.Description, "Dry run complete")
   288  
   289  	// Ensure there is an error when HideSecret True but not in a dry-run mode
   290  	instAction.DryRun = false
   291  	vals = map[string]interface{}{}
   292  	_, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
   293  	if err == nil {
   294  		t.Fatalf("Did not get expected an error when dry-run false and hide secret is true")
   295  	}
   296  }
   297  
   298  // Regression test for #7955
   299  func TestInstallRelease_DryRun_Lookup(t *testing.T) {
   300  	is := assert.New(t)
   301  	instAction := installAction(t)
   302  	instAction.DryRun = true
   303  	vals := map[string]interface{}{}
   304  
   305  	mockChart := buildChart(withSampleTemplates())
   306  	mockChart.Templates = append(mockChart.Templates, &chart.File{
   307  		Name: "templates/lookup",
   308  		Data: []byte(`goodbye: {{ lookup "v1" "Namespace" "" "___" }}`),
   309  	})
   310  
   311  	res, err := instAction.Run(mockChart, vals)
   312  	if err != nil {
   313  		t.Fatalf("Failed install: %s", err)
   314  	}
   315  
   316  	is.Contains(res.Manifest, "goodbye: map[]")
   317  }
   318  
   319  func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) {
   320  	is := assert.New(t)
   321  	instAction := installAction(t)
   322  	instAction.DryRun = true
   323  	vals := map[string]interface{}{}
   324  	_, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals)
   325  	expectedErr := "\"hello/templates/incorrect\" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh"
   326  	if err == nil {
   327  		t.Fatalf("Install should fail containing error: %s", expectedErr)
   328  	}
   329  	if err != nil {
   330  		is.Contains(err.Error(), expectedErr)
   331  	}
   332  }
   333  
   334  func TestInstallRelease_NoHooks(t *testing.T) {
   335  	is := assert.New(t)
   336  	instAction := installAction(t)
   337  	instAction.DisableHooks = true
   338  	instAction.ReleaseName = "no-hooks"
   339  	instAction.cfg.Releases.Create(releaseStub())
   340  
   341  	vals := map[string]interface{}{}
   342  	res, err := instAction.Run(buildChart(), vals)
   343  	if err != nil {
   344  		t.Fatalf("Failed install: %s", err)
   345  	}
   346  
   347  	is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "hooks should not run with no-hooks")
   348  }
   349  
   350  func TestInstallRelease_FailedHooks(t *testing.T) {
   351  	is := assert.New(t)
   352  	instAction := installAction(t)
   353  	instAction.ReleaseName = "failed-hooks"
   354  	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   355  	failer.WatchUntilReadyError = fmt.Errorf("Failed watch")
   356  	instAction.cfg.KubeClient = failer
   357  
   358  	vals := map[string]interface{}{}
   359  	res, err := instAction.Run(buildChart(), vals)
   360  	is.Error(err)
   361  	is.Contains(res.Info.Description, "failed post-install")
   362  	is.Equal(release.StatusFailed, res.Info.Status)
   363  }
   364  
   365  func TestInstallRelease_ReplaceRelease(t *testing.T) {
   366  	is := assert.New(t)
   367  	instAction := installAction(t)
   368  	instAction.Replace = true
   369  
   370  	rel := releaseStub()
   371  	rel.Info.Status = release.StatusUninstalled
   372  	instAction.cfg.Releases.Create(rel)
   373  	instAction.ReleaseName = rel.Name
   374  
   375  	vals := map[string]interface{}{}
   376  	res, err := instAction.Run(buildChart(), vals)
   377  	is.NoError(err)
   378  
   379  	// This should have been auto-incremented
   380  	is.Equal(2, res.Version)
   381  	is.Equal(res.Name, rel.Name)
   382  
   383  	getres, err := instAction.cfg.Releases.Get(rel.Name, res.Version)
   384  	is.NoError(err)
   385  	is.Equal(getres.Info.Status, release.StatusDeployed)
   386  }
   387  
   388  func TestInstallRelease_KubeVersion(t *testing.T) {
   389  	is := assert.New(t)
   390  	instAction := installAction(t)
   391  	vals := map[string]interface{}{}
   392  	_, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals)
   393  	is.NoError(err)
   394  
   395  	// This should fail for a few hundred years
   396  	instAction.ReleaseName = "should-fail"
   397  	vals = map[string]interface{}{}
   398  	_, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
   399  	is.Error(err)
   400  	is.Contains(err.Error(), "chart requires kubeVersion")
   401  }
   402  
   403  func TestInstallRelease_Wait(t *testing.T) {
   404  	is := assert.New(t)
   405  	instAction := installAction(t)
   406  	instAction.ReleaseName = "come-fail-away"
   407  	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   408  	failer.WaitError = fmt.Errorf("I timed out")
   409  	instAction.cfg.KubeClient = failer
   410  	instAction.Wait = true
   411  	vals := map[string]interface{}{}
   412  
   413  	goroutines := runtime.NumGoroutine()
   414  
   415  	res, err := instAction.Run(buildChart(), vals)
   416  	is.Error(err)
   417  	is.Contains(res.Info.Description, "I timed out")
   418  	is.Equal(res.Info.Status, release.StatusFailed)
   419  
   420  	is.Equal(goroutines, runtime.NumGoroutine())
   421  }
   422  func TestInstallRelease_Wait_Interrupted(t *testing.T) {
   423  	is := assert.New(t)
   424  	instAction := installAction(t)
   425  	instAction.ReleaseName = "interrupted-release"
   426  	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   427  	failer.WaitDuration = 10 * time.Second
   428  	instAction.cfg.KubeClient = failer
   429  	instAction.Wait = true
   430  	vals := map[string]interface{}{}
   431  
   432  	ctx := context.Background()
   433  	ctx, cancel := context.WithCancel(ctx)
   434  	time.AfterFunc(time.Second, cancel)
   435  
   436  	goroutines := runtime.NumGoroutine()
   437  
   438  	res, err := instAction.RunWithContext(ctx, buildChart(), vals)
   439  	is.Error(err)
   440  	is.Contains(res.Info.Description, "Release \"interrupted-release\" failed: context canceled")
   441  	is.Equal(res.Info.Status, release.StatusFailed)
   442  
   443  	is.Equal(goroutines+1, runtime.NumGoroutine()) // installation goroutine still is in background
   444  	time.Sleep(10 * time.Second)                   // wait for goroutine to finish
   445  	is.Equal(goroutines, runtime.NumGoroutine())
   446  }
   447  func TestInstallRelease_WaitForJobs(t *testing.T) {
   448  	is := assert.New(t)
   449  	instAction := installAction(t)
   450  	instAction.ReleaseName = "come-fail-away"
   451  	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   452  	failer.WaitError = fmt.Errorf("I timed out")
   453  	instAction.cfg.KubeClient = failer
   454  	instAction.Wait = true
   455  	instAction.WaitForJobs = true
   456  	vals := map[string]interface{}{}
   457  
   458  	res, err := instAction.Run(buildChart(), vals)
   459  	is.Error(err)
   460  	is.Contains(res.Info.Description, "I timed out")
   461  	is.Equal(res.Info.Status, release.StatusFailed)
   462  }
   463  
   464  func TestInstallRelease_Atomic(t *testing.T) {
   465  	is := assert.New(t)
   466  
   467  	t.Run("atomic uninstall succeeds", func(t *testing.T) {
   468  		instAction := installAction(t)
   469  		instAction.ReleaseName = "come-fail-away"
   470  		failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   471  		failer.WaitError = fmt.Errorf("I timed out")
   472  		instAction.cfg.KubeClient = failer
   473  		instAction.Atomic = true
   474  		// disabling hooks to avoid an early fail when the
   475  		// the WaitForDelete is called on the pre-delete hook execution
   476  		instAction.DisableHooks = true
   477  		vals := map[string]interface{}{}
   478  
   479  		res, err := instAction.Run(buildChart(), vals)
   480  		is.Error(err)
   481  		is.Contains(err.Error(), "I timed out")
   482  		is.Contains(err.Error(), "atomic")
   483  
   484  		// Now make sure it isn't in storage any more
   485  		_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
   486  		is.Error(err)
   487  		is.Equal(err, driver.ErrReleaseNotFound)
   488  	})
   489  
   490  	t.Run("atomic uninstall fails", func(t *testing.T) {
   491  		instAction := installAction(t)
   492  		instAction.ReleaseName = "come-fail-away-with-me"
   493  		failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   494  		failer.WaitError = fmt.Errorf("I timed out")
   495  		failer.DeleteError = fmt.Errorf("uninstall fail")
   496  		instAction.cfg.KubeClient = failer
   497  		instAction.Atomic = true
   498  		vals := map[string]interface{}{}
   499  
   500  		_, err := instAction.Run(buildChart(), vals)
   501  		is.Error(err)
   502  		is.Contains(err.Error(), "I timed out")
   503  		is.Contains(err.Error(), "uninstall fail")
   504  		is.Contains(err.Error(), "an error occurred while uninstalling the release")
   505  	})
   506  }
   507  func TestInstallRelease_Atomic_Interrupted(t *testing.T) {
   508  
   509  	is := assert.New(t)
   510  	instAction := installAction(t)
   511  	instAction.ReleaseName = "interrupted-release"
   512  	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   513  	failer.WaitDuration = 10 * time.Second
   514  	instAction.cfg.KubeClient = failer
   515  	instAction.Atomic = true
   516  	vals := map[string]interface{}{}
   517  
   518  	ctx := context.Background()
   519  	ctx, cancel := context.WithCancel(ctx)
   520  	time.AfterFunc(time.Second, cancel)
   521  
   522  	res, err := instAction.RunWithContext(ctx, buildChart(), vals)
   523  	is.Error(err)
   524  	is.Contains(err.Error(), "context canceled")
   525  	is.Contains(err.Error(), "atomic")
   526  	is.Contains(err.Error(), "uninstalled")
   527  
   528  	// Now make sure it isn't in storage any more
   529  	_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
   530  	is.Error(err)
   531  	is.Equal(err, driver.ErrReleaseNotFound)
   532  
   533  }
   534  func TestNameTemplate(t *testing.T) {
   535  	testCases := []nameTemplateTestCase{
   536  		// Just a straight up nop please
   537  		{
   538  			tpl:              "foobar",
   539  			expected:         "foobar",
   540  			expectedErrorStr: "",
   541  		},
   542  		// Random numbers at the end for fun & profit
   543  		{
   544  			tpl:              "foobar-{{randNumeric 6}}",
   545  			expected:         "foobar-[0-9]{6}$",
   546  			expectedErrorStr: "",
   547  		},
   548  		// Random numbers in the middle for fun & profit
   549  		{
   550  			tpl:              "foobar-{{randNumeric 4}}-baz",
   551  			expected:         "foobar-[0-9]{4}-baz$",
   552  			expectedErrorStr: "",
   553  		},
   554  		// No such function
   555  		{
   556  			tpl:              "foobar-{{randInteger}}",
   557  			expected:         "",
   558  			expectedErrorStr: "function \"randInteger\" not defined",
   559  		},
   560  		// Invalid template
   561  		{
   562  			tpl:              "foobar-{{",
   563  			expected:         "",
   564  			expectedErrorStr: "template: name-template:1: unclosed action",
   565  		},
   566  	}
   567  
   568  	for _, tc := range testCases {
   569  
   570  		n, err := TemplateName(tc.tpl)
   571  		if err != nil {
   572  			if tc.expectedErrorStr == "" {
   573  				t.Errorf("Was not expecting error, but got: %v", err)
   574  				continue
   575  			}
   576  			re, compErr := regexp.Compile(tc.expectedErrorStr)
   577  			if compErr != nil {
   578  				t.Errorf("Expected error string failed to compile: %v", compErr)
   579  				continue
   580  			}
   581  			if !re.MatchString(err.Error()) {
   582  				t.Errorf("Error didn't match for %s expected %s but got %v", tc.tpl, tc.expectedErrorStr, err)
   583  				continue
   584  			}
   585  		}
   586  		if err == nil && tc.expectedErrorStr != "" {
   587  			t.Errorf("Was expecting error %s but didn't get an error back", tc.expectedErrorStr)
   588  		}
   589  
   590  		if tc.expected != "" {
   591  			re, err := regexp.Compile(tc.expected)
   592  			if err != nil {
   593  				t.Errorf("Expected string failed to compile: %v", err)
   594  				continue
   595  			}
   596  			if !re.MatchString(n) {
   597  				t.Errorf("Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n)
   598  			}
   599  		}
   600  	}
   601  }
   602  
   603  func TestInstallReleaseOutputDir(t *testing.T) {
   604  	is := assert.New(t)
   605  	instAction := installAction(t)
   606  	vals := map[string]interface{}{}
   607  
   608  	dir := t.TempDir()
   609  
   610  	instAction.OutputDir = dir
   611  
   612  	_, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
   613  	if err != nil {
   614  		t.Fatalf("Failed install: %s", err)
   615  	}
   616  
   617  	_, err = os.Stat(filepath.Join(dir, "hello/templates/goodbye"))
   618  	is.NoError(err)
   619  
   620  	_, err = os.Stat(filepath.Join(dir, "hello/templates/hello"))
   621  	is.NoError(err)
   622  
   623  	_, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials"))
   624  	is.NoError(err)
   625  
   626  	_, err = os.Stat(filepath.Join(dir, "hello/templates/rbac"))
   627  	is.NoError(err)
   628  
   629  	test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt")
   630  
   631  	_, err = os.Stat(filepath.Join(dir, "hello/templates/empty"))
   632  	is.True(os.IsNotExist(err))
   633  }
   634  
   635  func TestInstallOutputDirWithReleaseName(t *testing.T) {
   636  	is := assert.New(t)
   637  	instAction := installAction(t)
   638  	vals := map[string]interface{}{}
   639  
   640  	dir := t.TempDir()
   641  
   642  	instAction.OutputDir = dir
   643  	instAction.UseReleaseName = true
   644  	instAction.ReleaseName = "madra"
   645  
   646  	newDir := filepath.Join(dir, instAction.ReleaseName)
   647  
   648  	_, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
   649  	if err != nil {
   650  		t.Fatalf("Failed install: %s", err)
   651  	}
   652  
   653  	_, err = os.Stat(filepath.Join(newDir, "hello/templates/goodbye"))
   654  	is.NoError(err)
   655  
   656  	_, err = os.Stat(filepath.Join(newDir, "hello/templates/hello"))
   657  	is.NoError(err)
   658  
   659  	_, err = os.Stat(filepath.Join(newDir, "hello/templates/with-partials"))
   660  	is.NoError(err)
   661  
   662  	_, err = os.Stat(filepath.Join(newDir, "hello/templates/rbac"))
   663  	is.NoError(err)
   664  
   665  	test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt")
   666  
   667  	_, err = os.Stat(filepath.Join(newDir, "hello/templates/empty"))
   668  	is.True(os.IsNotExist(err))
   669  }
   670  
   671  func TestNameAndChart(t *testing.T) {
   672  	is := assert.New(t)
   673  	instAction := installAction(t)
   674  	chartName := "./foo"
   675  
   676  	name, chrt, err := instAction.NameAndChart([]string{chartName})
   677  	if err != nil {
   678  		t.Fatal(err)
   679  	}
   680  	is.Equal(instAction.ReleaseName, name)
   681  	is.Equal(chartName, chrt)
   682  
   683  	instAction.GenerateName = true
   684  	_, _, err = instAction.NameAndChart([]string{"foo", chartName})
   685  	if err == nil {
   686  		t.Fatal("expected an error")
   687  	}
   688  	is.Equal("cannot set --generate-name and also specify a name", err.Error())
   689  
   690  	instAction.GenerateName = false
   691  	instAction.NameTemplate = "{{ . }}"
   692  	_, _, err = instAction.NameAndChart([]string{"foo", chartName})
   693  	if err == nil {
   694  		t.Fatal("expected an error")
   695  	}
   696  	is.Equal("cannot set --name-template and also specify a name", err.Error())
   697  
   698  	instAction.NameTemplate = ""
   699  	instAction.ReleaseName = ""
   700  	_, _, err = instAction.NameAndChart([]string{chartName})
   701  	if err == nil {
   702  		t.Fatal("expected an error")
   703  	}
   704  	is.Equal("must either provide a name or specify --generate-name", err.Error())
   705  
   706  	instAction.NameTemplate = ""
   707  	instAction.ReleaseName = ""
   708  	_, _, err = instAction.NameAndChart([]string{"foo", chartName, "bar"})
   709  	if err == nil {
   710  		t.Fatal("expected an error")
   711  	}
   712  	is.Equal("expected at most two arguments, unexpected arguments: bar", err.Error())
   713  }
   714  
   715  func TestNameAndChartGenerateName(t *testing.T) {
   716  	is := assert.New(t)
   717  	instAction := installAction(t)
   718  
   719  	instAction.ReleaseName = ""
   720  	instAction.GenerateName = true
   721  
   722  	tests := []struct {
   723  		Name         string
   724  		Chart        string
   725  		ExpectedName string
   726  	}{
   727  		{
   728  			"local filepath",
   729  			"./chart",
   730  			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
   731  		},
   732  		{
   733  			"dot filepath",
   734  			".",
   735  			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
   736  		},
   737  		{
   738  			"empty filepath",
   739  			"",
   740  			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
   741  		},
   742  		{
   743  			"packaged chart",
   744  			"chart.tgz",
   745  			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
   746  		},
   747  		{
   748  			"packaged chart with .tar.gz extension",
   749  			"chart.tar.gz",
   750  			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
   751  		},
   752  		{
   753  			"packaged chart with local extension",
   754  			"./chart.tgz",
   755  			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
   756  		},
   757  	}
   758  
   759  	for _, tc := range tests {
   760  		tc := tc
   761  		t.Run(tc.Name, func(t *testing.T) {
   762  			t.Parallel()
   763  
   764  			name, chrt, err := instAction.NameAndChart([]string{tc.Chart})
   765  			if err != nil {
   766  				t.Fatal(err)
   767  			}
   768  
   769  			is.Equal(tc.ExpectedName, name)
   770  			is.Equal(tc.Chart, chrt)
   771  		})
   772  	}
   773  }
   774  
   775  func TestInstallWithLabels(t *testing.T) {
   776  	is := assert.New(t)
   777  	instAction := installAction(t)
   778  	instAction.Labels = map[string]string{
   779  		"key1": "val1",
   780  		"key2": "val2",
   781  	}
   782  	res, err := instAction.Run(buildChart(), nil)
   783  	if err != nil {
   784  		t.Fatalf("Failed install: %s", err)
   785  	}
   786  
   787  	is.Equal(instAction.Labels, res.Labels)
   788  }
   789  
   790  func TestInstallWithSystemLabels(t *testing.T) {
   791  	is := assert.New(t)
   792  	instAction := installAction(t)
   793  	instAction.Labels = map[string]string{
   794  		"owner": "val1",
   795  		"key2":  "val2",
   796  	}
   797  	_, err := instAction.Run(buildChart(), nil)
   798  	if err == nil {
   799  		t.Fatal("expected an error")
   800  	}
   801  
   802  	is.Equal(fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err)
   803  }
   804  

View as plain text