1
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
65
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
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
167 updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3)
168 is.NoError(err)
169
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
226 res, err := upAction.Run(rel.Name, buildChart(), newValues)
227 is.NoError(err)
228
229
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
288 res, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]interface{}{})
289 is.NoError(err)
290
291
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
347 res, err := upAction.Run(rel.Name, buildChart(withValues(newChartValues)), newValues)
348 is.NoError(err)
349
350
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
440 updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3)
441 is.NoError(err)
442
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
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
483 res, err := upAction.Run(rel.Name, buildChart(), nil)
484 is.NoError(err)
485
486
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
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
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
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
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
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