github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/executors/docker/executor_docker_test.go (about) 1 package docker 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path" 9 "strings" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/api/types/container" 16 "github.com/docker/docker/api/types/network" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/mock" 19 "github.com/stretchr/testify/require" 20 21 "gitlab.com/gitlab-org/gitlab-runner/common" 22 "gitlab.com/gitlab-org/gitlab-runner/helpers" 23 "gitlab.com/gitlab-org/gitlab-runner/helpers/docker" 24 25 "golang.org/x/net/context" 26 ) 27 28 // ImagePullOptions contains the RegistryAuth which is inferred from the docker 29 // configuration for the user, so just mock it out here. 30 func buildImagePullOptions(e executor, configName string) mock.AnythingOfTypeArgument { 31 return mock.AnythingOfType("ImagePullOptions") 32 } 33 34 func TestParseDeviceStringOne(t *testing.T) { 35 e := executor{} 36 37 device, err := e.parseDeviceString("/dev/kvm") 38 39 assert.NoError(t, err) 40 assert.Equal(t, device.PathOnHost, "/dev/kvm") 41 assert.Equal(t, device.PathInContainer, "/dev/kvm") 42 assert.Equal(t, device.CgroupPermissions, "rwm") 43 } 44 45 func TestParseDeviceStringTwo(t *testing.T) { 46 e := executor{} 47 48 device, err := e.parseDeviceString("/dev/kvm:/devices/kvm") 49 50 assert.NoError(t, err) 51 assert.Equal(t, device.PathOnHost, "/dev/kvm") 52 assert.Equal(t, device.PathInContainer, "/devices/kvm") 53 assert.Equal(t, device.CgroupPermissions, "rwm") 54 } 55 56 func TestParseDeviceStringThree(t *testing.T) { 57 e := executor{} 58 59 device, err := e.parseDeviceString("/dev/kvm:/devices/kvm:r") 60 61 assert.NoError(t, err) 62 assert.Equal(t, device.PathOnHost, "/dev/kvm") 63 assert.Equal(t, device.PathInContainer, "/devices/kvm") 64 assert.Equal(t, device.CgroupPermissions, "r") 65 } 66 67 func TestParseDeviceStringFour(t *testing.T) { 68 e := executor{} 69 70 _, err := e.parseDeviceString("/dev/kvm:/devices/kvm:r:oops") 71 72 assert.Error(t, err) 73 } 74 75 type testAllowedImageDescription struct { 76 allowed bool 77 image string 78 allowed_images []string 79 } 80 81 var testAllowedImages = []testAllowedImageDescription{ 82 {true, "ruby", []string{"*"}}, 83 {true, "ruby:2.1", []string{"*"}}, 84 {true, "ruby:latest", []string{"*"}}, 85 {true, "library/ruby", []string{"*/*"}}, 86 {true, "library/ruby:2.1", []string{"*/*"}}, 87 {true, "library/ruby:2.1", []string{"*/*:*"}}, 88 {true, "my.registry.tld/library/ruby", []string{"my.registry.tld/*/*"}}, 89 {true, "my.registry.tld/library/ruby:2.1", []string{"my.registry.tld/*/*:*"}}, 90 {true, "my.registry.tld/group/subgroup/ruby", []string{"my.registry.tld/*/*/*"}}, 91 {true, "my.registry.tld/group/subgroup/ruby:2.1", []string{"my.registry.tld/*/*/*:*"}}, 92 {true, "ruby", []string{"**/*"}}, 93 {true, "ruby:2.1", []string{"**/*"}}, 94 {true, "ruby:latest", []string{"**/*"}}, 95 {true, "library/ruby", []string{"**/*"}}, 96 {true, "library/ruby:2.1", []string{"**/*"}}, 97 {true, "library/ruby:2.1", []string{"**/*:*"}}, 98 {true, "my.registry.tld/library/ruby", []string{"my.registry.tld/**/*"}}, 99 {true, "my.registry.tld/library/ruby:2.1", []string{"my.registry.tld/**/*:*"}}, 100 {true, "my.registry.tld/group/subgroup/ruby", []string{"my.registry.tld/**/*"}}, 101 {true, "my.registry.tld/group/subgroup/ruby:2.1", []string{"my.registry.tld/**/*:*"}}, 102 {false, "library/ruby", []string{"*"}}, 103 {false, "library/ruby:2.1", []string{"*"}}, 104 {false, "my.registry.tld/ruby", []string{"*"}}, 105 {false, "my.registry.tld/ruby:2.1", []string{"*"}}, 106 {false, "my.registry.tld/library/ruby", []string{"*"}}, 107 {false, "my.registry.tld/library/ruby:2.1", []string{"*"}}, 108 {false, "my.registry.tld/group/subgroup/ruby", []string{"*"}}, 109 {false, "my.registry.tld/group/subgroup/ruby:2.1", []string{"*"}}, 110 {false, "library/ruby", []string{"*/*:*"}}, 111 {false, "my.registry.tld/group/subgroup/ruby", []string{"my.registry.tld/*/*"}}, 112 {false, "my.registry.tld/group/subgroup/ruby:2.1", []string{"my.registry.tld/*/*:*"}}, 113 {false, "library/ruby", []string{"**/*:*"}}, 114 } 115 116 func TestVerifyAllowedImage(t *testing.T) { 117 e := executor{} 118 119 for _, test := range testAllowedImages { 120 err := e.verifyAllowedImage(test.image, "", test.allowed_images, []string{}) 121 122 if err != nil && test.allowed { 123 t.Errorf("%q must be allowed by %q", test.image, test.allowed_images) 124 } else if err == nil && !test.allowed { 125 t.Errorf("%q must not be allowed by %q", test.image, test.allowed_images) 126 } 127 } 128 } 129 130 type testServiceDescription struct { 131 description string 132 image string 133 service string 134 version string 135 alias string 136 alternative string 137 } 138 139 var testServices = []testServiceDescription{ 140 {"service", "service:latest", "service", "latest", "service", ""}, 141 {"service:version", "service:version", "service", "version", "service", ""}, 142 {"namespace/service", "namespace/service:latest", "namespace/service", "latest", "namespace__service", "namespace-service"}, 143 {"namespace/service:version", "namespace/service:version", "namespace/service", "version", "namespace__service", "namespace-service"}, 144 {"domain.tld/service", "domain.tld/service:latest", "domain.tld/service", "latest", "domain.tld__service", "domain.tld-service"}, 145 {"domain.tld/service:version", "domain.tld/service:version", "domain.tld/service", "version", "domain.tld__service", "domain.tld-service"}, 146 {"domain.tld/namespace/service", "domain.tld/namespace/service:latest", "domain.tld/namespace/service", "latest", "domain.tld__namespace__service", "domain.tld-namespace-service"}, 147 {"domain.tld/namespace/service:version", "domain.tld/namespace/service:version", "domain.tld/namespace/service", "version", "domain.tld__namespace__service", "domain.tld-namespace-service"}, 148 {"domain.tld:8080/service", "domain.tld:8080/service:latest", "domain.tld/service", "latest", "domain.tld__service", "domain.tld-service"}, 149 {"domain.tld:8080/service:version", "domain.tld:8080/service:version", "domain.tld/service", "version", "domain.tld__service", "domain.tld-service"}, 150 {"domain.tld:8080/namespace/service", "domain.tld:8080/namespace/service:latest", "domain.tld/namespace/service", "latest", "domain.tld__namespace__service", "domain.tld-namespace-service"}, 151 {"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"}, 152 {"subdomain.domain.tld:8080/service", "subdomain.domain.tld:8080/service:latest", "subdomain.domain.tld/service", "latest", "subdomain.domain.tld__service", "subdomain.domain.tld-service"}, 153 {"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"}, 154 {"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"}, 155 {"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"}, 156 } 157 158 func testSplitService(t *testing.T, test testServiceDescription) { 159 e := executor{} 160 service, version, imageName, linkNames := e.splitServiceAndVersion(test.description) 161 162 assert.Equal(t, test.service, service, "service for "+test.description) 163 assert.Equal(t, test.version, version, "version for "+test.description) 164 assert.Equal(t, test.image, imageName, "image for "+test.description) 165 assert.Equal(t, test.alias, linkNames[0], "alias for "+test.description) 166 if test.alternative != "" { 167 assert.Len(t, linkNames, 2, "linkNames len for "+test.description) 168 assert.Equal(t, test.alternative, linkNames[1], "alternative for "+test.description) 169 } else { 170 assert.Len(t, linkNames, 1, "linkNames len for "+test.description) 171 } 172 } 173 174 func TestSplitService(t *testing.T) { 175 for _, test := range testServices { 176 t.Run(test.description, func(t *testing.T) { 177 testSplitService(t, test) 178 }) 179 } 180 } 181 182 func testServiceFromNamedImage(t *testing.T, description, imageName, serviceName string) { 183 var c docker_helpers.MockClient 184 defer c.AssertExpectations(t) 185 186 containerName := fmt.Sprintf("runner-abcdef12-project-0-concurrent-0-%s-0", strings.Replace(serviceName, "/", "__", -1)) 187 networkID := "network-id" 188 189 e := executor{client: &c} 190 options := buildImagePullOptions(e, imageName) 191 e.Config = common.RunnerConfig{} 192 e.Config.Docker = &common.DockerConfig{} 193 e.Build = &common.Build{ 194 ProjectRunnerID: 0, 195 Runner: &common.RunnerConfig{}, 196 } 197 e.Build.JobInfo.ProjectID = 0 198 e.Build.Runner.Token = "abcdef1234567890" 199 e.Context = context.Background() 200 201 c.On("ImagePullBlocking", e.Context, imageName, options). 202 Return(nil). 203 Once() 204 205 c.On("ImageInspectWithRaw", e.Context, imageName). 206 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 207 Twice() 208 209 c.On("ContainerRemove", e.Context, containerName, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}). 210 Return(nil). 211 Once() 212 213 networkContainersMap := map[string]types.EndpointResource{ 214 "1": {Name: containerName}, 215 } 216 217 c.On("NetworkList", e.Context, types.NetworkListOptions{}). 218 Return([]types.NetworkResource{{ID: networkID, Name: "network-name", Containers: networkContainersMap}}, nil). 219 Once() 220 221 c.On("NetworkDisconnect", e.Context, networkID, containerName, true). 222 Return(nil). 223 Once() 224 225 c.On("ContainerCreate", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). 226 Return(container.ContainerCreateCreatedBody{ID: containerName}, nil). 227 Once() 228 229 c.On("ContainerStart", e.Context, mock.Anything, mock.Anything). 230 Return(nil). 231 Once() 232 233 linksMap := make(map[string]*types.Container) 234 err := e.createFromServiceDefinition(0, common.Image{Name: description}, linksMap) 235 assert.NoError(t, err) 236 } 237 238 func TestServiceFromNamedImage(t *testing.T) { 239 for _, test := range testServices { 240 t.Run(test.description, func(t *testing.T) { 241 testServiceFromNamedImage(t, test.description, test.image, test.service) 242 }) 243 } 244 } 245 246 func TestDockerForNamedImage(t *testing.T) { 247 var c docker_helpers.MockClient 248 defer c.AssertExpectations(t) 249 validSHA := "real@sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c" 250 251 e := executor{client: &c} 252 e.Context = context.Background() 253 options := buildImagePullOptions(e, "test") 254 255 c.On("ImagePullBlocking", e.Context, "test:latest", options). 256 Return(os.ErrNotExist). 257 Once() 258 259 c.On("ImagePullBlocking", e.Context, "tagged:tag", options). 260 Return(os.ErrNotExist). 261 Once() 262 263 c.On("ImagePullBlocking", e.Context, validSHA, options). 264 Return(os.ErrNotExist). 265 Once() 266 267 image, err := e.pullDockerImage("test", nil) 268 assert.Error(t, err) 269 assert.Nil(t, image) 270 271 image, err = e.pullDockerImage("tagged:tag", nil) 272 assert.Error(t, err) 273 assert.Nil(t, image) 274 275 image, err = e.pullDockerImage(validSHA, nil) 276 assert.Error(t, err) 277 assert.Nil(t, image) 278 } 279 280 func TestDockerForExistingImage(t *testing.T) { 281 var c docker_helpers.MockClient 282 defer c.AssertExpectations(t) 283 284 e := executor{client: &c} 285 e.Context = context.Background() 286 options := buildImagePullOptions(e, "existing") 287 288 c.On("ImagePullBlocking", e.Context, "existing:latest", options). 289 Return(nil). 290 Once() 291 292 c.On("ImageInspectWithRaw", e.Context, "existing"). 293 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 294 Once() 295 296 image, err := e.pullDockerImage("existing", nil) 297 assert.NoError(t, err) 298 assert.NotNil(t, image) 299 } 300 301 func (e *executor) setPolicyMode(pullPolicy common.DockerPullPolicy) { 302 e.Config = common.RunnerConfig{ 303 RunnerSettings: common.RunnerSettings{ 304 Docker: &common.DockerConfig{ 305 PullPolicy: pullPolicy, 306 }, 307 }, 308 } 309 } 310 311 func TestDockerGetImageById(t *testing.T) { 312 var c docker_helpers.MockClient 313 defer c.AssertExpectations(t) 314 315 // Use default policy 316 e := executor{client: &c} 317 e.Context = context.Background() 318 e.setPolicyMode("") 319 320 c.On("ImageInspectWithRaw", e.Context, "ID"). 321 Return(types.ImageInspect{ID: "ID"}, nil, nil). 322 Once() 323 324 image, err := e.getDockerImage("ID") 325 assert.NoError(t, err) 326 assert.NotNil(t, image) 327 assert.Equal(t, "ID", image.ID) 328 } 329 330 func TestDockerUnknownPolicyMode(t *testing.T) { 331 var c docker_helpers.MockClient 332 defer c.AssertExpectations(t) 333 334 e := executor{client: &c} 335 e.Context = context.Background() 336 e.setPolicyMode("unknown") 337 338 _, err := e.getDockerImage("not-existing") 339 assert.Error(t, err) 340 } 341 342 func TestDockerPolicyModeNever(t *testing.T) { 343 var c docker_helpers.MockClient 344 defer c.AssertExpectations(t) 345 346 e := executor{client: &c} 347 e.Context = context.Background() 348 e.setPolicyMode(common.PullPolicyNever) 349 350 c.On("ImageInspectWithRaw", e.Context, "existing"). 351 Return(types.ImageInspect{ID: "existing"}, nil, nil). 352 Once() 353 354 c.On("ImageInspectWithRaw", e.Context, "not-existing"). 355 Return(types.ImageInspect{}, nil, os.ErrNotExist). 356 Once() 357 358 image, err := e.getDockerImage("existing") 359 assert.NoError(t, err) 360 assert.Equal(t, "existing", image.ID) 361 362 image, err = e.getDockerImage("not-existing") 363 assert.Error(t, err) 364 } 365 366 func TestDockerPolicyModeIfNotPresentForExistingImage(t *testing.T) { 367 var c docker_helpers.MockClient 368 defer c.AssertExpectations(t) 369 370 e := executor{client: &c} 371 e.Context = context.Background() 372 e.setPolicyMode(common.PullPolicyIfNotPresent) 373 374 c.On("ImageInspectWithRaw", e.Context, "existing"). 375 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 376 Once() 377 378 image, err := e.getDockerImage("existing") 379 assert.NoError(t, err) 380 assert.NotNil(t, image) 381 } 382 383 func TestDockerPolicyModeIfNotPresentForNotExistingImage(t *testing.T) { 384 var c docker_helpers.MockClient 385 defer c.AssertExpectations(t) 386 387 e := executor{client: &c} 388 e.Context = context.Background() 389 e.setPolicyMode(common.PullPolicyIfNotPresent) 390 391 c.On("ImageInspectWithRaw", e.Context, "not-existing"). 392 Return(types.ImageInspect{}, nil, os.ErrNotExist). 393 Once() 394 395 options := buildImagePullOptions(e, "not-existing") 396 c.On("ImagePullBlocking", e.Context, "not-existing:latest", options). 397 Return(nil). 398 Once() 399 400 c.On("ImageInspectWithRaw", e.Context, "not-existing"). 401 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 402 Once() 403 404 image, err := e.getDockerImage("not-existing") 405 assert.NoError(t, err) 406 assert.NotNil(t, image) 407 408 c.On("ImageInspectWithRaw", e.Context, "not-existing"). 409 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 410 Once() 411 412 // It shouldn't execute the pull for second time 413 image, err = e.getDockerImage("not-existing") 414 assert.NoError(t, err) 415 assert.NotNil(t, image) 416 } 417 418 func TestDockerPolicyModeAlwaysForExistingImage(t *testing.T) { 419 var c docker_helpers.MockClient 420 defer c.AssertExpectations(t) 421 422 e := executor{client: &c} 423 e.Context = context.Background() 424 e.setPolicyMode(common.PullPolicyAlways) 425 426 c.On("ImageInspectWithRaw", e.Context, "existing"). 427 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 428 Once() 429 430 options := buildImagePullOptions(e, "existing:latest") 431 c.On("ImagePullBlocking", e.Context, "existing:latest", options). 432 Return(nil). 433 Once() 434 435 c.On("ImageInspectWithRaw", e.Context, "existing"). 436 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 437 Once() 438 439 image, err := e.getDockerImage("existing") 440 assert.NoError(t, err) 441 assert.NotNil(t, image) 442 } 443 444 func TestDockerPolicyModeAlwaysForLocalOnlyImage(t *testing.T) { 445 var c docker_helpers.MockClient 446 defer c.AssertExpectations(t) 447 448 e := executor{client: &c} 449 e.Context = context.Background() 450 e.setPolicyMode(common.PullPolicyAlways) 451 452 c.On("ImageInspectWithRaw", e.Context, "existing"). 453 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 454 Once() 455 456 options := buildImagePullOptions(e, "existing:lastest") 457 c.On("ImagePullBlocking", e.Context, "existing:latest", options). 458 Return(fmt.Errorf("not found")). 459 Once() 460 461 image, err := e.getDockerImage("existing") 462 assert.Error(t, err) 463 assert.Nil(t, image) 464 } 465 466 func TestDockerGetExistingDockerImageIfPullFails(t *testing.T) { 467 var c docker_helpers.MockClient 468 defer c.AssertExpectations(t) 469 470 e := executor{client: &c} 471 e.Context = context.Background() 472 e.setPolicyMode(common.PullPolicyAlways) 473 474 c.On("ImageInspectWithRaw", e.Context, "to-pull"). 475 Return(types.ImageInspect{ID: "image-id"}, nil, nil). 476 Once() 477 478 options := buildImagePullOptions(e, "to-pull") 479 c.On("ImagePullBlocking", e.Context, "to-pull:latest", options). 480 Return(os.ErrNotExist). 481 Once() 482 483 image, err := e.getDockerImage("to-pull") 484 assert.Error(t, err) 485 assert.Nil(t, image, "Forces to authorize pulling") 486 487 c.On("ImageInspectWithRaw", e.Context, "not-existing"). 488 Return(types.ImageInspect{}, nil, os.ErrNotExist). 489 Once() 490 491 c.On("ImagePullBlocking", e.Context, "not-existing:latest", options). 492 Return(os.ErrNotExist). 493 Once() 494 495 image, err = e.getDockerImage("not-existing") 496 assert.Error(t, err) 497 assert.Nil(t, image, "No existing image") 498 } 499 500 func TestHostMountedBuildsDirectory(t *testing.T) { 501 tests := []struct { 502 path string 503 volumes []string 504 result bool 505 }{ 506 {"/build", []string{"/build:/build"}, true}, 507 {"/build", []string{"/build/:/build"}, true}, 508 {"/build", []string{"/build"}, false}, 509 {"/build", []string{"/folder:/folder"}, false}, 510 {"/build", []string{"/folder"}, false}, 511 {"/build/other/directory", []string{"/build/:/build"}, true}, 512 {"/build/other/directory", []string{}, false}, 513 } 514 515 for _, i := range tests { 516 c := common.RunnerConfig{ 517 RunnerSettings: common.RunnerSettings{ 518 BuildsDir: i.path, 519 Docker: &common.DockerConfig{ 520 Volumes: i.volumes, 521 }, 522 }, 523 } 524 e := &executor{} 525 526 t.Log("Testing", i.path, "if volumes are configured to:", i.volumes, "...") 527 assert.Equal(t, i.result, e.isHostMountedVolume(i.path, i.volumes...)) 528 529 e.prepareBuildsDir(&c) 530 assert.Equal(t, i.result, e.SharedBuildsDir) 531 } 532 } 533 534 var testFileAuthConfigs = `{"auths":{"https://registry.domain.tld:5005/v1/":{"auth":"aW52YWxpZF91c2VyOmludmFsaWRfcGFzc3dvcmQ="},"registry2.domain.tld:5005":{"auth":"dGVzdF91c2VyOnRlc3RfcGFzc3dvcmQ="}}}` 535 var testVariableAuthConfigs = `{"auths":{"https://registry.domain.tld:5005/v1/":{"auth":"dGVzdF91c2VyOnRlc3RfcGFzc3dvcmQ="}}}` 536 537 func getAuthConfigTestExecutor(t *testing.T, precreateConfigFile bool) executor { 538 tempHomeDir, err := ioutil.TempDir("", "docker-auth-configs-test") 539 require.NoError(t, err) 540 541 if precreateConfigFile { 542 dockerConfigFile := path.Join(tempHomeDir, ".dockercfg") 543 ioutil.WriteFile(dockerConfigFile, []byte(testFileAuthConfigs), 0600) 544 docker_helpers.HomeDirectory = tempHomeDir 545 } else { 546 docker_helpers.HomeDirectory = "" 547 } 548 549 e := executor{} 550 e.Build = &common.Build{ 551 Runner: &common.RunnerConfig{}, 552 } 553 554 e.Build.Token = "abcd123456" 555 556 e.Config = common.RunnerConfig{} 557 e.Config.Docker = &common.DockerConfig{ 558 PullPolicy: common.PullPolicyAlways, 559 } 560 561 return e 562 } 563 564 func addGitLabRegistryCredentials(e *executor) { 565 e.Build.Credentials = []common.Credentials{ 566 { 567 Type: "registry", 568 URL: "registry.gitlab.tld:1234", 569 Username: "gitlab-ci-token", 570 Password: e.Build.Token, 571 }, 572 } 573 } 574 575 func addRemoteVariableCredentials(e *executor) { 576 e.Build.Variables = common.JobVariables{ 577 common.JobVariable{ 578 Key: "DOCKER_AUTH_CONFIG", 579 Value: testVariableAuthConfigs, 580 }, 581 } 582 } 583 584 func addLocalVariableCredentials(e *executor) { 585 e.Build.Runner.Environment = []string{ 586 "DOCKER_AUTH_CONFIG=" + testVariableAuthConfigs, 587 } 588 } 589 590 func assertEmptyCredentials(t *testing.T, ac *types.AuthConfig, messageElements ...string) { 591 if ac != nil { 592 assert.Empty(t, ac.ServerAddress, "ServerAddress for %v", messageElements) 593 assert.Empty(t, ac.Username, "Username for %v", messageElements) 594 assert.Empty(t, ac.Password, "Password for %v", messageElements) 595 } 596 } 597 598 func assertCredentials(t *testing.T, serverAddress, username, password string, ac *types.AuthConfig, messageElements ...string) { 599 assert.Equal(t, serverAddress, ac.ServerAddress, "ServerAddress for %v", messageElements) 600 assert.Equal(t, username, ac.Username, "Username for %v", messageElements) 601 assert.Equal(t, password, ac.Password, "Password for %v", messageElements) 602 } 603 604 func getTestAuthConfig(t *testing.T, e executor, imageName string) *types.AuthConfig { 605 ac := e.getAuthConfig(imageName) 606 607 return ac 608 } 609 610 func testVariableAuthConfig(t *testing.T, e executor) { 611 t.Run("withoutGitLabRegistry", func(t *testing.T) { 612 ac := getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version") 613 assertCredentials(t, "https://registry.domain.tld:5005/v1/", "test_user", "test_password", ac, "registry.domain.tld:5005/image/name:version") 614 615 ac = getTestAuthConfig(t, e, "registry2.domain.tld:5005/image/name:version") 616 assertCredentials(t, "registry2.domain.tld:5005", "test_user", "test_password", ac, "registry2.domain.tld:5005/image/name:version") 617 618 ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version") 619 assertEmptyCredentials(t, ac, "registry.gitlab.tld:1234") 620 }) 621 622 t.Run("withGitLabRegistry", func(t *testing.T) { 623 addGitLabRegistryCredentials(&e) 624 625 ac := getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version") 626 assertCredentials(t, "https://registry.domain.tld:5005/v1/", "test_user", "test_password", ac, "registry.domain.tld:5005/image/name:version") 627 628 ac = getTestAuthConfig(t, e, "registry2.domain.tld:5005/image/name:version") 629 assertCredentials(t, "registry2.domain.tld:5005", "test_user", "test_password", ac, "registry2.domain.tld:5005/image/name:version") 630 631 ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version") 632 assertCredentials(t, "registry.gitlab.tld:1234", "gitlab-ci-token", "abcd123456", ac, "registry.gitlab.tld:1234") 633 }) 634 } 635 636 func TestGetRemoteVariableAuthConfig(t *testing.T) { 637 e := getAuthConfigTestExecutor(t, true) 638 addRemoteVariableCredentials(&e) 639 640 testVariableAuthConfig(t, e) 641 } 642 643 func TestGetLocalVariableAuthConfig(t *testing.T) { 644 e := getAuthConfigTestExecutor(t, true) 645 addLocalVariableCredentials(&e) 646 647 testVariableAuthConfig(t, e) 648 } 649 650 func TestGetDefaultAuthConfig(t *testing.T) { 651 t.Run("withoutGitLabRegistry", func(t *testing.T) { 652 e := getAuthConfigTestExecutor(t, false) 653 654 ac := getTestAuthConfig(t, e, "docker:dind") 655 assertEmptyCredentials(t, ac, "docker:dind") 656 657 ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version") 658 assertEmptyCredentials(t, ac, "registry.gitlab.tld:1234") 659 660 ac = getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version") 661 assertEmptyCredentials(t, ac, "registry.domain.tld:5005/image/name:version") 662 }) 663 664 t.Run("withGitLabRegistry", func(t *testing.T) { 665 e := getAuthConfigTestExecutor(t, false) 666 addGitLabRegistryCredentials(&e) 667 668 ac := getTestAuthConfig(t, e, "docker:dind") 669 assertEmptyCredentials(t, ac, "docker:dind") 670 671 ac = getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version") 672 assertEmptyCredentials(t, ac, "registry.domain.tld:5005/image/name:version") 673 674 ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version") 675 assertCredentials(t, "registry.gitlab.tld:1234", "gitlab-ci-token", "abcd123456", ac, "registry.gitlab.tld:1234") 676 }) 677 } 678 679 func TestAuthConfigOverwritingOrder(t *testing.T) { 680 testVariableAuthConfigs = `{"auths":{"registry.gitlab.tld:1234":{"auth":"ZnJvbV92YXJpYWJsZTpwYXNzd29yZA=="}}}` 681 testFileAuthConfigs = `{"auths":{"registry.gitlab.tld:1234":{"auth":"ZnJvbV9maWxlOnBhc3N3b3Jk"}}}` 682 683 imageName := "registry.gitlab.tld:1234/image/name:latest" 684 685 t.Run("gitlabRegistryOnly", func(t *testing.T) { 686 e := getAuthConfigTestExecutor(t, false) 687 addGitLabRegistryCredentials(&e) 688 689 ac := getTestAuthConfig(t, e, imageName) 690 assertCredentials(t, "registry.gitlab.tld:1234", "gitlab-ci-token", e.Build.Token, ac, imageName) 691 }) 692 693 t.Run("withConfigFromRemoteVariable", func(t *testing.T) { 694 e := getAuthConfigTestExecutor(t, false) 695 addGitLabRegistryCredentials(&e) 696 addRemoteVariableCredentials(&e) 697 698 ac := getTestAuthConfig(t, e, imageName) 699 assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName) 700 }) 701 702 t.Run("withConfigFromLocalVariable", func(t *testing.T) { 703 e := getAuthConfigTestExecutor(t, false) 704 addGitLabRegistryCredentials(&e) 705 addLocalVariableCredentials(&e) 706 707 ac := getTestAuthConfig(t, e, imageName) 708 assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName) 709 }) 710 711 t.Run("withConfigFromFile", func(t *testing.T) { 712 e := getAuthConfigTestExecutor(t, true) 713 addGitLabRegistryCredentials(&e) 714 715 ac := getTestAuthConfig(t, e, imageName) 716 assertCredentials(t, "registry.gitlab.tld:1234", "from_file", "password", ac, imageName) 717 }) 718 719 t.Run("withConfigFromVariableAndFromFile", func(t *testing.T) { 720 e := getAuthConfigTestExecutor(t, true) 721 addGitLabRegistryCredentials(&e) 722 addRemoteVariableCredentials(&e) 723 724 ac := getTestAuthConfig(t, e, imageName) 725 assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName) 726 }) 727 728 t.Run("withConfigFromLocalAndRemoteVariable", func(t *testing.T) { 729 e := getAuthConfigTestExecutor(t, true) 730 addGitLabRegistryCredentials(&e) 731 addRemoteVariableCredentials(&e) 732 testVariableAuthConfigs = `{"auths":{"registry.gitlab.tld:1234":{"auth":"ZnJvbV9sb2NhbF92YXJpYWJsZTpwYXNzd29yZA=="}}}` 733 addLocalVariableCredentials(&e) 734 735 ac := getTestAuthConfig(t, e, imageName) 736 assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName) 737 }) 738 } 739 740 func testGetDockerImage(t *testing.T, e executor, imageName string, setClientExpectations func(c *docker_helpers.MockClient, imageName string)) { 741 t.Run("get:"+imageName, func(t *testing.T) { 742 var c docker_helpers.MockClient 743 defer c.AssertExpectations(t) 744 745 e.client = &c 746 747 setClientExpectations(&c, imageName) 748 749 image, err := e.getDockerImage(imageName) 750 assert.NoError(t, err, "Should not generate error") 751 assert.Equal(t, "this-image", image.ID, "Image ID") 752 }) 753 } 754 755 func testDeniesDockerImage(t *testing.T, e executor, imageName string, setClientExpectations func(c *docker_helpers.MockClient, imageName string)) { 756 t.Run("deny:"+imageName, func(t *testing.T) { 757 var c docker_helpers.MockClient 758 defer c.AssertExpectations(t) 759 760 e.client = &c 761 762 setClientExpectations(&c, imageName) 763 764 _, err := e.getDockerImage(imageName) 765 assert.Error(t, err, "Should generate error") 766 }) 767 } 768 769 func addFindsLocalImageExpectations(c *docker_helpers.MockClient, imageName string) { 770 c.On("ImageInspectWithRaw", mock.Anything, imageName). 771 Return(types.ImageInspect{ID: "this-image"}, nil, nil). 772 Once() 773 } 774 775 func addPullsRemoteImageExpectations(c *docker_helpers.MockClient, imageName string) { 776 c.On("ImageInspectWithRaw", mock.Anything, imageName). 777 Return(types.ImageInspect{ID: "not-this-image"}, nil, nil). 778 Once() 779 780 c.On("ImagePullBlocking", mock.Anything, imageName, mock.AnythingOfType("types.ImagePullOptions")). 781 Return(nil). 782 Once() 783 784 c.On("ImageInspectWithRaw", mock.Anything, imageName). 785 Return(types.ImageInspect{ID: "this-image"}, nil, nil). 786 Once() 787 } 788 789 func addDeniesPullExpectations(c *docker_helpers.MockClient, imageName string) { 790 c.On("ImageInspectWithRaw", mock.Anything, imageName). 791 Return(types.ImageInspect{ID: "image"}, nil, nil). 792 Once() 793 794 c.On("ImagePullBlocking", mock.Anything, imageName, mock.AnythingOfType("types.ImagePullOptions")). 795 Return(fmt.Errorf("deny pulling")). 796 Once() 797 } 798 799 func TestPullPolicyWhenAlwaysIsSet(t *testing.T) { 800 remoteImage := "registry.domain.tld:5005/image/name:version" 801 gitlabImage := "registry.gitlab.tld:1234/image/name:version" 802 803 e := getAuthConfigTestExecutor(t, false) 804 e.Context = context.Background() 805 e.Config.Docker.PullPolicy = common.PullPolicyAlways 806 807 testGetDockerImage(t, e, remoteImage, addPullsRemoteImageExpectations) 808 testDeniesDockerImage(t, e, remoteImage, addDeniesPullExpectations) 809 810 testGetDockerImage(t, e, gitlabImage, addPullsRemoteImageExpectations) 811 testDeniesDockerImage(t, e, gitlabImage, addDeniesPullExpectations) 812 } 813 814 func TestPullPolicyWhenIfNotPresentIsSet(t *testing.T) { 815 remoteImage := "registry.domain.tld:5005/image/name:version" 816 gitlabImage := "registry.gitlab.tld:1234/image/name:version" 817 818 e := getAuthConfigTestExecutor(t, false) 819 e.Context = context.Background() 820 e.Config.Docker.PullPolicy = common.PullPolicyIfNotPresent 821 822 testGetDockerImage(t, e, remoteImage, addFindsLocalImageExpectations) 823 testGetDockerImage(t, e, gitlabImage, addFindsLocalImageExpectations) 824 } 825 826 func TestDockerWatchOn_1_12_4(t *testing.T) { 827 if helpers.SkipIntegrationTests(t, "docker", "info") { 828 return 829 } 830 831 e := executor{} 832 e.Context = context.Background() 833 e.Build = &common.Build{ 834 Runner: &common.RunnerConfig{}, 835 } 836 e.Build.Token = "abcd123456" 837 e.BuildShell = &common.ShellConfiguration{ 838 Environment: []string{}, 839 } 840 841 e.Config = common.RunnerConfig{} 842 e.Config.Docker = &common.DockerConfig{ 843 PullPolicy: common.PullPolicyAlways, 844 } 845 846 e.Trace = &common.Trace{Writer: os.Stdout} 847 848 err := e.connectDocker() 849 assert.NoError(t, err) 850 851 container, err := e.createContainer("build", common.Image{Name: "alpine"}, []string{"/bin/sh"}, []string{}) 852 assert.NoError(t, err) 853 assert.NotNil(t, container) 854 855 input := bytes.NewBufferString("echo 'script'") 856 857 finished := make(chan bool, 1) 858 wg := &sync.WaitGroup{} 859 wg.Add(1) // Avoid a race where assert.NoError() is called too late in the goroutine 860 go func() { 861 err = e.watchContainer(e.Context, container.ID, input) 862 assert.NoError(t, err) 863 t.Log(err) 864 finished <- true 865 wg.Done() 866 }() 867 868 select { 869 case <-finished: 870 case <-time.After(15 * time.Second): 871 t.Error("Container script not finished") 872 } 873 874 err = e.removeContainer(e.Context, container.ID) 875 assert.NoError(t, err) 876 wg.Wait() 877 } 878 879 type containerConfigExpectations func(*testing.T, *container.Config, *container.HostConfig) 880 881 type dockerConfigurationTestFakeDockerClient struct { 882 docker_helpers.MockClient 883 884 cce containerConfigExpectations 885 t *testing.T 886 } 887 888 func (c *dockerConfigurationTestFakeDockerClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) { 889 c.cce(c.t, config, hostConfig) 890 return container.ContainerCreateCreatedBody{ID: "abc"}, nil 891 } 892 893 func prepareTestDockerConfiguration(t *testing.T, dockerConfig *common.DockerConfig, cce containerConfigExpectations) (*dockerConfigurationTestFakeDockerClient, *executor) { 894 c := &dockerConfigurationTestFakeDockerClient{ 895 cce: cce, 896 t: t, 897 } 898 899 e := &executor{} 900 e.client = c 901 e.Config.Docker = dockerConfig 902 e.Build = &common.Build{ 903 Runner: &common.RunnerConfig{}, 904 } 905 e.Build.Token = "abcd123456" 906 e.BuildShell = &common.ShellConfiguration{ 907 Environment: []string{}, 908 } 909 910 c.On("ImageInspectWithRaw", mock.Anything, "alpine"). 911 Return(types.ImageInspect{ID: "123"}, []byte{}, nil).Twice() 912 c.On("ImagePullBlocking", mock.Anything, "alpine:latest", mock.Anything). 913 Return(nil).Once() 914 c.On("NetworkList", mock.Anything, mock.Anything). 915 Return([]types.NetworkResource{}, nil).Once() 916 c.On("ContainerRemove", mock.Anything, mock.Anything, mock.Anything). 917 Return(nil).Once() 918 919 return c, e 920 } 921 922 func testDockerConfigurationWithJobContainer(t *testing.T, dockerConfig *common.DockerConfig, cce containerConfigExpectations) { 923 c, e := prepareTestDockerConfiguration(t, dockerConfig, cce) 924 defer c.AssertExpectations(t) 925 926 c.On("ContainerInspect", mock.Anything, "abc"). 927 Return(types.ContainerJSON{}, nil).Once() 928 929 _, err := e.createContainer("build", common.Image{Name: "alpine"}, []string{"/bin/sh"}, []string{}) 930 assert.NoError(t, err, "Should create container without errors") 931 } 932 933 func testDockerConfigurationWithServiceContainer(t *testing.T, dockerConfig *common.DockerConfig, cce containerConfigExpectations) { 934 c, e := prepareTestDockerConfiguration(t, dockerConfig, cce) 935 defer c.AssertExpectations(t) 936 937 c.On("ContainerStart", mock.Anything, "abc", mock.Anything). 938 Return(nil).Once() 939 940 _, err := e.createService(0, "build", "latest", "alpine", common.Image{Command: []string{"/bin/sh"}}) 941 assert.NoError(t, err, "Should create service container without errors") 942 } 943 944 func TestDockerCPUSSetting(t *testing.T) { 945 examples := []struct { 946 cpus string 947 nanocpus int64 948 }{ 949 {"0.5", 500000000}, 950 {"0.25", 250000000}, 951 {"1/3", 333333333}, 952 {"1/8", 125000000}, 953 {"0.0001", 100000}, 954 } 955 956 for _, example := range examples { 957 t.Run(example.cpus, func(t *testing.T) { 958 dockerConfig := &common.DockerConfig{ 959 CPUS: example.cpus, 960 } 961 962 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 963 assert.Equal(t, int64(example.nanocpus), hostConfig.NanoCPUs) 964 } 965 966 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 967 }) 968 } 969 } 970 971 func TestDockerCPUSetCPUsSetting(t *testing.T) { 972 dockerConfig := &common.DockerConfig{ 973 CPUSetCPUs: "1-3,5", 974 } 975 976 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 977 assert.Equal(t, "1-3,5", hostConfig.CpusetCpus) 978 } 979 980 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 981 } 982 983 func TestDockerServicesTmpfsSetting(t *testing.T) { 984 dockerConfig := &common.DockerConfig{ 985 ServicesTmpfs: map[string]string{ 986 "/tmpfs": "rw,noexec", 987 }, 988 } 989 990 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 991 require.NotEmpty(t, hostConfig.Tmpfs) 992 } 993 994 testDockerConfigurationWithServiceContainer(t, dockerConfig, cce) 995 } 996 func TestDockerTmpfsSetting(t *testing.T) { 997 dockerConfig := &common.DockerConfig{ 998 Tmpfs: map[string]string{ 999 "/tmpfs": "rw,noexec", 1000 }, 1001 } 1002 1003 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1004 require.NotEmpty(t, hostConfig.Tmpfs) 1005 } 1006 1007 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1008 } 1009 func TestDockerUserNSSetting(t *testing.T) { 1010 dockerConfig := &common.DockerConfig{ 1011 UsernsMode: "host", 1012 } 1013 1014 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1015 assert.Equal(t, container.UsernsMode("host"), hostConfig.UsernsMode) 1016 } 1017 1018 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1019 1020 } 1021 1022 func TestDockerRuntimeSetting(t *testing.T) { 1023 dockerConfig := &common.DockerConfig{ 1024 Runtime: "runc", 1025 } 1026 1027 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1028 assert.Equal(t, "runc", hostConfig.Runtime) 1029 } 1030 1031 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1032 } 1033 1034 func TestDockerSysctlsSetting(t *testing.T) { 1035 dockerConfig := &common.DockerConfig{ 1036 SysCtls: map[string]string{ 1037 "net.ipv4.ip_forward": "1", 1038 }, 1039 } 1040 1041 cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) { 1042 assert.Equal(t, "1", hostConfig.Sysctls["net.ipv4.ip_forward"]) 1043 } 1044 1045 testDockerConfigurationWithJobContainer(t, dockerConfig, cce) 1046 } 1047 1048 func init() { 1049 docker_helpers.HomeDirectory = "" 1050 }