...

Source file src/helm.sh/helm/v3/pkg/action/upgrade_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  	"reflect"
    23  	"testing"
    24  	"time"
    25  
    26  	"helm.sh/helm/v3/pkg/chart"
    27  	"helm.sh/helm/v3/pkg/storage/driver"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  
    32  	kubefake "helm.sh/helm/v3/pkg/kube/fake"
    33  	"helm.sh/helm/v3/pkg/release"
    34  	helmtime "helm.sh/helm/v3/pkg/time"
    35  )
    36  
    37  func upgradeAction(t *testing.T) *Upgrade {
    38  	config := actionConfigFixture(t)
    39  	upAction := NewUpgrade(config)
    40  	upAction.Namespace = "spaced"
    41  
    42  	return upAction
    43  }
    44  
    45  func TestUpgradeRelease_Success(t *testing.T) {
    46  	is := assert.New(t)
    47  	req := require.New(t)
    48  
    49  	upAction := upgradeAction(t)
    50  	rel := releaseStub()
    51  	rel.Name = "previous-release"
    52  	rel.Info.Status = release.StatusDeployed
    53  	req.NoError(upAction.cfg.Releases.Create(rel))
    54  
    55  	upAction.Wait = true
    56  	vals := map[string]interface{}{}
    57  
    58  	ctx, done := context.WithCancel(context.Background())
    59  	res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
    60  	done()
    61  	req.NoError(err)
    62  	is.Equal(res.Info.Status, release.StatusDeployed)
    63  
    64  	// Detecting previous bug where context termination after successful release
    65  	// caused release to fail.
    66  	time.Sleep(time.Millisecond * 100)
    67  	lastRelease, err := upAction.cfg.Releases.Last(rel.Name)
    68  	req.NoError(err)
    69  	is.Equal(lastRelease.Info.Status, release.StatusDeployed)
    70  }
    71  
    72  func TestUpgradeRelease_Wait(t *testing.T) {
    73  	is := assert.New(t)
    74  	req := require.New(t)
    75  
    76  	upAction := upgradeAction(t)
    77  	rel := releaseStub()
    78  	rel.Name = "come-fail-away"
    79  	rel.Info.Status = release.StatusDeployed
    80  	upAction.cfg.Releases.Create(rel)
    81  
    82  	failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
    83  	failer.WaitError = fmt.Errorf("I timed out")
    84  	upAction.cfg.KubeClient = failer
    85  	upAction.Wait = true
    86  	vals := map[string]interface{}{}
    87  
    88  	res, err := upAction.Run(rel.Name, buildChart(), vals)
    89  	req.Error(err)
    90  	is.Contains(res.Info.Description, "I timed out")
    91  	is.Equal(res.Info.Status, release.StatusFailed)
    92  }
    93  
    94  func TestUpgradeRelease_WaitForJobs(t *testing.T) {
    95  	is := assert.New(t)
    96  	req := require.New(t)
    97  
    98  	upAction := upgradeAction(t)
    99  	rel := releaseStub()
   100  	rel.Name = "come-fail-away"
   101  	rel.Info.Status = release.StatusDeployed
   102  	upAction.cfg.Releases.Create(rel)
   103  
   104  	failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   105  	failer.WaitError = fmt.Errorf("I timed out")
   106  	upAction.cfg.KubeClient = failer
   107  	upAction.Wait = true
   108  	upAction.WaitForJobs = true
   109  	vals := map[string]interface{}{}
   110  
   111  	res, err := upAction.Run(rel.Name, buildChart(), vals)
   112  	req.Error(err)
   113  	is.Contains(res.Info.Description, "I timed out")
   114  	is.Equal(res.Info.Status, release.StatusFailed)
   115  }
   116  
   117  func TestUpgradeRelease_CleanupOnFail(t *testing.T) {
   118  	is := assert.New(t)
   119  	req := require.New(t)
   120  
   121  	upAction := upgradeAction(t)
   122  	rel := releaseStub()
   123  	rel.Name = "come-fail-away"
   124  	rel.Info.Status = release.StatusDeployed
   125  	upAction.cfg.Releases.Create(rel)
   126  
   127  	failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   128  	failer.WaitError = fmt.Errorf("I timed out")
   129  	failer.DeleteError = fmt.Errorf("I tried to delete nil")
   130  	upAction.cfg.KubeClient = failer
   131  	upAction.Wait = true
   132  	upAction.CleanupOnFail = true
   133  	vals := map[string]interface{}{}
   134  
   135  	res, err := upAction.Run(rel.Name, buildChart(), vals)
   136  	req.Error(err)
   137  	is.NotContains(err.Error(), "unable to cleanup resources")
   138  	is.Contains(res.Info.Description, "I timed out")
   139  	is.Equal(res.Info.Status, release.StatusFailed)
   140  }
   141  
   142  func TestUpgradeRelease_Atomic(t *testing.T) {
   143  	is := assert.New(t)
   144  	req := require.New(t)
   145  
   146  	t.Run("atomic rollback succeeds", func(t *testing.T) {
   147  		upAction := upgradeAction(t)
   148  
   149  		rel := releaseStub()
   150  		rel.Name = "nuketown"
   151  		rel.Info.Status = release.StatusDeployed
   152  		upAction.cfg.Releases.Create(rel)
   153  
   154  		failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   155  		// We can't make Update error because then the rollback won't work
   156  		failer.WatchUntilReadyError = fmt.Errorf("arming key removed")
   157  		upAction.cfg.KubeClient = failer
   158  		upAction.Atomic = true
   159  		vals := map[string]interface{}{}
   160  
   161  		res, err := upAction.Run(rel.Name, buildChart(), vals)
   162  		req.Error(err)
   163  		is.Contains(err.Error(), "arming key removed")
   164  		is.Contains(err.Error(), "atomic")
   165  
   166  		// Now make sure it is actually upgraded
   167  		updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3)
   168  		is.NoError(err)
   169  		// Should have rolled back to the previous
   170  		is.Equal(updatedRes.Info.Status, release.StatusDeployed)
   171  	})
   172  
   173  	t.Run("atomic uninstall fails", func(t *testing.T) {
   174  		upAction := upgradeAction(t)
   175  		rel := releaseStub()
   176  		rel.Name = "fallout"
   177  		rel.Info.Status = release.StatusDeployed
   178  		upAction.cfg.Releases.Create(rel)
   179  
   180  		failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   181  		failer.UpdateError = fmt.Errorf("update fail")
   182  		upAction.cfg.KubeClient = failer
   183  		upAction.Atomic = true
   184  		vals := map[string]interface{}{}
   185  
   186  		_, err := upAction.Run(rel.Name, buildChart(), vals)
   187  		req.Error(err)
   188  		is.Contains(err.Error(), "update fail")
   189  		is.Contains(err.Error(), "an error occurred while rolling back the release")
   190  	})
   191  }
   192  
   193  func TestUpgradeRelease_ReuseValues(t *testing.T) {
   194  	is := assert.New(t)
   195  
   196  	t.Run("reuse values should work with values", func(t *testing.T) {
   197  		upAction := upgradeAction(t)
   198  
   199  		existingValues := map[string]interface{}{
   200  			"name":        "value",
   201  			"maxHeapSize": "128m",
   202  			"replicas":    2,
   203  		}
   204  		newValues := map[string]interface{}{
   205  			"name":        "newValue",
   206  			"maxHeapSize": "512m",
   207  			"cpu":         "12m",
   208  		}
   209  		expectedValues := map[string]interface{}{
   210  			"name":        "newValue",
   211  			"maxHeapSize": "512m",
   212  			"cpu":         "12m",
   213  			"replicas":    2,
   214  		}
   215  
   216  		rel := releaseStub()
   217  		rel.Name = "nuketown"
   218  		rel.Info.Status = release.StatusDeployed
   219  		rel.Config = existingValues
   220  
   221  		err := upAction.cfg.Releases.Create(rel)
   222  		is.NoError(err)
   223  
   224  		upAction.ReuseValues = true
   225  		// setting newValues and upgrading
   226  		res, err := upAction.Run(rel.Name, buildChart(), newValues)
   227  		is.NoError(err)
   228  
   229  		// Now make sure it is actually upgraded
   230  		updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
   231  		is.NoError(err)
   232  
   233  		if updatedRes == nil {
   234  			is.Fail("Updated Release is nil")
   235  			return
   236  		}
   237  		is.Equal(release.StatusDeployed, updatedRes.Info.Status)
   238  		is.Equal(expectedValues, updatedRes.Config)
   239  	})
   240  
   241  	t.Run("reuse values should not install disabled charts", func(t *testing.T) {
   242  		upAction := upgradeAction(t)
   243  		chartDefaultValues := map[string]interface{}{
   244  			"subchart": map[string]interface{}{
   245  				"enabled": true,
   246  			},
   247  		}
   248  		dependency := chart.Dependency{
   249  			Name:       "subchart",
   250  			Version:    "0.1.0",
   251  			Repository: "http://some-repo.com",
   252  			Condition:  "subchart.enabled",
   253  		}
   254  		sampleChart := buildChart(
   255  			withName("sample"),
   256  			withValues(chartDefaultValues),
   257  			withMetadataDependency(dependency),
   258  		)
   259  		now := helmtime.Now()
   260  		existingValues := map[string]interface{}{
   261  			"subchart": map[string]interface{}{
   262  				"enabled": false,
   263  			},
   264  		}
   265  		rel := &release.Release{
   266  			Name: "nuketown",
   267  			Info: &release.Info{
   268  				FirstDeployed: now,
   269  				LastDeployed:  now,
   270  				Status:        release.StatusDeployed,
   271  				Description:   "Named Release Stub",
   272  			},
   273  			Chart:   sampleChart,
   274  			Config:  existingValues,
   275  			Version: 1,
   276  		}
   277  		err := upAction.cfg.Releases.Create(rel)
   278  		is.NoError(err)
   279  
   280  		upAction.ReuseValues = true
   281  		sampleChartWithSubChart := buildChart(
   282  			withName(sampleChart.Name()),
   283  			withValues(sampleChart.Values),
   284  			withDependency(withName("subchart")),
   285  			withMetadataDependency(dependency),
   286  		)
   287  		// reusing values and upgrading
   288  		res, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]interface{}{})
   289  		is.NoError(err)
   290  
   291  		// Now get the upgraded release
   292  		updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
   293  		is.NoError(err)
   294  
   295  		if updatedRes == nil {
   296  			is.Fail("Updated Release is nil")
   297  			return
   298  		}
   299  		is.Equal(release.StatusDeployed, updatedRes.Info.Status)
   300  		is.Equal(0, len(updatedRes.Chart.Dependencies()), "expected 0 dependencies")
   301  
   302  		expectedValues := map[string]interface{}{
   303  			"subchart": map[string]interface{}{
   304  				"enabled": false,
   305  			},
   306  		}
   307  		is.Equal(expectedValues, updatedRes.Config)
   308  	})
   309  }
   310  
   311  func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) {
   312  	is := assert.New(t)
   313  
   314  	t.Run("reset then reuse values should work with values", func(t *testing.T) {
   315  		upAction := upgradeAction(t)
   316  
   317  		existingValues := map[string]interface{}{
   318  			"name":        "value",
   319  			"maxHeapSize": "128m",
   320  			"replicas":    2,
   321  		}
   322  		newValues := map[string]interface{}{
   323  			"name":        "newValue",
   324  			"maxHeapSize": "512m",
   325  			"cpu":         "12m",
   326  		}
   327  		newChartValues := map[string]interface{}{
   328  			"memory": "256m",
   329  		}
   330  		expectedValues := map[string]interface{}{
   331  			"name":        "newValue",
   332  			"maxHeapSize": "512m",
   333  			"cpu":         "12m",
   334  			"replicas":    2,
   335  		}
   336  
   337  		rel := releaseStub()
   338  		rel.Name = "nuketown"
   339  		rel.Info.Status = release.StatusDeployed
   340  		rel.Config = existingValues
   341  
   342  		err := upAction.cfg.Releases.Create(rel)
   343  		is.NoError(err)
   344  
   345  		upAction.ResetThenReuseValues = true
   346  		// setting newValues and upgrading
   347  		res, err := upAction.Run(rel.Name, buildChart(withValues(newChartValues)), newValues)
   348  		is.NoError(err)
   349  
   350  		// Now make sure it is actually upgraded
   351  		updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
   352  		is.NoError(err)
   353  
   354  		if updatedRes == nil {
   355  			is.Fail("Updated Release is nil")
   356  			return
   357  		}
   358  		is.Equal(release.StatusDeployed, updatedRes.Info.Status)
   359  		is.Equal(expectedValues, updatedRes.Config)
   360  		is.Equal(newChartValues, updatedRes.Chart.Values)
   361  	})
   362  }
   363  
   364  func TestUpgradeRelease_Pending(t *testing.T) {
   365  	req := require.New(t)
   366  
   367  	upAction := upgradeAction(t)
   368  	rel := releaseStub()
   369  	rel.Name = "come-fail-away"
   370  	rel.Info.Status = release.StatusDeployed
   371  	upAction.cfg.Releases.Create(rel)
   372  	rel2 := releaseStub()
   373  	rel2.Name = "come-fail-away"
   374  	rel2.Info.Status = release.StatusPendingUpgrade
   375  	rel2.Version = 2
   376  	upAction.cfg.Releases.Create(rel2)
   377  
   378  	vals := map[string]interface{}{}
   379  
   380  	_, err := upAction.Run(rel.Name, buildChart(), vals)
   381  	req.Contains(err.Error(), "progress", err)
   382  }
   383  
   384  func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
   385  
   386  	is := assert.New(t)
   387  	req := require.New(t)
   388  
   389  	upAction := upgradeAction(t)
   390  	rel := releaseStub()
   391  	rel.Name = "interrupted-release"
   392  	rel.Info.Status = release.StatusDeployed
   393  	upAction.cfg.Releases.Create(rel)
   394  
   395  	failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   396  	failer.WaitDuration = 10 * time.Second
   397  	upAction.cfg.KubeClient = failer
   398  	upAction.Wait = true
   399  	vals := map[string]interface{}{}
   400  
   401  	ctx := context.Background()
   402  	ctx, cancel := context.WithCancel(ctx)
   403  	time.AfterFunc(time.Second, cancel)
   404  
   405  	res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
   406  
   407  	req.Error(err)
   408  	is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled")
   409  	is.Equal(res.Info.Status, release.StatusFailed)
   410  
   411  }
   412  
   413  func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
   414  
   415  	is := assert.New(t)
   416  	req := require.New(t)
   417  
   418  	upAction := upgradeAction(t)
   419  	rel := releaseStub()
   420  	rel.Name = "interrupted-release"
   421  	rel.Info.Status = release.StatusDeployed
   422  	upAction.cfg.Releases.Create(rel)
   423  
   424  	failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   425  	failer.WaitDuration = 5 * time.Second
   426  	upAction.cfg.KubeClient = failer
   427  	upAction.Atomic = true
   428  	vals := map[string]interface{}{}
   429  
   430  	ctx := context.Background()
   431  	ctx, cancel := context.WithCancel(ctx)
   432  	time.AfterFunc(time.Second, cancel)
   433  
   434  	res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
   435  
   436  	req.Error(err)
   437  	is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to atomic being set: context canceled")
   438  
   439  	// Now make sure it is actually upgraded
   440  	updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3)
   441  	is.NoError(err)
   442  	// Should have rolled back to the previous
   443  	is.Equal(updatedRes.Info.Status, release.StatusDeployed)
   444  }
   445  
   446  func TestMergeCustomLabels(t *testing.T) {
   447  	var tests = [][3]map[string]string{
   448  		{nil, nil, map[string]string{}},
   449  		{map[string]string{}, map[string]string{}, map[string]string{}},
   450  		{map[string]string{"k1": "v1", "k2": "v2"}, nil, map[string]string{"k1": "v1", "k2": "v2"}},
   451  		{nil, map[string]string{"k1": "v1", "k2": "v2"}, map[string]string{"k1": "v1", "k2": "v2"}},
   452  		{map[string]string{"k1": "v1", "k2": "v2"}, map[string]string{"k1": "null", "k2": "v3"}, map[string]string{"k2": "v3"}},
   453  	}
   454  	for _, test := range tests {
   455  		if output := mergeCustomLabels(test[0], test[1]); !reflect.DeepEqual(test[2], output) {
   456  			t.Errorf("Expected {%v}, got {%v}", test[2], output)
   457  		}
   458  	}
   459  }
   460  
   461  func TestUpgradeRelease_Labels(t *testing.T) {
   462  	is := assert.New(t)
   463  	upAction := upgradeAction(t)
   464  
   465  	rel := releaseStub()
   466  	rel.Name = "labels"
   467  	// It's needed to check that suppressed release would keep original labels
   468  	rel.Labels = map[string]string{
   469  		"key1": "val1",
   470  		"key2": "val2.1",
   471  	}
   472  	rel.Info.Status = release.StatusDeployed
   473  
   474  	err := upAction.cfg.Releases.Create(rel)
   475  	is.NoError(err)
   476  
   477  	upAction.Labels = map[string]string{
   478  		"key1": "null",
   479  		"key2": "val2.2",
   480  		"key3": "val3",
   481  	}
   482  	// setting newValues and upgrading
   483  	res, err := upAction.Run(rel.Name, buildChart(), nil)
   484  	is.NoError(err)
   485  
   486  	// Now make sure it is actually upgraded and labels were merged
   487  	updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
   488  	is.NoError(err)
   489  
   490  	if updatedRes == nil {
   491  		is.Fail("Updated Release is nil")
   492  		return
   493  	}
   494  	is.Equal(release.StatusDeployed, updatedRes.Info.Status)
   495  	is.Equal(mergeCustomLabels(rel.Labels, upAction.Labels), updatedRes.Labels)
   496  
   497  	// Now make sure it is suppressed release still contains original labels
   498  	initialRes, err := upAction.cfg.Releases.Get(res.Name, 1)
   499  	is.NoError(err)
   500  
   501  	if initialRes == nil {
   502  		is.Fail("Updated Release is nil")
   503  		return
   504  	}
   505  	is.Equal(initialRes.Info.Status, release.StatusSuperseded)
   506  	is.Equal(initialRes.Labels, rel.Labels)
   507  }
   508  
   509  func TestUpgradeRelease_SystemLabels(t *testing.T) {
   510  	is := assert.New(t)
   511  	upAction := upgradeAction(t)
   512  
   513  	rel := releaseStub()
   514  	rel.Name = "labels"
   515  	// It's needed to check that suppressed release would keep original labels
   516  	rel.Labels = map[string]string{
   517  		"key1": "val1",
   518  		"key2": "val2.1",
   519  	}
   520  	rel.Info.Status = release.StatusDeployed
   521  
   522  	err := upAction.cfg.Releases.Create(rel)
   523  	is.NoError(err)
   524  
   525  	upAction.Labels = map[string]string{
   526  		"key1":  "null",
   527  		"key2":  "val2.2",
   528  		"owner": "val3",
   529  	}
   530  	// setting newValues and upgrading
   531  	_, err = upAction.Run(rel.Name, buildChart(), nil)
   532  	if err == nil {
   533  		t.Fatal("expected an error")
   534  	}
   535  
   536  	is.Equal(fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err)
   537  }
   538  
   539  func TestUpgradeRelease_DryRun(t *testing.T) {
   540  	is := assert.New(t)
   541  	req := require.New(t)
   542  
   543  	upAction := upgradeAction(t)
   544  	rel := releaseStub()
   545  	rel.Name = "previous-release"
   546  	rel.Info.Status = release.StatusDeployed
   547  	req.NoError(upAction.cfg.Releases.Create(rel))
   548  
   549  	upAction.DryRun = true
   550  	vals := map[string]interface{}{}
   551  
   552  	ctx, done := context.WithCancel(context.Background())
   553  	res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
   554  	done()
   555  	req.NoError(err)
   556  	is.Equal(release.StatusPendingUpgrade, res.Info.Status)
   557  	is.Contains(res.Manifest, "kind: Secret")
   558  
   559  	lastRelease, err := upAction.cfg.Releases.Last(rel.Name)
   560  	req.NoError(err)
   561  	is.Equal(lastRelease.Info.Status, release.StatusDeployed)
   562  	is.Equal(1, lastRelease.Version)
   563  
   564  	// Test the case for hiding the secret to ensure it is not displayed
   565  	upAction.HideSecret = true
   566  	vals = map[string]interface{}{}
   567  
   568  	ctx, done = context.WithCancel(context.Background())
   569  	res, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
   570  	done()
   571  	req.NoError(err)
   572  	is.Equal(release.StatusPendingUpgrade, res.Info.Status)
   573  	is.NotContains(res.Manifest, "kind: Secret")
   574  
   575  	lastRelease, err = upAction.cfg.Releases.Last(rel.Name)
   576  	req.NoError(err)
   577  	is.Equal(lastRelease.Info.Status, release.StatusDeployed)
   578  	is.Equal(1, lastRelease.Version)
   579  
   580  	// Ensure in a dry run mode when using HideSecret
   581  	upAction.DryRun = false
   582  	vals = map[string]interface{}{}
   583  
   584  	ctx, done = context.WithCancel(context.Background())
   585  	_, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals)
   586  	done()
   587  	req.Error(err)
   588  }
   589  

View as plain text