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 }