github.com/secure-build/gitlab-runner@v12.5.0+incompatible/executors/docker/executor_docker_command_test.go (about) 1 package docker_test 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "net/url" 8 "os" 9 "os/exec" 10 "regexp" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/hashicorp/go-version" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 19 "gitlab.com/gitlab-org/gitlab-runner/common" 20 "gitlab.com/gitlab-org/gitlab-runner/helpers" 21 docker_helpers "gitlab.com/gitlab-org/gitlab-runner/helpers/docker" 22 ) 23 24 func TestDockerCommandSuccessRun(t *testing.T) { 25 if helpers.SkipIntegrationTests(t, "docker", "info") { 26 return 27 } 28 29 successfulBuild, err := common.GetRemoteSuccessfulBuild() 30 assert.NoError(t, err) 31 build := &common.Build{ 32 JobResponse: successfulBuild, 33 Runner: &common.RunnerConfig{ 34 RunnerSettings: common.RunnerSettings{ 35 Executor: "docker", 36 Docker: &common.DockerConfig{ 37 Image: common.TestAlpineImage, 38 PullPolicy: common.PullPolicyIfNotPresent, 39 }, 40 }, 41 }, 42 } 43 44 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 45 assert.NoError(t, err) 46 } 47 48 func TestDockerCommandUsingCustomClonePath(t *testing.T) { 49 if helpers.SkipIntegrationTests(t, "docker", "info") { 50 return 51 } 52 53 jobResponse, err := common.GetRemoteBuildResponse( 54 "ls -al $CI_BUILDS_DIR/go/src/gitlab.com/gitlab-org/repo") 55 require.NoError(t, err) 56 57 tests := map[string]struct { 58 clonePath string 59 expectedErrorType interface{} 60 }{ 61 "uses custom clone path": { 62 clonePath: "$CI_BUILDS_DIR/go/src/gitlab.com/gitlab-org/repo", 63 expectedErrorType: nil, 64 }, 65 "path has to be within CI_BUILDS_DIR": { 66 clonePath: "/unknown/go/src/gitlab.com/gitlab-org/repo", 67 expectedErrorType: &common.BuildError{}, 68 }, 69 } 70 71 for name, test := range tests { 72 t.Run(name, func(t *testing.T) { 73 build := &common.Build{ 74 JobResponse: jobResponse, 75 Runner: &common.RunnerConfig{ 76 RunnerSettings: common.RunnerSettings{ 77 Executor: "docker", 78 Docker: &common.DockerConfig{ 79 Image: common.TestAlpineImage, 80 PullPolicy: common.PullPolicyIfNotPresent, 81 }, 82 Environment: []string{ 83 "GIT_CLONE_PATH=" + test.clonePath, 84 }, 85 }, 86 }, 87 } 88 89 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 90 assert.IsType(t, test.expectedErrorType, err) 91 }) 92 } 93 } 94 95 func TestDockerCommandNoRootImage(t *testing.T) { 96 if helpers.SkipIntegrationTests(t, "docker", "info") { 97 return 98 } 99 100 successfulBuild, err := common.GetRemoteSuccessfulBuildWithDumpedVariables() 101 102 assert.NoError(t, err) 103 successfulBuild.Image.Name = common.TestAlpineNoRootImage 104 build := &common.Build{ 105 JobResponse: successfulBuild, 106 Runner: &common.RunnerConfig{ 107 RunnerSettings: common.RunnerSettings{ 108 Executor: "docker", 109 Docker: &common.DockerConfig{ 110 PullPolicy: common.PullPolicyIfNotPresent, 111 }, 112 }, 113 }, 114 } 115 116 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 117 assert.NoError(t, err) 118 } 119 120 func TestDockerCommandBuildFail(t *testing.T) { 121 if helpers.SkipIntegrationTests(t, "docker", "info") { 122 return 123 } 124 125 failedBuild, err := common.GetRemoteFailedBuild() 126 assert.NoError(t, err) 127 build := &common.Build{ 128 JobResponse: failedBuild, 129 Runner: &common.RunnerConfig{ 130 RunnerSettings: common.RunnerSettings{ 131 Executor: "docker", 132 Docker: &common.DockerConfig{ 133 Image: common.TestAlpineImage, 134 PullPolicy: common.PullPolicyIfNotPresent, 135 }, 136 }, 137 }, 138 } 139 140 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 141 require.Error(t, err, "error") 142 assert.IsType(t, err, &common.BuildError{}) 143 assert.Contains(t, err.Error(), "exit code 1") 144 } 145 146 func TestDockerCommandWithAllowedImagesRun(t *testing.T) { 147 if helpers.SkipIntegrationTests(t, "docker", "info") { 148 return 149 } 150 151 successfulBuild, err := common.GetRemoteSuccessfulBuild() 152 successfulBuild.Image = common.Image{Name: "$IMAGE_NAME"} 153 successfulBuild.Variables = append(successfulBuild.Variables, common.JobVariable{ 154 Key: "IMAGE_NAME", 155 Value: common.TestAlpineImage, 156 Public: true, 157 Internal: false, 158 File: false, 159 }) 160 successfulBuild.Services = append(successfulBuild.Services, common.Image{Name: common.TestDockerDindImage}) 161 assert.NoError(t, err) 162 build := &common.Build{ 163 JobResponse: successfulBuild, 164 Runner: &common.RunnerConfig{ 165 RunnerSettings: common.RunnerSettings{ 166 Executor: "docker", 167 Docker: &common.DockerConfig{ 168 AllowedImages: []string{common.TestAlpineImage}, 169 AllowedServices: []string{common.TestDockerDindImage}, 170 Privileged: true, 171 PullPolicy: common.PullPolicyIfNotPresent, 172 }, 173 }, 174 }, 175 } 176 177 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 178 assert.NoError(t, err) 179 } 180 181 func TestDockerCommandDisableEntrypointOverwrite(t *testing.T) { 182 if helpers.SkipIntegrationTests(t, "docker", "info") { 183 return 184 } 185 186 tests := []struct { 187 name string 188 services bool 189 disabled bool 190 }{ 191 { 192 name: "Disabled - no services", 193 disabled: true, 194 }, 195 { 196 name: "Disabled - services", 197 disabled: true, 198 services: true, 199 }, 200 { 201 name: "Enabled - no services", 202 }, 203 { 204 name: "Enabled - services", 205 services: true, 206 }, 207 } 208 209 for _, test := range tests { 210 t.Run(test.name, func(t *testing.T) { 211 successfulBuild, err := common.GetRemoteSuccessfulBuild() 212 require.NoError(t, err) 213 214 successfulBuild.Image.Entrypoint = []string{"/bin/sh", "-c", "echo 'image overwritten'"} 215 216 if test.services { 217 successfulBuild.Services = common.Services{ 218 common.Image{ 219 Name: common.TestDockerDindImage, 220 Entrypoint: []string{"/bin/sh", "-c", "echo 'service overwritten'"}, 221 }, 222 } 223 } 224 225 build := &common.Build{ 226 JobResponse: successfulBuild, 227 Runner: &common.RunnerConfig{ 228 RunnerSettings: common.RunnerSettings{ 229 Executor: "docker", 230 Docker: &common.DockerConfig{ 231 Privileged: true, 232 Image: common.TestAlpineImage, 233 PullPolicy: common.PullPolicyIfNotPresent, 234 DisableEntrypointOverwrite: test.disabled, 235 }, 236 }, 237 }, 238 } 239 240 var buffer bytes.Buffer 241 err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer}) 242 assert.NoError(t, err) 243 out := buffer.String() 244 if test.disabled { 245 assert.NotContains(t, out, "image overwritten") 246 assert.NotContains(t, out, "service overwritten") 247 assert.Contains(t, out, "Entrypoint override disabled") 248 } else { 249 assert.Contains(t, out, "image overwritten") 250 if test.services { 251 assert.Contains(t, out, "service overwritten") 252 } 253 } 254 }) 255 } 256 } 257 258 func isDockerOlderThan17_07(t *testing.T) bool { 259 client, err := docker_helpers.New(docker_helpers.DockerCredentials{}, "") 260 require.NoError(t, err, "should be able to connect to docker") 261 262 types, err := client.Info(context.Background()) 263 require.NoError(t, err, "should be able to get docker info") 264 265 localVersion, err := version.NewVersion(types.ServerVersion) 266 require.NoError(t, err) 267 268 checkedVersion, err := version.NewVersion("17.07.0-ce") 269 require.NoError(t, err) 270 271 return localVersion.LessThan(checkedVersion) 272 } 273 274 func TestDockerCommandMissingImage(t *testing.T) { 275 if helpers.SkipIntegrationTests(t, "docker", "info") { 276 return 277 } 278 279 build := &common.Build{ 280 Runner: &common.RunnerConfig{ 281 RunnerSettings: common.RunnerSettings{ 282 Executor: "docker", 283 Docker: &common.DockerConfig{ 284 Image: "some/non-existing/image", 285 }, 286 }, 287 }, 288 } 289 290 err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 291 require.Error(t, err) 292 assert.IsType(t, &common.BuildError{}, err) 293 294 contains := "repository does not exist" 295 if isDockerOlderThan17_07(t) { 296 contains = "not found" 297 } 298 299 assert.Contains(t, err.Error(), contains) 300 } 301 302 func TestDockerCommandMissingTag(t *testing.T) { 303 if helpers.SkipIntegrationTests(t, "docker", "info") { 304 return 305 } 306 307 build := &common.Build{ 308 Runner: &common.RunnerConfig{ 309 RunnerSettings: common.RunnerSettings{ 310 Executor: "docker", 311 Docker: &common.DockerConfig{ 312 Image: "docker:missing-tag", 313 }, 314 }, 315 }, 316 } 317 318 err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 319 require.Error(t, err) 320 assert.IsType(t, &common.BuildError{}, err) 321 assert.Contains(t, err.Error(), "not found") 322 } 323 324 func TestDockerCommandBuildAbort(t *testing.T) { 325 if helpers.SkipIntegrationTests(t, "docker", "info") { 326 return 327 } 328 329 longRunningBuild, err := common.GetRemoteLongRunningBuild() 330 assert.NoError(t, err) 331 build := &common.Build{ 332 JobResponse: longRunningBuild, 333 Runner: &common.RunnerConfig{ 334 RunnerSettings: common.RunnerSettings{ 335 Executor: "docker", 336 Docker: &common.DockerConfig{ 337 Image: common.TestAlpineImage, 338 PullPolicy: common.PullPolicyIfNotPresent, 339 }, 340 }, 341 }, 342 SystemInterrupt: make(chan os.Signal, 1), 343 } 344 345 abortTimer := time.AfterFunc(time.Second, func() { 346 t.Log("Interrupt") 347 build.SystemInterrupt <- os.Interrupt 348 }) 349 defer abortTimer.Stop() 350 351 timeoutTimer := time.AfterFunc(time.Minute, func() { 352 t.Log("Timedout") 353 t.FailNow() 354 }) 355 defer timeoutTimer.Stop() 356 357 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 358 assert.EqualError(t, err, "aborted: interrupt") 359 } 360 361 func TestDockerCommandBuildCancel(t *testing.T) { 362 if helpers.SkipIntegrationTests(t, "docker", "info") { 363 return 364 } 365 366 longRunningBuild, err := common.GetRemoteLongRunningBuild() 367 assert.NoError(t, err) 368 build := &common.Build{ 369 JobResponse: longRunningBuild, 370 Runner: &common.RunnerConfig{ 371 RunnerSettings: common.RunnerSettings{ 372 Executor: "docker", 373 Docker: &common.DockerConfig{ 374 Image: common.TestAlpineImage, 375 PullPolicy: common.PullPolicyIfNotPresent, 376 }, 377 }, 378 }, 379 } 380 381 trace := &common.Trace{Writer: os.Stdout} 382 383 abortTimer := time.AfterFunc(time.Second, func() { 384 t.Log("Interrupt") 385 trace.CancelFunc() 386 }) 387 defer abortTimer.Stop() 388 389 timeoutTimer := time.AfterFunc(time.Minute, func() { 390 t.Log("Timedout") 391 t.FailNow() 392 }) 393 defer timeoutTimer.Stop() 394 395 err = build.Run(&common.Config{}, trace) 396 assert.IsType(t, err, &common.BuildError{}) 397 assert.Contains(t, err.Error(), "canceled") 398 } 399 400 func TestDockerCommandTwoServicesFromOneImage(t *testing.T) { 401 if helpers.SkipIntegrationTests(t, "docker", "info") { 402 return 403 } 404 405 successfulBuild, err := common.GetRemoteSuccessfulBuild() 406 successfulBuild.Services = common.Services{ 407 {Name: common.TestAlpineImage, Alias: "service-1"}, 408 {Name: common.TestAlpineImage, Alias: "service-2"}, 409 } 410 assert.NoError(t, err) 411 build := &common.Build{ 412 JobResponse: successfulBuild, 413 Runner: &common.RunnerConfig{ 414 RunnerSettings: common.RunnerSettings{ 415 Executor: "docker", 416 Docker: &common.DockerConfig{ 417 Image: common.TestAlpineImage, 418 PullPolicy: common.PullPolicyIfNotPresent, 419 }, 420 }, 421 }, 422 } 423 424 var buffer bytes.Buffer 425 426 err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer}) 427 assert.NoError(t, err) 428 str := buffer.String() 429 430 re, err := regexp.Compile("(?m)Conflict. The container name [^ ]+ is already in use by container") 431 require.NoError(t, err) 432 assert.NotRegexp(t, re, str, "Both service containers should be started and use different name") 433 } 434 435 func TestDockerCommandOutput(t *testing.T) { 436 if helpers.SkipIntegrationTests(t, "docker", "info") { 437 return 438 } 439 440 successfulBuild, err := common.GetRemoteSuccessfulBuild() 441 assert.NoError(t, err) 442 build := &common.Build{ 443 JobResponse: successfulBuild, 444 Runner: &common.RunnerConfig{ 445 RunnerSettings: common.RunnerSettings{ 446 Executor: "docker", 447 Docker: &common.DockerConfig{ 448 Image: common.TestAlpineImage, 449 PullPolicy: common.PullPolicyIfNotPresent, 450 }, 451 }, 452 }, 453 } 454 455 var buffer bytes.Buffer 456 457 err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer}) 458 assert.NoError(t, err) 459 460 re, err := regexp.Compile("(?m)^Initialized empty Git repository in /builds/gitlab-org/ci-cd/tests/gitlab-test/.git/") 461 assert.NoError(t, err) 462 assert.Regexp(t, re, buffer.String()) 463 } 464 465 func TestDockerPrivilegedServiceAccessingBuildsFolder(t *testing.T) { 466 if helpers.SkipIntegrationTests(t, "docker", "info") { 467 return 468 } 469 470 commands := []string{ 471 "docker info", 472 "docker run -v $(pwd):$(pwd) -w $(pwd) busybox touch test", 473 "cat test", 474 } 475 476 strategies := []string{ 477 "fetch", 478 "clone", 479 } 480 481 for _, strategy := range strategies { 482 t.Log("Testing", strategy, "strategy...") 483 longRunningBuild, err := common.GetRemoteLongRunningBuild() 484 assert.NoError(t, err) 485 build := &common.Build{ 486 JobResponse: longRunningBuild, 487 Runner: &common.RunnerConfig{ 488 RunnerSettings: common.RunnerSettings{ 489 Executor: "docker", 490 Docker: &common.DockerConfig{ 491 Image: common.TestAlpineImage, 492 PullPolicy: common.PullPolicyIfNotPresent, 493 Privileged: true, 494 }, 495 }, 496 }, 497 } 498 build.Steps = common.Steps{ 499 common.Step{ 500 Name: common.StepNameScript, 501 Script: common.StepScript(commands), 502 When: common.StepWhenOnSuccess, 503 AllowFailure: false, 504 }, 505 } 506 build.Image.Name = common.TestDockerGitImage 507 build.Services = common.Services{ 508 common.Image{ 509 Name: common.TestDockerDindImage, 510 }, 511 } 512 build.Variables = append(build.Variables, common.JobVariable{ 513 Key: "GIT_STRATEGY", Value: strategy, 514 }) 515 516 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 517 assert.NoError(t, err) 518 } 519 } 520 521 func getTestDockerJob(t *testing.T) *common.Build { 522 commands := []string{ 523 "docker info", 524 } 525 526 longRunningBuild, err := common.GetRemoteLongRunningBuild() 527 assert.NoError(t, err) 528 529 build := &common.Build{ 530 JobResponse: longRunningBuild, 531 Runner: &common.RunnerConfig{ 532 RunnerSettings: common.RunnerSettings{ 533 Executor: "docker", 534 Docker: &common.DockerConfig{ 535 Image: common.TestAlpineImage, 536 PullPolicy: common.PullPolicyIfNotPresent, 537 Privileged: true, 538 }, 539 }, 540 }, 541 } 542 build.Steps = common.Steps{ 543 common.Step{ 544 Name: common.StepNameScript, 545 Script: common.StepScript(commands), 546 When: common.StepWhenOnSuccess, 547 AllowFailure: false, 548 }, 549 } 550 551 return build 552 } 553 554 func TestDockerExtendedConfigurationFromJob(t *testing.T) { 555 if helpers.SkipIntegrationTests(t, "docker", "info") { 556 return 557 } 558 559 examples := []struct { 560 image common.Image 561 services common.Services 562 variables common.JobVariables 563 }{ 564 { 565 image: common.Image{ 566 Name: "$IMAGE_NAME", 567 Entrypoint: []string{"sh", "-c"}, 568 }, 569 services: common.Services{ 570 common.Image{ 571 Name: "$SERVICE_NAME", 572 Entrypoint: []string{"sh", "-c"}, 573 Command: []string{"dockerd-entrypoint.sh"}, 574 Alias: "my-docker-service", 575 }, 576 }, 577 variables: common.JobVariables{ 578 {Key: "DOCKER_HOST", Value: "tcp://my-docker-service:2375"}, 579 {Key: "IMAGE_NAME", Value: common.TestDockerGitImage}, 580 {Key: "SERVICE_NAME", Value: common.TestDockerDindImage}, 581 }, 582 }, 583 { 584 image: common.Image{ 585 Name: "$IMAGE_NAME", 586 }, 587 services: common.Services{ 588 common.Image{ 589 Name: "$SERVICE_NAME", 590 }, 591 }, 592 variables: common.JobVariables{ 593 {Key: "DOCKER_HOST", Value: "tcp://docker:2375"}, 594 {Key: "IMAGE_NAME", Value: common.TestDockerGitImage}, 595 {Key: "SERVICE_NAME", Value: common.TestDockerDindImage}, 596 }, 597 }, 598 } 599 600 for exampleID, example := range examples { 601 t.Run(fmt.Sprintf("example-%d", exampleID), func(t *testing.T) { 602 build := getTestDockerJob(t) 603 build.Image = example.image 604 build.Services = example.services 605 build.Variables = append(build.Variables, example.variables...) 606 607 err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 608 assert.NoError(t, err) 609 }) 610 } 611 } 612 613 func runTestJobWithOutput(t *testing.T, build *common.Build) (output string) { 614 var buffer bytes.Buffer 615 616 err := build.Run(&common.Config{}, &common.Trace{Writer: &buffer}) 617 assert.NoError(t, err) 618 619 output = buffer.String() 620 return 621 } 622 623 func TestCacheInContainer(t *testing.T) { 624 if helpers.SkipIntegrationTests(t, "docker", "info") { 625 return 626 } 627 628 successfulBuild, err := common.GetRemoteSuccessfulBuild() 629 assert.NoError(t, err) 630 631 successfulBuild.JobInfo.ProjectID = int(time.Now().Unix()) 632 successfulBuild.Steps[0].Script = common.StepScript{ 633 "(test -d cached/ && ls -lh cached/) || echo \"no cached directory\"", 634 "(test -f cached/date && cat cached/date) || echo \"no cached date\"", 635 "mkdir -p cached", 636 "date > cached/date", 637 } 638 successfulBuild.Cache = common.Caches{ 639 common.Cache{ 640 Key: "key", 641 Paths: common.ArtifactPaths{"cached/*"}, 642 Policy: common.CachePolicyPullPush, 643 }, 644 } 645 646 build := &common.Build{ 647 JobResponse: successfulBuild, 648 Runner: &common.RunnerConfig{ 649 RunnerSettings: common.RunnerSettings{ 650 Executor: "docker", 651 Docker: &common.DockerConfig{ 652 Image: common.TestAlpineImage, 653 PullPolicy: common.PullPolicyIfNotPresent, 654 Volumes: []string{"/cache"}, 655 }, 656 }, 657 }, 658 } 659 660 cacheNotPresentRE := regexp.MustCompile("(?m)^no cached directory") 661 skipCacheDownload := "Not downloading cache key due to policy" 662 skipCacheUpload := "Not uploading cache key due to policy" 663 664 // The first job lacks any cache to pull, but tries to both pull and push 665 output := runTestJobWithOutput(t, build) 666 assert.Regexp(t, cacheNotPresentRE, output, "First job execution should not have cached data") 667 assert.NotContains(t, output, skipCacheDownload, "Cache download should be performed with policy: %s", common.CachePolicyPullPush) 668 assert.NotContains(t, output, skipCacheUpload, "Cache upload should be performed with policy: %s", common.CachePolicyPullPush) 669 670 // pull-only jobs should skip the push step 671 build.JobResponse.Cache[0].Policy = common.CachePolicyPull 672 output = runTestJobWithOutput(t, build) 673 assert.NotRegexp(t, cacheNotPresentRE, output, "Second job execution should have cached data") 674 assert.NotContains(t, output, skipCacheDownload, "Cache download should be performed with policy: %s", common.CachePolicyPull) 675 assert.Contains(t, output, skipCacheUpload, "Cache upload should be skipped with policy: %s", common.CachePolicyPull) 676 677 // push-only jobs should skip the pull step 678 build.JobResponse.Cache[0].Policy = common.CachePolicyPush 679 output = runTestJobWithOutput(t, build) 680 assert.Regexp(t, cacheNotPresentRE, output, "Third job execution should not have cached data") 681 assert.Contains(t, output, skipCacheDownload, "Cache download be skipped with policy: push") 682 assert.NotContains(t, output, skipCacheUpload, "Cache upload should be performed with policy: push") 683 } 684 685 func TestDockerImageNameFromVariable(t *testing.T) { 686 if helpers.SkipIntegrationTests(t, "docker", "info") { 687 return 688 } 689 690 successfulBuild, err := common.GetRemoteSuccessfulBuild() 691 successfulBuild.Variables = append(successfulBuild.Variables, common.JobVariable{ 692 Key: "CI_REGISTRY_IMAGE", 693 Value: common.TestAlpineImage, 694 }) 695 successfulBuild.Image = common.Image{ 696 Name: "$CI_REGISTRY_IMAGE", 697 } 698 assert.NoError(t, err) 699 build := &common.Build{ 700 JobResponse: successfulBuild, 701 Runner: &common.RunnerConfig{ 702 RunnerSettings: common.RunnerSettings{ 703 Executor: "docker", 704 Docker: &common.DockerConfig{ 705 Image: common.TestAlpineImage, 706 PullPolicy: common.PullPolicyIfNotPresent, 707 AllowedServices: []string{common.TestAlpineImage}, 708 }, 709 }, 710 }, 711 } 712 713 re := regexp.MustCompile("(?m)^ERROR: The [^ ]+ is not present on list of allowed images") 714 715 output := runTestJobWithOutput(t, build) 716 assert.NotRegexp(t, re, output, "Image's name should be expanded from variable") 717 } 718 719 func TestDockerServiceNameFromVariable(t *testing.T) { 720 if helpers.SkipIntegrationTests(t, "docker", "info") { 721 return 722 } 723 724 successfulBuild, err := common.GetRemoteSuccessfulBuild() 725 successfulBuild.Variables = append(successfulBuild.Variables, common.JobVariable{ 726 Key: "CI_REGISTRY_IMAGE", 727 Value: common.TestAlpineImage, 728 }) 729 successfulBuild.Services = append(successfulBuild.Services, common.Image{ 730 Name: "$CI_REGISTRY_IMAGE", 731 }) 732 assert.NoError(t, err) 733 build := &common.Build{ 734 JobResponse: successfulBuild, 735 Runner: &common.RunnerConfig{ 736 RunnerSettings: common.RunnerSettings{ 737 Executor: "docker", 738 Docker: &common.DockerConfig{ 739 Image: common.TestAlpineImage, 740 PullPolicy: common.PullPolicyIfNotPresent, 741 AllowedServices: []string{common.TestAlpineImage}, 742 }, 743 }, 744 }, 745 } 746 747 re := regexp.MustCompile("(?m)^ERROR: The [^ ]+ is not present on list of allowed services") 748 749 output := runTestJobWithOutput(t, build) 750 assert.NotRegexp(t, re, output, "Service's name should be expanded from variable") 751 } 752 753 func runDockerInDocker(version string) (id string, err error) { 754 cmd := exec.Command("docker", "run", "--detach", "--privileged", "-p", "2375", "docker:"+version+"-dind") 755 cmd.Stderr = os.Stderr 756 data, err := cmd.Output() 757 if err != nil { 758 return 759 } 760 id = strings.TrimSpace(string(data)) 761 return 762 } 763 764 func getDockerCredentials(id string) (credentials docker_helpers.DockerCredentials, err error) { 765 cmd := exec.Command("docker", "port", id, "2375") 766 cmd.Stderr = os.Stderr 767 data, err := cmd.Output() 768 if err != nil { 769 return 770 } 771 772 hostPort := strings.Split(strings.TrimSpace(string(data)), ":") 773 if dockerHost, err := url.Parse(os.Getenv("DOCKER_HOST")); err == nil { 774 dockerHostPort := strings.Split(dockerHost.Host, ":") 775 hostPort[0] = dockerHostPort[0] 776 } else if hostPort[0] == "0.0.0.0" { 777 hostPort[0] = "localhost" 778 } 779 credentials.Host = "tcp://" + hostPort[0] + ":" + hostPort[1] 780 return 781 } 782 783 func waitForDocker(credentials docker_helpers.DockerCredentials) error { 784 client, err := docker_helpers.New(credentials, "") 785 if err != nil { 786 return err 787 } 788 789 for i := 0; i < 20; i++ { 790 _, err = client.Info(context.Background()) 791 if err == nil { 792 break 793 } 794 time.Sleep(time.Second) 795 } 796 return err 797 } 798 799 func testDockerVersion(t *testing.T, version string) { 800 t.Log("Running docker", version, "...") 801 id, err := runDockerInDocker(version) 802 if err != nil { 803 t.Error("Docker run:", err) 804 return 805 } 806 807 defer func() { 808 exec.Command("docker", "rm", "-f", "-v", id).Run() 809 }() 810 811 t.Log("Getting address of", version, "...") 812 credentials, err := getDockerCredentials(id) 813 if err != nil { 814 t.Error("Docker credentials:", err) 815 return 816 } 817 818 t.Log("Connecting to", credentials.Host, "...") 819 err = waitForDocker(credentials) 820 if err != nil { 821 t.Error("Wait for docker:", err) 822 return 823 } 824 825 t.Log("Docker", version, "is running at", credentials.Host) 826 827 successfulBuild, err := common.GetRemoteSuccessfulBuild() 828 assert.NoError(t, err) 829 build := &common.Build{ 830 JobResponse: successfulBuild, 831 Runner: &common.RunnerConfig{ 832 RunnerSettings: common.RunnerSettings{ 833 Executor: "docker", 834 Docker: &common.DockerConfig{ 835 Image: common.TestAlpineImage, 836 PullPolicy: common.PullPolicyIfNotPresent, 837 DockerCredentials: credentials, 838 CPUS: "0.1", 839 }, 840 }, 841 }, 842 } 843 844 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 845 assert.NoError(t, err) 846 } 847 848 func TestDocker1_8Compatibility(t *testing.T) { 849 if helpers.SkipIntegrationTests(t, "docker", "info") { 850 return 851 } 852 if os.Getenv("CI") != "" { 853 t.Skip("This test doesn't work in nested dind") 854 return 855 } 856 857 testDockerVersion(t, "1.8") 858 } 859 860 func TestDocker1_9Compatibility(t *testing.T) { 861 if helpers.SkipIntegrationTests(t, "docker", "info") { 862 return 863 } 864 if os.Getenv("CI") != "" { 865 t.Skip("This test doesn't work in nested dind") 866 return 867 } 868 869 testDockerVersion(t, "1.9") 870 } 871 872 func TestDocker1_10Compatibility(t *testing.T) { 873 if helpers.SkipIntegrationTests(t, "docker", "info") { 874 return 875 } 876 if os.Getenv("CI") != "" { 877 t.Skip("This test doesn't work in nested dind") 878 return 879 } 880 881 testDockerVersion(t, "1.10") 882 } 883 884 func TestDocker1_11Compatibility(t *testing.T) { 885 if helpers.SkipIntegrationTests(t, "docker", "info") { 886 return 887 } 888 if os.Getenv("CI") != "" { 889 t.Skip("This test doesn't work in nested dind") 890 return 891 } 892 893 testDockerVersion(t, "1.11") 894 } 895 896 func TestDocker1_12Compatibility(t *testing.T) { 897 if helpers.SkipIntegrationTests(t, "docker", "info") { 898 return 899 } 900 if os.Getenv("CI") != "" { 901 t.Skip("This test doesn't work in nested dind") 902 return 903 } 904 905 testDockerVersion(t, "1.12") 906 } 907 908 func TestDocker1_13Compatibility(t *testing.T) { 909 if helpers.SkipIntegrationTests(t, "docker", "info") { 910 return 911 } 912 if os.Getenv("CI") != "" { 913 t.Skip("This test doesn't work in nested dind") 914 return 915 } 916 917 testDockerVersion(t, "1.13") 918 } 919 920 func TestDockerCommandWithBrokenGitSSLCAInfo(t *testing.T) { 921 if helpers.SkipIntegrationTests(t, "docker", "info") { 922 return 923 } 924 925 successfulBuild, err := common.GetRemoteBrokenTLSBuild() 926 assert.NoError(t, err) 927 build := &common.Build{ 928 JobResponse: successfulBuild, 929 Runner: &common.RunnerConfig{ 930 RunnerCredentials: common.RunnerCredentials{ 931 URL: "https://gitlab.com", 932 }, 933 RunnerSettings: common.RunnerSettings{ 934 Executor: "docker", 935 Docker: &common.DockerConfig{ 936 Image: common.TestAlpineImage, 937 PullPolicy: common.PullPolicyIfNotPresent, 938 }, 939 }, 940 }, 941 } 942 943 var buffer bytes.Buffer 944 945 err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer}) 946 assert.Error(t, err) 947 out := buffer.String() 948 assert.Contains(t, out, "Created fresh repository") 949 assert.NotContains(t, out, "Updating/initializing submodules") 950 } 951 952 func TestDockerCommandWithGitSSLCAInfo(t *testing.T) { 953 if helpers.SkipIntegrationTests(t, "docker", "info") { 954 return 955 } 956 957 successfulBuild, err := common.GetRemoteGitLabComTLSBuild() 958 assert.NoError(t, err) 959 build := &common.Build{ 960 JobResponse: successfulBuild, 961 Runner: &common.RunnerConfig{ 962 RunnerCredentials: common.RunnerCredentials{ 963 URL: "https://gitlab.com", 964 }, 965 RunnerSettings: common.RunnerSettings{ 966 Executor: "docker", 967 Docker: &common.DockerConfig{ 968 Image: common.TestAlpineImage, 969 PullPolicy: common.PullPolicyIfNotPresent, 970 }, 971 }, 972 }, 973 } 974 975 var buffer bytes.Buffer 976 977 err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer}) 978 assert.NoError(t, err) 979 out := buffer.String() 980 assert.Contains(t, out, "Created fresh repository") 981 assert.Contains(t, out, "Updating/initializing submodules") 982 } 983 984 func TestDockerCommandWithHelperImageConfig(t *testing.T) { 985 if helpers.SkipIntegrationTests(t, "docker", "info") { 986 return 987 } 988 989 helperImageConfig := "gitlab/gitlab-runner-helper:x86_64-5a147c92" 990 991 successfulBuild, err := common.GetRemoteSuccessfulBuild() 992 assert.NoError(t, err) 993 build := &common.Build{ 994 JobResponse: successfulBuild, 995 Runner: &common.RunnerConfig{ 996 RunnerSettings: common.RunnerSettings{ 997 Executor: "docker", 998 Docker: &common.DockerConfig{ 999 Image: common.TestAlpineImage, 1000 HelperImage: helperImageConfig, 1001 PullPolicy: common.PullPolicyIfNotPresent, 1002 }, 1003 }, 1004 }, 1005 } 1006 1007 var buffer bytes.Buffer 1008 err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer}) 1009 assert.NoError(t, err) 1010 out := buffer.String() 1011 assert.Contains(t, out, "Using docker image sha256:3cf24b1b62b6a4c55c5de43db4f50c0ff8b455238c836945d4b5c645411bfc77 for gitlab/gitlab-runner-helper:x86_64-5a147c92 ...") 1012 } 1013 1014 func TestDockerCommandWithDoingPruneAndAfterScript(t *testing.T) { 1015 if helpers.SkipIntegrationTests(t, "docker", "info") { 1016 return 1017 } 1018 1019 successfulBuild, err := common.GetRemoteSuccessfulBuildWithAfterScript() 1020 1021 // This scripts removes self-created containers that do exit 1022 // It will fail if: cannot be removed, or no containers is found 1023 // It is assuming that name of each runner created container starts 1024 // with `runner-doprune-` 1025 successfulBuild.Steps[0].Script = common.StepScript{ 1026 "docker ps -a -f status=exited | grep runner-doprune-", 1027 "docker rm $(docker ps -a -f status=exited | grep runner-doprune- | awk '{print $1}')", 1028 } 1029 1030 assert.NoError(t, err) 1031 build := &common.Build{ 1032 JobResponse: successfulBuild, 1033 Runner: &common.RunnerConfig{ 1034 RunnerCredentials: common.RunnerCredentials{ 1035 Token: "doprune", 1036 }, 1037 RunnerSettings: common.RunnerSettings{ 1038 Executor: "docker", 1039 Docker: &common.DockerConfig{ 1040 Image: common.TestDockerGitImage, 1041 PullPolicy: common.PullPolicyIfNotPresent, 1042 Volumes: []string{ 1043 "/var/run/docker.sock:/var/run/docker.sock", 1044 }, 1045 }, 1046 }, 1047 }, 1048 } 1049 1050 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 1051 assert.NoError(t, err) 1052 } 1053 1054 func TestDockerCommandUsingBuildsVolume(t *testing.T) { 1055 if helpers.SkipIntegrationTests(t, "docker", "info") { 1056 return 1057 } 1058 1059 const buildsPath = "/builds" 1060 // the path is taken from `repoRemoteURL` 1061 const buildsGroupPath = "/builds/gitlab-org/ci-cd/tests" 1062 1063 tests := map[string]struct { 1064 validPath string 1065 invalidPath string 1066 variable string 1067 }{ 1068 "uses default state of FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER": { 1069 validPath: buildsPath, 1070 invalidPath: buildsGroupPath, 1071 variable: "", 1072 }, 1073 "disables FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER": { 1074 validPath: buildsPath, 1075 invalidPath: buildsGroupPath, 1076 variable: "FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER=false", 1077 }, 1078 "enables FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER": { 1079 validPath: buildsGroupPath, 1080 invalidPath: buildsPath, 1081 variable: "FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER=true", 1082 }, 1083 } 1084 1085 for name, test := range tests { 1086 t.Run(name, func(t *testing.T) { 1087 jobResponse, err := common.GetRemoteBuildResponse( 1088 "mountpoint "+test.validPath, 1089 "! mountpoint "+test.invalidPath, 1090 ) 1091 require.NoError(t, err) 1092 1093 build := &common.Build{ 1094 JobResponse: jobResponse, 1095 Runner: &common.RunnerConfig{ 1096 RunnerSettings: common.RunnerSettings{ 1097 Executor: "docker", 1098 Docker: &common.DockerConfig{ 1099 Image: common.TestAlpineImage, 1100 PullPolicy: common.PullPolicyIfNotPresent, 1101 }, 1102 Environment: []string{ 1103 "GIT_STRATEGY=none", 1104 test.variable, 1105 }, 1106 }, 1107 }, 1108 } 1109 1110 err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout}) 1111 assert.NoError(t, err) 1112 }) 1113 } 1114 }