github.com/azure-devops-engineer/helm@v3.0.0-alpha.2+incompatible/pkg/action/install_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 "fmt" 21 "io/ioutil" 22 "log" 23 "os" 24 "path/filepath" 25 "reflect" 26 "regexp" 27 "testing" 28 29 "github.com/stretchr/testify/assert" 30 31 "helm.sh/helm/internal/test" 32 "helm.sh/helm/pkg/chartutil" 33 kubefake "helm.sh/helm/pkg/kube/fake" 34 "helm.sh/helm/pkg/release" 35 "helm.sh/helm/pkg/storage/driver" 36 ) 37 38 type nameTemplateTestCase struct { 39 tpl string 40 expected string 41 expectedErrorStr string 42 } 43 44 func installAction(t *testing.T) *Install { 45 config := actionConfigFixture(t) 46 instAction := NewInstall(config) 47 instAction.Namespace = "spaced" 48 instAction.ReleaseName = "test-install-release" 49 50 return instAction 51 } 52 53 func TestInstallRelease(t *testing.T) { 54 is := assert.New(t) 55 instAction := installAction(t) 56 instAction.rawValues = map[string]interface{}{} 57 res, err := instAction.Run(buildChart()) 58 if err != nil { 59 t.Fatalf("Failed install: %s", err) 60 } 61 is.Equal(res.Name, "test-install-release", "Expected release name.") 62 is.Equal(res.Namespace, "spaced") 63 64 rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) 65 is.NoError(err) 66 67 is.Len(rel.Hooks, 1) 68 is.Equal(rel.Hooks[0].Manifest, manifestWithHook) 69 is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) 70 is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") 71 72 is.NotEqual(len(res.Manifest), 0) 73 is.NotEqual(len(rel.Manifest), 0) 74 is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") 75 is.Equal(rel.Info.Description, "Install complete") 76 } 77 78 func TestInstallReleaseClientOnly(t *testing.T) { 79 is := assert.New(t) 80 instAction := installAction(t) 81 instAction.ClientOnly = true 82 instAction.Run(buildChart()) // disregard output 83 84 is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities) 85 is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard}) 86 } 87 88 func TestInstallRelease_NoName(t *testing.T) { 89 instAction := installAction(t) 90 instAction.ReleaseName = "" 91 instAction.rawValues = map[string]interface{}{} 92 _, err := instAction.Run(buildChart()) 93 if err == nil { 94 t.Fatal("expected failure when no name is specified") 95 } 96 assert.Contains(t, err.Error(), "name is required") 97 } 98 99 func TestInstallRelease_WithNotes(t *testing.T) { 100 is := assert.New(t) 101 instAction := installAction(t) 102 instAction.ReleaseName = "with-notes" 103 instAction.rawValues = map[string]interface{}{} 104 res, err := instAction.Run(buildChart(withNotes("note here"))) 105 if err != nil { 106 t.Fatalf("Failed install: %s", err) 107 } 108 109 is.Equal(res.Name, "with-notes") 110 is.Equal(res.Namespace, "spaced") 111 112 rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) 113 is.NoError(err) 114 is.Len(rel.Hooks, 1) 115 is.Equal(rel.Hooks[0].Manifest, manifestWithHook) 116 is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) 117 is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") 118 is.NotEqual(len(res.Manifest), 0) 119 is.NotEqual(len(rel.Manifest), 0) 120 is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") 121 is.Equal(rel.Info.Description, "Install complete") 122 123 is.Equal(rel.Info.Notes, "note here") 124 } 125 126 func TestInstallRelease_WithNotesRendered(t *testing.T) { 127 is := assert.New(t) 128 instAction := installAction(t) 129 instAction.ReleaseName = "with-notes" 130 instAction.rawValues = map[string]interface{}{} 131 res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}"))) 132 if err != nil { 133 t.Fatalf("Failed install: %s", err) 134 } 135 136 rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) 137 is.NoError(err) 138 139 expectedNotes := fmt.Sprintf("got-%s", res.Name) 140 is.Equal(expectedNotes, rel.Info.Notes) 141 is.Equal(rel.Info.Description, "Install complete") 142 } 143 144 func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) { 145 // Regression: Make sure that the child's notes don't override the parent's 146 is := assert.New(t) 147 instAction := installAction(t) 148 instAction.ReleaseName = "with-notes" 149 instAction.rawValues = map[string]interface{}{} 150 res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child")))) 151 if err != nil { 152 t.Fatalf("Failed install: %s", err) 153 } 154 155 rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) 156 is.Equal("with-notes", rel.Name) 157 is.NoError(err) 158 is.Equal("parent", rel.Info.Notes) 159 is.Equal(rel.Info.Description, "Install complete") 160 } 161 162 func TestInstallRelease_DryRun(t *testing.T) { 163 is := assert.New(t) 164 instAction := installAction(t) 165 instAction.DryRun = true 166 instAction.rawValues = map[string]interface{}{} 167 res, err := instAction.Run(buildChart(withSampleTemplates())) 168 if err != nil { 169 t.Fatalf("Failed install: %s", err) 170 } 171 172 is.Contains(res.Manifest, "---\n# Source: hello/templates/hello\nhello: world") 173 is.Contains(res.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") 174 is.Contains(res.Manifest, "hello: Earth") 175 is.NotContains(res.Manifest, "hello: {{ template \"_planet\" . }}") 176 is.NotContains(res.Manifest, "empty") 177 178 _, err = instAction.cfg.Releases.Get(res.Name, res.Version) 179 is.Error(err) 180 is.Len(res.Hooks, 1) 181 is.True(res.Hooks[0].LastRun.IsZero(), "expect hook to not be marked as run") 182 is.Equal(res.Info.Description, "Dry run complete") 183 } 184 185 func TestInstallRelease_NoHooks(t *testing.T) { 186 is := assert.New(t) 187 instAction := installAction(t) 188 instAction.DisableHooks = true 189 instAction.ReleaseName = "no-hooks" 190 instAction.cfg.Releases.Create(releaseStub()) 191 192 instAction.rawValues = map[string]interface{}{} 193 res, err := instAction.Run(buildChart()) 194 if err != nil { 195 t.Fatalf("Failed install: %s", err) 196 } 197 198 is.True(res.Hooks[0].LastRun.IsZero(), "hooks should not run with no-hooks") 199 } 200 201 func TestInstallRelease_FailedHooks(t *testing.T) { 202 is := assert.New(t) 203 instAction := installAction(t) 204 instAction.ReleaseName = "failed-hooks" 205 failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) 206 failer.WatchUntilReadyError = fmt.Errorf("Failed watch") 207 instAction.cfg.KubeClient = failer 208 209 instAction.rawValues = map[string]interface{}{} 210 res, err := instAction.Run(buildChart()) 211 is.Error(err) 212 is.Contains(res.Info.Description, "failed post-install") 213 is.Equal(res.Info.Status, release.StatusFailed) 214 } 215 216 func TestInstallRelease_ReplaceRelease(t *testing.T) { 217 is := assert.New(t) 218 instAction := installAction(t) 219 instAction.Replace = true 220 221 rel := releaseStub() 222 rel.Info.Status = release.StatusUninstalled 223 instAction.cfg.Releases.Create(rel) 224 instAction.ReleaseName = rel.Name 225 226 instAction.rawValues = map[string]interface{}{} 227 res, err := instAction.Run(buildChart()) 228 is.NoError(err) 229 230 // This should have been auto-incremented 231 is.Equal(2, res.Version) 232 is.Equal(res.Name, rel.Name) 233 234 getres, err := instAction.cfg.Releases.Get(rel.Name, res.Version) 235 is.NoError(err) 236 is.Equal(getres.Info.Status, release.StatusDeployed) 237 } 238 239 func TestInstallRelease_KubeVersion(t *testing.T) { 240 is := assert.New(t) 241 instAction := installAction(t) 242 instAction.rawValues = map[string]interface{}{} 243 _, err := instAction.Run(buildChart(withKube(">=0.0.0"))) 244 is.NoError(err) 245 246 // This should fail for a few hundred years 247 instAction.ReleaseName = "should-fail" 248 instAction.rawValues = map[string]interface{}{} 249 _, err = instAction.Run(buildChart(withKube(">=99.0.0"))) 250 is.Error(err) 251 is.Contains(err.Error(), "chart requires kubernetesVersion") 252 } 253 254 func TestInstallRelease_Wait(t *testing.T) { 255 is := assert.New(t) 256 instAction := installAction(t) 257 instAction.ReleaseName = "come-fail-away" 258 failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) 259 failer.WaitError = fmt.Errorf("I timed out") 260 instAction.cfg.KubeClient = failer 261 instAction.Wait = true 262 instAction.rawValues = map[string]interface{}{} 263 264 res, err := instAction.Run(buildChart()) 265 is.Error(err) 266 is.Contains(res.Info.Description, "I timed out") 267 is.Equal(res.Info.Status, release.StatusFailed) 268 } 269 270 func TestInstallRelease_Atomic(t *testing.T) { 271 is := assert.New(t) 272 273 t.Run("atomic uninstall succeeds", func(t *testing.T) { 274 instAction := installAction(t) 275 instAction.ReleaseName = "come-fail-away" 276 failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) 277 failer.WaitError = fmt.Errorf("I timed out") 278 instAction.cfg.KubeClient = failer 279 instAction.Atomic = true 280 instAction.rawValues = map[string]interface{}{} 281 282 res, err := instAction.Run(buildChart()) 283 is.Error(err) 284 is.Contains(err.Error(), "I timed out") 285 is.Contains(err.Error(), "atomic") 286 287 // Now make sure it isn't in storage any more 288 _, err = instAction.cfg.Releases.Get(res.Name, res.Version) 289 is.Error(err) 290 is.Equal(err, driver.ErrReleaseNotFound) 291 }) 292 293 t.Run("atomic uninstall fails", func(t *testing.T) { 294 instAction := installAction(t) 295 instAction.ReleaseName = "come-fail-away-with-me" 296 failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) 297 failer.WaitError = fmt.Errorf("I timed out") 298 failer.DeleteError = fmt.Errorf("uninstall fail") 299 instAction.cfg.KubeClient = failer 300 instAction.Atomic = true 301 instAction.rawValues = map[string]interface{}{} 302 303 _, err := instAction.Run(buildChart()) 304 is.Error(err) 305 is.Contains(err.Error(), "I timed out") 306 is.Contains(err.Error(), "uninstall fail") 307 is.Contains(err.Error(), "an error occurred while uninstalling the release") 308 }) 309 } 310 311 func TestNameTemplate(t *testing.T) { 312 testCases := []nameTemplateTestCase{ 313 // Just a straight up nop please 314 { 315 tpl: "foobar", 316 expected: "foobar", 317 expectedErrorStr: "", 318 }, 319 // Random numbers at the end for fun & profit 320 { 321 tpl: "foobar-{{randNumeric 6}}", 322 expected: "foobar-[0-9]{6}$", 323 expectedErrorStr: "", 324 }, 325 // Random numbers in the middle for fun & profit 326 { 327 tpl: "foobar-{{randNumeric 4}}-baz", 328 expected: "foobar-[0-9]{4}-baz$", 329 expectedErrorStr: "", 330 }, 331 // No such function 332 { 333 tpl: "foobar-{{randInt}}", 334 expected: "", 335 expectedErrorStr: "function \"randInt\" not defined", 336 }, 337 // Invalid template 338 { 339 tpl: "foobar-{{", 340 expected: "", 341 expectedErrorStr: "unexpected unclosed action", 342 }, 343 } 344 345 for _, tc := range testCases { 346 347 n, err := TemplateName(tc.tpl) 348 if err != nil { 349 if tc.expectedErrorStr == "" { 350 t.Errorf("Was not expecting error, but got: %v", err) 351 continue 352 } 353 re, compErr := regexp.Compile(tc.expectedErrorStr) 354 if compErr != nil { 355 t.Errorf("Expected error string failed to compile: %v", compErr) 356 continue 357 } 358 if !re.MatchString(err.Error()) { 359 t.Errorf("Error didn't match for %s expected %s but got %v", tc.tpl, tc.expectedErrorStr, err) 360 continue 361 } 362 } 363 if err == nil && tc.expectedErrorStr != "" { 364 t.Errorf("Was expecting error %s but didn't get an error back", tc.expectedErrorStr) 365 } 366 367 if tc.expected != "" { 368 re, err := regexp.Compile(tc.expected) 369 if err != nil { 370 t.Errorf("Expected string failed to compile: %v", err) 371 continue 372 } 373 if !re.MatchString(n) { 374 t.Errorf("Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n) 375 } 376 } 377 } 378 } 379 380 func TestMergeValues(t *testing.T) { 381 nestedMap := map[string]interface{}{ 382 "foo": "bar", 383 "baz": map[string]string{ 384 "cool": "stuff", 385 }, 386 } 387 anotherNestedMap := map[string]interface{}{ 388 "foo": "bar", 389 "baz": map[string]string{ 390 "cool": "things", 391 "awesome": "stuff", 392 }, 393 } 394 flatMap := map[string]interface{}{ 395 "foo": "bar", 396 "baz": "stuff", 397 } 398 anotherFlatMap := map[string]interface{}{ 399 "testing": "fun", 400 } 401 402 testMap := mergeValues(flatMap, nestedMap) 403 equal := reflect.DeepEqual(testMap, nestedMap) 404 if !equal { 405 t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) 406 } 407 408 testMap = mergeValues(nestedMap, flatMap) 409 equal = reflect.DeepEqual(testMap, flatMap) 410 if !equal { 411 t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) 412 } 413 414 testMap = mergeValues(nestedMap, anotherNestedMap) 415 equal = reflect.DeepEqual(testMap, anotherNestedMap) 416 if !equal { 417 t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) 418 } 419 420 testMap = mergeValues(anotherFlatMap, anotherNestedMap) 421 expectedMap := map[string]interface{}{ 422 "testing": "fun", 423 "foo": "bar", 424 "baz": map[string]string{ 425 "cool": "things", 426 "awesome": "stuff", 427 }, 428 } 429 equal = reflect.DeepEqual(testMap, expectedMap) 430 if !equal { 431 t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) 432 } 433 } 434 435 func TestInstallReleaseOutputDir(t *testing.T) { 436 is := assert.New(t) 437 instAction := installAction(t) 438 instAction.rawValues = map[string]interface{}{} 439 440 dir, err := ioutil.TempDir("", "output-dir") 441 if err != nil { 442 log.Fatal(err) 443 } 444 defer os.RemoveAll(dir) 445 446 instAction.OutputDir = dir 447 448 _, err = instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate())) 449 if err != nil { 450 t.Fatalf("Failed install: %s", err) 451 } 452 453 _, err = os.Stat(filepath.Join(dir, "hello/templates/goodbye")) 454 is.NoError(err) 455 456 _, err = os.Stat(filepath.Join(dir, "hello/templates/hello")) 457 is.NoError(err) 458 459 _, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials")) 460 is.NoError(err) 461 462 _, err = os.Stat(filepath.Join(dir, "hello/templates/rbac")) 463 is.NoError(err) 464 465 test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt") 466 467 _, err = os.Stat(filepath.Join(dir, "hello/templates/empty")) 468 is.True(os.IsNotExist(err)) 469 }