github.com/SAP/jenkins-library@v1.362.0/cmd/helmExecute_test.go (about) 1 //go:build unit 2 // +build unit 3 4 package cmd 5 6 import ( 7 "fmt" 8 "os" 9 "path" 10 "testing" 11 12 "github.com/SAP/jenkins-library/pkg/kubernetes/mocks" 13 "github.com/SAP/jenkins-library/pkg/mock" 14 "github.com/SAP/jenkins-library/pkg/piperenv" 15 "github.com/pkg/errors" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 type helmMockUtilsBundle struct { 21 *mock.ExecMockRunner 22 *mock.FilesMock 23 *mock.HttpClientMock 24 } 25 26 func newHelmMockUtilsBundle() helmMockUtilsBundle { 27 utils := helmMockUtilsBundle{ 28 ExecMockRunner: &mock.ExecMockRunner{}, 29 FilesMock: &mock.FilesMock{}, 30 HttpClientMock: &mock.HttpClientMock{ 31 FileUploads: map[string]string{}, 32 }, 33 } 34 return utils 35 } 36 37 func TestRunHelmUpgrade(t *testing.T) { 38 t.Parallel() 39 40 cpe := helmExecuteCommonPipelineEnvironment{} 41 testTable := []struct { 42 config helmExecuteOptions 43 methodError error 44 expectedErrStr string 45 }{ 46 { 47 config: helmExecuteOptions{ 48 HelmCommand: "upgrade", 49 }, 50 methodError: nil, 51 }, 52 { 53 config: helmExecuteOptions{ 54 HelmCommand: "upgrade", 55 }, 56 methodError: errors.New("some error"), 57 expectedErrStr: "failed to execute upgrade: some error", 58 }, 59 } 60 61 for i, testCase := range testTable { 62 t.Run(fmt.Sprint("case ", i), func(t *testing.T) { 63 helmExecute := &mocks.HelmExecutor{} 64 helmExecute.On("RunHelmUpgrade").Return(testCase.methodError) 65 66 err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe) 67 if err != nil { 68 assert.Equal(t, testCase.expectedErrStr, err.Error()) 69 } 70 }) 71 72 } 73 } 74 75 func TestRunHelmLint(t *testing.T) { 76 t.Parallel() 77 78 cpe := helmExecuteCommonPipelineEnvironment{} 79 testTable := []struct { 80 config helmExecuteOptions 81 expectedConfig []string 82 methodError error 83 expectedErrStr string 84 }{ 85 { 86 config: helmExecuteOptions{ 87 HelmCommand: "lint", 88 }, 89 methodError: nil, 90 }, 91 { 92 config: helmExecuteOptions{ 93 HelmCommand: "lint", 94 }, 95 methodError: errors.New("some error"), 96 expectedErrStr: "failed to execute helm lint: some error", 97 }, 98 } 99 100 for i, testCase := range testTable { 101 t.Run(fmt.Sprint("case ", i), func(t *testing.T) { 102 helmExecute := &mocks.HelmExecutor{} 103 helmExecute.On("RunHelmLint").Return(testCase.methodError) 104 105 err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe) 106 if err != nil { 107 assert.Equal(t, testCase.expectedErrStr, err.Error()) 108 } 109 }) 110 111 } 112 } 113 114 func TestRunHelmInstall(t *testing.T) { 115 t.Parallel() 116 117 cpe := helmExecuteCommonPipelineEnvironment{} 118 testTable := []struct { 119 config helmExecuteOptions 120 expectedConfig []string 121 methodError error 122 expectedErrStr string 123 }{ 124 { 125 config: helmExecuteOptions{ 126 HelmCommand: "install", 127 }, 128 methodError: nil, 129 }, 130 { 131 config: helmExecuteOptions{ 132 HelmCommand: "install", 133 }, 134 methodError: errors.New("some error"), 135 expectedErrStr: "failed to execute helm install: some error", 136 }, 137 } 138 139 for i, testCase := range testTable { 140 t.Run(fmt.Sprint("case ", i), func(t *testing.T) { 141 helmExecute := &mocks.HelmExecutor{} 142 helmExecute.On("RunHelmInstall").Return(testCase.methodError) 143 144 err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe) 145 if err != nil { 146 assert.Equal(t, testCase.expectedErrStr, err.Error()) 147 } 148 }) 149 150 } 151 } 152 153 func TestRunHelmTest(t *testing.T) { 154 t.Parallel() 155 156 cpe := helmExecuteCommonPipelineEnvironment{} 157 testTable := []struct { 158 config helmExecuteOptions 159 methodError error 160 expectedErrStr string 161 }{ 162 { 163 config: helmExecuteOptions{ 164 HelmCommand: "test", 165 }, 166 methodError: nil, 167 }, 168 { 169 config: helmExecuteOptions{ 170 HelmCommand: "test", 171 }, 172 methodError: errors.New("some error"), 173 expectedErrStr: "failed to execute helm test: some error", 174 }, 175 } 176 177 for i, testCase := range testTable { 178 t.Run(fmt.Sprint("case ", i), func(t *testing.T) { 179 helmExecute := &mocks.HelmExecutor{} 180 helmExecute.On("RunHelmTest").Return(testCase.methodError) 181 182 err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe) 183 if err != nil { 184 assert.Equal(t, testCase.expectedErrStr, err.Error()) 185 } 186 }) 187 188 } 189 } 190 191 func TestRunHelmUninstall(t *testing.T) { 192 t.Parallel() 193 194 cpe := helmExecuteCommonPipelineEnvironment{} 195 testTable := []struct { 196 config helmExecuteOptions 197 methodError error 198 expectedErrStr string 199 }{ 200 { 201 config: helmExecuteOptions{ 202 HelmCommand: "uninstall", 203 }, 204 methodError: nil, 205 }, 206 { 207 config: helmExecuteOptions{ 208 HelmCommand: "uninstall", 209 }, 210 methodError: errors.New("some error"), 211 expectedErrStr: "failed to execute helm uninstall: some error", 212 }, 213 } 214 215 for i, testCase := range testTable { 216 t.Run(fmt.Sprint("case ", i), func(t *testing.T) { 217 helmExecute := &mocks.HelmExecutor{} 218 helmExecute.On("RunHelmUninstall").Return(testCase.methodError) 219 220 err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe) 221 if err != nil { 222 assert.Equal(t, testCase.expectedErrStr, err.Error()) 223 } 224 }) 225 226 } 227 } 228 229 func TestRunHelmDependency(t *testing.T) { 230 t.Parallel() 231 232 cpe := helmExecuteCommonPipelineEnvironment{} 233 testTable := []struct { 234 config helmExecuteOptions 235 methodError error 236 expectedErrStr string 237 }{ 238 { 239 config: helmExecuteOptions{ 240 HelmCommand: "dependency", 241 }, 242 methodError: nil, 243 }, 244 { 245 config: helmExecuteOptions{ 246 HelmCommand: "dependency", 247 }, 248 methodError: errors.New("some error"), 249 expectedErrStr: "failed to execute helm dependency: some error", 250 }, 251 } 252 253 for i, testCase := range testTable { 254 t.Run(fmt.Sprint("case ", i), func(t *testing.T) { 255 helmExecute := &mocks.HelmExecutor{} 256 helmExecute.On("RunHelmDependency").Return(testCase.methodError) 257 258 err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe) 259 if err != nil { 260 assert.Equal(t, testCase.expectedErrStr, err.Error()) 261 } 262 }) 263 264 } 265 } 266 267 func TestRunHelmPush(t *testing.T) { 268 t.Parallel() 269 270 cpe := helmExecuteCommonPipelineEnvironment{} 271 testTable := []struct { 272 config helmExecuteOptions 273 methodString string 274 methodError error 275 expectedErrStr string 276 }{ 277 { 278 config: helmExecuteOptions{ 279 HelmCommand: "publish", 280 }, 281 methodString: "https://my.target.repository", 282 methodError: nil, 283 }, 284 { 285 config: helmExecuteOptions{ 286 HelmCommand: "publish", 287 }, 288 methodError: errors.New("some error"), 289 expectedErrStr: "failed to execute helm publish: some error", 290 }, 291 } 292 293 for i, testCase := range testTable { 294 t.Run(fmt.Sprint("case ", i), func(t *testing.T) { 295 helmExecute := &mocks.HelmExecutor{} 296 helmExecute.On("RunHelmPublish").Return(testCase.methodString, testCase.methodError) 297 298 err := runHelmExecute(testCase.config, helmExecute, &fileHandlerMock{}, &cpe) 299 if err != nil { 300 assert.Equal(t, testCase.expectedErrStr, err.Error()) 301 } 302 }) 303 304 } 305 } 306 307 func TestRunHelmDefaultCommand(t *testing.T) { 308 t.Parallel() 309 310 cpe := helmExecuteCommonPipelineEnvironment{} 311 testTable := []struct { 312 config helmExecuteOptions 313 methodLintError error 314 methodPackageError error 315 methodPublishError error 316 expectedErrStr string 317 fileUtils fileHandlerMock 318 assertFunc func(fileHandlerMock) error 319 }{ 320 { 321 config: helmExecuteOptions{ 322 HelmCommand: "", 323 }, 324 methodLintError: nil, 325 methodPackageError: nil, 326 methodPublishError: nil, 327 fileUtils: fileHandlerMock{}, 328 }, 329 { 330 // this test checks if parseAndRenderCPETemplate is called properly 331 // when config.RenderValuesTemplate is true 332 config: helmExecuteOptions{ 333 HelmCommand: "", 334 RenderValuesTemplate: true, 335 }, 336 methodLintError: nil, 337 methodPackageError: nil, 338 methodPublishError: nil, 339 fileUtils: fileHandlerMock{}, 340 // we expect the values file is traversed since parsing and rendering according to cpe template is active 341 assertFunc: func(f fileHandlerMock) error { 342 if len(f.fileExistsCalled) == 1 && f.fileExistsCalled[0] == "/values.yaml" { 343 return nil 344 } 345 return fmt.Errorf("expected FileExists called for ['/values.yaml'] but was: %+v", f.fileExistsCalled) 346 }, 347 }, 348 { 349 // this test checks if parseAndRenderCPETemplate is NOT called 350 // when config.RenderValuesTemplate is false 351 config: helmExecuteOptions{ 352 HelmCommand: "", 353 RenderValuesTemplate: false, 354 }, 355 methodLintError: nil, 356 methodPackageError: nil, 357 methodPublishError: nil, 358 fileUtils: fileHandlerMock{}, 359 // we expect the values file is not traversed since parsing and rendering according to cpe template is not active 360 assertFunc: func(f fileHandlerMock) error { 361 if len(f.fileExistsCalled) > 0 { 362 return fmt.Errorf("expected FileExists not called, but was for: %+v", f.fileExistsCalled) 363 } 364 return nil 365 }, 366 }, 367 { 368 config: helmExecuteOptions{ 369 HelmCommand: "", 370 }, 371 methodLintError: errors.New("some error"), 372 expectedErrStr: "failed to execute helm lint: some error", 373 fileUtils: fileHandlerMock{}, 374 }, 375 { 376 config: helmExecuteOptions{ 377 HelmCommand: "", 378 }, 379 methodPackageError: errors.New("some error"), 380 expectedErrStr: "failed to execute helm dependency: some error", 381 fileUtils: fileHandlerMock{}, 382 }, 383 { 384 config: helmExecuteOptions{ 385 HelmCommand: "", 386 }, 387 methodPublishError: errors.New("some error"), 388 expectedErrStr: "failed to execute helm publish: some error", 389 fileUtils: fileHandlerMock{}, 390 }, 391 } 392 393 for i, testCase := range testTable { 394 t.Run(fmt.Sprint("case ", i), func(t *testing.T) { 395 helmExecute := &mocks.HelmExecutor{} 396 helmExecute.On("RunHelmDependency").Return(testCase.methodPackageError) 397 helmExecute.On("RunHelmLint").Return(testCase.methodLintError) 398 helmExecute.On("RunHelmPublish").Return(testCase.methodPublishError) 399 400 err := runHelmExecute(testCase.config, helmExecute, &testCase.fileUtils, &cpe) 401 if err != nil { 402 assert.Equal(t, testCase.expectedErrStr, err.Error()) 403 } 404 if testCase.assertFunc != nil { 405 assert.NoError(t, testCase.assertFunc(testCase.fileUtils)) 406 } 407 408 }) 409 } 410 411 } 412 413 func TestParseAndRenderCPETemplate(t *testing.T) { 414 commonPipelineEnvironment := "commonPipelineEnvironment" 415 valuesYaml := []byte(` 416 image: "image_1" 417 tag: {{ cpe "artifactVersion" }} 418 `) 419 values1Yaml := []byte(` 420 image: "image_2" 421 tag: {{ cpe "artVersion" }} 422 `) 423 values3Yaml := []byte(` 424 image: "image_3" 425 tag: {{ .CPE.artVersion 426 `) 427 values4Yaml := []byte(` 428 image: "test-image" 429 tag: {{ imageTag "test-image" }} 430 `) 431 432 tmpDir := t.TempDir() 433 require.DirExists(t, tmpDir) 434 err := os.Mkdir(path.Join(tmpDir, commonPipelineEnvironment), 0700) 435 require.NoError(t, err) 436 cpe := piperenv.CPEMap{ 437 "artifactVersion": "1.0.0-123456789", 438 "container/imageNameTags": []string{"test-image:1.0.0-123456789"}, 439 } 440 err = cpe.WriteToDisk(tmpDir) 441 require.NoError(t, err) 442 443 defaultValueFile := "values.yaml" 444 config := helmExecuteOptions{ 445 ChartPath: ".", 446 } 447 448 tt := []struct { 449 name string 450 defaultValueFile string 451 config helmExecuteOptions 452 expectedErr error 453 valueFile []byte 454 }{ 455 { 456 name: "'artifactVersion' file exists in CPE", 457 defaultValueFile: defaultValueFile, 458 config: config, 459 expectedErr: nil, 460 valueFile: valuesYaml, 461 }, 462 { 463 name: "'artVersion' file does not exist in CPE", 464 defaultValueFile: defaultValueFile, 465 config: config, 466 expectedErr: nil, 467 valueFile: values1Yaml, 468 }, 469 { 470 name: "Good template ({{ imageTag 'test-image' }})", 471 defaultValueFile: defaultValueFile, 472 config: config, 473 expectedErr: nil, 474 valueFile: values4Yaml, 475 }, 476 { 477 name: "Wrong template ({{ .CPE.artVersion)", 478 defaultValueFile: defaultValueFile, 479 config: config, 480 expectedErr: fmt.Errorf("failed to parse template: failed to parse cpe template '\nimage: \"image_3\"\ntag: {{ .CPE.artVersion\n': template: cpetemplate:4: unclosed action started at cpetemplate:3"), 481 valueFile: values3Yaml, 482 }, 483 { 484 name: "Multiple value files", 485 defaultValueFile: defaultValueFile, 486 config: helmExecuteOptions{ 487 ChartPath: ".", 488 HelmValues: []string{"./values_1.yaml", "./values_2.yaml"}, 489 }, 490 expectedErr: nil, 491 valueFile: valuesYaml, 492 }, 493 { 494 name: "No value file is provided", 495 defaultValueFile: "", 496 config: helmExecuteOptions{ 497 ChartPath: ".", 498 HelmValues: []string{}, 499 }, 500 expectedErr: fmt.Errorf("no value file to proccess, please provide value file(s)"), 501 valueFile: valuesYaml, 502 }, 503 { 504 name: "Wrong path to value file", 505 defaultValueFile: defaultValueFile, 506 config: helmExecuteOptions{ 507 ChartPath: ".", 508 HelmValues: []string{"wrong/path/to/values_1.yaml"}, 509 }, 510 expectedErr: fmt.Errorf("failed to read file: could not read 'wrong/path/to/values_1.yaml'"), 511 valueFile: valuesYaml, 512 }, 513 } 514 515 for _, test := range tt { 516 t.Run(test.name, func(t *testing.T) { 517 utils := newHelmMockUtilsBundle() 518 utils.AddFile(fmt.Sprintf("%s/%s", config.ChartPath, test.defaultValueFile), test.valueFile) 519 520 if len(test.config.HelmValues) == 2 { 521 for _, value := range test.config.HelmValues { 522 utils.AddFile(value, test.valueFile) 523 } 524 } 525 526 err := parseAndRenderCPETemplate(test.config, tmpDir, utils) 527 if test.expectedErr != nil { 528 assert.EqualError(t, err, test.expectedErr.Error()) 529 } else { 530 assert.NoError(t, err) 531 } 532 }) 533 } 534 } 535 536 type fileHandlerMock struct { 537 fileExistsCalled []string 538 fileReadCalled []string 539 fileWriteCalled []string 540 } 541 542 func (f *fileHandlerMock) FileWrite(name string, content []byte, mode os.FileMode) error { 543 f.fileWriteCalled = append(f.fileWriteCalled, name) 544 return nil 545 } 546 547 func (f *fileHandlerMock) FileRead(name string) ([]byte, error) { 548 f.fileReadCalled = append(f.fileReadCalled, name) 549 return []byte{}, nil 550 } 551 552 func (f *fileHandlerMock) FileExists(name string) (bool, error) { 553 f.fileExistsCalled = append(f.fileExistsCalled, name) 554 return true, nil 555 }