github.com/openshift/source-to-image@v1.4.1-0.20240516041539-bf52fc02204e/test/integration/docker/integration_test.go (about) 1 //go:build integration 2 // +build integration 3 4 package docker 5 6 import ( 7 "encoding/json" 8 "errors" 9 "flag" 10 "fmt" 11 "io/ioutil" 12 "net" 13 "net/http" 14 "net/http/httptest" 15 "os" 16 "path/filepath" 17 "runtime" 18 "testing" 19 "time" 20 21 "k8s.io/klog/v2" 22 23 dockertypes "github.com/docker/docker/api/types" 24 dockercontainer "github.com/docker/docker/api/types/container" 25 dockerapi "github.com/docker/docker/client" 26 "golang.org/x/net/context" 27 28 "github.com/openshift/source-to-image/pkg/api" 29 "github.com/openshift/source-to-image/pkg/build" 30 "github.com/openshift/source-to-image/pkg/build/strategies" 31 "github.com/openshift/source-to-image/pkg/docker" 32 dockerpkg "github.com/openshift/source-to-image/pkg/docker" 33 "github.com/openshift/source-to-image/pkg/scm/git" 34 "github.com/openshift/source-to-image/pkg/tar" 35 "github.com/openshift/source-to-image/pkg/util" 36 "github.com/openshift/source-to-image/pkg/util/fs" 37 ) 38 39 const ( 40 DefaultDockerSocket = "unix:///var/run/docker.sock" 41 TestSource = "https://github.com/openshift/ruby-hello-world" 42 43 FakeBuilderImage = "sti_test/sti-fake" 44 FakeUserImage = "sti_test/sti-fake-user" 45 FakeImageScripts = "sti_test/sti-fake-scripts" 46 FakeImageScriptsNoSaveArtifacts = "sti_test/sti-fake-scripts-no-save-artifacts" 47 FakeImageNoTar = "sti_test/sti-fake-no-tar" 48 FakeImageOnBuild = "sti_test/sti-fake-onbuild" 49 FakeNumericUserImage = "sti_test/sti-fake-numericuser" 50 FakeImageOnBuildRootUser = "sti_test/sti-fake-onbuild-rootuser" 51 FakeImageOnBuildNumericUser = "sti_test/sti-fake-onbuild-numericuser" 52 FakeImageAssembleRoot = "sti_test/sti-fake-assemble-root" 53 FakeImageAssembleUser = "sti_test/sti-fake-assemble-user" 54 55 TagCleanBuild = "test/sti-fake-app" 56 TagCleanBuildUser = "test/sti-fake-app-user" 57 TagIncrementalBuild = "test/sti-incremental-app" 58 TagIncrementalBuildUser = "test/sti-incremental-app-user" 59 TagCleanBuildScripts = "test/sti-fake-app-scripts" 60 TagIncrementalBuildScripts = "test/sti-incremental-app-scripts" 61 TagIncrementalBuildScriptsNoSaveArtifacts = "test/sti-incremental-app-scripts-no-save-artifacts" 62 TagCleanLayeredBuildNoTar = "test/sti-fake-no-tar" 63 TagCleanBuildOnBuild = "test/sti-fake-app-onbuild" 64 TagIncrementalBuildOnBuild = "test/sti-incremental-app-onbuild" 65 TagCleanBuildOnBuildNoName = "test/sti-fake-app-onbuild-noname" 66 TagCleanBuildNoName = "test/sti-fake-app-noname" 67 TagCleanLayeredBuildNoTarNoName = "test/sti-fake-no-tar-noname" 68 TagCleanBuildAllowedUIDsNamedUser = "test/sti-fake-alloweduids-nameduser" 69 TagCleanBuildAllowedUIDsNumericUser = "test/sti-fake-alloweduids-numericuser" 70 TagCleanBuildAllowedUIDsOnBuildRoot = "test/sti-fake-alloweduids-onbuildroot" 71 TagCleanBuildAllowedUIDsOnBuildNumericUser = "test/sti-fake-alloweduids-onbuildnumeric" 72 TagCleanBuildAllowedUIDsAssembleRoot = "test/sti-fake-alloweduids-assembleroot" 73 TagCleanBuildAllowedUIDsAssembleUser = "test/sti-fake-alloweduids-assembleuser" 74 75 // Need to serve the scripts from local host so any potential changes to the 76 // scripts are made available for integration testing. 77 // 78 // Port 23456 must match the port used in the fake image Dockerfiles 79 FakeScriptsHTTPURL = "http://127.0.0.1:23456/.s2i/bin" 80 ) 81 82 var engineClient docker.Client 83 84 func init() { 85 klog.InitFlags(nil) 86 87 var err error 88 engineClient, err = docker.NewEngineAPIClient(docker.GetDefaultDockerConfig()) 89 if err != nil { 90 panic(err) 91 } 92 93 // get the full path to this .go file so we can construct the file url 94 // using this file's dirname 95 _, filename, _, _ := runtime.Caller(0) 96 testImagesDir := filepath.Join(filepath.Dir(filepath.Dir(filename)), "scripts") 97 98 l, err := net.Listen("tcp", ":23456") 99 if err != nil { 100 panic(err) 101 } 102 103 hs := http.Server{Handler: http.FileServer(http.Dir(testImagesDir))} 104 hs.SetKeepAlivesEnabled(false) 105 go hs.Serve(l) 106 } 107 108 func getDefaultContext() (context.Context, context.CancelFunc) { 109 return context.WithTimeout(context.Background(), 20*time.Second) 110 } 111 112 // TestInjectionBuild tests the build where we inject files to assemble script. 113 func TestInjectionBuild(t *testing.T) { 114 tempdir, err := ioutil.TempDir("", "s2i-test-dir") 115 if err != nil { 116 t.Errorf("Unable to create temporary directory: %v", err) 117 } 118 defer os.RemoveAll(tempdir) 119 120 err = ioutil.WriteFile(filepath.Join(tempdir, "secret"), []byte("secret"), 0666) 121 if err != nil { 122 t.Errorf("Unable to write content to temporary injection file: %v", err) 123 } 124 125 integration(t).exerciseInjectionBuild(TagCleanBuild, FakeBuilderImage, []string{ 126 tempdir + ":/tmp", 127 tempdir + ":", 128 tempdir + ":test;" + tempdir + ":test2", 129 }, true) 130 } 131 132 func TestInjectionBuildBadDestination(t *testing.T) { 133 tempdir, err := ioutil.TempDir("", "s2i-test-dir") 134 if err != nil { 135 t.Errorf("Unable to create temporary directory: %v", err) 136 } 137 defer os.RemoveAll(tempdir) 138 139 err = ioutil.WriteFile(filepath.Join(tempdir, "secret"), []byte("secret"), 0666) 140 if err != nil { 141 t.Errorf("Unable to write content to temporary injection file: %v", err) 142 } 143 144 integration(t).exerciseInjectionBuild(TagCleanBuild, FakeBuilderImage, []string{tempdir + ":/bad/dir"}, false) 145 } 146 147 type integrationTest struct { 148 t *testing.T 149 setupComplete bool 150 } 151 152 func (i integrationTest) InspectImage(name string) (*dockertypes.ImageInspect, error) { 153 ctx, cancel := getDefaultContext() 154 defer cancel() 155 resp, _, err := engineClient.ImageInspectWithRaw(ctx, name) 156 if err != nil { 157 if dockerapi.IsErrNotFound(err) { 158 return nil, fmt.Errorf("no such image :%q", name) 159 } 160 return nil, err 161 } 162 return &resp, nil 163 } 164 165 var ( 166 FakeScriptsFileURL string 167 ) 168 169 func getLogLevel() (level int) { 170 for level = 0; level <= 5; level++ { 171 if klog.V(klog.Level(level)).Enabled() { 172 break 173 } 174 } 175 return 176 } 177 178 // setup sets up integration tests 179 func (i *integrationTest) setup() { 180 if !i.setupComplete { 181 // get the full path to this .go file so we can construct the file url 182 // using this file's dirname 183 _, filename, _, _ := runtime.Caller(0) 184 185 testImagesDir := filepath.Join(filepath.Dir(filepath.Dir(filename)), "scripts") 186 FakeScriptsFileURL = "file://" + filepath.ToSlash(filepath.Join(testImagesDir, ".s2i", "bin")) 187 188 for _, image := range []string{TagCleanBuild, TagCleanBuildUser, TagIncrementalBuild, TagIncrementalBuildUser} { 189 ctx, cancel := getDefaultContext() 190 engineClient.ImageRemove(ctx, image, dockertypes.ImageRemoveOptions{}) 191 cancel() 192 } 193 194 i.setupComplete = true 195 } 196 197 from := flag.CommandLine 198 if vflag := from.Lookup("v"); vflag != nil { 199 // the thing here is that we are looking for the bash -v passed into test-integration.sh (with no value), 200 // but for klog (https://k8s.io/klog/v2/blob/master/klog.go), one specifies 201 // the logging level with -v=# (i.e. -v=0 or -v=3 or -v=5). 202 // so, for the changes stemming from issue 133, we 'reuse' the bash -v, and set the highest klog level. 203 // (if you look at STI's main.go, and setupGlog, it essentially maps klog's -v to --loglevel for use by the sti command) 204 //NOTE - passing --loglevel or -v=5 into test-integration.sh does not work 205 if getLogLevel() != 5 { 206 vflag.Value.Set("5") 207 // FIXME currently klog has only option to redirect output to stderr 208 // the preferred for STI would be to redirect to stdout 209 flag.CommandLine.Set("logtostderr", "true") 210 } 211 } 212 } 213 214 func integration(t *testing.T) *integrationTest { 215 i := &integrationTest{t: t} 216 i.setup() 217 return i 218 } 219 220 // Test a clean build. The simplest case. 221 func TestCleanBuild(t *testing.T) { 222 integration(t).exerciseCleanBuild(TagCleanBuild, false, FakeBuilderImage, "", true, true, false) 223 } 224 225 // Test Labels 226 func TestCleanBuildLabel(t *testing.T) { 227 integration(t).exerciseCleanBuild(TagCleanBuild, false, FakeBuilderImage, "", true, true, true) 228 } 229 230 func TestCleanBuildUser(t *testing.T) { 231 integration(t).exerciseCleanBuild(TagCleanBuildUser, false, FakeUserImage, "", true, true, false) 232 } 233 234 func TestCleanBuildFileScriptsURL(t *testing.T) { 235 integration(t).exerciseCleanBuild(TagCleanBuild, false, FakeBuilderImage, FakeScriptsFileURL, true, true, false) 236 } 237 238 func TestCleanBuildHttpScriptsURL(t *testing.T) { 239 integration(t).exerciseCleanBuild(TagCleanBuild, false, FakeBuilderImage, FakeScriptsHTTPURL, true, true, false) 240 } 241 242 func TestCleanBuildScripts(t *testing.T) { 243 integration(t).exerciseCleanBuild(TagCleanBuildScripts, false, FakeImageScripts, "", true, true, false) 244 } 245 246 func TestLayeredBuildNoTar(t *testing.T) { 247 integration(t).exerciseCleanBuild(TagCleanLayeredBuildNoTar, false, FakeImageNoTar, FakeScriptsFileURL, false, true, false) 248 } 249 250 // Test that a build config with a callbackURL will invoke HTTP endpoint 251 func TestCleanBuildCallbackInvoked(t *testing.T) { 252 integration(t).exerciseCleanBuild(TagCleanBuild, true, FakeBuilderImage, "", true, true, false) 253 } 254 255 func TestCleanBuildOnBuild(t *testing.T) { 256 integration(t).exerciseCleanBuild(TagCleanBuildOnBuild, false, FakeImageOnBuild, "", true, true, false) 257 } 258 259 func TestCleanBuildOnBuildNoName(t *testing.T) { 260 integration(t).exerciseCleanBuild(TagCleanBuildOnBuildNoName, false, FakeImageOnBuild, "", false, false, false) 261 } 262 263 func TestCleanBuildNoName(t *testing.T) { 264 integration(t).exerciseCleanBuild(TagCleanBuildNoName, false, FakeBuilderImage, "", true, false, false) 265 } 266 267 func TestLayeredBuildNoTarNoName(t *testing.T) { 268 integration(t).exerciseCleanBuild(TagCleanLayeredBuildNoTarNoName, false, FakeImageNoTar, FakeScriptsFileURL, false, false, false) 269 } 270 271 func TestAllowedUIDsNamedUser(t *testing.T) { 272 integration(t).exerciseCleanAllowedUIDsBuild(TagCleanBuildAllowedUIDsNamedUser, FakeUserImage, true) 273 } 274 275 func TestAllowedUIDsNumericUser(t *testing.T) { 276 integration(t).exerciseCleanAllowedUIDsBuild(TagCleanBuildAllowedUIDsNumericUser, FakeNumericUserImage, false) 277 } 278 279 func TestAllowedUIDsOnBuildRootUser(t *testing.T) { 280 integration(t).exerciseCleanAllowedUIDsBuild(TagCleanBuildAllowedUIDsNamedUser, FakeImageOnBuildRootUser, true) 281 } 282 283 func TestAllowedUIDsOnBuildNumericUser(t *testing.T) { 284 integration(t).exerciseCleanAllowedUIDsBuild(TagCleanBuildAllowedUIDsNumericUser, FakeImageOnBuildNumericUser, false) 285 } 286 287 func TestAllowedUIDsAssembleRoot(t *testing.T) { 288 integration(t).exerciseCleanAllowedUIDsBuild(TagCleanBuildAllowedUIDsAssembleRoot, FakeImageAssembleRoot, true) 289 } 290 291 func TestAllowedUIDsAssembleUser(t *testing.T) { 292 integration(t).exerciseCleanAllowedUIDsBuild(TagCleanBuildAllowedUIDsAssembleUser, FakeImageAssembleUser, false) 293 } 294 295 func (i *integrationTest) exerciseCleanAllowedUIDsBuild(tag, imageName string, expectError bool) { 296 t := i.t 297 config := &api.Config{ 298 DockerConfig: docker.GetDefaultDockerConfig(), 299 BuilderImage: imageName, 300 BuilderPullPolicy: api.DefaultBuilderPullPolicy, 301 Source: git.MustParse(TestSource), 302 Tag: tag, 303 Incremental: false, 304 ScriptsURL: "", 305 ExcludeRegExp: tar.DefaultExclusionPattern.String(), 306 } 307 config.AllowedUIDs.Set("1-") 308 _, _, err := strategies.Strategy(engineClient, config, build.Overrides{}) 309 if err != nil && !expectError { 310 t.Fatalf("Cannot create a new builder: %v", err) 311 } 312 if err == nil && expectError { 313 t.Fatalf("Did not get an error and was expecting one.") 314 } 315 } 316 317 func (i *integrationTest) exerciseCleanBuild(tag string, verifyCallback bool, imageName string, scriptsURL string, expectImageName bool, setTag bool, checkLabel bool) { 318 t := i.t 319 callbackURL := "" 320 callbackInvoked := false 321 callbackHasValidJSON := false 322 if verifyCallback { 323 handler := func(w http.ResponseWriter, r *http.Request) { 324 // we got called 325 callbackInvoked = true 326 // the header is as expected 327 contentType := r.Header["Content-Type"][0] 328 callbackHasValidJSON = contentType == "application/json" 329 // the request body is as expected 330 if callbackHasValidJSON { 331 defer r.Body.Close() 332 body, _ := ioutil.ReadAll(r.Body) 333 type CallbackMessage struct { 334 Success bool 335 Labels map[string]string 336 } 337 var callbackMessage CallbackMessage 338 err := json.Unmarshal(body, &callbackMessage) 339 callbackHasValidJSON = (err == nil) && callbackMessage.Success && len(callbackMessage.Labels) > 0 340 } 341 } 342 ts := httptest.NewServer(http.HandlerFunc(handler)) 343 defer ts.Close() 344 callbackURL = ts.URL 345 } 346 347 var buildTag string 348 if setTag { 349 buildTag = tag 350 } else { 351 buildTag = "" 352 } 353 354 config := &api.Config{ 355 DockerConfig: docker.GetDefaultDockerConfig(), 356 BuilderImage: imageName, 357 BuilderPullPolicy: api.DefaultBuilderPullPolicy, 358 Source: git.MustParse(TestSource), 359 Tag: buildTag, 360 Incremental: false, 361 CallbackURL: callbackURL, 362 ScriptsURL: scriptsURL, 363 ExcludeRegExp: tar.DefaultExclusionPattern.String(), 364 } 365 366 b, _, err := strategies.Strategy(engineClient, config, build.Overrides{}) 367 if err != nil { 368 t.Fatalf("Cannot create a new builder.") 369 } 370 resp, err := b.Build(config) 371 if err != nil { 372 t.Fatalf("An error occurred during the build: %v", err) 373 } else if !resp.Success { 374 t.Fatalf("The build failed.") 375 } 376 if callbackInvoked != verifyCallback { 377 t.Fatalf("S2I build did not invoke callback") 378 } 379 if callbackHasValidJSON != verifyCallback { 380 t.Fatalf("S2I build did not invoke callback with valid json message") 381 } 382 383 // We restrict this check to only when we are passing tag through the build config 384 // since we will not end up with an available tag by that name from build 385 if setTag { 386 i.checkForImage(tag) 387 containerID := i.createContainer(tag) 388 i.checkBasicBuildState(containerID, resp.WorkingDir) 389 390 if checkLabel { 391 i.checkForLabel(tag) 392 } 393 394 i.removeContainer(containerID) 395 } 396 397 // Check if we receive back an ImageID when we are expecting to 398 if expectImageName && len(resp.ImageID) == 0 { 399 t.Fatalf("S2I build did not receive an ImageID in response") 400 } 401 if !expectImageName && len(resp.ImageID) > 0 { 402 t.Fatalf("S2I build received an ImageID in response") 403 } 404 } 405 406 // Test an incremental build. 407 func TestIncrementalBuildAndRemovePreviousImage(t *testing.T) { 408 integration(t).exerciseIncrementalBuild(TagIncrementalBuild, FakeBuilderImage, true, false, false) 409 } 410 411 func TestIncrementalBuildAndKeepPreviousImage(t *testing.T) { 412 integration(t).exerciseIncrementalBuild(TagIncrementalBuild, FakeBuilderImage, false, false, false) 413 } 414 415 func TestIncrementalBuildUser(t *testing.T) { 416 integration(t).exerciseIncrementalBuild(TagIncrementalBuildUser, FakeBuilderImage, true, false, false) 417 } 418 419 func TestIncrementalBuildScripts(t *testing.T) { 420 integration(t).exerciseIncrementalBuild(TagIncrementalBuildScripts, FakeImageScripts, true, false, false) 421 } 422 423 func TestIncrementalBuildScriptsNoSaveArtifacts(t *testing.T) { 424 integration(t).exerciseIncrementalBuild(TagIncrementalBuildScriptsNoSaveArtifacts, FakeImageScriptsNoSaveArtifacts, true, true, false) 425 } 426 427 func TestIncrementalBuildOnBuild(t *testing.T) { 428 integration(t).exerciseIncrementalBuild(TagIncrementalBuildOnBuild, FakeImageOnBuild, false, true, true) 429 } 430 431 func (i *integrationTest) exerciseInjectionBuild(tag, imageName string, injections []string, expectSuccess bool) { 432 t := i.t 433 434 injectionList := api.VolumeList{} 435 for _, i := range injections { 436 err := injectionList.Set(i) 437 if err != nil { 438 t.Errorf("injectionList.Set() failed with error %s\n", err) 439 } 440 } 441 // For test purposes, keep at least one injected source 442 var keptVolume *api.VolumeSpec 443 if len(injectionList) > 0 { 444 injectionList[0].Keep = true 445 keptVolume = &injectionList[0] 446 } 447 config := &api.Config{ 448 DockerConfig: docker.GetDefaultDockerConfig(), 449 BuilderImage: imageName, 450 BuilderPullPolicy: api.DefaultBuilderPullPolicy, 451 Source: git.MustParse(TestSource), 452 Tag: tag, 453 Injections: injectionList, 454 ExcludeRegExp: tar.DefaultExclusionPattern.String(), 455 } 456 builder, _, err := strategies.Strategy(engineClient, config, build.Overrides{}) 457 if err != nil { 458 t.Fatalf("Unable to create builder: %v", err) 459 } 460 resp, err := builder.Build(config) 461 if !expectSuccess { 462 if resp.Success { 463 t.Fatal("Success was returned, but should have failed") 464 } 465 return 466 } 467 if err != nil { 468 t.Fatalf("Unexpected error occurred during build: %v", err) 469 } 470 if !resp.Success { 471 t.Fatalf("S2I build failed.") 472 } 473 i.checkForImage(tag) 474 containerID := i.createContainer(tag) 475 defer i.removeContainer(containerID) 476 477 // Check that the injected file is delivered to assemble script 478 i.fileExists(containerID, "/sti-fake/secret-delivered") 479 i.fileExists(containerID, "/sti-fake/relative-secret-delivered") 480 481 // Make sure the injected file does not exists in resulting image 482 testFs := fs.NewFileSystem() 483 files, err := util.ListFilesToTruncate(testFs, injectionList) 484 if err != nil { 485 t.Errorf("Unexpected error: %v", err) 486 } 487 for _, f := range files { 488 if err = i.testFile(tag, f); err == nil { 489 t.Errorf("The file %q must be empty or not exist", f) 490 } 491 } 492 if keptVolume != nil { 493 keptFiles, err := util.ListFiles(testFs, *keptVolume) 494 if err != nil { 495 t.Errorf("Unexpected error: %v", err) 496 } 497 for _, f := range keptFiles { 498 if err = i.testFile(tag, f); err != nil { 499 t.Errorf("The file %q must exist and not be empty", f) 500 } 501 } 502 } 503 } 504 505 func (i *integrationTest) testFile(tag, path string) error { 506 exitCode := i.runInImage(tag, "test -s "+path) 507 if exitCode != 0 { 508 return fmt.Errorf("file %s does not exist or is empty in the container %s", path, tag) 509 } 510 return nil 511 } 512 513 func (i *integrationTest) exerciseIncrementalBuild(tag, imageName string, removePreviousImage bool, expectClean bool, checkOnBuild bool) { 514 t := i.t 515 start := time.Now() 516 config := &api.Config{ 517 DockerConfig: docker.GetDefaultDockerConfig(), 518 BuilderImage: imageName, 519 BuilderPullPolicy: api.DefaultBuilderPullPolicy, 520 Source: git.MustParse(TestSource), 521 Tag: tag, 522 Incremental: false, 523 RemovePreviousImage: removePreviousImage, 524 ExcludeRegExp: tar.DefaultExclusionPattern.String(), 525 } 526 527 builder, _, err := strategies.Strategy(engineClient, config, build.Overrides{}) 528 if err != nil { 529 t.Fatalf("Unable to create builder: %v", err) 530 } 531 resp, err := builder.Build(config) 532 if err != nil { 533 t.Fatalf("Unexpected error occurred during build: %v", err) 534 } 535 if !resp.Success { 536 t.Fatalf("S2I build failed.") 537 } 538 539 previousImageID := resp.ImageID 540 config = &api.Config{ 541 DockerConfig: docker.GetDefaultDockerConfig(), 542 BuilderImage: imageName, 543 BuilderPullPolicy: api.DefaultBuilderPullPolicy, 544 Source: git.MustParse(TestSource), 545 Tag: tag, 546 Incremental: true, 547 RemovePreviousImage: removePreviousImage, 548 PreviousImagePullPolicy: api.PullIfNotPresent, 549 ExcludeRegExp: tar.DefaultExclusionPattern.String(), 550 } 551 552 builder, _, err = strategies.Strategy(engineClient, config, build.Overrides{}) 553 if err != nil { 554 t.Fatalf("Unable to create incremental builder: %v", err) 555 } 556 resp, err = builder.Build(config) 557 if err != nil { 558 t.Fatalf("Unexpected error occurred during incremental build: %v", err) 559 } 560 if !resp.Success { 561 t.Fatalf("S2I incremental build failed.") 562 } 563 564 i.checkForImage(tag) 565 containerID := i.createContainer(tag) 566 defer i.removeContainer(containerID) 567 i.checkIncrementalBuildState(containerID, resp.WorkingDir, expectClean) 568 569 _, err = i.InspectImage(previousImageID) 570 if removePreviousImage { 571 if err == nil { 572 t.Errorf("Previous image %s not deleted", previousImageID) 573 } 574 } else { 575 if err != nil { 576 t.Errorf("Couldn't find previous image %s", previousImageID) 577 } 578 } 579 580 if checkOnBuild { 581 i.fileExists(containerID, "/sti-fake/src/onbuild") 582 } 583 584 if took := time.Since(start); took > docker.DefaultDockerTimeout { 585 // https://github.com/openshift/source-to-image/issues/301 is a 586 // case where incremental builds would get stuck until the 587 // timeout. 588 t.Errorf("Test took too long (%v), some operation may have gotten stuck waiting for the DefaultDockerTimeout (%v). Inspect the logs to find operations that took long.", took, docker.DefaultDockerTimeout) 589 } 590 } 591 592 // Support methods 593 func (i *integrationTest) checkForImage(tag string) { 594 _, err := i.InspectImage(tag) 595 if err != nil { 596 i.t.Errorf("Couldn't find image with tag: %s", tag) 597 } 598 } 599 600 func (i *integrationTest) createContainer(image string) string { 601 ctx, cancel := getDefaultContext() 602 defer cancel() 603 opts := dockertypes.ContainerCreateConfig{Name: "", Config: &dockercontainer.Config{Image: image}} 604 container, err := engineClient.ContainerCreate(ctx, opts.Config, opts.HostConfig, opts.NetworkingConfig, nil, opts.Name) 605 if err != nil { 606 i.t.Errorf("Couldn't create container from image %s with error %+v", image, err) 607 return "" 608 } 609 610 ctx, cancel = getDefaultContext() 611 defer cancel() 612 err = engineClient.ContainerStart(ctx, container.ID, dockertypes.ContainerStartOptions{}) 613 if err != nil { 614 i.t.Errorf("Couldn't start container: %s with error %+v", container.ID, err) 615 return "" 616 } 617 618 ctx, cancel = getDefaultContext() 619 defer cancel() 620 waitC, errC := engineClient.ContainerWait(ctx, container.ID, dockercontainer.WaitConditionNextExit) 621 select { 622 case result := <-waitC: 623 if result.StatusCode != 0 { 624 i.t.Errorf("Bad exit code from container: %d", result.StatusCode) 625 return "" 626 } 627 case err := <-errC: 628 if errors.Is(err, context.DeadlineExceeded) { 629 ctx, cancel := getDefaultContext() 630 defer cancel() 631 inspectInfo, err2 := engineClient.ContainerInspect(ctx, container.ID) 632 if err2 == nil && inspectInfo.State != nil && inspectInfo.State.Status == "exited" { 633 // it must have finished before we tried to wait for a not-exited->exited state change 634 i.t.Logf("timed out waiting for container %q: it had already exited; continuing", container.ID) 635 err = nil 636 } 637 } 638 if err != nil { 639 i.t.Errorf("Error waiting for container: %v", err) 640 return "" 641 } 642 } 643 return container.ID 644 } 645 646 func (i *integrationTest) runInContainer(image string, command []string) int { 647 ctx, cancel := getDefaultContext() 648 defer cancel() 649 opts := dockertypes.ContainerCreateConfig{Name: "", Config: &dockercontainer.Config{Image: image, AttachStdout: false, AttachStdin: false, Cmd: command}} 650 container, err := engineClient.ContainerCreate(ctx, opts.Config, opts.HostConfig, opts.NetworkingConfig, nil, opts.Name) 651 if err != nil { 652 i.t.Errorf("Couldn't create container from image %s err %+v", image, err) 653 return -1 654 } 655 656 ctx, cancel = getDefaultContext() 657 defer cancel() 658 err = engineClient.ContainerStart(ctx, container.ID, dockertypes.ContainerStartOptions{}) 659 if err != nil { 660 i.t.Errorf("Couldn't start container: %s", container.ID) 661 } 662 ctx, cancel = getDefaultContext() 663 defer cancel() 664 waitC, errC := engineClient.ContainerWait(ctx, container.ID, dockercontainer.WaitConditionNextExit) 665 exitCode := -1 666 select { 667 case result := <-waitC: 668 exitCode = int(result.StatusCode) 669 case err := <-errC: 670 if errors.Is(err, context.DeadlineExceeded) { 671 ctx, cancel := getDefaultContext() 672 defer cancel() 673 inspectInfo, err2 := engineClient.ContainerInspect(ctx, container.ID) 674 if err2 == nil && inspectInfo.State != nil && inspectInfo.State.Status == "exited" { 675 // it must have finished before we tried to wait for a not-exited->exited state change 676 i.t.Logf("timed out waiting for container %q: it had already exited; continuing", container.ID) 677 exitCode = inspectInfo.State.ExitCode 678 err = nil 679 } 680 } 681 if err != nil { 682 i.t.Errorf("Couldn't wait for container: %s: %v", container.ID, err) 683 } 684 } 685 ctx, cancel = getDefaultContext() 686 defer cancel() 687 err = engineClient.ContainerRemove(ctx, container.ID, dockertypes.ContainerRemoveOptions{}) 688 if err != nil { 689 i.t.Errorf("Couldn't remove container: %s", container.ID) 690 } 691 return exitCode 692 } 693 694 func (i *integrationTest) removeContainer(cID string) { 695 ctx, cancel := getDefaultContext() 696 defer cancel() 697 engineClient.ContainerKill(ctx, cID, "SIGKILL") 698 removeOpts := dockertypes.ContainerRemoveOptions{ 699 RemoveVolumes: true, 700 } 701 err := engineClient.ContainerRemove(ctx, cID, removeOpts) 702 if err != nil { 703 i.t.Errorf("Couldn't remove container %s: %s", cID, err) 704 } 705 } 706 707 func (i *integrationTest) fileExists(cID string, filePath string) { 708 res := i.fileExistsInContainer(cID, filePath) 709 710 if !res { 711 i.t.Errorf("Couldn't find file %s in container %s", filePath, cID) 712 } 713 } 714 715 func (i *integrationTest) fileNotExists(cID string, filePath string) { 716 res := i.fileExistsInContainer(cID, filePath) 717 718 if res { 719 i.t.Errorf("Unexpected file %s in container %s", filePath, cID) 720 } 721 } 722 723 func (i *integrationTest) runInImage(image string, cmd string) int { 724 return i.runInContainer(image, []string{"/bin/sh", "-c", cmd}) 725 } 726 727 func (i *integrationTest) checkBasicBuildState(cID string, workingDir string) { 728 i.fileExists(cID, "/sti-fake/assemble-invoked") 729 i.fileExists(cID, "/sti-fake/run-invoked") 730 i.fileExists(cID, "/sti-fake/src/Gemfile") 731 732 _, err := os.Stat(workingDir) 733 if !os.IsNotExist(err) { 734 i.t.Errorf("Unexpected error from stat check on %s", workingDir) 735 } 736 } 737 738 func (i *integrationTest) checkIncrementalBuildState(cID string, workingDir string, expectClean bool) { 739 i.checkBasicBuildState(cID, workingDir) 740 if expectClean { 741 i.fileNotExists(cID, "/sti-fake/save-artifacts-invoked") 742 } else { 743 i.fileExists(cID, "/sti-fake/save-artifacts-invoked") 744 } 745 } 746 747 func (i *integrationTest) fileExistsInContainer(cID string, filePath string) bool { 748 ctx, cancel := getDefaultContext() 749 defer cancel() 750 rdr, stats, err := engineClient.CopyFromContainer(ctx, cID, filePath) 751 if err != nil { 752 return false 753 } 754 defer rdr.Close() 755 return "" != stats.Name 756 } 757 758 func (i *integrationTest) checkForLabel(image string) { 759 docker := dockerpkg.New(engineClient, (&api.Config{}).PullAuthentication) 760 761 labelMap, err := docker.GetLabels(image) 762 if err != nil { 763 i.t.Fatalf("Unable to get labels from image %s: %v", image, err) 764 } 765 766 if labelMap["testLabel"] != "testLabel_value" { 767 i.t.Errorf("Unable to verify 'testLabel' for image '%s'", image) 768 } 769 }