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