github.com/secure-build/gitlab-runner@v12.5.0+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  var volumesTestsDefaultCacheDir = "/default-cache-dir"
   670  
   671  func getExecutorForVolumesTests(t *testing.T, test volumesTestCase) (*executor, func()) {
   672  	clientMock := new(docker_helpers.MockClient)
   673  	volumesManagerMock := new(volumes.MockManager)
   674  
   675  	oldCreateVolumesManager := createVolumesManager
   676  	closureFn := func() {
   677  		createVolumesManager = oldCreateVolumesManager
   678  
   679  		volumesManagerMock.AssertExpectations(t)
   680  		clientMock.AssertExpectations(t)
   681  	}
   682  
   683  	createVolumesManager = func(_ *executor) (volumes.Manager, error) {
   684  		return volumesManagerMock, nil
   685  	}
   686  
   687  	if test.volumesManagerAssertions != nil {
   688  		test.volumesManagerAssertions(volumesManagerMock)
   689  	}
   690  
   691  	if test.clientAssertions != nil {
   692  		test.clientAssertions(clientMock)
   693  	}
   694  
   695  	c := common.RunnerConfig{
   696  		RunnerCredentials: common.RunnerCredentials{
   697  			Token: "abcdef1234567890",
   698  		},
   699  		RunnerSettings: common.RunnerSettings{
   700  			BuildsDir: test.buildsDir,
   701  			Docker: &common.DockerConfig{
   702  				Volumes: test.volumes,
   703  			},
   704  		},
   705  	}
   706  
   707  	e := &executor{
   708  		AbstractExecutor: executors.AbstractExecutor{
   709  			Build: &common.Build{
   710  				ProjectRunnerID: 0,
   711  				Runner:          &c,
   712  				JobResponse: common.JobResponse{
   713  					JobInfo: common.JobInfo{
   714  						ProjectID: 0,
   715  					},
   716  					GitInfo: common.GitInfo{
   717  						RepoURL: "https://gitlab.example.com/group/project.git",
   718  					},
   719  				},
   720  			},
   721  			Config: c,
   722  			ExecutorOptions: executors.ExecutorOptions{
   723  				DefaultBuildsDir: volumesTestsDefaultBuildsDir,
   724  				DefaultCacheDir:  volumesTestsDefaultCacheDir,
   725  			},
   726  		},
   727  		client: clientMock,
   728  		info: types.Info{
   729  			OSType: helperimage.OSTypeLinux,
   730  		},
   731  	}
   732  
   733  	e.Build.Variables = append(e.Build.Variables, common.JobVariable{
   734  		Key:   "GIT_STRATEGY",
   735  		Value: test.gitStrategy,
   736  	})
   737  
   738  	if test.adjustConfiguration != nil {
   739  		test.adjustConfiguration(e)
   740  	}
   741  
   742  	err := e.Build.StartBuild(
   743  		e.RootDir(),
   744  		e.CacheDir(),
   745  		e.CustomBuildEnabled(),
   746  		e.SharedBuildsDir,
   747  	)
   748  	require.NoError(t, err)
   749  
   750  	if test.createVolumeManager {
   751  		err = e.createVolumesManager()
   752  		require.NoError(t, err)
   753  	}
   754  
   755  	return e, closureFn
   756  }
   757  
   758  func TestCreateVolumes(t *testing.T) {
   759  	tests := map[string]volumesTestCase{
   760  		"volumes manager not created": {
   761  			expectedError: errVolumesManagerUndefined,
   762  		},
   763  		"no volumes defined, empty buildsDir, clone strategy, no errors": {
   764  			gitStrategy:         "clone",
   765  			createVolumeManager: true,
   766  		},
   767  		"no volumes defined, defined buildsDir, clone strategy, no errors": {
   768  			buildsDir:           "/builds",
   769  			gitStrategy:         "clone",
   770  			createVolumeManager: true,
   771  		},
   772  		"no volumes defined, defined buildsDir, fetch strategy, no errors": {
   773  			buildsDir:           "/builds",
   774  			gitStrategy:         "fetch",
   775  			createVolumeManager: true,
   776  		},
   777  		"volumes defined, empty buildsDir, clone strategy, no errors on user volume": {
   778  			volumes:     []string{"/volume"},
   779  			gitStrategy: "clone",
   780  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   781  				vm.On("Create", "/volume").
   782  					Return(nil).
   783  					Once()
   784  			},
   785  			createVolumeManager: true,
   786  		},
   787  		"volumes defined, empty buildsDir, clone strategy, cache containers disabled error on user volume": {
   788  			volumes:     []string{"/volume"},
   789  			gitStrategy: "clone",
   790  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   791  				vm.On("Create", "/volume").
   792  					Return(volumes.ErrCacheVolumesDisabled).
   793  					Once()
   794  			},
   795  			createVolumeManager: true,
   796  		},
   797  		"volumes defined, empty buildsDir, clone strategy, duplicated error on user volume": {
   798  			volumes:     []string{"/volume"},
   799  			gitStrategy: "clone",
   800  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   801  				vm.On("Create", "/volume").
   802  					Return(volumes.NewErrVolumeAlreadyDefined("/volume")).
   803  					Once()
   804  			},
   805  			createVolumeManager: true,
   806  			expectedError:       volumes.NewErrVolumeAlreadyDefined("/volume"),
   807  		},
   808  		"volumes defined, empty buildsDir, clone strategy, other error on user volume": {
   809  			volumes:     []string{"/volume"},
   810  			gitStrategy: "clone",
   811  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   812  				vm.On("Create", "/volume").
   813  					Return(errors.New("test-error")).
   814  					Once()
   815  			},
   816  			createVolumeManager: true,
   817  			expectedError:       errors.New("test-error"),
   818  		},
   819  	}
   820  
   821  	for testName, test := range tests {
   822  		t.Run(testName, func(t *testing.T) {
   823  			e, closureFn := getExecutorForVolumesTests(t, test)
   824  			defer closureFn()
   825  
   826  			err := e.createVolumes()
   827  			assert.Equal(t, test.expectedError, err)
   828  		})
   829  	}
   830  }
   831  
   832  func TestCreateBuildVolume(t *testing.T) {
   833  	tests := map[string]volumesTestCase{
   834  		"volumes manager not created": {
   835  			expectedError: errVolumesManagerUndefined,
   836  		},
   837  		"git strategy clone, empty buildsDir, no error": {
   838  			gitStrategy: "clone",
   839  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   840  				vm.On("CreateTemporary", volumesTestsDefaultBuildsDir).
   841  					Return(nil).
   842  					Once()
   843  			},
   844  			createVolumeManager: true,
   845  		},
   846  		"git strategy clone, empty buildsDir, duplicated error": {
   847  			gitStrategy: "clone",
   848  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   849  				vm.On("CreateTemporary", volumesTestsDefaultBuildsDir).
   850  					Return(volumes.NewErrVolumeAlreadyDefined(volumesTestsDefaultBuildsDir)).
   851  					Once()
   852  			},
   853  			createVolumeManager: true,
   854  		},
   855  		"git strategy clone, empty buildsDir, other error": {
   856  			gitStrategy: "clone",
   857  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   858  				vm.On("CreateTemporary", volumesTestsDefaultBuildsDir).
   859  					Return(errors.New("test-error")).
   860  					Once()
   861  			},
   862  			createVolumeManager: true,
   863  			expectedError:       errors.New("test-error"),
   864  		},
   865  		"git strategy clone, non-empty buildsDir, no error": {
   866  			gitStrategy: "clone",
   867  			buildsDir:   "/builds",
   868  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   869  				vm.On("CreateTemporary", "/builds").
   870  					Return(nil).
   871  					Once()
   872  			},
   873  			createVolumeManager: true,
   874  		},
   875  		"git strategy clone, non-empty buildsDir, duplicated error": {
   876  			gitStrategy: "clone",
   877  			buildsDir:   "/builds",
   878  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   879  				vm.On("CreateTemporary", "/builds").
   880  					Return(volumes.NewErrVolumeAlreadyDefined("/builds")).
   881  					Once()
   882  			},
   883  			createVolumeManager: true,
   884  		},
   885  		"git strategy clone, non-empty buildsDir, other error": {
   886  			gitStrategy: "clone",
   887  			buildsDir:   "/builds",
   888  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   889  				vm.On("CreateTemporary", "/builds").
   890  					Return(errors.New("test-error")).
   891  					Once()
   892  			},
   893  			createVolumeManager: true,
   894  			expectedError:       errors.New("test-error"),
   895  		},
   896  		"git strategy fetch, empty buildsDir, no error": {
   897  			gitStrategy: "fetch",
   898  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   899  				vm.On("Create", volumesTestsDefaultBuildsDir).
   900  					Return(nil).
   901  					Once()
   902  			},
   903  			createVolumeManager: true,
   904  		},
   905  		"git strategy fetch, empty buildsDir, duplicated error": {
   906  			gitStrategy: "fetch",
   907  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   908  				vm.On("Create", volumesTestsDefaultBuildsDir).
   909  					Return(volumes.NewErrVolumeAlreadyDefined(volumesTestsDefaultBuildsDir)).
   910  					Once()
   911  			},
   912  			createVolumeManager: true,
   913  		},
   914  		"git strategy fetch, empty buildsDir, other error": {
   915  			gitStrategy: "fetch",
   916  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   917  				vm.On("Create", volumesTestsDefaultBuildsDir).
   918  					Return(errors.New("test-error")).
   919  					Once()
   920  			},
   921  			createVolumeManager: true,
   922  			expectedError:       errors.New("test-error"),
   923  		},
   924  		"git strategy fetch, non-empty buildsDir, no error": {
   925  			gitStrategy: "fetch",
   926  			buildsDir:   "/builds",
   927  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   928  				vm.On("Create", "/builds").
   929  					Return(nil).
   930  					Once()
   931  			},
   932  			createVolumeManager: true,
   933  		},
   934  		"git strategy fetch, non-empty buildsDir, duplicated error": {
   935  			gitStrategy: "fetch",
   936  			buildsDir:   "/builds",
   937  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   938  				vm.On("Create", "/builds").
   939  					Return(volumes.NewErrVolumeAlreadyDefined("/builds")).
   940  					Once()
   941  			},
   942  			createVolumeManager: true,
   943  		},
   944  		"git strategy fetch, non-empty buildsDir, other error": {
   945  			gitStrategy: "fetch",
   946  			buildsDir:   "/builds",
   947  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   948  				vm.On("Create", "/builds").
   949  					Return(errors.New("test-error")).
   950  					Once()
   951  			},
   952  			createVolumeManager: true,
   953  			expectedError:       errors.New("test-error"),
   954  		},
   955  		"git strategy fetch, non-empty buildsDir, cache volumes disabled": {
   956  			gitStrategy: "fetch",
   957  			buildsDir:   "/builds",
   958  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   959  				vm.On("Create", "/builds").
   960  					Return(volumes.ErrCacheVolumesDisabled).
   961  					Once()
   962  				vm.On("CreateTemporary", "/builds").
   963  					Return(nil).
   964  					Once()
   965  			},
   966  			createVolumeManager: true,
   967  		},
   968  		"git strategy fetch, non-empty buildsDir, cache volumes disabled, duplicated error": {
   969  			gitStrategy: "fetch",
   970  			buildsDir:   "/builds",
   971  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   972  				vm.On("Create", "/builds").
   973  					Return(volumes.ErrCacheVolumesDisabled).
   974  					Once()
   975  				vm.On("CreateTemporary", "/builds").
   976  					Return(volumes.NewErrVolumeAlreadyDefined("/builds")).
   977  					Once()
   978  			},
   979  			createVolumeManager: true,
   980  		},
   981  		"git strategy fetch, non-empty buildsDir, no error, legacy builds dir": {
   982  			// TODO: Remove in 12.3
   983  			gitStrategy: "fetch",
   984  			buildsDir:   "/builds",
   985  			adjustConfiguration: func(e *executor) {
   986  				e.Build.Variables = append(e.Build.Variables, common.JobVariable{
   987  					Key:   featureflags.UseLegacyBuildsDirForDocker,
   988  					Value: "true",
   989  				})
   990  			},
   991  			volumesManagerAssertions: func(vm *volumes.MockManager) {
   992  				vm.On("Create", "/builds/group").
   993  					Return(nil).
   994  					Once()
   995  			},
   996  			createVolumeManager: true,
   997  		},
   998  		"git strategy clone, non-empty buildsDir, no error, legacy builds dir": {
   999  			// TODO: Remove in 12.3
  1000  			gitStrategy: "clone",
  1001  			buildsDir:   "/builds",
  1002  			adjustConfiguration: func(e *executor) {
  1003  				e.Build.Variables = append(e.Build.Variables, common.JobVariable{
  1004  					Key:   featureflags.UseLegacyBuildsDirForDocker,
  1005  					Value: "true",
  1006  				})
  1007  			},
  1008  			volumesManagerAssertions: func(vm *volumes.MockManager) {
  1009  				vm.On("CreateTemporary", "/builds/group").
  1010  					Return(nil).
  1011  					Once()
  1012  			},
  1013  			createVolumeManager: true,
  1014  		},
  1015  	}
  1016  
  1017  	for testName, test := range tests {
  1018  		t.Run(testName, func(t *testing.T) {
  1019  			e, closureFn := getExecutorForVolumesTests(t, test)
  1020  			defer closureFn()
  1021  
  1022  			err := e.createBuildVolume()
  1023  			assert.Equal(t, test.expectedError, err)
  1024  		})
  1025  	}
  1026  }
  1027  
  1028  func TestCreateDependencies(t *testing.T) {
  1029  	testError := errors.New("test-error")
  1030  
  1031  	tests := map[string]struct {
  1032  		legacyVolumesMountingOrder string
  1033  		expectedServiceVolumes     []string
  1034  	}{
  1035  		"UseLegacyVolumesMountingOrder is false": {
  1036  			legacyVolumesMountingOrder: "false",
  1037  			expectedServiceVolumes:     []string{"/volume", "/builds"},
  1038  		},
  1039  		// TODO: Remove in 12.6
  1040  		"UseLegacyVolumesMountingOrder is true": {
  1041  			legacyVolumesMountingOrder: "true",
  1042  			expectedServiceVolumes:     []string{"/builds"},
  1043  		},
  1044  	}
  1045  
  1046  	for testName, test := range tests {
  1047  		t.Run(testName, func(t *testing.T) {
  1048  			testCase := volumesTestCase{
  1049  				buildsDir: "/builds",
  1050  				volumes:   []string{"/volume"},
  1051  				adjustConfiguration: func(e *executor) {
  1052  					e.Build.Services = append(e.Build.Services, common.Image{
  1053  						Name: "alpine:latest",
  1054  					})
  1055  
  1056  					e.Build.Variables = append(e.Build.Variables, common.JobVariable{
  1057  						Key:   featureflags.UseLegacyVolumesMountingOrder,
  1058  						Value: test.legacyVolumesMountingOrder,
  1059  					})
  1060  				},
  1061  				volumesManagerAssertions: func(vm *volumes.MockManager) {
  1062  					binds := make([]string, 0)
  1063  
  1064  					vm.On("CreateTemporary", "/builds").
  1065  						Return(nil).
  1066  						Run(func(args mock.Arguments) {
  1067  							binds = append(binds, args.Get(0).(string))
  1068  						}).
  1069  						Once()
  1070  					vm.On("Create", "/volume").
  1071  						Return(nil).
  1072  						Run(func(args mock.Arguments) {
  1073  							binds = append(binds, args.Get(0).(string))
  1074  						}).
  1075  						Maybe() // In the FF enabled case this assertion will be not met because of error during service starts
  1076  					vm.On("Binds").
  1077  						Return(func() []string {
  1078  							return binds
  1079  						}).
  1080  						Once()
  1081  					vm.On("ContainerIDs").
  1082  						Return(nil).
  1083  						Once()
  1084  				},
  1085  				clientAssertions: func(c *docker_helpers.MockClient) {
  1086  					hostConfigMatcher := mock.MatchedBy(func(conf *container.HostConfig) bool {
  1087  						return assert.Equal(t, test.expectedServiceVolumes, conf.Binds)
  1088  					})
  1089  
  1090  					c.On("ImageInspectWithRaw", mock.Anything, "alpine:latest").
  1091  						Return(types.ImageInspect{}, nil, nil).
  1092  						Once()
  1093  					c.On("NetworkList", mock.Anything, mock.Anything).
  1094  						Return(nil, nil).
  1095  						Once()
  1096  					c.On("ContainerRemove", mock.Anything, "runner-abcdef12-project-0-concurrent-0-alpine-0", mock.Anything).
  1097  						Return(nil).
  1098  						Once()
  1099  					c.On("ContainerCreate", mock.Anything, mock.Anything, hostConfigMatcher, mock.Anything, "runner-abcdef12-project-0-concurrent-0-alpine-0").
  1100  						Return(container.ContainerCreateCreatedBody{ID: "container-ID"}, nil).
  1101  						Once()
  1102  					c.On("ContainerStart", mock.Anything, "container-ID", mock.Anything).
  1103  						Return(testError).
  1104  						Once()
  1105  				},
  1106  			}
  1107  
  1108  			e, closureFn := getExecutorForVolumesTests(t, testCase)
  1109  			defer closureFn()
  1110  
  1111  			err := e.createDependencies()
  1112  			assert.Equal(t, testError, err)
  1113  		})
  1114  	}
  1115  }
  1116  
  1117  var testFileAuthConfigs = `{"auths":{"https://registry.domain.tld:5005/v1/":{"auth":"aW52YWxpZF91c2VyOmludmFsaWRfcGFzc3dvcmQ="},"registry2.domain.tld:5005":{"auth":"dGVzdF91c2VyOnRlc3RfcGFzc3dvcmQ="}}}`
  1118  var testVariableAuthConfigs = `{"auths":{"https://registry.domain.tld:5005/v1/":{"auth":"dGVzdF91c2VyOnRlc3RfcGFzc3dvcmQ="}}}`
  1119  
  1120  func getAuthConfigTestExecutor(t *testing.T, precreateConfigFile bool) executor {
  1121  	tempHomeDir, err := ioutil.TempDir("", "docker-auth-configs-test")
  1122  	require.NoError(t, err)
  1123  
  1124  	if precreateConfigFile {
  1125  		dockerConfigFile := path.Join(tempHomeDir, ".dockercfg")
  1126  		err = ioutil.WriteFile(dockerConfigFile, []byte(testFileAuthConfigs), 0600)
  1127  		require.NoError(t, err)
  1128  		docker_helpers.HomeDirectory = tempHomeDir
  1129  	} else {
  1130  		docker_helpers.HomeDirectory = ""
  1131  	}
  1132  
  1133  	e := executor{}
  1134  	e.Build = &common.Build{
  1135  		Runner: &common.RunnerConfig{},
  1136  	}
  1137  
  1138  	e.Build.Token = "abcd123456"
  1139  
  1140  	e.Config = common.RunnerConfig{}
  1141  	e.Config.Docker = &common.DockerConfig{
  1142  		PullPolicy: common.PullPolicyAlways,
  1143  	}
  1144  
  1145  	return e
  1146  }
  1147  
  1148  func addGitLabRegistryCredentials(e *executor) {
  1149  	e.Build.Credentials = []common.Credentials{
  1150  		{
  1151  			Type:     "registry",
  1152  			URL:      "registry.gitlab.tld:1234",
  1153  			Username: "gitlab-ci-token",
  1154  			Password: e.Build.Token,
  1155  		},
  1156  	}
  1157  }
  1158  
  1159  func addRemoteVariableCredentials(e *executor) {
  1160  	e.Build.Variables = common.JobVariables{
  1161  		common.JobVariable{
  1162  			Key:   "DOCKER_AUTH_CONFIG",
  1163  			Value: testVariableAuthConfigs,
  1164  		},
  1165  	}
  1166  }
  1167  
  1168  func addLocalVariableCredentials(e *executor) {
  1169  	e.Build.Runner.Environment = []string{
  1170  		"DOCKER_AUTH_CONFIG=" + testVariableAuthConfigs,
  1171  	}
  1172  }
  1173  
  1174  func assertEmptyCredentials(t *testing.T, ac *types.AuthConfig, messageElements ...string) {
  1175  	if ac != nil {
  1176  		assert.Empty(t, ac.ServerAddress, "ServerAddress for %v", messageElements)
  1177  		assert.Empty(t, ac.Username, "Username for %v", messageElements)
  1178  		assert.Empty(t, ac.Password, "Password for %v", messageElements)
  1179  	}
  1180  }
  1181  
  1182  func assertCredentials(t *testing.T, serverAddress, username, password string, ac *types.AuthConfig, messageElements ...string) {
  1183  	assert.Equal(t, serverAddress, ac.ServerAddress, "ServerAddress for %v", messageElements)
  1184  	assert.Equal(t, username, ac.Username, "Username for %v", messageElements)
  1185  	assert.Equal(t, password, ac.Password, "Password for %v", messageElements)
  1186  }
  1187  
  1188  func getTestAuthConfig(t *testing.T, e executor, imageName string) *types.AuthConfig {
  1189  	ac := e.getAuthConfig(imageName)
  1190  
  1191  	return ac
  1192  }
  1193  
  1194  func testVariableAuthConfig(t *testing.T, e executor) {
  1195  	t.Run("withoutGitLabRegistry", func(t *testing.T) {
  1196  		ac := getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version")
  1197  		assertCredentials(t, "https://registry.domain.tld:5005/v1/", "test_user", "test_password", ac, "registry.domain.tld:5005/image/name:version")
  1198  
  1199  		ac = getTestAuthConfig(t, e, "registry2.domain.tld:5005/image/name:version")
  1200  		assertCredentials(t, "registry2.domain.tld:5005", "test_user", "test_password", ac, "registry2.domain.tld:5005/image/name:version")
  1201  
  1202  		ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version")
  1203  		assertEmptyCredentials(t, ac, "registry.gitlab.tld:1234")
  1204  	})
  1205  
  1206  	t.Run("withGitLabRegistry", func(t *testing.T) {
  1207  		addGitLabRegistryCredentials(&e)
  1208  
  1209  		ac := getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version")
  1210  		assertCredentials(t, "https://registry.domain.tld:5005/v1/", "test_user", "test_password", ac, "registry.domain.tld:5005/image/name:version")
  1211  
  1212  		ac = getTestAuthConfig(t, e, "registry2.domain.tld:5005/image/name:version")
  1213  		assertCredentials(t, "registry2.domain.tld:5005", "test_user", "test_password", ac, "registry2.domain.tld:5005/image/name:version")
  1214  
  1215  		ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version")
  1216  		assertCredentials(t, "registry.gitlab.tld:1234", "gitlab-ci-token", "abcd123456", ac, "registry.gitlab.tld:1234")
  1217  	})
  1218  }
  1219  
  1220  func TestGetRemoteVariableAuthConfig(t *testing.T) {
  1221  	e := getAuthConfigTestExecutor(t, true)
  1222  	addRemoteVariableCredentials(&e)
  1223  
  1224  	testVariableAuthConfig(t, e)
  1225  }
  1226  
  1227  func TestGetLocalVariableAuthConfig(t *testing.T) {
  1228  	e := getAuthConfigTestExecutor(t, true)
  1229  	addLocalVariableCredentials(&e)
  1230  
  1231  	testVariableAuthConfig(t, e)
  1232  }
  1233  
  1234  func TestGetDefaultAuthConfig(t *testing.T) {
  1235  	t.Run("withoutGitLabRegistry", func(t *testing.T) {
  1236  		e := getAuthConfigTestExecutor(t, false)
  1237  
  1238  		ac := getTestAuthConfig(t, e, "docker:dind")
  1239  		assertEmptyCredentials(t, ac, "docker:dind")
  1240  
  1241  		ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version")
  1242  		assertEmptyCredentials(t, ac, "registry.gitlab.tld:1234")
  1243  
  1244  		ac = getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version")
  1245  		assertEmptyCredentials(t, ac, "registry.domain.tld:5005/image/name:version")
  1246  	})
  1247  
  1248  	t.Run("withGitLabRegistry", func(t *testing.T) {
  1249  		e := getAuthConfigTestExecutor(t, false)
  1250  		addGitLabRegistryCredentials(&e)
  1251  
  1252  		ac := getTestAuthConfig(t, e, "docker:dind")
  1253  		assertEmptyCredentials(t, ac, "docker:dind")
  1254  
  1255  		ac = getTestAuthConfig(t, e, "registry.domain.tld:5005/image/name:version")
  1256  		assertEmptyCredentials(t, ac, "registry.domain.tld:5005/image/name:version")
  1257  
  1258  		ac = getTestAuthConfig(t, e, "registry.gitlab.tld:1234/image/name:version")
  1259  		assertCredentials(t, "registry.gitlab.tld:1234", "gitlab-ci-token", "abcd123456", ac, "registry.gitlab.tld:1234")
  1260  	})
  1261  }
  1262  
  1263  func TestAuthConfigOverwritingOrder(t *testing.T) {
  1264  	testVariableAuthConfigs = `{"auths":{"registry.gitlab.tld:1234":{"auth":"ZnJvbV92YXJpYWJsZTpwYXNzd29yZA=="}}}`
  1265  	testFileAuthConfigs = `{"auths":{"registry.gitlab.tld:1234":{"auth":"ZnJvbV9maWxlOnBhc3N3b3Jk"}}}`
  1266  
  1267  	imageName := "registry.gitlab.tld:1234/image/name:latest"
  1268  
  1269  	t.Run("gitlabRegistryOnly", func(t *testing.T) {
  1270  		e := getAuthConfigTestExecutor(t, false)
  1271  		addGitLabRegistryCredentials(&e)
  1272  
  1273  		ac := getTestAuthConfig(t, e, imageName)
  1274  		assertCredentials(t, "registry.gitlab.tld:1234", "gitlab-ci-token", e.Build.Token, ac, imageName)
  1275  	})
  1276  
  1277  	t.Run("withConfigFromRemoteVariable", func(t *testing.T) {
  1278  		e := getAuthConfigTestExecutor(t, false)
  1279  		addGitLabRegistryCredentials(&e)
  1280  		addRemoteVariableCredentials(&e)
  1281  
  1282  		ac := getTestAuthConfig(t, e, imageName)
  1283  		assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName)
  1284  	})
  1285  
  1286  	t.Run("withConfigFromLocalVariable", func(t *testing.T) {
  1287  		e := getAuthConfigTestExecutor(t, false)
  1288  		addGitLabRegistryCredentials(&e)
  1289  		addLocalVariableCredentials(&e)
  1290  
  1291  		ac := getTestAuthConfig(t, e, imageName)
  1292  		assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName)
  1293  	})
  1294  
  1295  	t.Run("withConfigFromFile", func(t *testing.T) {
  1296  		e := getAuthConfigTestExecutor(t, true)
  1297  		addGitLabRegistryCredentials(&e)
  1298  
  1299  		ac := getTestAuthConfig(t, e, imageName)
  1300  		assertCredentials(t, "registry.gitlab.tld:1234", "from_file", "password", ac, imageName)
  1301  	})
  1302  
  1303  	t.Run("withConfigFromVariableAndFromFile", func(t *testing.T) {
  1304  		e := getAuthConfigTestExecutor(t, true)
  1305  		addGitLabRegistryCredentials(&e)
  1306  		addRemoteVariableCredentials(&e)
  1307  
  1308  		ac := getTestAuthConfig(t, e, imageName)
  1309  		assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName)
  1310  	})
  1311  
  1312  	t.Run("withConfigFromLocalAndRemoteVariable", func(t *testing.T) {
  1313  		e := getAuthConfigTestExecutor(t, true)
  1314  		addGitLabRegistryCredentials(&e)
  1315  		addRemoteVariableCredentials(&e)
  1316  		testVariableAuthConfigs = `{"auths":{"registry.gitlab.tld:1234":{"auth":"ZnJvbV9sb2NhbF92YXJpYWJsZTpwYXNzd29yZA=="}}}`
  1317  		addLocalVariableCredentials(&e)
  1318  
  1319  		ac := getTestAuthConfig(t, e, imageName)
  1320  		assertCredentials(t, "registry.gitlab.tld:1234", "from_variable", "password", ac, imageName)
  1321  	})
  1322  }
  1323  
  1324  func testGetDockerImage(t *testing.T, e executor, imageName string, setClientExpectations func(c *docker_helpers.MockClient, imageName string)) {
  1325  	t.Run("get:"+imageName, func(t *testing.T) {
  1326  		var c docker_helpers.MockClient
  1327  		defer c.AssertExpectations(t)
  1328  
  1329  		e.client = &c
  1330  
  1331  		setClientExpectations(&c, imageName)
  1332  
  1333  		image, err := e.getDockerImage(imageName)
  1334  		assert.NoError(t, err, "Should not generate error")
  1335  		assert.Equal(t, "this-image", image.ID, "Image ID")
  1336  	})
  1337  }
  1338  
  1339  func testDeniesDockerImage(t *testing.T, e executor, imageName string, setClientExpectations func(c *docker_helpers.MockClient, imageName string)) {
  1340  	t.Run("deny:"+imageName, func(t *testing.T) {
  1341  		var c docker_helpers.MockClient
  1342  		defer c.AssertExpectations(t)
  1343  
  1344  		e.client = &c
  1345  
  1346  		setClientExpectations(&c, imageName)
  1347  
  1348  		_, err := e.getDockerImage(imageName)
  1349  		assert.Error(t, err, "Should generate error")
  1350  	})
  1351  }
  1352  
  1353  func addFindsLocalImageExpectations(c *docker_helpers.MockClient, imageName string) {
  1354  	c.On("ImageInspectWithRaw", mock.Anything, imageName).
  1355  		Return(types.ImageInspect{ID: "this-image"}, nil, nil).
  1356  		Once()
  1357  }
  1358  
  1359  func addPullsRemoteImageExpectations(c *docker_helpers.MockClient, imageName string) {
  1360  	c.On("ImageInspectWithRaw", mock.Anything, imageName).
  1361  		Return(types.ImageInspect{ID: "not-this-image"}, nil, nil).
  1362  		Once()
  1363  
  1364  	c.On("ImagePullBlocking", mock.Anything, imageName, mock.AnythingOfType("types.ImagePullOptions")).
  1365  		Return(nil).
  1366  		Once()
  1367  
  1368  	c.On("ImageInspectWithRaw", mock.Anything, imageName).
  1369  		Return(types.ImageInspect{ID: "this-image"}, nil, nil).
  1370  		Once()
  1371  }
  1372  
  1373  func addDeniesPullExpectations(c *docker_helpers.MockClient, imageName string) {
  1374  	c.On("ImageInspectWithRaw", mock.Anything, imageName).
  1375  		Return(types.ImageInspect{ID: "image"}, nil, nil).
  1376  		Once()
  1377  
  1378  	c.On("ImagePullBlocking", mock.Anything, imageName, mock.AnythingOfType("types.ImagePullOptions")).
  1379  		Return(fmt.Errorf("deny pulling")).
  1380  		Once()
  1381  }
  1382  
  1383  func TestPullPolicyWhenAlwaysIsSet(t *testing.T) {
  1384  	remoteImage := "registry.domain.tld:5005/image/name:version"
  1385  	gitlabImage := "registry.gitlab.tld:1234/image/name:version"
  1386  
  1387  	e := getAuthConfigTestExecutor(t, false)
  1388  	e.Context = context.Background()
  1389  	e.Config.Docker.PullPolicy = common.PullPolicyAlways
  1390  
  1391  	testGetDockerImage(t, e, remoteImage, addPullsRemoteImageExpectations)
  1392  	testDeniesDockerImage(t, e, remoteImage, addDeniesPullExpectations)
  1393  
  1394  	testGetDockerImage(t, e, gitlabImage, addPullsRemoteImageExpectations)
  1395  	testDeniesDockerImage(t, e, gitlabImage, addDeniesPullExpectations)
  1396  }
  1397  
  1398  func TestPullPolicyWhenIfNotPresentIsSet(t *testing.T) {
  1399  	remoteImage := "registry.domain.tld:5005/image/name:version"
  1400  	gitlabImage := "registry.gitlab.tld:1234/image/name:version"
  1401  
  1402  	e := getAuthConfigTestExecutor(t, false)
  1403  	e.Context = context.Background()
  1404  	e.Config.Docker.PullPolicy = common.PullPolicyIfNotPresent
  1405  
  1406  	testGetDockerImage(t, e, remoteImage, addFindsLocalImageExpectations)
  1407  	testGetDockerImage(t, e, gitlabImage, addFindsLocalImageExpectations)
  1408  }
  1409  
  1410  func TestDockerWatchOn_1_12_4(t *testing.T) {
  1411  	if helpers.SkipIntegrationTests(t, "docker", "info") {
  1412  		return
  1413  	}
  1414  
  1415  	e := executor{
  1416  		AbstractExecutor: executors.AbstractExecutor{
  1417  			ExecutorOptions: executors.ExecutorOptions{
  1418  				Metadata: map[string]string{
  1419  					metadataOSType: osTypeLinux,
  1420  				},
  1421  			},
  1422  		},
  1423  		volumeParser: parser.NewLinuxParser(),
  1424  	}
  1425  	e.Context = context.Background()
  1426  	e.Build = &common.Build{
  1427  		Runner: &common.RunnerConfig{},
  1428  	}
  1429  	e.Build.Token = "abcd123456"
  1430  	e.BuildShell = &common.ShellConfiguration{
  1431  		Environment: []string{},
  1432  	}
  1433  
  1434  	e.Config = common.RunnerConfig{}
  1435  	e.Config.Docker = &common.DockerConfig{
  1436  		PullPolicy: common.PullPolicyIfNotPresent,
  1437  	}
  1438  
  1439  	output := bytes.NewBufferString("")
  1440  	e.Trace = &common.Trace{Writer: output}
  1441  
  1442  	err := e.connectDocker()
  1443  	require.NoError(t, err)
  1444  
  1445  	err = e.createVolumesManager()
  1446  	require.NoError(t, err)
  1447  
  1448  	container, err := e.createContainer("build", common.Image{Name: common.TestAlpineImage}, []string{"/bin/sh"}, []string{})
  1449  	assert.NoError(t, err)
  1450  	assert.NotNil(t, container)
  1451  
  1452  	input := bytes.NewBufferString("echo 'script'")
  1453  
  1454  	finished := make(chan bool, 1)
  1455  	wg := &sync.WaitGroup{}
  1456  	wg.Add(1) // Avoid a race where assert.NoError() is called too late in the goroutine
  1457  	go func() {
  1458  		err = e.watchContainer(e.Context, container.ID, input)
  1459  		assert.NoError(t, err)
  1460  		finished <- true
  1461  		wg.Done()
  1462  	}()
  1463  
  1464  	select {
  1465  	case <-finished:
  1466  		assert.Equal(t, "script\n", output.String())
  1467  	case <-time.After(15 * time.Second):
  1468  		t.Error("Container script not finished")
  1469  	}
  1470  
  1471  	err = e.removeContainer(e.Context, container.ID)
  1472  	assert.NoError(t, err)
  1473  	wg.Wait()
  1474  }
  1475  
  1476  type containerConfigExpectations func(*testing.T, *container.Config, *container.HostConfig)
  1477  
  1478  type dockerConfigurationTestFakeDockerClient struct {
  1479  	docker_helpers.MockClient
  1480  
  1481  	cce containerConfigExpectations
  1482  	t   *testing.T
  1483  }
  1484  
  1485  func (c *dockerConfigurationTestFakeDockerClient) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
  1486  	c.cce(c.t, config, hostConfig)
  1487  	return container.ContainerCreateCreatedBody{ID: "abc"}, nil
  1488  }
  1489  
  1490  func prepareTestDockerConfiguration(t *testing.T, dockerConfig *common.DockerConfig, cce containerConfigExpectations) (*dockerConfigurationTestFakeDockerClient, *executor) {
  1491  	c := &dockerConfigurationTestFakeDockerClient{
  1492  		cce: cce,
  1493  		t:   t,
  1494  	}
  1495  
  1496  	e := &executor{}
  1497  	e.client = c
  1498  	e.volumeParser = parser.NewLinuxParser()
  1499  	e.info = types.Info{
  1500  		OSType:       helperimage.OSTypeLinux,
  1501  		Architecture: "amd64",
  1502  	}
  1503  	e.Config.Docker = dockerConfig
  1504  	e.Build = &common.Build{
  1505  		Runner: &common.RunnerConfig{},
  1506  	}
  1507  	e.Build.Token = "abcd123456"
  1508  	e.BuildShell = &common.ShellConfiguration{
  1509  		Environment: []string{},
  1510  	}
  1511  	var err error
  1512  	e.helperImageInfo, err = helperimage.Get(common.REVISION, helperimage.Config{
  1513  		OSType:          e.info.OSType,
  1514  		Architecture:    e.info.Architecture,
  1515  		OperatingSystem: e.info.OperatingSystem,
  1516  	})
  1517  	require.NoError(t, err)
  1518  
  1519  	c.On("ImageInspectWithRaw", mock.Anything, "gitlab/gitlab-runner-helper:x86_64-latest").
  1520  		Return(types.ImageInspect{ID: "helper-image-id"}, nil, nil).Once()
  1521  	c.On("ImageInspectWithRaw", mock.Anything, "alpine").
  1522  		Return(types.ImageInspect{ID: "123"}, []byte{}, nil).Twice()
  1523  	c.On("ImagePullBlocking", mock.Anything, "alpine:latest", mock.Anything).
  1524  		Return(nil).Once()
  1525  	c.On("NetworkList", mock.Anything, mock.Anything).
  1526  		Return([]types.NetworkResource{}, nil).Once()
  1527  	c.On("ContainerRemove", mock.Anything, mock.Anything, mock.Anything).
  1528  		Return(nil).Once()
  1529  
  1530  	return c, e
  1531  }
  1532  
  1533  func testDockerConfigurationWithJobContainer(t *testing.T, dockerConfig *common.DockerConfig, cce containerConfigExpectations) {
  1534  	c, e := prepareTestDockerConfiguration(t, dockerConfig, cce)
  1535  	defer c.AssertExpectations(t)
  1536  
  1537  	c.On("ContainerInspect", mock.Anything, "abc").
  1538  		Return(types.ContainerJSON{}, nil).Once()
  1539  
  1540  	err := e.createVolumesManager()
  1541  	require.NoError(t, err)
  1542  
  1543  	_, err = e.createContainer("build", common.Image{Name: "alpine"}, []string{"/bin/sh"}, []string{})
  1544  	assert.NoError(t, err, "Should create container without errors")
  1545  }
  1546  
  1547  func testDockerConfigurationWithServiceContainer(t *testing.T, dockerConfig *common.DockerConfig, cce containerConfigExpectations) {
  1548  	c, e := prepareTestDockerConfiguration(t, dockerConfig, cce)
  1549  	defer c.AssertExpectations(t)
  1550  
  1551  	c.On("ContainerStart", mock.Anything, "abc", mock.Anything).
  1552  		Return(nil).Once()
  1553  
  1554  	err := e.createVolumesManager()
  1555  	require.NoError(t, err)
  1556  
  1557  	_, err = e.createService(0, "build", "latest", "alpine", common.Image{Command: []string{"/bin/sh"}})
  1558  	assert.NoError(t, err, "Should create service container without errors")
  1559  }
  1560  
  1561  func TestDockerMemorySetting(t *testing.T) {
  1562  	dockerConfig := &common.DockerConfig{
  1563  		Memory: "42m",
  1564  	}
  1565  
  1566  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1567  		assert.Equal(t, int64(44040192), hostConfig.Memory)
  1568  	}
  1569  
  1570  	testDockerConfigurationWithJobContainer(t, dockerConfig, cce)
  1571  }
  1572  
  1573  func TestDockerMemorySwapSetting(t *testing.T) {
  1574  	dockerConfig := &common.DockerConfig{
  1575  		MemorySwap: "2g",
  1576  	}
  1577  
  1578  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1579  		assert.Equal(t, int64(2147483648), hostConfig.MemorySwap)
  1580  	}
  1581  
  1582  	testDockerConfigurationWithJobContainer(t, dockerConfig, cce)
  1583  }
  1584  
  1585  func TestDockerMemoryReservationSetting(t *testing.T) {
  1586  	dockerConfig := &common.DockerConfig{
  1587  		MemoryReservation: "64m",
  1588  	}
  1589  
  1590  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1591  		assert.Equal(t, int64(67108864), hostConfig.MemoryReservation)
  1592  	}
  1593  
  1594  	testDockerConfigurationWithJobContainer(t, dockerConfig, cce)
  1595  }
  1596  
  1597  func TestDockerCPUSSetting(t *testing.T) {
  1598  	examples := []struct {
  1599  		cpus     string
  1600  		nanocpus int64
  1601  	}{
  1602  		{"0.5", 500000000},
  1603  		{"0.25", 250000000},
  1604  		{"1/3", 333333333},
  1605  		{"1/8", 125000000},
  1606  		{"0.0001", 100000},
  1607  	}
  1608  
  1609  	for _, example := range examples {
  1610  		t.Run(example.cpus, func(t *testing.T) {
  1611  			dockerConfig := &common.DockerConfig{
  1612  				CPUS: example.cpus,
  1613  			}
  1614  
  1615  			cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1616  				assert.Equal(t, int64(example.nanocpus), hostConfig.NanoCPUs)
  1617  			}
  1618  
  1619  			testDockerConfigurationWithJobContainer(t, dockerConfig, cce)
  1620  		})
  1621  	}
  1622  }
  1623  
  1624  func TestDockerCPUSetCPUsSetting(t *testing.T) {
  1625  	dockerConfig := &common.DockerConfig{
  1626  		CPUSetCPUs: "1-3,5",
  1627  	}
  1628  
  1629  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1630  		assert.Equal(t, "1-3,5", hostConfig.CpusetCpus)
  1631  	}
  1632  
  1633  	testDockerConfigurationWithJobContainer(t, dockerConfig, cce)
  1634  }
  1635  
  1636  func TestDockerServicesTmpfsSetting(t *testing.T) {
  1637  	dockerConfig := &common.DockerConfig{
  1638  		ServicesTmpfs: map[string]string{
  1639  			"/tmpfs": "rw,noexec",
  1640  		},
  1641  	}
  1642  
  1643  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1644  		require.NotEmpty(t, hostConfig.Tmpfs)
  1645  	}
  1646  
  1647  	testDockerConfigurationWithServiceContainer(t, dockerConfig, cce)
  1648  }
  1649  func TestDockerTmpfsSetting(t *testing.T) {
  1650  	dockerConfig := &common.DockerConfig{
  1651  		Tmpfs: map[string]string{
  1652  			"/tmpfs": "rw,noexec",
  1653  		},
  1654  	}
  1655  
  1656  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1657  		require.NotEmpty(t, hostConfig.Tmpfs)
  1658  	}
  1659  
  1660  	testDockerConfigurationWithJobContainer(t, dockerConfig, cce)
  1661  }
  1662  
  1663  func TestDockerServicesDNSSetting(t *testing.T) {
  1664  	dockerConfig := &common.DockerConfig{
  1665  		DNS: []string{"2001:db8::1", "192.0.2.1"},
  1666  	}
  1667  
  1668  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1669  		require.Equal(t, dockerConfig.DNS, hostConfig.DNS)
  1670  	}
  1671  
  1672  	testDockerConfigurationWithServiceContainer(t, dockerConfig, cce)
  1673  }
  1674  
  1675  func TestDockerServicesDNSSearchSetting(t *testing.T) {
  1676  	dockerConfig := &common.DockerConfig{
  1677  		DNSSearch: []string{"mydomain.example"},
  1678  	}
  1679  
  1680  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1681  		require.Equal(t, dockerConfig.DNSSearch, hostConfig.DNSSearch)
  1682  	}
  1683  
  1684  	testDockerConfigurationWithServiceContainer(t, dockerConfig, cce)
  1685  }
  1686  
  1687  func TestDockerServicesExtraHostsSetting(t *testing.T) {
  1688  	dockerConfig := &common.DockerConfig{
  1689  		ExtraHosts: []string{"foo.example:2001:db8::1", "bar.example:192.0.2.1"},
  1690  	}
  1691  
  1692  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1693  		require.Equal(t, dockerConfig.ExtraHosts, hostConfig.ExtraHosts)
  1694  	}
  1695  
  1696  	testDockerConfigurationWithServiceContainer(t, dockerConfig, cce)
  1697  }
  1698  
  1699  func TestDockerUserNSSetting(t *testing.T) {
  1700  	dockerConfig := &common.DockerConfig{
  1701  		UsernsMode: "host",
  1702  	}
  1703  
  1704  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1705  		assert.Equal(t, container.UsernsMode("host"), hostConfig.UsernsMode)
  1706  	}
  1707  
  1708  	testDockerConfigurationWithJobContainer(t, dockerConfig, cce)
  1709  
  1710  }
  1711  
  1712  func TestDockerRuntimeSetting(t *testing.T) {
  1713  	dockerConfig := &common.DockerConfig{
  1714  		Runtime: "runc",
  1715  	}
  1716  
  1717  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1718  		assert.Equal(t, "runc", hostConfig.Runtime)
  1719  	}
  1720  
  1721  	testDockerConfigurationWithJobContainer(t, dockerConfig, cce)
  1722  }
  1723  
  1724  func TestDockerSysctlsSetting(t *testing.T) {
  1725  	dockerConfig := &common.DockerConfig{
  1726  		SysCtls: map[string]string{
  1727  			"net.ipv4.ip_forward": "1",
  1728  		},
  1729  	}
  1730  
  1731  	cce := func(t *testing.T, config *container.Config, hostConfig *container.HostConfig) {
  1732  		assert.Equal(t, "1", hostConfig.Sysctls["net.ipv4.ip_forward"])
  1733  	}
  1734  
  1735  	testDockerConfigurationWithJobContainer(t, dockerConfig, cce)
  1736  }
  1737  
  1738  func TestCheckOSType(t *testing.T) {
  1739  	cases := map[string]struct {
  1740  		executorMetadata map[string]string
  1741  		dockerInfoOSType string
  1742  		expectedErr      string
  1743  	}{
  1744  		"executor and docker info mismatch": {
  1745  			executorMetadata: map[string]string{
  1746  				metadataOSType: osTypeWindows,
  1747  			},
  1748  			dockerInfoOSType: osTypeLinux,
  1749  			expectedErr:      "executor requires OSType=windows, but Docker Engine supports only OSType=linux",
  1750  		},
  1751  		"executor and docker info match": {
  1752  			executorMetadata: map[string]string{
  1753  				metadataOSType: osTypeLinux,
  1754  			},
  1755  			dockerInfoOSType: osTypeLinux,
  1756  			expectedErr:      "",
  1757  		},
  1758  		"executor OSType not defined": {
  1759  			executorMetadata: nil,
  1760  			dockerInfoOSType: osTypeLinux,
  1761  			expectedErr:      " does not have any OSType specified",
  1762  		},
  1763  	}
  1764  
  1765  	for name, c := range cases {
  1766  		t.Run(name, func(t *testing.T) {
  1767  			executor := executor{
  1768  				info: types.Info{
  1769  					OSType: c.dockerInfoOSType,
  1770  				},
  1771  				AbstractExecutor: executors.AbstractExecutor{
  1772  					ExecutorOptions: executors.ExecutorOptions{
  1773  						Metadata: c.executorMetadata,
  1774  					},
  1775  				},
  1776  			}
  1777  
  1778  			err := executor.validateOSType()
  1779  			if c.expectedErr == "" {
  1780  				assert.NoError(t, err)
  1781  				return
  1782  			}
  1783  			assert.EqualError(t, err, c.expectedErr)
  1784  		})
  1785  	}
  1786  }
  1787  
  1788  func init() {
  1789  	docker_helpers.HomeDirectory = ""
  1790  }