mirror of https://github.com/helm/helm.git
				
				
				
			
		
			
				
	
	
		
			801 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			801 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Go
		
	
	
	
| /*
 | |
| Copyright The Helm Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package action
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"regexp"
 | |
| 	"runtime"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/stretchr/testify/assert"
 | |
| 	"github.com/stretchr/testify/require"
 | |
| 
 | |
| 	"helm.sh/helm/v3/internal/test"
 | |
| 	"helm.sh/helm/v3/pkg/chart"
 | |
| 	"helm.sh/helm/v3/pkg/chartutil"
 | |
| 	kubefake "helm.sh/helm/v3/pkg/kube/fake"
 | |
| 	"helm.sh/helm/v3/pkg/release"
 | |
| 	"helm.sh/helm/v3/pkg/storage/driver"
 | |
| 	helmtime "helm.sh/helm/v3/pkg/time"
 | |
| )
 | |
| 
 | |
| type nameTemplateTestCase struct {
 | |
| 	tpl              string
 | |
| 	expected         string
 | |
| 	expectedErrorStr string
 | |
| }
 | |
| 
 | |
| func installAction(t *testing.T) *Install {
 | |
| 	config := actionConfigFixture(t)
 | |
| 	instAction := NewInstall(config)
 | |
| 	instAction.Namespace = "spaced"
 | |
| 	instAction.ReleaseName = "test-install-release"
 | |
| 
 | |
| 	return instAction
 | |
| }
 | |
| 
 | |
| func TestInstallRelease(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	req := require.New(t)
 | |
| 
 | |
| 	instAction := installAction(t)
 | |
| 	vals := map[string]interface{}{}
 | |
| 	ctx, done := context.WithCancel(context.Background())
 | |
| 	res, err := instAction.RunWithContext(ctx, buildChart(), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 	is.Equal(res.Name, "test-install-release", "Expected release name.")
 | |
| 	is.Equal(res.Namespace, "spaced")
 | |
| 
 | |
| 	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	is.Len(rel.Hooks, 1)
 | |
| 	is.Equal(rel.Hooks[0].Manifest, manifestWithHook)
 | |
| 	is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall)
 | |
| 	is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete")
 | |
| 
 | |
| 	is.NotEqual(len(res.Manifest), 0)
 | |
| 	is.NotEqual(len(rel.Manifest), 0)
 | |
| 	is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
 | |
| 	is.Equal(rel.Info.Description, "Install complete")
 | |
| 
 | |
| 	// Detecting previous bug where context termination after successful release
 | |
| 	// caused release to fail.
 | |
| 	done()
 | |
| 	time.Sleep(time.Millisecond * 100)
 | |
| 	lastRelease, err := instAction.cfg.Releases.Last(rel.Name)
 | |
| 	req.NoError(err)
 | |
| 	is.Equal(lastRelease.Info.Status, release.StatusDeployed)
 | |
| }
 | |
| 
 | |
| func TestInstallReleaseWithValues(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	userVals := map[string]interface{}{
 | |
| 		"nestedKey": map[string]interface{}{
 | |
| 			"simpleKey": "simpleValue",
 | |
| 		},
 | |
| 	}
 | |
| 	expectedUserValues := map[string]interface{}{
 | |
| 		"nestedKey": map[string]interface{}{
 | |
| 			"simpleKey": "simpleValue",
 | |
| 		},
 | |
| 	}
 | |
| 	res, err := instAction.Run(buildChart(withSampleValues()), userVals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 	is.Equal(res.Name, "test-install-release", "Expected release name.")
 | |
| 	is.Equal(res.Namespace, "spaced")
 | |
| 
 | |
| 	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	is.Len(rel.Hooks, 1)
 | |
| 	is.Equal(rel.Hooks[0].Manifest, manifestWithHook)
 | |
| 	is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall)
 | |
| 	is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete")
 | |
| 
 | |
| 	is.NotEqual(len(res.Manifest), 0)
 | |
| 	is.NotEqual(len(rel.Manifest), 0)
 | |
| 	is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
 | |
| 	is.Equal("Install complete", rel.Info.Description)
 | |
| 	is.Equal(expectedUserValues, rel.Config)
 | |
| }
 | |
| 
 | |
| func TestInstallReleaseClientOnly(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ClientOnly = true
 | |
| 	instAction.Run(buildChart(), nil) // disregard output
 | |
| 
 | |
| 	is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities)
 | |
| 	is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: io.Discard})
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_NoName(t *testing.T) {
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ReleaseName = ""
 | |
| 	vals := map[string]interface{}{}
 | |
| 	_, err := instAction.Run(buildChart(), vals)
 | |
| 	if err == nil {
 | |
| 		t.Fatal("expected failure when no name is specified")
 | |
| 	}
 | |
| 	assert.Contains(t, err.Error(), "no name provided")
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_WithNotes(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ReleaseName = "with-notes"
 | |
| 	vals := map[string]interface{}{}
 | |
| 	res, err := instAction.Run(buildChart(withNotes("note here")), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	is.Equal(res.Name, "with-notes")
 | |
| 	is.Equal(res.Namespace, "spaced")
 | |
| 
 | |
| 	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
 | |
| 	is.NoError(err)
 | |
| 	is.Len(rel.Hooks, 1)
 | |
| 	is.Equal(rel.Hooks[0].Manifest, manifestWithHook)
 | |
| 	is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall)
 | |
| 	is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete")
 | |
| 	is.NotEqual(len(res.Manifest), 0)
 | |
| 	is.NotEqual(len(rel.Manifest), 0)
 | |
| 	is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
 | |
| 	is.Equal(rel.Info.Description, "Install complete")
 | |
| 
 | |
| 	is.Equal(rel.Info.Notes, "note here")
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_WithNotesRendered(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ReleaseName = "with-notes"
 | |
| 	vals := map[string]interface{}{}
 | |
| 	res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	expectedNotes := fmt.Sprintf("got-%s", res.Name)
 | |
| 	is.Equal(expectedNotes, rel.Info.Notes)
 | |
| 	is.Equal(rel.Info.Description, "Install complete")
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) {
 | |
| 	// Regression: Make sure that the child's notes don't override the parent's
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ReleaseName = "with-notes"
 | |
| 	vals := map[string]interface{}{}
 | |
| 	res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
 | |
| 	is.Equal("with-notes", rel.Name)
 | |
| 	is.NoError(err)
 | |
| 	is.Equal("parent", rel.Info.Notes)
 | |
| 	is.Equal(rel.Info.Description, "Install complete")
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) {
 | |
| 	// Regression: Make sure that the child's notes don't override the parent's
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ReleaseName = "with-notes"
 | |
| 	instAction.SubNotes = true
 | |
| 	vals := map[string]interface{}{}
 | |
| 	res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	rel, err := instAction.cfg.Releases.Get(res.Name, res.Version)
 | |
| 	is.Equal("with-notes", rel.Name)
 | |
| 	is.NoError(err)
 | |
| 	// test run can return as either 'parent\nchild' or 'child\nparent'
 | |
| 	if !strings.Contains(rel.Info.Notes, "parent") && !strings.Contains(rel.Info.Notes, "child") {
 | |
| 		t.Fatalf("Expected 'parent\nchild' or 'child\nparent', got '%s'", rel.Info.Notes)
 | |
| 	}
 | |
| 	is.Equal(rel.Info.Description, "Install complete")
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_DryRun(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.DryRun = true
 | |
| 	vals := map[string]interface{}{}
 | |
| 	res, err := instAction.Run(buildChart(withSampleTemplates()), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	is.Contains(res.Manifest, "---\n# Source: hello/templates/hello\nhello: world")
 | |
| 	is.Contains(res.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world")
 | |
| 	is.Contains(res.Manifest, "hello: Earth")
 | |
| 	is.NotContains(res.Manifest, "hello: {{ template \"_planet\" . }}")
 | |
| 	is.NotContains(res.Manifest, "empty")
 | |
| 
 | |
| 	_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
 | |
| 	is.Error(err)
 | |
| 	is.Len(res.Hooks, 1)
 | |
| 	is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "expect hook to not be marked as run")
 | |
| 	is.Equal(res.Info.Description, "Dry run complete")
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_DryRunHiddenSecret(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 
 | |
| 	// First perform a normal dry-run with the secret and confirm its presence.
 | |
| 	instAction.DryRun = true
 | |
| 	vals := map[string]interface{}{}
 | |
| 	res, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 	is.Contains(res.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret")
 | |
| 
 | |
| 	_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
 | |
| 	is.Error(err)
 | |
| 	is.Equal(res.Info.Description, "Dry run complete")
 | |
| 
 | |
| 	// Perform a dry-run where the secret should not be present
 | |
| 	instAction.HideSecret = true
 | |
| 	vals = map[string]interface{}{}
 | |
| 	res2, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	is.NotContains(res2.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret")
 | |
| 
 | |
| 	_, err = instAction.cfg.Releases.Get(res2.Name, res2.Version)
 | |
| 	is.Error(err)
 | |
| 	is.Equal(res2.Info.Description, "Dry run complete")
 | |
| 
 | |
| 	// Ensure there is an error when HideSecret True but not in a dry-run mode
 | |
| 	instAction.DryRun = false
 | |
| 	vals = map[string]interface{}{}
 | |
| 	_, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals)
 | |
| 	if err == nil {
 | |
| 		t.Fatalf("Did not get expected an error when dry-run false and hide secret is true")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Regression test for #7955
 | |
| func TestInstallRelease_DryRun_Lookup(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.DryRun = true
 | |
| 	vals := map[string]interface{}{}
 | |
| 
 | |
| 	mockChart := buildChart(withSampleTemplates())
 | |
| 	mockChart.Templates = append(mockChart.Templates, &chart.File{
 | |
| 		Name: "templates/lookup",
 | |
| 		Data: []byte(`goodbye: {{ lookup "v1" "Namespace" "" "___" }}`),
 | |
| 	})
 | |
| 
 | |
| 	res, err := instAction.Run(mockChart, vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	is.Contains(res.Manifest, "goodbye: map[]")
 | |
| }
 | |
| 
 | |
| func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.DryRun = true
 | |
| 	vals := map[string]interface{}{}
 | |
| 	_, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals)
 | |
| 	expectedErr := "\"hello/templates/incorrect\" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh"
 | |
| 	if err == nil {
 | |
| 		t.Fatalf("Install should fail containing error: %s", expectedErr)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		is.Contains(err.Error(), expectedErr)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_NoHooks(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.DisableHooks = true
 | |
| 	instAction.ReleaseName = "no-hooks"
 | |
| 	instAction.cfg.Releases.Create(releaseStub())
 | |
| 
 | |
| 	vals := map[string]interface{}{}
 | |
| 	res, err := instAction.Run(buildChart(), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "hooks should not run with no-hooks")
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_FailedHooks(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ReleaseName = "failed-hooks"
 | |
| 	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
 | |
| 	failer.WatchUntilReadyError = fmt.Errorf("Failed watch")
 | |
| 	instAction.cfg.KubeClient = failer
 | |
| 
 | |
| 	vals := map[string]interface{}{}
 | |
| 	res, err := instAction.Run(buildChart(), vals)
 | |
| 	is.Error(err)
 | |
| 	is.Contains(res.Info.Description, "failed post-install")
 | |
| 	is.Equal(release.StatusFailed, res.Info.Status)
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_ReplaceRelease(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.Replace = true
 | |
| 
 | |
| 	rel := releaseStub()
 | |
| 	rel.Info.Status = release.StatusUninstalled
 | |
| 	instAction.cfg.Releases.Create(rel)
 | |
| 	instAction.ReleaseName = rel.Name
 | |
| 
 | |
| 	vals := map[string]interface{}{}
 | |
| 	res, err := instAction.Run(buildChart(), vals)
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	// This should have been auto-incremented
 | |
| 	is.Equal(2, res.Version)
 | |
| 	is.Equal(res.Name, rel.Name)
 | |
| 
 | |
| 	getres, err := instAction.cfg.Releases.Get(rel.Name, res.Version)
 | |
| 	is.NoError(err)
 | |
| 	is.Equal(getres.Info.Status, release.StatusDeployed)
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_KubeVersion(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	vals := map[string]interface{}{}
 | |
| 	_, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals)
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	// This should fail for a few hundred years
 | |
| 	instAction.ReleaseName = "should-fail"
 | |
| 	vals = map[string]interface{}{}
 | |
| 	_, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals)
 | |
| 	is.Error(err)
 | |
| 	is.Contains(err.Error(), "chart requires kubeVersion")
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_Wait(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ReleaseName = "come-fail-away"
 | |
| 	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
 | |
| 	failer.WaitError = fmt.Errorf("I timed out")
 | |
| 	instAction.cfg.KubeClient = failer
 | |
| 	instAction.Wait = true
 | |
| 	vals := map[string]interface{}{}
 | |
| 
 | |
| 	goroutines := runtime.NumGoroutine()
 | |
| 
 | |
| 	res, err := instAction.Run(buildChart(), vals)
 | |
| 	is.Error(err)
 | |
| 	is.Contains(res.Info.Description, "I timed out")
 | |
| 	is.Equal(res.Info.Status, release.StatusFailed)
 | |
| 
 | |
| 	is.Equal(goroutines, runtime.NumGoroutine())
 | |
| }
 | |
| func TestInstallRelease_Wait_Interrupted(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ReleaseName = "interrupted-release"
 | |
| 	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
 | |
| 	failer.WaitDuration = 10 * time.Second
 | |
| 	instAction.cfg.KubeClient = failer
 | |
| 	instAction.Wait = true
 | |
| 	vals := map[string]interface{}{}
 | |
| 
 | |
| 	ctx, cancel := context.WithCancel(context.Background())
 | |
| 	time.AfterFunc(time.Second, cancel)
 | |
| 
 | |
| 	goroutines := runtime.NumGoroutine()
 | |
| 
 | |
| 	_, err := instAction.RunWithContext(ctx, buildChart(), vals)
 | |
| 	is.Error(err)
 | |
| 	is.Contains(err.Error(), "context canceled")
 | |
| 
 | |
| 	is.Equal(goroutines+1, runtime.NumGoroutine()) // installation goroutine still is in background
 | |
| 	time.Sleep(10 * time.Second)                   // wait for goroutine to finish
 | |
| 	is.Equal(goroutines, runtime.NumGoroutine())
 | |
| }
 | |
| func TestInstallRelease_WaitForJobs(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ReleaseName = "come-fail-away"
 | |
| 	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
 | |
| 	failer.WaitError = fmt.Errorf("I timed out")
 | |
| 	instAction.cfg.KubeClient = failer
 | |
| 	instAction.Wait = true
 | |
| 	instAction.WaitForJobs = true
 | |
| 	vals := map[string]interface{}{}
 | |
| 
 | |
| 	res, err := instAction.Run(buildChart(), vals)
 | |
| 	is.Error(err)
 | |
| 	is.Contains(res.Info.Description, "I timed out")
 | |
| 	is.Equal(res.Info.Status, release.StatusFailed)
 | |
| }
 | |
| 
 | |
| func TestInstallRelease_Atomic(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 
 | |
| 	t.Run("atomic uninstall succeeds", func(t *testing.T) {
 | |
| 		instAction := installAction(t)
 | |
| 		instAction.ReleaseName = "come-fail-away"
 | |
| 		failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
 | |
| 		failer.WaitError = fmt.Errorf("I timed out")
 | |
| 		instAction.cfg.KubeClient = failer
 | |
| 		instAction.Atomic = true
 | |
| 		// disabling hooks to avoid an early fail when
 | |
| 		// WaitForDelete is called on the pre-delete hook execution
 | |
| 		instAction.DisableHooks = true
 | |
| 		vals := map[string]interface{}{}
 | |
| 
 | |
| 		res, err := instAction.Run(buildChart(), vals)
 | |
| 		is.Error(err)
 | |
| 		is.Contains(err.Error(), "I timed out")
 | |
| 		is.Contains(err.Error(), "atomic")
 | |
| 
 | |
| 		// Now make sure it isn't in storage anymore
 | |
| 		_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
 | |
| 		is.Error(err)
 | |
| 		is.Equal(err, driver.ErrReleaseNotFound)
 | |
| 	})
 | |
| 
 | |
| 	t.Run("atomic uninstall fails", func(t *testing.T) {
 | |
| 		instAction := installAction(t)
 | |
| 		instAction.ReleaseName = "come-fail-away-with-me"
 | |
| 		failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
 | |
| 		failer.WaitError = fmt.Errorf("I timed out")
 | |
| 		failer.DeleteError = fmt.Errorf("uninstall fail")
 | |
| 		instAction.cfg.KubeClient = failer
 | |
| 		instAction.Atomic = true
 | |
| 		vals := map[string]interface{}{}
 | |
| 
 | |
| 		_, err := instAction.Run(buildChart(), vals)
 | |
| 		is.Error(err)
 | |
| 		is.Contains(err.Error(), "I timed out")
 | |
| 		is.Contains(err.Error(), "uninstall fail")
 | |
| 		is.Contains(err.Error(), "an error occurred while uninstalling the release")
 | |
| 	})
 | |
| }
 | |
| func TestInstallRelease_Atomic_Interrupted(t *testing.T) {
 | |
| 
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.ReleaseName = "interrupted-release"
 | |
| 	failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
 | |
| 	failer.WaitDuration = 10 * time.Second
 | |
| 	instAction.cfg.KubeClient = failer
 | |
| 	instAction.Atomic = true
 | |
| 	vals := map[string]interface{}{}
 | |
| 
 | |
| 	ctx, cancel := context.WithCancel(context.Background())
 | |
| 	time.AfterFunc(time.Second, cancel)
 | |
| 
 | |
| 	res, err := instAction.RunWithContext(ctx, buildChart(), vals)
 | |
| 	is.Error(err)
 | |
| 	is.Contains(err.Error(), "context canceled")
 | |
| 	is.Contains(err.Error(), "atomic")
 | |
| 	is.Contains(err.Error(), "uninstalled")
 | |
| 
 | |
| 	// Now make sure it isn't in storage anymore
 | |
| 	_, err = instAction.cfg.Releases.Get(res.Name, res.Version)
 | |
| 	is.Error(err)
 | |
| 	is.Equal(err, driver.ErrReleaseNotFound)
 | |
| 
 | |
| }
 | |
| func TestNameTemplate(t *testing.T) {
 | |
| 	testCases := []nameTemplateTestCase{
 | |
| 		// Just a straight up nop please
 | |
| 		{
 | |
| 			tpl:              "foobar",
 | |
| 			expected:         "foobar",
 | |
| 			expectedErrorStr: "",
 | |
| 		},
 | |
| 		// Random numbers at the end for fun & profit
 | |
| 		{
 | |
| 			tpl:              "foobar-{{randNumeric 6}}",
 | |
| 			expected:         "foobar-[0-9]{6}$",
 | |
| 			expectedErrorStr: "",
 | |
| 		},
 | |
| 		// Random numbers in the middle for fun & profit
 | |
| 		{
 | |
| 			tpl:              "foobar-{{randNumeric 4}}-baz",
 | |
| 			expected:         "foobar-[0-9]{4}-baz$",
 | |
| 			expectedErrorStr: "",
 | |
| 		},
 | |
| 		// No such function
 | |
| 		{
 | |
| 			tpl:              "foobar-{{randInteger}}",
 | |
| 			expected:         "",
 | |
| 			expectedErrorStr: "function \"randInteger\" not defined",
 | |
| 		},
 | |
| 		// Invalid template
 | |
| 		{
 | |
| 			tpl:              "foobar-{{",
 | |
| 			expected:         "",
 | |
| 			expectedErrorStr: "template: name-template:1: unclosed action",
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range testCases {
 | |
| 
 | |
| 		n, err := TemplateName(tc.tpl)
 | |
| 		if err != nil {
 | |
| 			if tc.expectedErrorStr == "" {
 | |
| 				t.Errorf("Was not expecting error, but got: %v", err)
 | |
| 				continue
 | |
| 			}
 | |
| 			re, compErr := regexp.Compile(tc.expectedErrorStr)
 | |
| 			if compErr != nil {
 | |
| 				t.Errorf("Expected error string failed to compile: %v", compErr)
 | |
| 				continue
 | |
| 			}
 | |
| 			if !re.MatchString(err.Error()) {
 | |
| 				t.Errorf("Error didn't match for %s expected %s but got %v", tc.tpl, tc.expectedErrorStr, err)
 | |
| 				continue
 | |
| 			}
 | |
| 		}
 | |
| 		if err == nil && tc.expectedErrorStr != "" {
 | |
| 			t.Errorf("Was expecting error %s but didn't get an error back", tc.expectedErrorStr)
 | |
| 		}
 | |
| 
 | |
| 		if tc.expected != "" {
 | |
| 			re, err := regexp.Compile(tc.expected)
 | |
| 			if err != nil {
 | |
| 				t.Errorf("Expected string failed to compile: %v", err)
 | |
| 				continue
 | |
| 			}
 | |
| 			if !re.MatchString(n) {
 | |
| 				t.Errorf("Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestInstallReleaseOutputDir(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	vals := map[string]interface{}{}
 | |
| 
 | |
| 	dir := t.TempDir()
 | |
| 
 | |
| 	instAction.OutputDir = dir
 | |
| 
 | |
| 	_, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	_, err = os.Stat(filepath.Join(dir, "hello/templates/goodbye"))
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	_, err = os.Stat(filepath.Join(dir, "hello/templates/hello"))
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	_, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials"))
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	_, err = os.Stat(filepath.Join(dir, "hello/templates/rbac"))
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt")
 | |
| 
 | |
| 	_, err = os.Stat(filepath.Join(dir, "hello/templates/empty"))
 | |
| 	is.True(os.IsNotExist(err))
 | |
| }
 | |
| 
 | |
| func TestInstallOutputDirWithReleaseName(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	vals := map[string]interface{}{}
 | |
| 
 | |
| 	dir := t.TempDir()
 | |
| 
 | |
| 	instAction.OutputDir = dir
 | |
| 	instAction.UseReleaseName = true
 | |
| 	instAction.ReleaseName = "madra"
 | |
| 
 | |
| 	newDir := filepath.Join(dir, instAction.ReleaseName)
 | |
| 
 | |
| 	_, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	_, err = os.Stat(filepath.Join(newDir, "hello/templates/goodbye"))
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	_, err = os.Stat(filepath.Join(newDir, "hello/templates/hello"))
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	_, err = os.Stat(filepath.Join(newDir, "hello/templates/with-partials"))
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	_, err = os.Stat(filepath.Join(newDir, "hello/templates/rbac"))
 | |
| 	is.NoError(err)
 | |
| 
 | |
| 	test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt")
 | |
| 
 | |
| 	_, err = os.Stat(filepath.Join(newDir, "hello/templates/empty"))
 | |
| 	is.True(os.IsNotExist(err))
 | |
| }
 | |
| 
 | |
| func TestNameAndChart(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	chartName := "./foo"
 | |
| 
 | |
| 	name, chrt, err := instAction.NameAndChart([]string{chartName})
 | |
| 	if err != nil {
 | |
| 		t.Fatal(err)
 | |
| 	}
 | |
| 	is.Equal(instAction.ReleaseName, name)
 | |
| 	is.Equal(chartName, chrt)
 | |
| 
 | |
| 	instAction.GenerateName = true
 | |
| 	_, _, err = instAction.NameAndChart([]string{"foo", chartName})
 | |
| 	if err == nil {
 | |
| 		t.Fatal("expected an error")
 | |
| 	}
 | |
| 	is.Equal("cannot set --generate-name and also specify a name", err.Error())
 | |
| 
 | |
| 	instAction.GenerateName = false
 | |
| 	instAction.NameTemplate = "{{ . }}"
 | |
| 	_, _, err = instAction.NameAndChart([]string{"foo", chartName})
 | |
| 	if err == nil {
 | |
| 		t.Fatal("expected an error")
 | |
| 	}
 | |
| 	is.Equal("cannot set --name-template and also specify a name", err.Error())
 | |
| 
 | |
| 	instAction.NameTemplate = ""
 | |
| 	instAction.ReleaseName = ""
 | |
| 	_, _, err = instAction.NameAndChart([]string{chartName})
 | |
| 	if err == nil {
 | |
| 		t.Fatal("expected an error")
 | |
| 	}
 | |
| 	is.Equal("must either provide a name or specify --generate-name", err.Error())
 | |
| 
 | |
| 	instAction.NameTemplate = ""
 | |
| 	instAction.ReleaseName = ""
 | |
| 	_, _, err = instAction.NameAndChart([]string{"foo", chartName, "bar"})
 | |
| 	if err == nil {
 | |
| 		t.Fatal("expected an error")
 | |
| 	}
 | |
| 	is.Equal("expected at most two arguments, unexpected arguments: bar", err.Error())
 | |
| }
 | |
| 
 | |
| func TestNameAndChartGenerateName(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 
 | |
| 	instAction.ReleaseName = ""
 | |
| 	instAction.GenerateName = true
 | |
| 
 | |
| 	tests := []struct {
 | |
| 		Name         string
 | |
| 		Chart        string
 | |
| 		ExpectedName string
 | |
| 	}{
 | |
| 		{
 | |
| 			"local filepath",
 | |
| 			"./chart",
 | |
| 			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
 | |
| 		},
 | |
| 		{
 | |
| 			"dot filepath",
 | |
| 			".",
 | |
| 			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
 | |
| 		},
 | |
| 		{
 | |
| 			"empty filepath",
 | |
| 			"",
 | |
| 			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
 | |
| 		},
 | |
| 		{
 | |
| 			"packaged chart",
 | |
| 			"chart.tgz",
 | |
| 			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
 | |
| 		},
 | |
| 		{
 | |
| 			"packaged chart with .tar.gz extension",
 | |
| 			"chart.tar.gz",
 | |
| 			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
 | |
| 		},
 | |
| 		{
 | |
| 			"packaged chart with local extension",
 | |
| 			"./chart.tgz",
 | |
| 			fmt.Sprintf("chart-%d", helmtime.Now().Unix()),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, tc := range tests {
 | |
| 		tc := tc
 | |
| 		t.Run(tc.Name, func(t *testing.T) {
 | |
| 			t.Parallel()
 | |
| 
 | |
| 			name, chrt, err := instAction.NameAndChart([]string{tc.Chart})
 | |
| 			if err != nil {
 | |
| 				t.Fatal(err)
 | |
| 			}
 | |
| 
 | |
| 			is.Equal(tc.ExpectedName, name)
 | |
| 			is.Equal(tc.Chart, chrt)
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestInstallWithLabels(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.Labels = map[string]string{
 | |
| 		"key1": "val1",
 | |
| 		"key2": "val2",
 | |
| 	}
 | |
| 	res, err := instAction.Run(buildChart(), nil)
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("Failed install: %s", err)
 | |
| 	}
 | |
| 
 | |
| 	is.Equal(instAction.Labels, res.Labels)
 | |
| }
 | |
| 
 | |
| func TestInstallWithSystemLabels(t *testing.T) {
 | |
| 	is := assert.New(t)
 | |
| 	instAction := installAction(t)
 | |
| 	instAction.Labels = map[string]string{
 | |
| 		"owner": "val1",
 | |
| 		"key2":  "val2",
 | |
| 	}
 | |
| 	_, err := instAction.Run(buildChart(), nil)
 | |
| 	if err == nil {
 | |
| 		t.Fatal("expected an error")
 | |
| 	}
 | |
| 
 | |
| 	is.Equal(fmt.Errorf("user supplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err)
 | |
| }
 |