gitlab.com/jfprevost/gitlab-runner-notlscheck@v11.11.4+incompatible/executors/docker/executor_docker_test.go (about) 1 package docker 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "flag" 8 "fmt" 9 "io/ioutil" 10 "os" 11 "path" 12 "strings" 13 "sync" 14 "testing" 15 "time" 16 17 "github.com/docker/docker/api/types" 18 "github.com/docker/docker/api/types/container" 19 "github.com/docker/docker/api/types/network" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/mock" 22 "github.com/stretchr/testify/require" 23 24 "gitlab.com/gitlab-org/gitlab-runner/common" 25 "gitlab.com/gitlab-org/gitlab-runner/executors" 26 "gitlab.com/gitlab-org/gitlab-runner/executors/docker/internal/volumes" 27 "gitlab.com/gitlab-org/gitlab-runner/executors/docker/internal/volumes/parser" 28 "gitlab.com/gitlab-org/gitlab-runner/helpers" 29 docker_helpers "gitlab.com/gitlab-org/gitlab-runner/helpers/docker" 30 "gitlab.com/gitlab-org/gitlab-runner/helpers/docker/helperimage" 31 "gitlab.com/gitlab-org/gitlab-runner/helpers/featureflags" 32 ) 33 34 func TestMain(m *testing.M) { 35 DockerPrebuiltImagesPaths = []string{"../../out/helper-images/"} 36 37 flag.Parse() 38 os.Exit(m.Run()) 39 } 40 41 // ImagePullOptions contains the RegistryAuth which is inferred from the docker 42 // configuration for the user, so just mock it out here. 43 func buildImagePullOptions(e executor, configName string) mock.AnythingOfTypeArgument { 44 return mock.AnythingOfType("ImagePullOptions") 45 } 46 47 func TestParseDeviceStringOne(t *testing.T) { 48 e := executor{} 49 50 device, err := e.parseDeviceString("/dev/kvm") 51 52 assert.NoError(t, err) 53 assert.Equal(t, "/dev/kvm", device.PathOnHost) 54 assert.Equal(t, "/dev/kvm", device.PathInContainer) 55 assert.Equal(t, "rwm", device.CgroupPermissions) 56 } 57 58 func TestParseDeviceStringTwo(t *testing.T) { 59 e := executor{} 60 61 device, err := e.parseDeviceString("/dev/kvm:/devices/kvm") 62 63 assert.NoError(t, err) 64 assert.Equal(t, "/dev/kvm", device.PathOnHost) 65 assert.Equal(t, "/devices/kvm", device.PathInContainer) 66 assert.Equal(t, "rwm", device.CgroupPermissions) 67 } 68 69 func TestParseDeviceStringThree(t *testing.T) { 70 e := executor{} 71 72 device, err := e.parseDeviceString("/dev/kvm:/devices/kvm:r") 73 74 assert.NoError(t, err) 75 assert.Equal(t, "/dev/kvm", device.PathOnHost) 76 assert.Equal(t, "/devices/kvm", device.PathInContainer) 77 assert.Equal(t, "r", device.CgroupPermissions) 78 } 79 80 func TestParseDeviceStringFour(t *testing.T) { 81 e := executor{} 82 83 _, err := e.parseDeviceString("/dev/kvm:/devices/kvm:r:oops") 84 85 assert.Error(t, err) 86 } 87 88 type testAllowedImageDescription struct { 89 allowed bool 90 image string 91 allowedImages []string 92 } 93 94 var testAllowedImages = []testAllowedImageDescription{ 95 {true, "ruby", []string{"*"}}, 96 {true, "ruby:2.1", []string{"*"}}, 97 {true, "ruby:latest", []string{"*"}}, 98 {true, "library/ruby", []string{"*/*"}}, 99 {true, "library/ruby:2.1", []string{"*/*"}}, 100 {true, "library/ruby:2.1", []string{"*/*:*"}}, 101 {true, "my.registry.tld/library/ruby", []string{"my.registry.tld/*/*"}}, 102 {true, "my.registry.tld/library/ruby:2.1", []string{"my.registry.tld/*/*:*"}}, 103 {true, "my.registry.tld/group/subgroup/ruby", []string{"my.registry.tld/*/*/*"}}, 104 {true, "my.registry.tld/group/subgroup/ruby:2.1", []string{"my.registry.tld/*/*/*:*"}}, 105 {true, "ruby", []string{"**/*"}}, 106 {true, "ruby:2.1", []string{"**/*"}}, 107 {true, "ruby:latest", []string{"**/*"}}, 108 {true, "library/ruby", []string{"**/*"}}, 109 {true, "library/ruby:2.1", []string{"**/*"}}, 110 {true, "library/ruby:2.1", []string{"**/*:*"}}, 111 {true, "my.registry.tld/library/ruby", []string{"my.registry.tld/**/*"}}, 112 {true, "my.registry.tld/library/ruby:2.1", []string{"my.registry.tld/**/*:*"}}, 113 {true, "my.registry.tld/group/subgroup/ruby", []string{"my.registry.tld/**/*"}}, 114 {true, "my.registry.tld/group/subgroup/ruby:2.1", []string{"my.registry.tld/**/*:*"}}, 115 {false, "library/ruby", []string{"*"}}, 116 {false, "library/ruby:2.1", []string{"*"}}, 117 {false, "my.registry.tld/ruby", []string{"*"}}, 118 {false, "my.registry.tld/ruby:2.1", []string{"*"}}, 119 {false, "my.registry.tld/library/ruby", []string{"*"}}, 120 {false, "my.registry.tld/library/ruby:2.1", []string{"*"}}, 121 {false, "my.registry.tld/group/subgroup/ruby", []string{"*"}}, 122 {false, "my.registry.tld/group/subgroup/ruby:2.1", []string{"*"}}, 123 {false, "library/ruby", []string{"*/*:*"}}, 124 {false, "my.registry.tld/group/subgroup/ruby", []string{"my.registry.tld/*/*"}}, 125 {false, "my.registry.tld/group/subgroup/ruby:2.1", []string{"my.registry.tld/*/*:*"}}, 126 {false, "library/ruby", []string{"**/*:*"}}, 127 } 128 129 func TestVerifyAllowedImage(t *testing.T) { 130 e := executor{} 131 132 for _, test := range testAllowedImages { 133 err := e.verifyAllowedImage(test.image, "", test.allowedImages, []string{}) 134 135 if err != nil && test.allowed { 136 t.Errorf("%q must be allowed by %q", test.image, test.allowedImages) 137 } else if err == nil && !test.allowed { 138 t.Errorf("%q must not be allowed by %q", test.image, test.allowedImages) 139 } 140 } 141 } 142 143 type testServiceDescription struct { 144 description string 145 image string 146 service string 147 version string 148 alias string 149 alternative string 150 } 151 152 var testServices = []testServiceDescription{ 153 {"service", "service:latest", "service", "latest", "service", ""}, 154 {"service:version", "service:version", "service", "version", "service", ""}, 155 {"namespace/service", "namespace/service:latest", "namespace/service", "latest", "namespace__service", "namespace-service"}, 156 {"namespace/service:version", "namespace/service:version", "namespace/service", "version", "namespace__service", "namespace-service"}, 157 {"domain.tld/service", "domain.tld/service:latest", "domain.tld/service", "latest", "domain.tld__service", "domain.tld-service"}, 158 {"domain.tld/service:version", "domain.tld/service:version", "domain.tld/service", "version", "domain.tld__service", "domain.tld-service"}, 159 {"domain.tld/namespace/service", "domain.tld/namespace/service:latest", "domain.tld/namespace/service", "latest", "domain.tld__namespace__service", "domain.tld-namespace-service"}, 160 {"domain.tld/namespace/service:version", "domain.tld/namespace/service:version", "domain.tld/namespace/service", "version", "domain.tld__namespace__service", "domain.tld-namespace-service"}, 161 {"domain.tld:8080/service", "domain.tld:8080/service:latest", "domain.tld/service", "latest", "domain.tld__service", "domain.tld-service"}, 162 {"domain.tld:8080/service:version", "domain.tld:8080/service:version", "domain.tld/service", "version", "domain.tld__service", "domain.tld-service"}, 163 {"domain.tld:8080/namespace/service", "domain.tld:8080/namespace/service:latest", "domain.tld/namespace/service", "latest", "domain.tld__namespace__service", "domain.tld-namespace-service"}, 164 {"domain.tld:8080/namespace/service:version", "domain.tld:8080/namespace/service:version", "domain.tld/namespace/service", "version", "domain.tld__namespace__service", "domain.tld-namespace-service"}, 165 {"subdomain.domain.tld:8080/service", "subdomain.domain.tld:8080/service:latest", "subdomain.domain.tld/service", "latest", "subdomain.domain.tld__service", "subdomain.domain.tld-service"}, 166 {"subdomain.domain.tld:8080/service:version", "subdomain.domain.tld:8080/service:version", "subdomain.domain.tld/service", "version", "subdomain.domain.tld__service", "subdomain.domain.tld-service"}, 167 {"subdomain.domain.tld:8080/namespace/service", "subdomain.domain.tld:8080/namespace/service:latest", "subdomain.domain.tld/namespace/service", "latest", "subdomain.domain.tld__namespace__service", "subdomain.domain.tld-namespace-service"}, 168 {"subdomain.domain.tld:8080/namespace/service:version", "subdomain.domain.tld:8080/namespace/service:version", "subdomain.domain.tld/namespace/service", "version", "subdomain.domain.tld__namespace__service", "subdomain.domain.tld-namespace-service"}, 169 } 170 171 func testSplitService(t *testing.T, test testServiceDescription) { 172 e := executor{} 173 service, version, imageName, linkNames := e.splitServiceAndVersion(test.description) 174 175 assert.Equal(t, test.service, service, "service for "+test.description) 176 assert.Equal(t, test.version, version, "version for "+test.description) 177 assert.Equal(t, test.image, imageName, "image for "+test.description) 178 assert.Equal(t, test.alias, linkNames[0], "alias for "+test.description) 179 if test.alternative != "" { 180 assert.Len(t, linkNames, 2, "linkNames len for "+test.description) 181 assert.Equal(t, test.alternative, linkNames[1], "alternative for "+test.description) 182 } else { 183 assert.Len(t, linkNames, 1, "linkNames len for "+test.description) 184 } 185 } 186 187 func TestSplitService(t *testing.T) { 188 for _, test := range testServices { 189 t.Run(test.description, func(t *testing.T) { 190 testSplitService(t, test) 191 }) 192 } 193 } 194 195 func testServiceFromNamedImage(t *testing.T, description, imageName, serviceName string) { 196 var c docker_helpers.MockClient 197 defer c.AssertExpectations(t) 198 199 containerName := fmt.Sprintf("runner-abcdef12-project-0-concurrent-0-%s-0", strings.Replace(serviceName, "/", "__", -1)) 200 networkID := "network-id" 201 202 e := executor{ 203 client: &c, 204 info: types.Info{ 205 OSType: helperimage.OSTypeLinux, 206 Architecture: "amd64", 207 }, 208 volumeParser: parser.NewLinuxParser(), 209 } 210 211 options := buildImagePullOptions(e, imageName) 212 e.Config = common.RunnerConfig{} 213 e.Config.Docker = &common.DockerConfig{} 214 e.Build = &common.Build{ 215 ProjectRunnerID: 0, 216 Runner: &common.RunnerConfig{}, 217 } 218 e.Build.JobInfo.ProjectID = 0 219 e.Build.Runner.Token = "abcdef1234567890" 220 e.Context = context.Background() 221 var err error 222 e.helperImageInfo, err = helperimage.Get(common.REVISION, helperimage.Config{ 223 OSType: e.info.OSType, 224 Architecture: e.info.Architecture, 225 OperatingSystem: e.info.OperatingSystem, 226 }) 227 require.NoError(t, err) 228 229 c.On("ImagePullBlocking", e.Context, imageName, options). 230 Return(nil). 231 Once() 232 233 c.On("ImageInspectWithRaw", e.Context, imageName). 234 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 235 Twice() 236 237 c.On("ContainerRemove", e.Context, containerName, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}). 238 Return(nil). 239 Once() 240 241 networkContainersMap := map[string]types.EndpointResource{ 242 "1": {Name: containerName}, 243 } 244 245 c.On("NetworkList", e.Context, types.NetworkListOptions{}). 246 Return([]types.NetworkResource{{ID: networkID, Name: "network-name", Containers: networkContainersMap}}, nil). 247 Once() 248 249 c.On("NetworkDisconnect", e.Context, networkID, containerName, true). 250 Return(nil). 251 Once() 252 253 c.On("ImageInspectWithRaw", mock.Anything, "gitlab/gitlab-runner-helper:x86_64-latest"). 254 Return(types.ImageInspect{ID: "helper-image-id"}, nil, nil). 255 Once() 256 257 c.On("ContainerCreate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). 258 Return(container.ContainerCreateCreatedBody{ID: containerName}, nil). 259 Once() 260 261 c.On("ContainerStart", e.Context, mock.Anything, mock.Anything). 262 Return(nil). 263 Once() 264 265 err = e.createVolumesManager() 266 require.NoError(t, err) 267 268 linksMap := make(map[string]*types.Container) 269 err = e.createFromServiceDefinition(0, common.Image{Name: description}, linksMap) 270 assert.NoError(t, err) 271 } 272 273 func TestServiceFromNamedImage(t *testing.T) { 274 for _, test := range testServices { 275 t.Run(test.description, func(t *testing.T) { 276 testServiceFromNamedImage(t, test.description, test.image, test.service) 277 }) 278 } 279 } 280 281 func TestDockerForNamedImage(t *testing.T) { 282 var c docker_helpers.MockClient 283 defer c.AssertExpectations(t) 284 validSHA := "real@sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c" 285 286 e := executor{client: &c} 287 e.Context = context.Background() 288 options := buildImagePullOptions(e, "test") 289 290 c.On("ImagePullBlocking", e.Context, "test:latest", options). 291 Return(os.ErrNotExist). 292 Once() 293 294 c.On("ImagePullBlocking", e.Context, "tagged:tag", options). 295 Return(os.ErrNotExist). 296 Once() 297 298 c.On("ImagePullBlocking", e.Context, validSHA, options). 299 Return(os.ErrNotExist). 300 Once() 301 302 image, err := e.pullDockerImage("test", nil) 303 assert.Error(t, err) 304 assert.Nil(t, image) 305 306 image, err = e.pullDockerImage("tagged:tag", nil) 307 assert.Error(t, err) 308 assert.Nil(t, image) 309 310 image, err = e.pullDockerImage(validSHA, nil) 311 assert.Error(t, err) 312 assert.Nil(t, image) 313 } 314 315 func TestDockerForExistingImage(t *testing.T) { 316 var c docker_helpers.MockClient 317 defer c.AssertExpectations(t) 318 319 e := executor{client: &c} 320 e.Context = context.Background() 321 options := buildImagePullOptions(e, "existing") 322 323 c.On("ImagePullBlocking", e.Context, "existing:latest", options). 324 Return(nil). 325 Once() 326 327 c.On("ImageInspectWithRaw", e.Context, "existing"). 328 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 329 Once() 330 331 image, err := e.pullDockerImage("existing", nil) 332 assert.NoError(t, err) 333 assert.NotNil(t, image) 334 } 335 336 func TestHelperImageWithVariable(t *testing.T) { 337 c := new(docker_helpers.MockClient) 338 defer c.AssertExpectations(t) 339 340 c.On("ImageInspectWithRaw", mock.Anything, "gitlab/gitlab-runner:HEAD"). 341 Return(types.ImageInspect{}, nil, errors.New("not found")). 342 Once() 343 c.On("ImagePullBlocking", mock.Anything, "gitlab/gitlab-runner:HEAD", mock.Anything). 344 Return(nil). 345 Once() 346 c.On("ImageInspectWithRaw", mock.Anything, "gitlab/gitlab-runner:HEAD"). 347 Return(types.ImageInspect{ID: "helper-image"}, nil, nil). 348 Once() 349 350 e := executor{ 351 AbstractExecutor: executors.AbstractExecutor{ 352 Build: &common.Build{ 353 JobResponse: common.JobResponse{}, 354 }, 355 }, 356 client: c, 357 } 358 359 e.Config = common.RunnerConfig{} 360 e.Config.Docker = &common.DockerConfig{ 361 HelperImage: "gitlab/gitlab-runner:${CI_RUNNER_REVISION}", 362 } 363 364 img, err := e.getPrebuiltImage() 365 assert.NoError(t, err) 366 require.NotNil(t, img) 367 assert.Equal(t, "helper-image", img.ID) 368 } 369 370 func (e *executor) setPolicyMode(pullPolicy common.DockerPullPolicy) { 371 e.Config = common.RunnerConfig{ 372 RunnerSettings: common.RunnerSettings{ 373 Docker: &common.DockerConfig{ 374 PullPolicy: pullPolicy, 375 }, 376 }, 377 } 378 } 379 380 func TestDockerGetImageById(t *testing.T) { 381 var c docker_helpers.MockClient 382 defer c.AssertExpectations(t) 383 384 // Use default policy 385 e := executor{client: &c} 386 e.Context = context.Background() 387 e.setPolicyMode("") 388 389 c.On("ImageInspectWithRaw", e.Context, "ID"). 390 Return(types.ImageInspect{ID: "ID"}, nil, nil). 391 Once() 392 393 image, err := e.getDockerImage("ID") 394 assert.NoError(t, err) 395 assert.NotNil(t, image) 396 assert.Equal(t, "ID", image.ID) 397 } 398 399 func TestDockerUnknownPolicyMode(t *testing.T) { 400 var c docker_helpers.MockClient 401 defer c.AssertExpectations(t) 402 403 e := executor{client: &c} 404 e.Context = context.Background() 405 e.setPolicyMode("unknown") 406 407 _, err := e.getDockerImage("not-existing") 408 assert.Error(t, err) 409 } 410 411 func TestDockerPolicyModeNever(t *testing.T) { 412 var c docker_helpers.MockClient 413 defer c.AssertExpectations(t) 414 415 e := executor{client: &c} 416 e.Context = context.Background() 417 e.setPolicyMode(common.PullPolicyNever) 418 419 c.On("ImageInspectWithRaw", e.Context, "existing"). 420 Return(types.ImageInspect{ID: "existing"}, nil, nil). 421 Once() 422 423 c.On("ImageInspectWithRaw", e.Context, "not-existing"). 424 Return(types.ImageInspect{}, nil, os.ErrNotExist). 425 Once() 426 427 image, err := e.getDockerImage("existing") 428 assert.NoError(t, err) 429 assert.Equal(t, "existing", image.ID) 430 431 _, err = e.getDockerImage("not-existing") 432 assert.Error(t, err) 433 } 434 435 func TestDockerPolicyModeIfNotPresentForExistingImage(t *testing.T) { 436 var c docker_helpers.MockClient 437 defer c.AssertExpectations(t) 438 439 e := executor{client: &c} 440 e.Context = context.Background() 441 e.setPolicyMode(common.PullPolicyIfNotPresent) 442 443 c.On("ImageInspectWithRaw", e.Context, "existing"). 444 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 445 Once() 446 447 image, err := e.getDockerImage("existing") 448 assert.NoError(t, err) 449 assert.NotNil(t, image) 450 } 451 452 func TestDockerPolicyModeIfNotPresentForNotExistingImage(t *testing.T) { 453 var c docker_helpers.MockClient 454 defer c.AssertExpectations(t) 455 456 e := executor{client: &c} 457 e.Context = context.Background() 458 e.setPolicyMode(common.PullPolicyIfNotPresent) 459 460 c.On("ImageInspectWithRaw", e.Context, "not-existing"). 461 Return(types.ImageInspect{}, nil, os.ErrNotExist). 462 Once() 463 464 options := buildImagePullOptions(e, "not-existing") 465 c.On("ImagePullBlocking", e.Context, "not-existing:latest", options). 466 Return(nil). 467 Once() 468 469 c.On("ImageInspectWithRaw", e.Context, "not-existing"). 470 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 471 Once() 472 473 image, err := e.getDockerImage("not-existing") 474 assert.NoError(t, err) 475 assert.NotNil(t, image) 476 477 c.On("ImageInspectWithRaw", e.Context, "not-existing"). 478 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 479 Once() 480 481 // It shouldn't execute the pull for second time 482 image, err = e.getDockerImage("not-existing") 483 assert.NoError(t, err) 484 assert.NotNil(t, image) 485 } 486 487 func TestDockerPolicyModeAlwaysForExistingImage(t *testing.T) { 488 var c docker_helpers.MockClient 489 defer c.AssertExpectations(t) 490 491 e := executor{client: &c} 492 e.Context = context.Background() 493 e.setPolicyMode(common.PullPolicyAlways) 494 495 c.On("ImageInspectWithRaw", e.Context, "existing"). 496 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 497 Once() 498 499 options := buildImagePullOptions(e, "existing:latest") 500 c.On("ImagePullBlocking", e.Context, "existing:latest", options). 501 Return(nil). 502 Once() 503 504 c.On("ImageInspectWithRaw", e.Context, "existing"). 505 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 506 Once() 507 508 image, err := e.getDockerImage("existing") 509 assert.NoError(t, err) 510 assert.NotNil(t, image) 511 } 512 513 func TestDockerPolicyModeAlwaysForLocalOnlyImage(t *testing.T) { 514 var c docker_helpers.MockClient 515 defer c.AssertExpectations(t) 516 517 e := executor{client: &c} 518 e.Context = context.Background() 519 e.setPolicyMode(common.PullPolicyAlways) 520 521 c.On("ImageInspectWithRaw", e.Context, "existing"). 522 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 523 Once() 524 525 options := buildImagePullOptions(e, "existing:lastest") 526 c.On("ImagePullBlocking", e.Context, "existing:latest", options). 527 Return(fmt.Errorf("not found")). 528 Once() 529 530 image, err := e.getDockerImage("existing") 531 assert.Error(t, err) 532 assert.Nil(t, image) 533 } 534 535 func TestDockerGetExistingDockerImageIfPullFails(t *testing.T) { 536 var c docker_helpers.MockClient 537 defer c.AssertExpectations(t) 538 539 e := executor{client: &c} 540 e.Context = context.Background() 541 e.setPolicyMode(common.PullPolicyAlways) 542 543 c.On("ImageInspectWithRaw", e.Context, "to-pull"). 544 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 545 Once() 546 547 options := buildImagePullOptions(e, "to-pull") 548 c.On("ImagePullBlocking", e.Context, "to-pull:latest", options). 549 Return(os.ErrNotExist). 550 Once() 551 552 image, err := e.getDockerImage("to-pull") 553 assert.Error(t, err) 554 assert.Nil(t, image, "Forces to authorize pulling") 555 556 c.On("ImageInspectWithRaw", e.Context, "not-existing"). 557 Return(types.ImageInspect{}, nil, os.ErrNotExist). 558 Once() 559 560 c.On("ImagePullBlocking", e.Context, "not-existing:latest", options). 561 Return(os.ErrNotExist). 562 Once() 563 564 image, err = e.getDockerImage("not-existing") 565 assert.Error(t, err) 566 assert.Nil(t, image, "No existing image") 567 } 568 569 func TestPrepareBuildsDir(t *testing.T) { 570 tests := map[string]struct { 571 parser parser.Parser 572 rootDir string 573 volumes []string 574 expectedSharedBuildsDir bool 575 expectedError string 576 }{ 577 "rootDir mounted as host based volume": { 578 parser: parser.NewLinuxParser(), 579 rootDir: "/build", 580 volumes: []string{"/build:/build"}, 581 expectedSharedBuildsDir: true, 582 }, 583 "rootDir mounted as container based volume": { 584 parser: parser.NewLinuxParser(), 585 rootDir: "/build", 586 volumes: []string{"/build"}, 587 expectedSharedBuildsDir: false, 588 }, 589 "rootDir not mounted as volume": { 590 parser: parser.NewLinuxParser(), 591 rootDir: "/build", 592 volumes: []string{"/folder:/folder"}, 593 expectedSharedBuildsDir: false, 594 }, 595 "rootDir's parent mounted as volume": { 596 parser: parser.NewLinuxParser(), 597 rootDir: "/build/other/directory", 598 volumes: []string{"/build/:/build"}, 599 expectedSharedBuildsDir: true, 600 }, 601 "rootDir is not an absolute path": { 602 parser: parser.NewLinuxParser(), 603 rootDir: "builds", 604 expectedError: "build directory needs to be an absolute path", 605 }, 606 "rootDir is /": { 607 parser: parser.NewLinuxParser(), 608 rootDir: "/", 609 expectedError: "build directory needs to be a non-root path", 610 }, 611 "error on volume parsing": { 612 parser: parser.NewLinuxParser(), 613 rootDir: "/build", 614 volumes: []string{""}, 615 expectedError: "invalid volume specification", 616 }, 617 "error on volume parser creation": { 618 expectedError: `missing volume parser`, 619 }, 620 } 621 622 for testName, test := range tests { 623 t.Run(testName, func(t *testing.T) { 624 c := common.RunnerConfig{ 625 RunnerSettings: common.RunnerSettings{ 626 BuildsDir: test.rootDir, 627 Docker: &common.DockerConfig{ 628 Volumes: test.volumes, 629 }, 630 }, 631 } 632 633 options := common.ExecutorPrepareOptions{ 634 Config: &c, 635 } 636 637 e := &executor{ 638 AbstractExecutor: executors.AbstractExecutor{ 639 Config: c, 640 }, 641 volumeParser: test.parser, 642 } 643 644 err := e.prepareBuildsDir(options) 645 if test.expectedError != "" { 646 assert.Error(t, err) 647 assert.Contains(t, err.Error(), test.expectedError) 648 return 649 } 650 651 assert.NoError(t, err) 652 assert.Equal(t, test.expectedSharedBuildsDir, e.SharedBuildsDir) 653 }) 654 } 655 } 656 657 type volumesTestCase struct { 658 volumes []string 659 buildsDir string 660 gitStrategy string 661 adjustConfiguration func(e *executor) 662 volumesManagerAssertions func(*volumes.MockManager) 663 clientAssertions func(*docker_helpers.MockClient) 664 createVolumeManager bool 665 expectedError error 666 } 667 668 var volumesTestsDefaultBuildsDir = "/default-builds-dir" 669 670 func getExecutorForVolumesTests(t *testing.T, test volumesTestCase) (*executor, func()) { 671 clientMock := new(docker_helpers.MockClient) 672 volumesManagerMock := new(volumes.MockManager) 673 674 oldCreateVolumesManager := createVolumesManager 675 closureFn := func() { 676 createVolumesManager = oldCreateVolumesManager 677 678 volumesManagerMock.AssertExpectations(t) 679 clientMock.AssertExpectations(t) 680 } 681 682 createVolumesManager = func(_ *executor) (volumes.Manager, error) { 683 return volumesManagerMock, nil 684 } 685 686 if test.volumesManagerAssertions != nil { 687 test.volumesManagerAssertions(volumesManagerMock) 688 } 689 690 if test.clientAssertions != nil { 691 test.clientAssertions(clientMock) 692 } 693 694 c := common.RunnerConfig{ 695 RunnerCredentials: common.RunnerCredentials{ 696 Token: "abcdef1234567890", 697 }, 698 RunnerSettings: common.RunnerSettings{ 699 BuildsDir: test.buildsDir, 700 Docker: &common.DockerConfig{ 701 Volumes: test.volumes, 702 }, 703 }, 704 } 705 706 e := &executor{ 707 AbstractExecutor: executors.AbstractExecutor{ 708 Build: &common.Build{ 709 ProjectRunnerID: 0, 710 Runner: &c, 711 JobResponse: common.JobResponse{ 712 JobInfo: common.JobInfo{ 713 ProjectID: 0, 714 }, 715 GitInfo: common.GitInfo{ 716 RepoURL: "https://gitlab.example.com/group/project.git", 717 }, 718 }, 719 }, 720 Config: c, 721 ExecutorOptions: executors.ExecutorOptions{ 722 DefaultBuildsDir: volumesTestsDefaultBuildsDir, 723 }, 724 }, 725 client: clientMock, 726 info: types.Info{ 727 OSType: helperimage.OSTypeLinux, 728 }, 729 } 730 731 e.Build.Variables = append(e.Build.Variables, common.JobVariable{ 732 Key: "GIT_STRATEGY", 733 Value: test.gitStrategy, 734 }) 735 736 if test.adjustConfiguration != nil { 737 test.adjustConfiguration(e) 738 } 739 740 err := e.Build.StartBuild( 741 e.RootDir(), 742 e.CacheDir(), 743 e.CustomBuildEnabled(), 744 e.SharedBuildsDir, 745 ) 746 require.NoError(t, err) 747 748 if test.createVolumeManager { 749 err = e.createVolumesManager() 750 require.NoError(t, err) 751 } 752 753 return e, closureFn 754 } 755 756 func TestCreateVolumes(t *testing.T) { 757 tests := map[string]volumesTestCase{ 758 "volumes manager not created": { 759 expectedError: errVolumesManagerUndefined, 760 }, 761 "no volumes defined, empty buildsDir, clone strategy, no errors": { 762 gitStrategy: "clone", 763 createVolumeManager: true, 764 }, 765 "no volumes defined, defined buildsDir, clone strategy, no errors": { 766 buildsDir: "/builds", 767 gitStrategy: "clone", 768 createVolumeManager: true, 769 }, 770 "no volumes defined, defined buildsDir, fetch strategy, no errors": { 771 buildsDir: "/builds", 772 gitStrategy: "fetch", 773 createVolumeManager: true, 774 }, 775 "volumes defined, empty buildsDir, clone strategy, no errors on user volume": { 776 volumes: []string{"/volume"}, 777 gitStrategy: "clone", 778 volumesManagerAssertions: func(vm *volumes.MockManager) { 779 vm.On("Create", "/volume"). 780 Return(nil). 781 Once() 782 }, 783 createVolumeManager: true, 784 }, 785 "volumes defined, empty buildsDir, clone strategy, cache containers disabled error on user volume": { 786 volumes: []string{"/volume"}, 787 gitStrategy: "clone", 788 volumesManagerAssertions: func(vm *volumes.MockManager) { 789 vm.On("Create", "/volume"). 790 Return(volumes.ErrCacheVolumesDisabled). 791 Once() 792 }, 793 createVolumeManager: true, 794 }, 795 "volumes defined, empty buildsDir, clone strategy, duplicated error on user volume": { 796 volumes: []string{"/volume"}, 797 gitStrategy: "clone", 798 volumesManagerAssertions: func(vm *volumes.MockManager) { 799 vm.On("Create", "/volume"). 800 Return(volumes.NewErrVolumeAlreadyDefined("/volume")). 801 Once() 802 }, 803 createVolumeManager: true, 804 expectedError: volumes.NewErrVolumeAlreadyDefined("/volume"), 805 }, 806 "volumes defined, empty buildsDir, clone strategy, other error on user volume": { 807 volumes: []string{"/volume"}, 808 gitStrategy: "clone", 809 volumesManagerAssertions: func(vm *volumes.MockManager) { 810 vm.On("Create", "/volume"). 811 Return(errors.New("test-error")). 812 Once() 813 }, 814 createVolumeManager: true, 815 expectedError: errors.New("test-error"), 816 }, 817 } 818 819 for testName, test := range tests { 820 t.Run(testName, func(t *testing.T) { 821 e, closureFn := getExecutorForVolumesTests(t, test) 822 defer closureFn() 823 824 err := e.createVolumes() 825 assert.Equal(t, test.expectedError, err) 826 }) 827 } 828 } 829 830 func TestCreateBuildVolume(t *testing.T) { 831 tests := map[string]volumesTestCase{ 832 "volumes manager not created": { 833 expectedError: errVolumesManagerUndefined, 834 }, 835 "git strategy clone, empty buildsDir, no error": { 836 gitStrategy: "clone", 837 volumesManagerAssertions: func(vm *volumes.MockManager) { 838 vm.On("CreateTemporary", volumesTestsDefaultBuildsDir). 839 Return(nil). 840 Once() 841 }, 842 createVolumeManager: true, 843 }, 844 "git strategy clone, empty buildsDir, duplicated error": { 845 gitStrategy: "clone", 846 volumesManagerAssertions: func(vm *volumes.MockManager) { 847 vm.On("CreateTemporary", volumesTestsDefaultBuildsDir). 848 Return(volumes.NewErrVolumeAlreadyDefined(volumesTestsDefaultBuildsDir)). 849 Once() 850 }, 851 createVolumeManager: true, 852 }, 853 "git strategy clone, empty buildsDir, other error": { 854 gitStrategy: "clone", 855 volumesManagerAssertions: func(vm *volumes.MockManager) { 856 vm.On("CreateTemporary", volumesTestsDefaultBuildsDir). 857 Return(errors.New("test-error")). 858 Once() 859 }, 860 createVolumeManager: true, 861 expectedError: errors.New("test-error"), 862 }, 863 "git strategy clone, non-empty buildsDir, no error": { 864 gitStrategy: "clone", 865 buildsDir: "/builds", 866 volumesManagerAssertions: func(vm *volumes.MockManager) { 867 vm.On("CreateTemporary", "/builds"). 868 Return(nil). 869 Once() 870 }, 871 createVolumeManager: true, 872 }, 873 "git strategy clone, non-empty buildsDir, duplicated error": { 874 gitStrategy: "clone", 875 buildsDir: "/builds", 876 volumesManagerAssertions: func(vm *volumes.MockManager) { 877 vm.On("CreateTemporary", "/builds"). 878 Return(volumes.NewErrVolumeAlreadyDefined("/builds")). 879 Once() 880 }, 881 createVolumeManager: true, 882 }, 883 "git strategy clone, non-empty buildsDir, other error": { 884 gitStrategy: "clone", 885 buildsDir: "/builds", 886 volumesManagerAssertions: func(vm *volumes.MockManager) { 887 vm.On("CreateTemporary", "/builds"). 888 Return(errors.New("test-error")). 889 Once() 890 }, 891 createVolumeManager: true, 892 expectedError: errors.New("test-error"), 893 }, 894 "git strategy fetch, empty buildsDir, no error": { 895 gitStrategy: "fetch", 896 volumesManagerAssertions: func(vm *volumes.MockManager) { 897 vm.On("Create", volumesTestsDefaultBuildsDir). 898 Return(nil). 899 Once() 900 }, 901 createVolumeManager: true, 902 }, 903 "git strategy fetch, empty buildsDir, duplicated error": { 904 gitStrategy: "fetch", 905 volumesManagerAssertions: func(vm *volumes.MockManager) { 906 vm.On("Create", volumesTestsDefaultBuildsDir). 907 Return(volumes.NewErrVolumeAlreadyDefined(volumesTestsDefaultBuildsDir)). 908 Once() 909 }, 910 createVolumeManager: true, 911 }, 912 "git strategy fetch, empty buildsDir, other error": { 913 gitStrategy: "fetch", 914 volumesManagerAssertions: func(vm *volumes.MockManager) { 915 vm.On("Create", volumesTestsDefaultBuildsDir). 916 Return(errors.New("test-error")). 917 Once() 918 }, 919 createVolumeManager: true, 920 expectedError: errors.New("test-error"), 921 }, 922 "git strategy fetch, non-empty buildsDir, no error": { 923 gitStrategy: "fetch", 924 buildsDir: "/builds", 925 volumesManagerAssertions: func(vm *volumes.MockManager) { 926 vm.On("Create", "/builds"). 927 Return(nil). 928 Once() 929 }, 930 createVolumeManager: true, 931 }, 932 "git strategy fetch, non-empty buildsDir, duplicated error": { 933 gitStrategy: "fetch", 934 buildsDir: "/builds", 935 volumesManagerAssertions: func(vm *volumes.MockManager) { 936 vm.On("Create", "/builds"). 937 Return(volumes.NewErrVolumeAlreadyDefined("/builds")). 938 Once() 939 }, 940 createVolumeManager: true, 941 }, 942 "git strategy fetch, non-empty buildsDir, other error": { 943 gitStrategy: "fetch", 944 buildsDir: "/builds", 945 volumesManagerAssertions: func(vm *volumes.MockManager) { 946 vm.On("Create", "/builds"). 947 Return(errors.New("test-error")). 948 Once() 949 }, 950 createVolumeManager: true, 951 expectedError: errors.New("test-error"), 952 }, 953 "git strategy fetch, non-empty buildsDir, cache volumes disabled": { 954 gitStrategy: "fetch", 955 buildsDir: "/builds", 956 volumesManagerAssertions: func(vm *volumes.MockManager) { 957 vm.On("Create", "/builds"). 958 Return(volumes.ErrCacheVolumesDisabled). 959 Once() 960 vm.On("CreateTemporary", "/builds"). 961 Return(nil). 962 Once() 963 }, 964 createVolumeManager: true, 965 }, 966 "git strategy fetch, non-empty buildsDir, cache volumes disabled, duplicated error": { 967 gitStrategy: "fetch", 968 buildsDir: "/builds", 969 volumesManagerAssertions: func(vm *volumes.MockManager) { 970 vm.On("Create", "/builds"). 971 Return(volumes.ErrCacheVolumesDisabled). 972 Once() 973 vm.On("CreateTemporary", "/builds"). 974 Return(volumes.NewErrVolumeAlreadyDefined("/builds")). 975 Once() 976 }, 977 createVolumeManager: true, 978 }, 979 "git strategy fetch, non-empty buildsDir, no error, legacy builds dir": { 980 // TODO: Remove in 12.3 981 gitStrategy: "fetch", 982 buildsDir: "/builds", 983 adjustConfiguration: func(e *executor) { 984 e.Build.Variables = append(e.Build.Variables, common.JobVariable{ 985 Key: featureflags.UseLegacyBuildsDirForDocker, 986 Value: "true", 987 }) 988 }, 989 volumesManagerAssertions: func(vm *volumes.MockManager) { 990 vm.On("Create", "/builds/group"). 991 Return(nil). 992 Once() 993 }, 994 createVolumeManager: true, 995 }, 996 "git strategy clone, non-empty buildsDir, no error, legacy builds dir": { 997 // TODO: Remove in 12.3 998 gitStrategy: "clone", 999 buildsDir: "/builds", 1000 adjustConfiguration: func(e *executor) { 1001 e.Build.Variables = append(e.Build.Variables, common.JobVariable{ 1002 Key: featureflags.UseLegacyBuildsDirForDocker, 1003 Value: "true", 1004 }) 1005 }, 1006 volumesManagerAssertions: func(vm *volumes.MockManager) { 1007 vm.On("CreateTemporary", "/builds/group"). 1008 Return(nil). 1009 Once() 1010 }, 1011 createVolumeManager: true, 1012 }, 1013 } 1014 1015 for testName, test := range tests { 1016 t.Run(testName, func(t *testing.T) { 1017 e, closureFn := getExecutorForVolumesTests(t, test) 1018 defer closureFn() 1019 1020 err := e.createBuildVolume() 1021 assert.Equal(t, test.expectedError, err) 1022 }) 1023 } 1024 } 1025 1026 func TestCreateDependencies(t *testing.T) { 1027 testError := errors.New("test-error") 1028 1029 tests := map[string]struct { 1030 legacyVolumesMountingOrder string 1031 expectedServiceVolumes []string 1032 }{ 1033 "UseLegacyVolumesMountingOrder is false": { 1034 legacyVolumesMountingOrder: "false", 1035 expectedServiceVolumes: []string{"/volume", "/builds"}, 1036 }, 1037 // TODO: Remove in 12.6 1038 "UseLegacyVolumesMountingOrder is true": { 1039 legacyVolumesMountingOrder: "true", 1040 expectedServiceVolumes: []string{"/builds"}, 1041 }, 1042 } 1043 1044 for testName, test := range tests { 1045 t.Run(testName, func(t *testing.T) { 1046 testCase := volumesTestCase{ 1047 buildsDir: "/builds", 1048 volumes: []string{"/volume"}, 1049 adjustConfiguration: func(e *executor) { 1050 e.Build.Services = append(e.Build.Services, common.Image{ 1051 Name: "alpine:latest", 1052 }) 1053 1054 e.Build.Variables = append(e.Build.Variables, common.JobVariable{ 1055 Key: featureflags.UseLegacyVolumesMountingOrder, 1056 Value: test.legacyVolumesMountingOrder, 1057 }) 1058 }, 1059 volumesManagerAssertions: func(vm *volumes.MockManager) { 1060 binds := make([]string, 0) 1061 1062 vm.On("CreateTemporary", "/builds"). 1063 Return(nil). 1064 Run(func(args mock.Arguments) { 1065 binds = append(binds, args.Get(0).(string)) 1066 }). 1067 Once() 1068 vm.On("Create", "/volume"). 1069 Return(nil). 1070 Run(func(args mock.Arguments) { 1071 binds = append(binds, args.Get(0).(string)) 1072 }). 1073 Maybe() // In the FF enabled case this assertion will be not met because of error during service starts 1074 vm.On("Binds"). 1075 Return(func() []string { 1076 return binds 1077 }). 1078 Once() 1079 vm.On("ContainerIDs"). 1080 Return(nil). 1081 Once() 1082 }, 1083 clientAssertions: func(c *docker_helpers.MockClient) { 1084 hostConfigMatcher := mock.MatchedBy(func(conf *container.HostConfig) bool { 1085 return assert.Equal(t, test.expectedServiceVolumes, conf.Binds) 1086 }) 1087 1088 c.On("ImageInspectWithRaw", mock.Anything, "alpine:latest"). 1089 Return(types.ImageInspect{}, nil, nil). 1090 Once() 1091 c.On("NetworkList", mock.Anything, mock.Anything). 1092 Return(nil, nil). 1093 Once() 1094 c.On("ContainerRemove", mock.Anything, "runner-abcdef12-project-0-concurrent-0-alpine-0", mock.Anything). 1095 Return(nil). 1096 Once() 1097 c.On("ContainerCreate", mock.Anything, mock.Anything, hostConfigMatcher, mock.Anything, "runner-abcdef12-project-0-concurrent-0-alpine-0"). 1098 Return(container.ContainerCreateCreatedBody{ID: "container-ID"}, nil). 1099 Once() 1100 c.On("ContainerStart", mock.Anything, "container-ID", mock.Anything). 1101 Return(testError). 1102 Once() 1103 }, 1104 } 1105 1106 e, closureFn := getExecutorForVolumesTests(t, testCase) 1107 defer closureFn() 1108 1109 err := e.createDependencies() 1110 assert.Equal(t, testError, err) 1111 }) 1112 } 1113 } 1114 1115 var testFileAuthConfigs = `{"auths":{"https://registry.domain.tld:5005/v1/":{"auth":"aW52YWxpZF91c2VyOmludmFsaWRfcGFzc3dvcmQ="},"registry2.domain.tld:5005":{"auth":"dGVzdF91c2VyOnRlc3RfcGFzc3dvcmQ="}}}` 1116 var testVariableAuthConfigs = `{"auths":{"https://registry.domain.tld:5005/v1/":{"auth":"dGVzdF91c2VyOnRlc3RfcGFzc3dvcmQ="}}}` 1117 1118 func getAuthConfigTestExecutor(t *testing.T, precreateConfigFile bool) executor { 1119 tempHomeDir, err := ioutil.TempDir("", "docker-auth-configs-test") 1120 require.NoError(t, err) 1121 1122 if precreateConfigFile { 1123 dockerConfigFile := path.Join(tempHomeDir, ".dockercfg") 1124 ioutil.WriteFile(dockerConfigFile, []byte(testFileAuthConfigs), 0600) 1125 docker_helpers.HomeDirectory = tempHomeDir 1126 } else { 1127 docker_helpers.HomeDirectory = "" 1128 } 1129 1130 e := executor{} 1131 e.Build = &common.Build{ 1132 Runner: &common.RunnerConfig{}, 1133 } 1134 1135 e.Build.Token = "abcd123456" 1136 1137 e.Config = common.RunnerConfig{} 1138 e.Config.Docker = &common.DockerConfig{ 1139 PullPolicy: common.PullPolicyAlways, 1140 } 1141 1142 return e 1143 } 1144 1145 func addGitLabRegistryCredentials(e *executor) { 1146 e.Build.Credentials = []common.Credentials{ 1147 { 1148 Type: "registry", 1149 URL: "registry.gitlab.tld:1234", 1150 Username: "gitlab-ci-token", 1151 Password: e.Build.Token, 1152 }, 1153 } 1154 } 1155 1156 func addRemoteVariableCredentials(e *executor) { 1157 e.Build.Variables = common.JobVariables{ 1158 common.JobVariable{ 1159 Key: "DOCKER_AUTH_CONFIG", 1160 Value: testVariableAuthConfigs, 1161 }, 1162 } 1163 } 1164 1165 func addLocalVariableCredentials(e *executor) { 1166 e.Build.Runner.Environment = []string{ 1167 "DOCKER_AUTH_CONFIG=" + testVariableAuthConfigs, 1168 } 1169 } 1170 1171 func assertEmptyCredentials(t *testing.T, ac *types.AuthConfig, messageElements ...string) { 1172 if ac != nil { 1173 assert.Empty(t, ac.ServerAddress, "ServerAddress for %v", messageElements) 1174 assert.Empty(t, ac.Username, "Username for %v", messageElements) 1175 assert.Empty(t, ac.Password, "Password for %v", messageElements) 1176 } 1177 } 1178 1179 func assertCredentials(t *testing.T, serverAddress, username, password string, ac *types.AuthConfig, messageElements ...string) { 1180 assert.Equal(t, serverAddress, ac.ServerAddress, "ServerAddress for %v", messageElements) 1181 assert.Equal(t, username, ac.Username, "Username for %v", messageElements) 1182 assert.Equal(t, password, ac.Password, "Password for %v", messageElements) 1183 } 1184 1185 func getTestAuthConfig(t *testing.T, e executor, imageName string) *types.AuthConfig { 1186 ac := e.getAuthConfig(imageName) 1187 1188 return ac 1189 } 1190 1191 func testVariableAuthConfig(t *testing.T, e executor) { 1192 t.Run("withoutGitLabRegistry", func(t *testing.T) { 1193 ac := getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version") 1194 assertCredentials(t, "https://registry.domain.tld:5005/v1/", "test_user", "test_password", ac, "registry.domain.tld:5005/image/name:version") 1195 1196 ac = getTestAuthConfig(t, e, "registry2.domain.tld:5005/image/name:version") 1197 assertCredentials(t, "registry2.domain.tld:5005", "test_user", "test_password", ac, "registry2.domain.tld:5005/image/name:version") 1198 1199 ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version") 1200 assertEmptyCredentials(t, ac, "registry.gitlab.tld:1234") 1201 }) 1202 1203 t.Run("withGitLabRegistry", func(t *testing.T) { 1204 addGitLabRegistryCredentials(&e) 1205 1206 ac := getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version") 1207 assertCredentials(t, "https://registry.domain.tld:5005/v1/", "test_user", "test_password", ac, "registry.domain.tld:5005/image/name:version") 1208 1209 ac = getTestAuthConfig(t, e, "registry2.domain.tld:5005/image/name:version") 1210 assertCredentials(t, "registry2.domain.tld:5005", "test_user", "test_password", ac, "registry2.domain.tld:5005/image/name:version") 1211 1212 ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version") 1213 assertCredentials(t, "registry.gitlab.tld:1234", "gitlab-ci-token", "abcd123456", ac, "registry.gitlab.tld:1234") 1214 }) 1215 } 1216 1217 func TestGetRemoteVariableAuthConfig(t *testing.T) { 1218 e := getAuthConfigTestExecutor(t, true) 1219 addRemoteVariableCredentials(&e) 1220 1221 testVariableAuthConfig(t, e) 1222 } 1223 1224 func TestGetLocalVariableAuthConfig(t *testing.T) { 1225 e := getAuthConfigTestExecutor(t, true) 1226 addLocalVariableCredentials(&e) 1227 1228 testVariableAuthConfig(t, e) 1229 } 1230 1231 func TestGetDefaultAuthConfig(t *testing.T) { 1232 t.Run("withoutGitLabRegistry", func(t *testing.T) { 1233 e := getAuthConfigTestExecutor(t, false) 1234 1235 ac := getTestAuthConfig(t, e, "docker:dind") 1236 assertEmptyCredentials(t, ac, "docker:dind") 1237 1238 ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version") 1239 assertEmptyCredentials(t, ac, "registry.gitlab.tld:1234") 1240 1241 ac = getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version") 1242 assertEmptyCredentials(t, ac, "registry.domain.tld:5005/image/name:version") 1243 }) 1244 1245 t.Run("withGitLabRegistry", func(t *testing.T) { 1246 e := getAuthConfigTestExecutor(t, false) 1247 addGitLabRegistryCredentials(&e) 1248 1249 ac := getTestAuthConfig(t, e, "docker:dind") 1250 assertEmptyCredentials(t, ac, "docker:dind") 1251 1252 ac = getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version") 1253 assertEmptyCredentials(t, ac, "registry.domain.tld:5005/image/name:version") 1254 1255 ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version") 1256 assertCredentials(t, "registry.gitlab.tld:1234", "gitlab-ci-token", "abcd123456", ac, "registry.gitlab.tld:1234") 1257 }) 1258 } 1259 1260 func TestAuthConfigOverwritingOrder(t *testing.T) { 1261 testVariableAuthConfigs = `{"auths":{"registry.gitlab.tld:1234":{"auth":"ZnJvbV92YXJpYWJsZTpwYXNzd29yZA=="}}}` 1262 testFileAuthConfigs = `{"auths":{"registry.gitlab.tld:1234":{"auth":"ZnJvbV9maWxlOnBhc3N3b3Jk"}}}` 1263 1264 imageName := "registry.gitlab.tld:1234/image/name:latest" 1265 1266 t.Run("gitlabRegistryOnly", func(t *testing.T) { 1267 e := getAuthConfigTestExecutor(t, false) 1268 addGitLabRegistryCredentials(&e) 1269 1270 ac := getTestAuthConfig(t, e, imageName) 1271 assertCredentials(t, "registry.gitlab.tld:1234", "gitlab-ci-token", e.Build.Token, ac, imageName) 1272 }) 1273 1274 t.Run("withConfigFromRemoteVariable", func(t *testing.T) { 1275 e := getAuthConfigTestExecutor(t, false) 1276 addGitLabRegistryCredentials(&e) 1277 addRemoteVariableCredentials(&e) 1278 1279 ac := getTestAuthConfig(t, e, imageName) 1280 assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName) 1281 }) 1282 1283 t.Run("withConfigFromLocalVariable", func(t *testing.T) { 1284 e := getAuthConfigTestExecutor(t, false) 1285 addGitLabRegistryCredentials(&e) 1286 addLocalVariableCredentials(&e) 1287 1288 ac := getTestAuthConfig(t, e, imageName) 1289 assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName) 1290 }) 1291 1292 t.Run("withConfigFromFile", func(t *testing.T) { 1293 e := getAuthConfigTestExecutor(t, true) 1294 addGitLabRegistryCredentials(&e) 1295 1296 ac := getTestAuthConfig(t, e, imageName) 1297 assertCredentials(t, "registry.gitlab.tld:1234", "from_file", "password", ac, imageName) 1298 }) 1299 1300 t.Run("withConfigFromVariableAndFromFile", func(t *testing.T) { 1301 e := getAuthConfigTestExecutor(t, true) 1302 addGitLabRegistryCredentials(&e) 1303 addRemoteVariableCredentials(&e) 1304 1305 ac := getTestAuthConfig(t, e, imageName) 1306 assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName) 1307 }) 1308 1309 t.Run("withConfigFromLocalAndRemoteVariable", func(t *testing.T) { 1310 e := getAuthConfigTestExecutor(t, true) 1311 addGitLabRegistryCredentials(&e) 1312 addRemoteVariableCredentials(&e) 1313 testVariableAuthConfigs = `{"auths":{"registry.gitlab.tld:1234":{"auth":"ZnJvbV9sb2NhbF92YXJpYWJsZTpwYXNzd29yZA=="}}}` 1314 addLocalVariableCredentials(&e) 1315 1316 ac := getTestAuthConfig(t, e, imageName) 1317 assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName) 1318 }) 1319 } 1320 1321 func testGetDockerImage(t *testing.T, e executor, imageName string, setClientExpectations func(c *docker_helpers.MockClient, imageName string)) { 1322 t.Run("get:"+imageName, func(t *testing.T) { 1323 var c docker_helpers.MockClient 1324 defer c.AssertExpectations(t) 1325 1326 e.client = &c 1327 1328 setClientExpectations(&c, imageName) 1329 1330 image, err := e.getDockerImage(imageName) 1331 assert.NoError(t, err, "Should not generate error") 1332 assert.Equal(t, "this-image", image.ID, "Image ID") 1333 }) 1334 } 1335 1336 func testDeniesDockerImage(t *testing.T, e executor, imageName string, setClientExpectations func(c *docker_helpers.MockClient, imageName string)) { 1337 t.Run("deny:"+imageName, func(t *testing.T) { 1338 var c docker_helpers.MockClient 1339 defer c.AssertExpectations(t) 1340 1341 e.client = &c 1342 1343 setClientExpectations(&c, imageName) 1344 1345 _, err := e.getDockerImage(imageName) 1346 assert.Error(t, err, "Should generate error") 1347 }) 1348 } 1349 1350 func addFindsLocalImageExpectations(c *docker_helpers.MockClient, imageName string) { 1351 c.On("ImageInspectWithRaw", mock.Anything, imageName). 1352 Return(types.ImageInspect{ID: "this-image"}, nil, nil). 1353 Once() 1354 } 1355 1356 func addPullsRemoteImageExpectations(c *docker_helpers.MockClient, imageName string) { 1357 c.On("ImageInspectWithRaw", mock.Anything, imageName). 1358 Return(types.ImageInspect{ID: "not-this-image"}, nil, nil). 1359 Once() 1360 1361 c.On("ImagePullBlocking", mock.Anything, imageName, mock.AnythingOfType("types.ImagePullOptions")). 1362 Return(nil). 1363 Once() 1364 1365 c.On("ImageInspectWithRaw", mock.Anything, imageName). 1366 Return(types.ImageInspect{ID: "this-image"}, nil, nil). 1367 Once() 1368 } 1369 1370 func addDeniesPullExpectations(c *docker_helpers.MockClient, imageName string) { 1371 c.On("ImageInspectWithRaw", mock.Anything, imageName). 1372 Return(types.ImageInspect{ID: "image"}, nil, nil). 1373 Once() 1374 1375 c.On("ImagePullBlocking", mock.Anything, imageName, mock.AnythingOfType("types.ImagePullOptions")). 1376 Return(fmt.Errorf("deny pulling")). 1377 Once() 1378 } 1379 1380 func TestPullPolicyWhenAlwaysIsSet(t *testing.T) { 1381 remoteImage := "registry.domain.tld:5005/image/name:version" 1382 gitlabImage := "registry.gitlab.tld:1234/image/name:version" 1383 1384 e := getAuthConfigTestExecutor(t, false) 1385 e.Context = context.Background() 1386 e.Config.Docker.PullPolicy = common.PullPolicyAlways 1387 1388 testGetDockerImage(t, e, remoteImage, addPullsRemoteImageExpectations) 1389 testDeniesDockerImage(t, e, remoteImage, addDeniesPullExpectations) 1390 1391 testGetDockerImage(t, e, gitlabImage, addPullsRemoteImageExpectations) 1392 testDeniesDockerImage(t, e, gitlabImage, addDeniesPullExpectations) 1393 } 1394 1395 func TestPullPolicyWhenIfNotPresentIsSet(t *testing.T) { 1396 remoteImage := "registry.domain.tld:5005/image/name:version" 1397 gitlabImage := "registry.gitlab.tld:1234/image/name:version" 1398 1399 e := getAuthConfigTestExecutor(t, false) 1400 e.Context = context.Background() 1401 e.Config.Docker.PullPolicy = common.PullPolicyIfNotPresent 1402 1403 testGetDockerImage(t, e, remoteImage, addFindsLocalImageExpectations) 1404 testGetDockerImage(t, e, gitlabImage, addFindsLocalImageExpectations) 1405 } 1406 1407 func TestDockerWatchOn_1_12_4(t *testing.T) { 1408 if helpers.SkipIntegrationTests(t, "docker", "info") { 1409 return 1410 } 1411 1412 e := executor{ 1413 AbstractExecutor: executors.AbstractExecutor{ 1414 ExecutorOptions: executors.ExecutorOptions{ 1415 Metadata: map[string]string{ 1416 metadataOSType: osTypeLinux, 1417 }, 1418 }, 1419 }, 1420 volumeParser: parser.NewLinuxParser(), 1421 } 1422 e.Context = context.Background() 1423 e.Build = &common.Build{ 1424 Runner: &common.RunnerConfig{}, 1425 } 1426 e.Build.Token = "abcd123456" 1427 e.BuildShell = &common.ShellConfiguration{ 1428 Environment: []string{}, 1429 } 1430 1431 e.Config = common.RunnerConfig{} 1432 e.Config.Docker = &common.DockerConfig{ 1433 PullPolicy: common.PullPolicyIfNotPresent, 1434 } 1435 1436 output := bytes.NewBufferString("") 1437 e.Trace = &common.Trace{Writer: output} 1438 1439 err := e.connectDocker() 1440 require.NoError(t, err) 1441 1442 err = e.createVolumesManager() 1443 require.NoError(t, err) 1444 1445 container, err := e.createContainer("build", common.Image{Name: common.TestAlpineImage}, []string{"/bin/sh"}, []string{}) 1446 assert.NoError(t, err) 1447 assert.NotNil(t, container) 1448 1449 input := bytes.NewBufferString("echo 'script'") 1450 1451 finished := make(chan bool, 1) 1452 wg := &sync.WaitGroup{} 1453 wg.Add(1) // Avoid a race where assert.NoError() is called too late in the goroutine 1454 go func() { 1455 err = e.watchContainer(e.Context, container.ID, input) 1456 assert.NoError(t, err) 1457 finished <- true 1458 wg.Done() 1459 }() 1460 1461 select { 1462 case <-finished: 1463 assert.Equal(t, "script\n", output.String()) 1464 case <-time.After(15 * time.Second): 1465 t.Error("Container script not finished") 1466 } 1467 1468 err = e.removeContainer(e.Context, container.ID) 1469 assert.NoError(t, err) 1470 wg.Wait() 1471 } 1472 1473 type containerConfigExpectations func(*testing.T, *container.Config, *container.HostConfig) 1474 1475 type dockerConfigurationTestFakeDockerClient struct { 1476 docker_helpers.MockClient 1477 1478 cce containerConfigExpectations 1479 t *testing.T 1480 } 1481 1482 func (c *dockerConfigurationTestFakeDockerClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) { 1483 c.cce(c.t, config, hostConfig) 1484 return container.ContainerCreateCreatedBody{ID: "abc"}, nil 1485 } 1486 1487 func prepareTestDockerConfiguration(t *testing.T, dockerConfig *common.DockerConfig, cce containerConfigExpectations) (*dockerConfigurationTestFakeDockerClient, *executor) { 1488 c := &dockerConfigurationTestFakeDockerClient{ 1489 cce: cce, 1490 t: t, 1491 } 1492 1493 e := &executor{} 1494 e.client = c 1495 e.volumeParser = parser.NewLinuxParser() 1496 e.info = types.Info{ 1497 OSType: helperimage.OSTypeLinux, 1498 Architecture: "amd64", 1499 } 1500 e.Config.Docker = dockerConfig 1501 e.Build = &common.Build{ 1502 Runner: &common.RunnerConfig{}, 1503 } 1504 e.Build.Token = "abcd123456" 1505 e.BuildShell = &common.ShellConfiguration{ 1506 Environment: []string{}, 1507 } 1508 var err error 1509 e.helperImageInfo, err = helperimage.Get(common.REVISION, helperimage.Config{ 1510 OSType: e.info.OSType, 1511 Architecture: e.info.Architecture, 1512 OperatingSystem: e.info.OperatingSystem, 1513 }) 1514 require.NoError(t, err) 1515 1516 c.On("ImageInspectWithRaw", mock.Anything, "gitlab/gitlab-runner-helper:x86_64-latest"). 1517 Return(types.ImageInspect{ID: "helper-image-id"}, nil, nil).Once() 1518 c.On("ImageInspectWithRaw", mock.Anything, "alpine"). 1519 Return(types.ImageInspect{ID: "123"}, []byte{}, nil).Twice() 1520 c.On("ImagePullBlocking", mock.Anything, "alpine:latest", mock.Anything). 1521 Return(nil).Once() 1522 c.On("NetworkList", mock.Anything, mock.Anything). 1523 Return([]types.NetworkResource{}, nil).Once() 1524 c.On("ContainerRemove", mock.Anything, mock.Anything, mock.Anything). 1525 Return(nil).Once() 1526 1527 return c, e 1528 } 1529 1530 func testDockerConfigurationWithJobContainer(t *testing.T, dockerConfig *common.DockerConfig, cce containerConfigExpectations) { 1531 c, e := prepareTestDockerConfiguration(t, dockerConfig, cce) 1532 defer c.AssertExpectations(t) 1533 1534 c.On("ContainerInspect", mock.Anything, "abc"). 1535 Return(types.ContainerJSON{}, nil).Once() 1536 1537 err := e.createVolumesManager() 1538 require.NoError(t, err) 1539 1540 _, err = e.createContainer("build", common.Image{Name: "alpine"}, []string{"/bin/sh"}, []string{}) 1541 assert.NoError(t, err, "Should create container without errors") 1542 } 1543 1544 func testDockerConfigurationWithServiceContainer(t *testing.T, dockerConfig *common.DockerConfig, cce containerConfigExpectations) { 1545 c, e := prepareTestDockerConfiguration(t, dockerConfig, cce) 1546 defer c.AssertExpectations(t) 1547 1548 c.On("ContainerStart", mock.Anything, "abc", mock.Anything). 1549 Return(nil).Once() 1550 1551 err := e.createVolumesManager() 1552 require.NoError(t, err) 1553 1554 _, err = e.createService(0, "build", "latest", "alpine", common.Image{Command: []string{"/bin/sh"}}) 1555 assert.NoError(t, err, "Should create service container without errors") 1556 } 1557 1558 func TestDockerMemorySetting(t *testing.T) { 1559 dockerConfig := &common.DockerConfig{ 1560 Memory: "42m", 1561 } 1562 1563 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1564 assert.Equal(t, int64(44040192), hostConfig.Memory) 1565 } 1566 1567 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1568 } 1569 1570 func TestDockerMemorySwapSetting(t *testing.T) { 1571 dockerConfig := &common.DockerConfig{ 1572 MemorySwap: "2g", 1573 } 1574 1575 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1576 assert.Equal(t, int64(2147483648), hostConfig.MemorySwap) 1577 } 1578 1579 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1580 } 1581 1582 func TestDockerMemoryReservationSetting(t *testing.T) { 1583 dockerConfig := &common.DockerConfig{ 1584 MemoryReservation: "64m", 1585 } 1586 1587 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1588 assert.Equal(t, int64(67108864), hostConfig.MemoryReservation) 1589 } 1590 1591 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1592 } 1593 1594 func TestDockerCPUSSetting(t *testing.T) { 1595 examples := []struct { 1596 cpus string 1597 nanocpus int64 1598 }{ 1599 {"0.5", 500000000}, 1600 {"0.25", 250000000}, 1601 {"1/3", 333333333}, 1602 {"1/8", 125000000}, 1603 {"0.0001", 100000}, 1604 } 1605 1606 for _, example := range examples { 1607 t.Run(example.cpus, func(t *testing.T) { 1608 dockerConfig := &common.DockerConfig{ 1609 CPUS: example.cpus, 1610 } 1611 1612 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1613 assert.Equal(t, int64(example.nanocpus), hostConfig.NanoCPUs) 1614 } 1615 1616 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1617 }) 1618 } 1619 } 1620 1621 func TestDockerCPUSetCPUsSetting(t *testing.T) { 1622 dockerConfig := &common.DockerConfig{ 1623 CPUSetCPUs: "1-3,5", 1624 } 1625 1626 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1627 assert.Equal(t, "1-3,5", hostConfig.CpusetCpus) 1628 } 1629 1630 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1631 } 1632 1633 func TestDockerServicesTmpfsSetting(t *testing.T) { 1634 dockerConfig := &common.DockerConfig{ 1635 ServicesTmpfs: map[string]string{ 1636 "/tmpfs": "rw,noexec", 1637 }, 1638 } 1639 1640 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1641 require.NotEmpty(t, hostConfig.Tmpfs) 1642 } 1643 1644 testDockerConfigurationWithServiceContainer(t, dockerConfig, cce) 1645 } 1646 func TestDockerTmpfsSetting(t *testing.T) { 1647 dockerConfig := &common.DockerConfig{ 1648 Tmpfs: map[string]string{ 1649 "/tmpfs": "rw,noexec", 1650 }, 1651 } 1652 1653 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1654 require.NotEmpty(t, hostConfig.Tmpfs) 1655 } 1656 1657 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1658 } 1659 1660 func TestDockerServicesDNSSetting(t *testing.T) { 1661 dockerConfig := &common.DockerConfig{ 1662 DNS: []string{"2001:db8::1", "192.0.2.1"}, 1663 } 1664 1665 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1666 require.Equal(t, dockerConfig.DNS, hostConfig.DNS) 1667 } 1668 1669 testDockerConfigurationWithServiceContainer(t, dockerConfig, cce) 1670 } 1671 1672 func TestDockerServicesDNSSearchSetting(t *testing.T) { 1673 dockerConfig := &common.DockerConfig{ 1674 DNSSearch: []string{"mydomain.example"}, 1675 } 1676 1677 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1678 require.Equal(t, dockerConfig.DNSSearch, hostConfig.DNSSearch) 1679 } 1680 1681 testDockerConfigurationWithServiceContainer(t, dockerConfig, cce) 1682 } 1683 1684 func TestDockerServicesExtraHostsSetting(t *testing.T) { 1685 dockerConfig := &common.DockerConfig{ 1686 ExtraHosts: []string{"foo.example:2001:db8::1", "bar.example:192.0.2.1"}, 1687 } 1688 1689 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1690 require.Equal(t, dockerConfig.ExtraHosts, hostConfig.ExtraHosts) 1691 } 1692 1693 testDockerConfigurationWithServiceContainer(t, dockerConfig, cce) 1694 } 1695 1696 func TestDockerUserNSSetting(t *testing.T) { 1697 dockerConfig := &common.DockerConfig{ 1698 UsernsMode: "host", 1699 } 1700 1701 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1702 assert.Equal(t, container.UsernsMode("host"), hostConfig.UsernsMode) 1703 } 1704 1705 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1706 1707 } 1708 1709 func TestDockerRuntimeSetting(t *testing.T) { 1710 dockerConfig := &common.DockerConfig{ 1711 Runtime: "runc", 1712 } 1713 1714 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1715 assert.Equal(t, "runc", hostConfig.Runtime) 1716 } 1717 1718 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1719 } 1720 1721 func TestDockerSysctlsSetting(t *testing.T) { 1722 dockerConfig := &common.DockerConfig{ 1723 SysCtls: map[string]string{ 1724 "net.ipv4.ip_forward": "1", 1725 }, 1726 } 1727 1728 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1729 assert.Equal(t, "1", hostConfig.Sysctls["net.ipv4.ip_forward"]) 1730 } 1731 1732 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1733 } 1734 1735 func TestCheckOSType(t *testing.T) { 1736 cases := map[string]struct { 1737 executorMetadata map[string]string 1738 dockerInfoOSType string 1739 expectedErr string 1740 }{ 1741 "executor and docker info mismatch": { 1742 executorMetadata: map[string]string{ 1743 metadataOSType: osTypeWindows, 1744 }, 1745 dockerInfoOSType: osTypeLinux, 1746 expectedErr: "executor requires OSType=windows, but Docker Engine supports only OSType=linux", 1747 }, 1748 "executor and docker info match": { 1749 executorMetadata: map[string]string{ 1750 metadataOSType: osTypeLinux, 1751 }, 1752 dockerInfoOSType: osTypeLinux, 1753 expectedErr: "", 1754 }, 1755 "executor OSType not defined": { 1756 executorMetadata: nil, 1757 dockerInfoOSType: osTypeLinux, 1758 expectedErr: " does not have any OSType specified", 1759 }, 1760 } 1761 1762 for name, c := range cases { 1763 t.Run(name, func(t *testing.T) { 1764 executor := executor{ 1765 info: types.Info{ 1766 OSType: c.dockerInfoOSType, 1767 }, 1768 AbstractExecutor: executors.AbstractExecutor{ 1769 ExecutorOptions: executors.ExecutorOptions{ 1770 Metadata: c.executorMetadata, 1771 }, 1772 }, 1773 } 1774 1775 err := executor.validateOSType() 1776 if c.expectedErr == "" { 1777 assert.NoError(t, err) 1778 return 1779 } 1780 assert.EqualError(t, err, c.expectedErr) 1781 }) 1782 } 1783 } 1784 1785 // TODO: Remove in 12.0 1786 func TestOutdatedHelperImage(t *testing.T) { 1787 ffNotSet := common.JobVariables{} 1788 ffSet := common.JobVariables{ 1789 {Key: featureflags.DockerHelperImageV2, Value: "true"}, 1790 } 1791 1792 testCases := map[string]struct { 1793 helperImage string 1794 variables common.JobVariables 1795 expectedResult bool 1796 }{ 1797 "helper image not set and FF set to false": { 1798 variables: ffNotSet, 1799 helperImage: "", 1800 expectedResult: false, 1801 }, 1802 "helper image not set and FF set to true": { 1803 variables: ffSet, 1804 helperImage: "", 1805 expectedResult: false, 1806 }, 1807 "helper image set and FF set to false": { 1808 variables: ffNotSet, 1809 helperImage: "gitlab/gitlab-runner-helper:x86_64-latest", 1810 expectedResult: true, 1811 }, 1812 "helper image set and FF set to true": { 1813 variables: ffSet, 1814 helperImage: "gitlab/gitlab-runner-helper:x86_64-latest", 1815 expectedResult: false, 1816 }, 1817 } 1818 1819 for testName, testCase := range testCases { 1820 t.Run(testName, func(t *testing.T) { 1821 e := setUpExecutorForFeatureFlag(testCase.variables, testCase.helperImage, nil) 1822 assert.Equal(t, testCase.expectedResult, e.checkOutdatedHelperImage()) 1823 }) 1824 } 1825 } 1826 1827 // TODO: Remove in 12.0 1828 func TestRunServiceHealthCheckContainerFeatureFlag(t *testing.T) { 1829 var cases = []struct { 1830 name string 1831 variables common.JobVariables 1832 helperImage string 1833 expectedCmd []string 1834 }{ 1835 { 1836 name: "Helper image is not specified", 1837 variables: common.JobVariables{}, 1838 helperImage: "", 1839 expectedCmd: []string{"gitlab-runner-helper", "health-check"}, 1840 }, 1841 { 1842 name: "Helper image is not specified and FF still turned on", 1843 variables: common.JobVariables{ 1844 common.JobVariable{Key: featureflags.DockerHelperImageV2, Value: "true"}, 1845 }, 1846 helperImage: "", 1847 expectedCmd: []string{"gitlab-runner-helper", "health-check"}, 1848 }, 1849 { 1850 name: "Helper image is specified", 1851 variables: common.JobVariables{}, 1852 helperImage: "gitlab/gitlab-runner-helper:x86_64-latest", 1853 expectedCmd: []string{"gitlab-runner-service"}, 1854 }, 1855 { 1856 name: "Helper image is specified & FF variable is set to true", 1857 variables: common.JobVariables{ 1858 common.JobVariable{Key: featureflags.DockerHelperImageV2, Value: "true"}, 1859 }, 1860 helperImage: "gitlab/gitlab-runner-helper:x86_64-latest", 1861 expectedCmd: []string{"gitlab-runner-helper", "health-check"}, 1862 }, 1863 } 1864 1865 for _, testCase := range cases { 1866 t.Run(testCase.name, func(t *testing.T) { 1867 helperImageID := fmt.Sprintf("%s-helperImage-%d", t.Name(), time.Now().Unix()) 1868 containerID := fmt.Sprintf("%s-%d", t.Name(), time.Now().Unix()) 1869 serviceContainerID := fmt.Sprintf("%s-wait-for-service", containerID) 1870 1871 mClient := docker_helpers.MockClient{} 1872 defer mClient.AssertExpectations(t) 1873 mClient.On("ImageInspectWithRaw", mock.Anything, mock.Anything). 1874 Return(types.ImageInspect{ID: helperImageID}, nil, nil) 1875 mClient.On("ContainerStart", mock.Anything, serviceContainerID, mock.Anything).Return(nil).Once() 1876 mClient.On("ContainerInspect", mock.Anything, serviceContainerID).Return(types.ContainerJSON{ 1877 ContainerJSONBase: &types.ContainerJSONBase{ 1878 State: &types.ContainerState{ 1879 ExitCode: 0, 1880 }, 1881 }, 1882 }, nil).Once() 1883 mClient.On("NetworkList", mock.Anything, mock.Anything).Return([]types.NetworkResource{}, nil).Once() 1884 mClient.On("ContainerRemove", mock.Anything, serviceContainerID, mock.Anything).Return(nil).Once() 1885 if testCase.helperImage != "" { 1886 mClient.On("ImagePullBlocking", mock.Anything, mock.Anything, mock.Anything).Return(nil).Maybe() 1887 } 1888 1889 executor := setUpExecutorForFeatureFlag(testCase.variables, testCase.helperImage, &mClient) 1890 1891 service := &types.Container{ 1892 ID: containerID, 1893 Names: []string{containerID}, 1894 } 1895 1896 expectedConfig := &container.Config{ 1897 Cmd: testCase.expectedCmd, 1898 Image: helperImageID, 1899 Labels: executor.getLabels("wait", "wait="+service.ID), 1900 } 1901 1902 mClient.On("ContainerCreate", mock.Anything, expectedConfig, mock.Anything, mock.Anything, serviceContainerID). 1903 Return(container.ContainerCreateCreatedBody{ID: serviceContainerID}, nil). 1904 Once() 1905 1906 err := executor.runServiceHealthCheckContainer(service, time.Minute) 1907 assert.NoError(t, err) 1908 }) 1909 } 1910 } 1911 1912 // TODO: Remove in 12.0 1913 func setUpExecutorForFeatureFlag(variables common.JobVariables, helperImage string, client docker_helpers.Client) executor { 1914 return executor{ 1915 AbstractExecutor: executors.AbstractExecutor{ 1916 Config: common.RunnerConfig{ 1917 RunnerSettings: common.RunnerSettings{ 1918 Docker: &common.DockerConfig{ 1919 HelperImage: helperImage, 1920 }, 1921 }, 1922 }, 1923 Build: &common.Build{ 1924 JobResponse: common.JobResponse{ 1925 Variables: variables, 1926 }, 1927 Runner: &common.RunnerConfig{ 1928 RunnerCredentials: common.RunnerCredentials{ 1929 Token: "xxxxx", 1930 }, 1931 }, 1932 }, 1933 Context: context.Background(), 1934 }, 1935 client: client, 1936 info: types.Info{ 1937 OSType: helperimage.OSTypeLinux, 1938 }, 1939 } 1940 } 1941 1942 func init() { 1943 docker_helpers.HomeDirectory = "" 1944 }