github.com/openshift/source-to-image@v1.4.1-0.20240516041539-bf52fc02204e/pkg/build/strategies/sti/sti_test.go (about) 1 package sti 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "path/filepath" 8 "reflect" 9 "regexp/syntax" 10 "strings" 11 "testing" 12 13 "github.com/openshift/source-to-image/pkg/api" 14 "github.com/openshift/source-to-image/pkg/api/constants" 15 "github.com/openshift/source-to-image/pkg/build" 16 "github.com/openshift/source-to-image/pkg/docker" 17 s2ierr "github.com/openshift/source-to-image/pkg/errors" 18 "github.com/openshift/source-to-image/pkg/ignore" 19 "github.com/openshift/source-to-image/pkg/scm/downloaders/empty" 20 "github.com/openshift/source-to-image/pkg/scm/downloaders/file" 21 gitdownloader "github.com/openshift/source-to-image/pkg/scm/downloaders/git" 22 "github.com/openshift/source-to-image/pkg/scm/git" 23 "github.com/openshift/source-to-image/pkg/test" 24 testfs "github.com/openshift/source-to-image/pkg/test/fs" 25 "github.com/openshift/source-to-image/pkg/util/fs" 26 ) 27 28 type FakeSTI struct { 29 CleanupCalled bool 30 PrepareCalled bool 31 SetupRequired []string 32 SetupOptional []string 33 SetupError error 34 ExistsCalled bool 35 ExistsError error 36 BuildRequest *api.Config 37 BuildResult *api.Result 38 DownloadError error 39 SaveArtifactsCalled bool 40 SaveArtifactsError error 41 FetchSourceCalled bool 42 FetchSourceError error 43 ExecuteCommand string 44 ExecuteUser string 45 ExecuteError error 46 ExpectedError bool 47 LayeredBuildCalled bool 48 LayeredBuildError error 49 PostExecuteDestination string 50 PostExecuteContainerID string 51 PostExecuteError error 52 } 53 54 func newFakeBaseSTI() *STI { 55 return &STI{ 56 config: &api.Config{}, 57 result: &api.Result{}, 58 docker: &docker.FakeDocker{}, 59 installer: &test.FakeInstaller{}, 60 git: &test.FakeGit{}, 61 fs: &testfs.FakeFileSystem{}, 62 tar: &test.FakeTar{}, 63 } 64 } 65 66 func newFakeSTI(f *FakeSTI) *STI { 67 s := &STI{ 68 config: &api.Config{}, 69 result: &api.Result{}, 70 docker: &docker.FakeDocker{}, 71 runtimeDocker: &docker.FakeDocker{}, 72 installer: &test.FakeInstaller{}, 73 git: &test.FakeGit{}, 74 fs: &testfs.FakeFileSystem{}, 75 tar: &test.FakeTar{}, 76 preparer: f, 77 ignorer: &ignore.DockerIgnorer{}, 78 artifacts: f, 79 scripts: f, 80 garbage: f, 81 layered: &FakeDockerBuild{f}, 82 } 83 s.source = &gitdownloader.Clone{Git: s.git, FileSystem: s.fs} 84 return s 85 } 86 87 func (f *FakeSTI) Cleanup(*api.Config) { 88 f.CleanupCalled = true 89 } 90 91 func (f *FakeSTI) Prepare(*api.Config) error { 92 f.PrepareCalled = true 93 f.SetupRequired = []string{constants.Assemble, constants.Run} 94 f.SetupOptional = []string{constants.SaveArtifacts} 95 return nil 96 } 97 98 func (f *FakeSTI) Exists(*api.Config) bool { 99 f.ExistsCalled = true 100 return true 101 } 102 103 func (f *FakeSTI) Request() *api.Config { 104 return f.BuildRequest 105 } 106 107 func (f *FakeSTI) Result() *api.Result { 108 return f.BuildResult 109 } 110 111 func (f *FakeSTI) Save(*api.Config) error { 112 f.SaveArtifactsCalled = true 113 return f.SaveArtifactsError 114 } 115 116 func (f *FakeSTI) fetchSource() error { 117 return f.FetchSourceError 118 } 119 120 func (f *FakeSTI) Download(*api.Config) (*git.SourceInfo, error) { 121 return nil, f.DownloadError 122 } 123 124 func (f *FakeSTI) Execute(command string, user string, r *api.Config) error { 125 f.ExecuteCommand = command 126 f.ExecuteUser = user 127 return f.ExecuteError 128 } 129 130 func (f *FakeSTI) wasExpectedError(text string) bool { 131 return f.ExpectedError 132 } 133 134 func (f *FakeSTI) PostExecute(id, destination string) error { 135 f.PostExecuteContainerID = id 136 f.PostExecuteDestination = destination 137 return f.PostExecuteError 138 } 139 140 type FakeDockerBuild struct { 141 *FakeSTI 142 } 143 144 func (f *FakeDockerBuild) Build(*api.Config) (*api.Result, error) { 145 f.LayeredBuildCalled = true 146 return &api.Result{}, f.LayeredBuildError 147 } 148 149 func TestDefaultSource(t *testing.T) { 150 config := &api.Config{ 151 Source: git.MustParse("."), 152 DockerConfig: &api.DockerConfig{Endpoint: "unix:///var/run/docker.sock"}, 153 } 154 client, err := docker.NewEngineAPIClient(config.DockerConfig) 155 if err != nil { 156 t.Fatal(err) 157 } 158 sti, err := New(client, config, fs.NewFileSystem(), build.Overrides{}) 159 if err != nil { 160 t.Fatal(err) 161 } 162 if config.Source == nil { 163 t.Errorf("Config.Source not set: %v", config.Source) 164 } 165 if _, ok := sti.source.(*file.File); !ok || sti.source == nil { 166 t.Errorf("Source interface not set: %#v", sti.source) 167 } 168 } 169 170 func TestEmptySource(t *testing.T) { 171 config := &api.Config{ 172 Source: nil, 173 DockerConfig: &api.DockerConfig{Endpoint: "unix:///var/run/docker.sock"}, 174 } 175 client, err := docker.NewEngineAPIClient(config.DockerConfig) 176 if err != nil { 177 t.Fatal(err) 178 } 179 sti, err := New(client, config, fs.NewFileSystem(), build.Overrides{}) 180 if err != nil { 181 t.Fatal(err) 182 } 183 if config.Source != nil { 184 t.Errorf("Config.Source unexpectantly changed: %v", config.Source) 185 } 186 if _, ok := sti.source.(*empty.Noop); !ok || sti.source == nil { 187 t.Errorf("Source interface not set: %#v", sti.source) 188 } 189 } 190 191 func TestScriptInstallerWithLabels(t *testing.T) { 192 config := &api.Config{ 193 BuilderImageLabels: map[string]string{ 194 constants.ScriptsURLLabel: "image:///usr/some/dir", 195 }, 196 DockerConfig: &api.DockerConfig{Endpoint: "unix:///var/run/docker.sock"}, 197 } 198 client, err := docker.NewEngineAPIClient(config.DockerConfig) 199 if err != nil { 200 t.Fatal(err) 201 } 202 sti, err := New(client, config, fs.NewFileSystem(), build.Overrides{}) 203 if err != nil { 204 t.Fatal(err) 205 } 206 if config.BuilderImageLabels == nil { 207 t.Errorf("Config.BuilderImageLabels unexpectantly changed: %v", config.BuilderImageLabels) 208 } 209 if sti.installer == nil { 210 t.Errorf("sti installer not set") 211 } 212 } 213 214 func TestOverrides(t *testing.T) { 215 fd := &FakeSTI{} 216 client, err := docker.NewEngineAPIClient(&api.DockerConfig{Endpoint: "unix:///var/run/docker.sock"}) 217 if err != nil { 218 t.Fatal(err) 219 } 220 sti, err := New(client, 221 &api.Config{ 222 DockerConfig: &api.DockerConfig{Endpoint: "unix:///var/run/docker.sock"}, 223 }, 224 fs.NewFileSystem(), 225 build.Overrides{ 226 Downloader: fd, 227 }, 228 ) 229 if err != nil { 230 t.Fatal(err) 231 } 232 if sti.source != fd { 233 t.Errorf("Override of downloader not set: %#v", sti) 234 } 235 } 236 237 func TestBuild(t *testing.T) { 238 incrementalTest := []bool{false, true} 239 for _, incremental := range incrementalTest { 240 fh := &FakeSTI{ 241 BuildRequest: &api.Config{Incremental: incremental}, 242 BuildResult: &api.Result{}, 243 } 244 245 builder := newFakeSTI(fh) 246 builder.Build(&api.Config{Incremental: incremental}) 247 248 // Verify the right scripts were configed 249 if !reflect.DeepEqual(fh.SetupRequired, []string{constants.Assemble, constants.Run}) { 250 t.Errorf("Unexpected required scripts configured: %#v", fh.SetupRequired) 251 } 252 if !reflect.DeepEqual(fh.SetupOptional, []string{constants.SaveArtifacts}) { 253 t.Errorf("Unexpected optional scripts configured: %#v", fh.SetupOptional) 254 } 255 256 // Verify that Exists was called 257 if !fh.ExistsCalled { 258 t.Errorf("Exists was not called.") 259 } 260 261 // Verify that Save was called for an incremental build 262 if incremental && !fh.SaveArtifactsCalled { 263 t.Errorf("Save artifacts was not called for an incremental build") 264 } 265 266 // Verify that Execute was called with the right script 267 if fh.ExecuteCommand != constants.Assemble { 268 t.Errorf("Unexpected execute command: %s", fh.ExecuteCommand) 269 } 270 } 271 } 272 273 func TestLayeredBuild(t *testing.T) { 274 fh := &FakeSTI{ 275 BuildRequest: &api.Config{ 276 BuilderImage: "testimage", 277 }, 278 BuildResult: &api.Result{ 279 BuildInfo: api.BuildInfo{ 280 Stages: []api.StageInfo{}, 281 }, 282 }, 283 ExecuteError: errMissingRequirements, 284 ExpectedError: true, 285 } 286 builder := newFakeSTI(fh) 287 builder.Build(&api.Config{BuilderImage: "testimage"}) 288 // Verify layered build 289 if !fh.LayeredBuildCalled { 290 t.Errorf("Layered build was not called.") 291 } 292 } 293 294 func TestBuildErrorExecute(t *testing.T) { 295 fh := &FakeSTI{ 296 BuildRequest: &api.Config{ 297 BuilderImage: "testimage", 298 }, 299 BuildResult: &api.Result{}, 300 ExecuteError: errors.New("ExecuteError"), 301 ExpectedError: false, 302 } 303 builder := newFakeSTI(fh) 304 _, err := builder.Build(&api.Config{BuilderImage: "testimage"}) 305 if err == nil || err.Error() != "ExecuteError" { 306 t.Errorf("An error was expected, but got different %v", err) 307 } 308 } 309 310 func TestWasExpectedError(t *testing.T) { 311 type expErr struct { 312 text string 313 expected bool 314 } 315 316 tests := []expErr{ 317 { // 0 - tar error 318 text: `/bin/sh: tar: not found`, 319 expected: true, 320 }, 321 { // 1 - tar error 322 text: `/bin/sh: tar: command not found`, 323 expected: true, 324 }, 325 { // 2 - /bin/sh error 326 text: `exec: "/bin/sh": stat /bin/sh: no such file or directory`, 327 expected: true, 328 }, 329 { // 3 - non container error 330 text: "other error", 331 expected: false, 332 }, 333 } 334 335 for i, ti := range tests { 336 result := isMissingRequirements(ti.text) 337 if result != ti.expected { 338 t.Errorf("(%d) Unexpected result: %v. Expected: %v", i, result, ti.expected) 339 } 340 } 341 } 342 343 func testBuildHandler() *STI { 344 s := &STI{ 345 docker: &docker.FakeDocker{}, 346 incrementalDocker: &docker.FakeDocker{}, 347 installer: &test.FakeInstaller{}, 348 git: &test.FakeGit{}, 349 fs: &testfs.FakeFileSystem{ExistsResult: map[string]bool{filepath.FromSlash("a-repo-source"): true}}, 350 tar: &test.FakeTar{}, 351 config: &api.Config{}, 352 result: &api.Result{}, 353 callbackInvoker: &test.FakeCallbackInvoker{}, 354 } 355 s.source = &gitdownloader.Clone{Git: s.git, FileSystem: s.fs} 356 s.initPostExecutorSteps() 357 return s 358 } 359 360 func TestPostExecute(t *testing.T) { 361 type postExecuteTest struct { 362 tag string 363 incremental bool 364 previousImageID string 365 scriptsFromImage bool 366 } 367 testCases := []postExecuteTest{ 368 // 0: tagged, incremental, without previous image 369 {"test/tag", true, "", true}, 370 // 1: tagged, incremental, with previous image 371 {"test/tag", true, "test-image", true}, 372 // 2: tagged, no incremental, without previous image 373 {"test/tag", false, "", true}, 374 // 3: tagged, no incremental, with previous image 375 {"test/tag", false, "test-image", true}, 376 377 // 4: no tag, incremental, without previous image 378 {"", true, "", false}, 379 // 5: no tag, incremental, with previous image 380 {"", true, "test-image", false}, 381 // 6: no tag, no incremental, without previous image 382 {"", false, "", false}, 383 // 7: no tag, no incremental, with previous image 384 {"", false, "test-image", false}, 385 } 386 387 for i, tc := range testCases { 388 bh := testBuildHandler() 389 containerID := "test-container-id" 390 bh.result.Messages = []string{"one", "two"} 391 bh.config.Tag = tc.tag 392 bh.config.Incremental = tc.incremental 393 dh := bh.docker.(*docker.FakeDocker) 394 if tc.previousImageID != "" { 395 bh.config.RemovePreviousImage = true 396 bh.incremental = tc.incremental 397 bh.docker.(*docker.FakeDocker).GetImageIDResult = tc.previousImageID 398 } 399 if tc.scriptsFromImage { 400 bh.scriptsURL = map[string]string{constants.Run: "image:///usr/libexec/s2i/run"} 401 } 402 err := bh.PostExecute(containerID, "cmd1") 403 if err != nil { 404 t.Errorf("(%d) Unexpected error from postExecute: %v", i, err) 405 } 406 // Ensure CommitContainer was called with the right parameters 407 expectedCmd := []string{"cmd1/scripts/" + constants.Run} 408 if tc.scriptsFromImage { 409 expectedCmd = []string{"/usr/libexec/s2i/" + constants.Run} 410 } 411 if !reflect.DeepEqual(dh.CommitContainerOpts.Command, expectedCmd) { 412 t.Errorf("(%d) Unexpected commit container command: %#v, expected %q", i, dh.CommitContainerOpts.Command, expectedCmd) 413 } 414 if dh.CommitContainerOpts.Repository != tc.tag { 415 t.Errorf("(%d) Unexpected tag committed, expected %q, got %q", i, tc.tag, dh.CommitContainerOpts.Repository) 416 } 417 // Ensure image removal when incremental and previousImageID present 418 if tc.incremental && tc.previousImageID != "" { 419 if dh.RemoveImageName != "test-image" { 420 t.Errorf("(%d) Previous image was not removed: %q", i, dh.RemoveImageName) 421 } 422 } else { 423 if dh.RemoveImageName != "" { 424 t.Errorf("(%d) Unexpected image removed: %s", i, dh.RemoveImageName) 425 } 426 } 427 } 428 } 429 430 func TestExists(t *testing.T) { 431 type incrementalTest struct { 432 // incremental flag was passed 433 incremental bool 434 // previous image existence 435 previousImage bool 436 // script installed 437 scriptInstalled bool 438 // expected result 439 expected bool 440 } 441 442 tests := []incrementalTest{ 443 // 0-1: incremental, no image, no matter what with scripts 444 {true, false, false, false}, 445 {true, false, true, false}, 446 447 // 2: incremental, previous image, no scripts 448 {true, true, false, false}, 449 // 3: incremental, previous image, scripts installed 450 {true, true, true, true}, 451 452 // 4-7: no incremental build - should always return false no matter what other flags are 453 {false, false, false, false}, 454 {false, false, true, false}, 455 {false, true, false, false}, 456 {false, true, true, false}, 457 } 458 459 for i, ti := range tests { 460 bh := testBuildHandler() 461 bh.config.WorkingDir = "/working-dir" 462 bh.config.Incremental = ti.incremental 463 bh.config.BuilderPullPolicy = api.PullAlways 464 bh.installedScripts = map[string]bool{constants.SaveArtifacts: ti.scriptInstalled} 465 bh.incrementalDocker.(*docker.FakeDocker).PullResult = ti.previousImage 466 bh.config.DockerConfig = &api.DockerConfig{Endpoint: "http://localhost:4243"} 467 incremental := bh.Exists(bh.config) 468 if incremental != ti.expected { 469 t.Errorf("(%d) Unexpected incremental result: %v. Expected: %v", 470 i, incremental, ti.expected) 471 } 472 if ti.incremental && ti.previousImage && ti.scriptInstalled { 473 if len(bh.fs.(*testfs.FakeFileSystem).ExistsFile) == 0 { 474 continue 475 } 476 scriptChecked := bh.fs.(*testfs.FakeFileSystem).ExistsFile[0] 477 expectedScript := "/working-dir/upload/scripts/save-artifacts" 478 if scriptChecked != expectedScript { 479 t.Errorf("(%d) Unexpected script checked. Actual: %s. Expected: %s", 480 i, scriptChecked, expectedScript) 481 } 482 } 483 } 484 } 485 486 func TestSaveArtifacts(t *testing.T) { 487 bh := testBuildHandler() 488 bh.config.WorkingDir = "/working-dir" 489 bh.config.Tag = "image/tag" 490 fakeFS := bh.fs.(*testfs.FakeFileSystem) 491 fd := bh.docker.(*docker.FakeDocker) 492 th := bh.tar.(*test.FakeTar) 493 err := bh.Save(bh.config) 494 if err != nil { 495 t.Errorf("Unexpected error when saving artifacts: %v", err) 496 } 497 expectedArtifactDir := "/working-dir/upload/artifacts" 498 if filepath.ToSlash(fakeFS.MkdirDir) != expectedArtifactDir { 499 t.Errorf("Mkdir was not called with the expected directory: %s", 500 fakeFS.MkdirDir) 501 } 502 if fd.RunContainerOpts.Image != bh.config.Tag { 503 t.Errorf("Unexpected image sent to RunContainer: %s", 504 fd.RunContainerOpts.Image) 505 } 506 if filepath.ToSlash(th.ExtractTarDir) != expectedArtifactDir || th.ExtractTarReader == nil { 507 t.Errorf("ExtractTar was not called with the expected parameters.") 508 } 509 } 510 511 func TestSaveArtifactsCustomTag(t *testing.T) { 512 bh := testBuildHandler() 513 bh.config.WorkingDir = "/working-dir" 514 bh.config.IncrementalFromTag = "custom/tag" 515 bh.config.Tag = "image/tag" 516 fakeFS := bh.fs.(*testfs.FakeFileSystem) 517 fd := bh.docker.(*docker.FakeDocker) 518 th := bh.tar.(*test.FakeTar) 519 err := bh.Save(bh.config) 520 if err != nil { 521 t.Errorf("Unexpected error when saving artifacts: %v", err) 522 } 523 expectedArtifactDir := "/working-dir/upload/artifacts" 524 if filepath.ToSlash(fakeFS.MkdirDir) != expectedArtifactDir { 525 t.Errorf("Mkdir was not called with the expected directory: %s", 526 fakeFS.MkdirDir) 527 } 528 if fd.RunContainerOpts.Image != bh.config.IncrementalFromTag { 529 t.Errorf("Unexpected image sent to RunContainer: %s", 530 fd.RunContainerOpts.Image) 531 } 532 if filepath.ToSlash(th.ExtractTarDir) != expectedArtifactDir || th.ExtractTarReader == nil { 533 t.Errorf("ExtractTar was not called with the expected parameters.") 534 } 535 } 536 537 func TestSaveArtifactsRunError(t *testing.T) { 538 tests := []error{ 539 fmt.Errorf("Run error"), 540 s2ierr.NewContainerError("", -1, ""), 541 } 542 expected := []error{ 543 tests[0], 544 s2ierr.NewSaveArtifactsError("", "", tests[1]), 545 } 546 // test with tar extract error or not 547 tarError := []bool{true, false} 548 for i := range tests { 549 for _, te := range tarError { 550 bh := testBuildHandler() 551 fd := bh.docker.(*docker.FakeDocker) 552 th := bh.tar.(*test.FakeTar) 553 fd.RunContainerError = tests[i] 554 if te { 555 th.ExtractTarError = fmt.Errorf("tar error") 556 } 557 err := bh.Save(bh.config) 558 if !te && err != expected[i] { 559 t.Errorf("Unexpected error returned from saveArtifacts: %v", err) 560 } else if te && err != th.ExtractTarError { 561 t.Errorf("Expected tar error. Got %v", err) 562 } 563 } 564 } 565 } 566 567 func TestSaveArtifactsErrorBeforeStart(t *testing.T) { 568 bh := testBuildHandler() 569 fd := bh.docker.(*docker.FakeDocker) 570 expected := fmt.Errorf("run error") 571 fd.RunContainerError = expected 572 fd.RunContainerErrorBeforeStart = true 573 err := bh.Save(bh.config) 574 if err != expected { 575 t.Errorf("Unexpected error returned from saveArtifacts: %v", err) 576 } 577 } 578 579 func TestSaveArtifactsExtractError(t *testing.T) { 580 bh := testBuildHandler() 581 th := bh.tar.(*test.FakeTar) 582 expected := fmt.Errorf("extract error") 583 th.ExtractTarError = expected 584 err := bh.Save(bh.config) 585 if err != expected { 586 t.Errorf("Unexpected error returned from saveArtifacts: %v", err) 587 } 588 } 589 590 func TestFetchSource(t *testing.T) { 591 type fetchTest struct { 592 refSpecified bool 593 checkoutExpected bool 594 } 595 596 tests := []fetchTest{ 597 // 0 598 { 599 refSpecified: false, 600 checkoutExpected: false, 601 }, 602 // 1 603 { 604 refSpecified: true, 605 checkoutExpected: true, 606 }, 607 } 608 609 for testNum, ft := range tests { 610 bh := testBuildHandler() 611 gh := bh.git.(*test.FakeGit) 612 613 bh.config.WorkingDir = "/working-dir" 614 bh.config.Source = git.MustParse("a-repo-source") 615 if ft.refSpecified { 616 bh.config.Source.URL.Fragment = "a-branch" 617 } 618 619 expectedTargetDir := "/working-dir/upload/src" 620 _, e := bh.source.Download(bh.config) 621 if e != nil { 622 t.Errorf("Unexpected error %v [%d]", e, testNum) 623 } 624 if gh.CloneSource.StringNoFragment() != "a-repo-source" { 625 t.Errorf("Clone was not called with the expected source. Got %s, expected %s [%d]", gh.CloneSource, "a-source-repo-source", testNum) 626 } 627 if filepath.ToSlash(gh.CloneTarget) != expectedTargetDir { 628 t.Errorf("Unexpected target directory for clone operation. Got %s, expected %s [%d]", gh.CloneTarget, expectedTargetDir, testNum) 629 } 630 if ft.checkoutExpected { 631 if gh.CheckoutRef != "a-branch" { 632 t.Errorf("Checkout was not called with the expected branch. Got %s, expected %s [%d]", gh.CheckoutRef, "a-branch", testNum) 633 } 634 if filepath.ToSlash(gh.CheckoutRepo) != expectedTargetDir { 635 t.Errorf("Unexpected target repository for checkout operation. Got %s, expected %s [%d]", gh.CheckoutRepo, expectedTargetDir, testNum) 636 } 637 } 638 } 639 } 640 641 func TestPrepareOK(t *testing.T) { 642 rh := newFakeSTI(&FakeSTI{}) 643 rh.SetScripts([]string{constants.Assemble, constants.Run}, []string{constants.SaveArtifacts}) 644 rh.fs.(*testfs.FakeFileSystem).WorkingDirResult = "/working-dir" 645 err := rh.Prepare(rh.config) 646 if err != nil { 647 t.Errorf("An error occurred setting up the config handler: %v", err) 648 } 649 if !rh.fs.(*testfs.FakeFileSystem).WorkingDirCalled { 650 t.Errorf("Working directory was not created.") 651 } 652 var expected []string 653 for _, dir := range workingDirs { 654 expected = append(expected, filepath.FromSlash("/working-dir/"+dir)) 655 } 656 mkdirs := rh.fs.(*testfs.FakeFileSystem).MkdirAllDir 657 if !reflect.DeepEqual(mkdirs, expected) { 658 t.Errorf("Unexpected set of MkdirAll calls: %#v", mkdirs) 659 } 660 scripts := rh.installer.(*test.FakeInstaller).Scripts 661 if !reflect.DeepEqual(scripts[0], []string{constants.Assemble, constants.Run}) { 662 t.Errorf("Unexpected set of required scripts: %#v", scripts[0]) 663 } 664 if !reflect.DeepEqual(scripts[1], []string{constants.SaveArtifacts}) { 665 t.Errorf("Unexpected set of optional scripts: %#v", scripts[1]) 666 } 667 } 668 669 func TestPrepareErrorCreatingWorkingDir(t *testing.T) { 670 rh := newFakeSTI(&FakeSTI{}) 671 rh.fs.(*testfs.FakeFileSystem).WorkingDirError = errors.New("WorkingDirError") 672 err := rh.Prepare(rh.config) 673 if err == nil || err.Error() != "WorkingDirError" { 674 t.Errorf("An error was expected for WorkingDir, but got different: %v", err) 675 } 676 } 677 678 func TestPrepareErrorMkdirAll(t *testing.T) { 679 rh := newFakeSTI(&FakeSTI{}) 680 rh.fs.(*testfs.FakeFileSystem).MkdirAllError = errors.New("MkdirAllError") 681 err := rh.Prepare(rh.config) 682 if err == nil || err.Error() != "MkdirAllError" { 683 t.Errorf("An error was expected for MkdirAll, but got different: %v", err) 684 } 685 } 686 687 func TestPrepareErrorRequiredDownloadAndInstall(t *testing.T) { 688 rh := newFakeSTI(&FakeSTI{}) 689 rh.SetScripts([]string{constants.Assemble, constants.Run}, []string{constants.SaveArtifacts}) 690 rh.installer.(*test.FakeInstaller).Error = fmt.Errorf("%v", constants.Assemble) 691 err := rh.Prepare(rh.config) 692 if err == nil || err.Error() != constants.Assemble { 693 t.Errorf("An error was expected for required DownloadAndInstall, but got different: %v", err) 694 } 695 } 696 697 func TestPrepareErrorOptionalDownloadAndInstall(t *testing.T) { 698 rh := newFakeSTI(&FakeSTI{}) 699 rh.SetScripts([]string{constants.Assemble, constants.Run}, []string{constants.SaveArtifacts}) 700 err := rh.Prepare(rh.config) 701 if err != nil { 702 t.Errorf("Unexpected error when downloading optional scripts: %v", err) 703 } 704 } 705 706 func TestPrepareUseCustomRuntimeArtifacts(t *testing.T) { 707 expectedMapping := filepath.FromSlash("/src") + ":dst" 708 709 builder := newFakeSTI(&FakeSTI{}) 710 711 config := builder.config 712 config.RuntimeImage = "my-app" 713 config.RuntimeArtifacts.Set(expectedMapping) 714 715 if err := builder.Prepare(config); err != nil { 716 t.Fatalf("Prepare() unexpectedly failed with error: %v", err) 717 } 718 719 if actualMapping := config.RuntimeArtifacts.String(); actualMapping != expectedMapping { 720 t.Errorf("Prepare() shouldn't change mapping, but it was modified from %v to %v", expectedMapping, actualMapping) 721 } 722 } 723 724 func TestPrepareFailForEmptyRuntimeArtifacts(t *testing.T) { 725 builder := newFakeSTI(&FakeSTI{}) 726 727 fakeDocker := builder.docker.(*docker.FakeDocker) 728 fakeDocker.AssembleInputFilesResult = "" 729 730 config := builder.config 731 config.RuntimeImage = "my-app" 732 733 if len(config.RuntimeArtifacts) > 0 { 734 t.Fatalf("RuntimeArtifacts must be empty by default") 735 } 736 737 err := builder.Prepare(config) 738 if err == nil { 739 t.Errorf("Prepare() should fail but it didn't") 740 741 } else if expectedError := "no runtime artifacts to copy"; !strings.Contains(err.Error(), expectedError) { 742 t.Errorf("Prepare() should fail with error that contains text %q but failed with error: %q", expectedError, err) 743 } 744 } 745 746 func TestPrepareRuntimeArtifactsValidation(t *testing.T) { 747 testCases := []struct { 748 mapping string 749 expectedError string 750 }{ 751 { 752 mapping: "src:dst", 753 expectedError: "source must be an absolute path", 754 }, 755 { 756 mapping: "/src:/dst", 757 expectedError: "destination must be a relative path", 758 }, 759 { 760 mapping: "/src:../dst", 761 expectedError: "destination cannot start with '..'", 762 }, 763 } 764 765 for _, testCase := range testCases { 766 for _, mappingFromUser := range []bool{true, false} { 767 builder := newFakeSTI(&FakeSTI{}) 768 769 config := builder.config 770 config.RuntimeImage = "my-app" 771 772 if mappingFromUser { 773 config.RuntimeArtifacts.Set(testCase.mapping) 774 } else { 775 fakeDocker := builder.docker.(*docker.FakeDocker) 776 fakeDocker.AssembleInputFilesResult = testCase.mapping 777 } 778 779 err := builder.Prepare(config) 780 if err == nil { 781 t.Errorf("Prepare() should fail but it didn't") 782 783 } else if !strings.Contains(err.Error(), testCase.expectedError) { 784 t.Errorf("Prepare() should fail to validate mapping %q with error that contains text %q but failed with error: %q", testCase.mapping, testCase.expectedError, err) 785 } 786 } 787 } 788 } 789 790 func TestPrepareSetRuntimeArtifacts(t *testing.T) { 791 for _, mapping := range []string{filepath.FromSlash("/src") + ":dst", filepath.FromSlash("/src1") + ":dst1;" + filepath.FromSlash("/src1") + ":dst1"} { 792 expectedMapping := strings.Replace(mapping, ";", ",", -1) 793 794 builder := newFakeSTI(&FakeSTI{}) 795 796 fakeDocker := builder.docker.(*docker.FakeDocker) 797 fakeDocker.AssembleInputFilesResult = mapping 798 799 config := builder.config 800 config.RuntimeImage = "my-app" 801 802 if len(config.RuntimeArtifacts) > 0 { 803 t.Fatalf("RuntimeArtifacts must be empty by default") 804 } 805 806 if err := builder.Prepare(config); err != nil { 807 t.Fatalf("Prepare() unexpectedly failed with error: %v", err) 808 } 809 810 if actualMapping := config.RuntimeArtifacts.String(); actualMapping != expectedMapping { 811 t.Errorf("Prepare() shouldn't change mapping, but it was modified from %v to %v", expectedMapping, actualMapping) 812 } 813 } 814 } 815 816 func TestPrepareDownloadAssembleRuntime(t *testing.T) { 817 installer := &test.FakeInstaller{} 818 819 builder := newFakeSTI(&FakeSTI{}) 820 builder.runtimeInstaller = installer 821 builder.optionalRuntimeScripts = []string{constants.AssembleRuntime} 822 823 config := builder.config 824 config.RuntimeImage = "my-app" 825 config.RuntimeArtifacts.Set("/src:dst") 826 827 if err := builder.Prepare(config); err != nil { 828 t.Fatalf("Prepare() unexpectedly failed with error: %v", err) 829 } 830 831 if len(installer.Scripts) != 1 || installer.Scripts[0][0] != constants.AssembleRuntime { 832 t.Errorf("Prepare() should download %q script but it downloaded %v", constants.AssembleRuntime, installer.Scripts) 833 } 834 } 835 836 func TestExecuteOK(t *testing.T) { 837 rh := newFakeBaseSTI() 838 pe := &FakeSTI{} 839 rh.postExecutor = pe 840 rh.config.WorkingDir = "/working-dir" 841 rh.config.BuilderImage = "test/image" 842 rh.config.BuilderPullPolicy = api.PullAlways 843 rh.config.Environment = api.EnvironmentList{ 844 api.EnvironmentSpec{ 845 Name: "Key1", 846 Value: "Value1", 847 }, 848 api.EnvironmentSpec{ 849 Name: "Key2", 850 Value: "Value2", 851 }, 852 } 853 expectedEnv := []string{"Key1=Value1", "Key2=Value2"} 854 th := rh.tar.(*test.FakeTar) 855 th.CreateTarResult = "/working-dir/test.tar" 856 fd := rh.docker.(*docker.FakeDocker) 857 fd.RunContainerContainerID = "1234" 858 fd.RunContainerCmd = []string{"one", "two"} 859 860 err := rh.Execute("test-command", "foo", rh.config) 861 if err != nil { 862 t.Errorf("Unexpected error returned: %v", err) 863 } 864 th = rh.tar.(*test.FakeTar).Copy() 865 if th.CreateTarBase != "" { 866 t.Errorf("Unexpected tar base directory: %s", th.CreateTarBase) 867 } 868 if filepath.ToSlash(th.CreateTarDir) != "/working-dir/upload" { 869 t.Errorf("Unexpected tar directory: %s", th.CreateTarDir) 870 } 871 fh, ok := rh.fs.(*testfs.FakeFileSystem) 872 if !ok { 873 t.Fatalf("Unable to convert %v to FakeFilesystem", rh.fs) 874 } 875 if fh.OpenFile != "" { 876 t.Fatalf("Unexpected file opened: %s", fh.OpenFile) 877 } 878 if fh.OpenFileResult != nil { 879 t.Errorf("Tar file was opened.") 880 } 881 ro := fd.RunContainerOpts 882 883 if ro.User != "foo" { 884 t.Errorf("Expected user to be foo, got %q", ro.User) 885 } 886 887 if ro.Image != rh.config.BuilderImage { 888 t.Errorf("Unexpected Image passed to RunContainer") 889 } 890 if _, ok := ro.Stdin.(*io.PipeReader); !ok { 891 t.Errorf("Unexpected input stream: %#v", ro.Stdin) 892 } 893 if ro.PullImage { 894 t.Errorf("PullImage is true for RunContainer, should be false") 895 } 896 if ro.Command != "test-command" { 897 t.Errorf("Unexpected command passed to RunContainer: %s", 898 ro.Command) 899 } 900 if pe.PostExecuteContainerID != "1234" { 901 t.Errorf("PostExecutor not called with expected ID: %s", 902 pe.PostExecuteContainerID) 903 } 904 if !reflect.DeepEqual(ro.Env, expectedEnv) { 905 t.Errorf("Unexpected container environment passed to RunContainer: %v, should be %v", ro.Env, expectedEnv) 906 } 907 if !reflect.DeepEqual(pe.PostExecuteDestination, "test-command") { 908 t.Errorf("PostExecutor not called with expected command: %s", pe.PostExecuteDestination) 909 } 910 } 911 912 func TestExecuteRunContainerError(t *testing.T) { 913 rh := newFakeSTI(&FakeSTI{}) 914 fd := rh.docker.(*docker.FakeDocker) 915 runContainerError := fmt.Errorf("an error") 916 fd.RunContainerError = runContainerError 917 err := rh.Execute("test-command", "", rh.config) 918 if err != runContainerError { 919 t.Errorf("Did not get expected error, got %v", err) 920 } 921 } 922 923 func TestExecuteErrorCreateTarFile(t *testing.T) { 924 rh := newFakeSTI(&FakeSTI{}) 925 rh.tar.(*test.FakeTar).CreateTarError = errors.New("CreateTarError") 926 err := rh.Execute("test-command", "", rh.config) 927 if err == nil || err.Error() != "CreateTarError" { 928 t.Errorf("An error was expected for CreateTarFile, but got different: %#v", err) 929 } 930 } 931 932 func TestCleanup(t *testing.T) { 933 rh := newFakeBaseSTI() 934 935 rh.config.WorkingDir = "/working-dir" 936 preserve := []bool{false, true} 937 for _, p := range preserve { 938 rh.config.PreserveWorkingDir = p 939 rh.fs = &testfs.FakeFileSystem{} 940 rh.garbage = build.NewDefaultCleaner(rh.fs, rh.docker) 941 rh.garbage.Cleanup(rh.config) 942 removedDir := rh.fs.(*testfs.FakeFileSystem).RemoveDirName 943 if p && removedDir != "" { 944 t.Errorf("Expected working directory to be preserved, but it was removed.") 945 } else if !p && removedDir == "" { 946 t.Errorf("Expected working directory to be removed, but it was preserved.") 947 } 948 } 949 } 950 951 func TestNewWithInvalidExcludeRegExp(t *testing.T) { 952 _, err := New(nil, &api.Config{ 953 DockerConfig: docker.GetDefaultDockerConfig(), 954 ExcludeRegExp: "(", 955 }, nil, build.Overrides{}) 956 if syntaxErr, ok := err.(*syntax.Error); ok && syntaxErr.Code != syntax.ErrMissingParen { 957 t.Errorf("expected regexp compilation error, got %v", err) 958 } 959 }