github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/executors/docker/executor_docker_test.go (about)

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