gitlab.com/jfprevost/gitlab-runner-notlscheck@v11.11.4+incompatible/executors/docker/executor_docker_test.go (about)

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