github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/action/upgrade_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  	"context"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/stefanmcshane/helm/pkg/chart"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  
    30  	kubefake "github.com/stefanmcshane/helm/pkg/kube/fake"
    31  	"github.com/stefanmcshane/helm/pkg/release"
    32  	helmtime "github.com/stefanmcshane/helm/pkg/time"
    33  )
    34  
    35  func upgradeAction(t *testing.T) *Upgrade {
    36  	config := actionConfigFixture(t)
    37  	upAction := NewUpgrade(config)
    38  	upAction.Namespace = "spaced"
    39  
    40  	return upAction
    41  }
    42  
    43  func TestUpgradeRelease_Success(t *testing.T) {
    44  	is := assert.New(t)
    45  	req := require.New(t)
    46  
    47  	upAction := upgradeAction(t)
    48  	rel := releaseStub()
    49  	rel.Name = "previous-release"
    50  	rel.Info.Status = release.StatusDeployed
    51  	req.NoError(upAction.cfg.Releases.Create(rel))
    52  
    53  	upAction.Wait = true
    54  	vals := map[string]interface{}{}
    55  
    56  	ctx, done := context.WithCancel(context.Background())
    57  	res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
    58  	done()
    59  	req.NoError(err)
    60  	is.Equal(res.Info.Status, release.StatusDeployed)
    61  
    62  	// Detecting previous bug where context termination after successful release
    63  	// caused release to fail.
    64  	time.Sleep(time.Millisecond * 100)
    65  	lastRelease, err := upAction.cfg.Releases.Last(rel.Name)
    66  	req.NoError(err)
    67  	is.Equal(lastRelease.Info.Status, release.StatusDeployed)
    68  }
    69  
    70  func TestUpgradeRelease_Wait(t *testing.T) {
    71  	is := assert.New(t)
    72  	req := require.New(t)
    73  
    74  	upAction := upgradeAction(t)
    75  	rel := releaseStub()
    76  	rel.Name = "come-fail-away"
    77  	rel.Info.Status = release.StatusDeployed
    78  	upAction.cfg.Releases.Create(rel)
    79  
    80  	failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
    81  	failer.WaitError = fmt.Errorf("I timed out")
    82  	upAction.cfg.KubeClient = failer
    83  	upAction.Wait = true
    84  	vals := map[string]interface{}{}
    85  
    86  	res, err := upAction.Run(rel.Name, buildChart(), vals)
    87  	req.Error(err)
    88  	is.Contains(res.Info.Description, "I timed out")
    89  	is.Equal(res.Info.Status, release.StatusFailed)
    90  }
    91  
    92  func TestUpgradeRelease_WaitForJobs(t *testing.T) {
    93  	is := assert.New(t)
    94  	req := require.New(t)
    95  
    96  	upAction := upgradeAction(t)
    97  	rel := releaseStub()
    98  	rel.Name = "come-fail-away"
    99  	rel.Info.Status = release.StatusDeployed
   100  	upAction.cfg.Releases.Create(rel)
   101  
   102  	failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   103  	failer.WaitError = fmt.Errorf("I timed out")
   104  	upAction.cfg.KubeClient = failer
   105  	upAction.Wait = true
   106  	upAction.WaitForJobs = true
   107  	vals := map[string]interface{}{}
   108  
   109  	res, err := upAction.Run(rel.Name, buildChart(), vals)
   110  	req.Error(err)
   111  	is.Contains(res.Info.Description, "I timed out")
   112  	is.Equal(res.Info.Status, release.StatusFailed)
   113  }
   114  
   115  func TestUpgradeRelease_CleanupOnFail(t *testing.T) {
   116  	is := assert.New(t)
   117  	req := require.New(t)
   118  
   119  	upAction := upgradeAction(t)
   120  	rel := releaseStub()
   121  	rel.Name = "come-fail-away"
   122  	rel.Info.Status = release.StatusDeployed
   123  	upAction.cfg.Releases.Create(rel)
   124  
   125  	failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   126  	failer.WaitError = fmt.Errorf("I timed out")
   127  	failer.DeleteError = fmt.Errorf("I tried to delete nil")
   128  	upAction.cfg.KubeClient = failer
   129  	upAction.Wait = true
   130  	upAction.CleanupOnFail = true
   131  	vals := map[string]interface{}{}
   132  
   133  	res, err := upAction.Run(rel.Name, buildChart(), vals)
   134  	req.Error(err)
   135  	is.NotContains(err.Error(), "unable to cleanup resources")
   136  	is.Contains(res.Info.Description, "I timed out")
   137  	is.Equal(res.Info.Status, release.StatusFailed)
   138  }
   139  
   140  func TestUpgradeRelease_Atomic(t *testing.T) {
   141  	is := assert.New(t)
   142  	req := require.New(t)
   143  
   144  	t.Run("atomic rollback succeeds", func(t *testing.T) {
   145  		upAction := upgradeAction(t)
   146  
   147  		rel := releaseStub()
   148  		rel.Name = "nuketown"
   149  		rel.Info.Status = release.StatusDeployed
   150  		upAction.cfg.Releases.Create(rel)
   151  
   152  		failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   153  		// We can't make Update error because then the rollback won't work
   154  		failer.WatchUntilReadyError = fmt.Errorf("arming key removed")
   155  		upAction.cfg.KubeClient = failer
   156  		upAction.Atomic = true
   157  		vals := map[string]interface{}{}
   158  
   159  		res, err := upAction.Run(rel.Name, buildChart(), vals)
   160  		req.Error(err)
   161  		is.Contains(err.Error(), "arming key removed")
   162  		is.Contains(err.Error(), "atomic")
   163  
   164  		// Now make sure it is actually upgraded
   165  		updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3)
   166  		is.NoError(err)
   167  		// Should have rolled back to the previous
   168  		is.Equal(updatedRes.Info.Status, release.StatusDeployed)
   169  	})
   170  
   171  	t.Run("atomic uninstall fails", func(t *testing.T) {
   172  		upAction := upgradeAction(t)
   173  		rel := releaseStub()
   174  		rel.Name = "fallout"
   175  		rel.Info.Status = release.StatusDeployed
   176  		upAction.cfg.Releases.Create(rel)
   177  
   178  		failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   179  		failer.UpdateError = fmt.Errorf("update fail")
   180  		upAction.cfg.KubeClient = failer
   181  		upAction.Atomic = true
   182  		vals := map[string]interface{}{}
   183  
   184  		_, err := upAction.Run(rel.Name, buildChart(), vals)
   185  		req.Error(err)
   186  		is.Contains(err.Error(), "update fail")
   187  		is.Contains(err.Error(), "an error occurred while rolling back the release")
   188  	})
   189  }
   190  
   191  func TestUpgradeRelease_ReuseValues(t *testing.T) {
   192  	is := assert.New(t)
   193  
   194  	t.Run("reuse values should work with values", func(t *testing.T) {
   195  		upAction := upgradeAction(t)
   196  
   197  		existingValues := map[string]interface{}{
   198  			"name":        "value",
   199  			"maxHeapSize": "128m",
   200  			"replicas":    2,
   201  		}
   202  		newValues := map[string]interface{}{
   203  			"name":        "newValue",
   204  			"maxHeapSize": "512m",
   205  			"cpu":         "12m",
   206  		}
   207  		expectedValues := map[string]interface{}{
   208  			"name":        "newValue",
   209  			"maxHeapSize": "512m",
   210  			"cpu":         "12m",
   211  			"replicas":    2,
   212  		}
   213  
   214  		rel := releaseStub()
   215  		rel.Name = "nuketown"
   216  		rel.Info.Status = release.StatusDeployed
   217  		rel.Config = existingValues
   218  
   219  		err := upAction.cfg.Releases.Create(rel)
   220  		is.NoError(err)
   221  
   222  		upAction.ReuseValues = true
   223  		// setting newValues and upgrading
   224  		res, err := upAction.Run(rel.Name, buildChart(), newValues)
   225  		is.NoError(err)
   226  
   227  		// Now make sure it is actually upgraded
   228  		updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
   229  		is.NoError(err)
   230  
   231  		if updatedRes == nil {
   232  			is.Fail("Updated Release is nil")
   233  			return
   234  		}
   235  		is.Equal(release.StatusDeployed, updatedRes.Info.Status)
   236  		is.Equal(expectedValues, updatedRes.Config)
   237  	})
   238  
   239  	t.Run("reuse values should not install disabled charts", func(t *testing.T) {
   240  		upAction := upgradeAction(t)
   241  		chartDefaultValues := map[string]interface{}{
   242  			"subchart": map[string]interface{}{
   243  				"enabled": true,
   244  			},
   245  		}
   246  		dependency := chart.Dependency{
   247  			Name:       "subchart",
   248  			Version:    "0.1.0",
   249  			Repository: "http://some-repo.com",
   250  			Condition:  "subchart.enabled",
   251  		}
   252  		sampleChart := buildChart(
   253  			withName("sample"),
   254  			withValues(chartDefaultValues),
   255  			withMetadataDependency(dependency),
   256  		)
   257  		now := helmtime.Now()
   258  		existingValues := map[string]interface{}{
   259  			"subchart": map[string]interface{}{
   260  				"enabled": false,
   261  			},
   262  		}
   263  		rel := &release.Release{
   264  			Name: "nuketown",
   265  			Info: &release.Info{
   266  				FirstDeployed: now,
   267  				LastDeployed:  now,
   268  				Status:        release.StatusDeployed,
   269  				Description:   "Named Release Stub",
   270  			},
   271  			Chart:   sampleChart,
   272  			Config:  existingValues,
   273  			Version: 1,
   274  		}
   275  		err := upAction.cfg.Releases.Create(rel)
   276  		is.NoError(err)
   277  
   278  		upAction.ReuseValues = true
   279  		sampleChartWithSubChart := buildChart(
   280  			withName(sampleChart.Name()),
   281  			withValues(sampleChart.Values),
   282  			withDependency(withName("subchart")),
   283  			withMetadataDependency(dependency),
   284  		)
   285  		// reusing values and upgrading
   286  		res, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]interface{}{})
   287  		is.NoError(err)
   288  
   289  		// Now get the upgraded release
   290  		updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
   291  		is.NoError(err)
   292  
   293  		if updatedRes == nil {
   294  			is.Fail("Updated Release is nil")
   295  			return
   296  		}
   297  		is.Equal(release.StatusDeployed, updatedRes.Info.Status)
   298  		is.Equal(0, len(updatedRes.Chart.Dependencies()), "expected 0 dependencies")
   299  
   300  		expectedValues := map[string]interface{}{
   301  			"subchart": map[string]interface{}{
   302  				"enabled": false,
   303  			},
   304  		}
   305  		is.Equal(expectedValues, updatedRes.Config)
   306  	})
   307  }
   308  
   309  func TestUpgradeRelease_Pending(t *testing.T) {
   310  	req := require.New(t)
   311  
   312  	upAction := upgradeAction(t)
   313  	rel := releaseStub()
   314  	rel.Name = "come-fail-away"
   315  	rel.Info.Status = release.StatusDeployed
   316  	upAction.cfg.Releases.Create(rel)
   317  	rel2 := releaseStub()
   318  	rel2.Name = "come-fail-away"
   319  	rel2.Info.Status = release.StatusPendingUpgrade
   320  	rel2.Version = 2
   321  	upAction.cfg.Releases.Create(rel2)
   322  
   323  	vals := map[string]interface{}{}
   324  
   325  	_, err := upAction.Run(rel.Name, buildChart(), vals)
   326  	req.Contains(err.Error(), "progress", err)
   327  }
   328  
   329  func TestUpgradeRelease_Interrupted_Wait(t *testing.T) {
   330  
   331  	is := assert.New(t)
   332  	req := require.New(t)
   333  
   334  	upAction := upgradeAction(t)
   335  	rel := releaseStub()
   336  	rel.Name = "interrupted-release"
   337  	rel.Info.Status = release.StatusDeployed
   338  	upAction.cfg.Releases.Create(rel)
   339  
   340  	failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   341  	failer.WaitDuration = 10 * time.Second
   342  	upAction.cfg.KubeClient = failer
   343  	upAction.Wait = true
   344  	vals := map[string]interface{}{}
   345  
   346  	ctx := context.Background()
   347  	ctx, cancel := context.WithCancel(ctx)
   348  	time.AfterFunc(time.Second, cancel)
   349  
   350  	res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
   351  
   352  	req.Error(err)
   353  	is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled")
   354  	is.Equal(res.Info.Status, release.StatusFailed)
   355  
   356  }
   357  
   358  func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
   359  
   360  	is := assert.New(t)
   361  	req := require.New(t)
   362  
   363  	upAction := upgradeAction(t)
   364  	rel := releaseStub()
   365  	rel.Name = "interrupted-release"
   366  	rel.Info.Status = release.StatusDeployed
   367  	upAction.cfg.Releases.Create(rel)
   368  
   369  	failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
   370  	failer.WaitDuration = 5 * time.Second
   371  	upAction.cfg.KubeClient = failer
   372  	upAction.Atomic = true
   373  	vals := map[string]interface{}{}
   374  
   375  	ctx := context.Background()
   376  	ctx, cancel := context.WithCancel(ctx)
   377  	time.AfterFunc(time.Second, cancel)
   378  
   379  	res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals)
   380  
   381  	req.Error(err)
   382  	is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to atomic being set: context canceled")
   383  
   384  	// Now make sure it is actually upgraded
   385  	updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3)
   386  	is.NoError(err)
   387  	// Should have rolled back to the previous
   388  	is.Equal(updatedRes.Info.Status, release.StatusDeployed)
   389  
   390  }