github.com/secure-build/gitlab-runner@v12.5.0+incompatible/executors/docker/executor_docker_command_test.go (about)

     1  package docker_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"net/url"
     8  	"os"
     9  	"os/exec"
    10  	"regexp"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/hashicorp/go-version"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	"gitlab.com/gitlab-org/gitlab-runner/common"
    20  	"gitlab.com/gitlab-org/gitlab-runner/helpers"
    21  	docker_helpers "gitlab.com/gitlab-org/gitlab-runner/helpers/docker"
    22  )
    23  
    24  func TestDockerCommandSuccessRun(t *testing.T) {
    25  	if helpers.SkipIntegrationTests(t, "docker", "info") {
    26  		return
    27  	}
    28  
    29  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
    30  	assert.NoError(t, err)
    31  	build := &common.Build{
    32  		JobResponse: successfulBuild,
    33  		Runner: &common.RunnerConfig{
    34  			RunnerSettings: common.RunnerSettings{
    35  				Executor: "docker",
    36  				Docker: &common.DockerConfig{
    37  					Image:      common.TestAlpineImage,
    38  					PullPolicy: common.PullPolicyIfNotPresent,
    39  				},
    40  			},
    41  		},
    42  	}
    43  
    44  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
    45  	assert.NoError(t, err)
    46  }
    47  
    48  func TestDockerCommandUsingCustomClonePath(t *testing.T) {
    49  	if helpers.SkipIntegrationTests(t, "docker", "info") {
    50  		return
    51  	}
    52  
    53  	jobResponse, err := common.GetRemoteBuildResponse(
    54  		"ls -al $CI_BUILDS_DIR/go/src/gitlab.com/gitlab-org/repo")
    55  	require.NoError(t, err)
    56  
    57  	tests := map[string]struct {
    58  		clonePath         string
    59  		expectedErrorType interface{}
    60  	}{
    61  		"uses custom clone path": {
    62  			clonePath:         "$CI_BUILDS_DIR/go/src/gitlab.com/gitlab-org/repo",
    63  			expectedErrorType: nil,
    64  		},
    65  		"path has to be within CI_BUILDS_DIR": {
    66  			clonePath:         "/unknown/go/src/gitlab.com/gitlab-org/repo",
    67  			expectedErrorType: &common.BuildError{},
    68  		},
    69  	}
    70  
    71  	for name, test := range tests {
    72  		t.Run(name, func(t *testing.T) {
    73  			build := &common.Build{
    74  				JobResponse: jobResponse,
    75  				Runner: &common.RunnerConfig{
    76  					RunnerSettings: common.RunnerSettings{
    77  						Executor: "docker",
    78  						Docker: &common.DockerConfig{
    79  							Image:      common.TestAlpineImage,
    80  							PullPolicy: common.PullPolicyIfNotPresent,
    81  						},
    82  						Environment: []string{
    83  							"GIT_CLONE_PATH=" + test.clonePath,
    84  						},
    85  					},
    86  				},
    87  			}
    88  
    89  			err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
    90  			assert.IsType(t, test.expectedErrorType, err)
    91  		})
    92  	}
    93  }
    94  
    95  func TestDockerCommandNoRootImage(t *testing.T) {
    96  	if helpers.SkipIntegrationTests(t, "docker", "info") {
    97  		return
    98  	}
    99  
   100  	successfulBuild, err := common.GetRemoteSuccessfulBuildWithDumpedVariables()
   101  
   102  	assert.NoError(t, err)
   103  	successfulBuild.Image.Name = common.TestAlpineNoRootImage
   104  	build := &common.Build{
   105  		JobResponse: successfulBuild,
   106  		Runner: &common.RunnerConfig{
   107  			RunnerSettings: common.RunnerSettings{
   108  				Executor: "docker",
   109  				Docker: &common.DockerConfig{
   110  					PullPolicy: common.PullPolicyIfNotPresent,
   111  				},
   112  			},
   113  		},
   114  	}
   115  
   116  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
   117  	assert.NoError(t, err)
   118  }
   119  
   120  func TestDockerCommandBuildFail(t *testing.T) {
   121  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   122  		return
   123  	}
   124  
   125  	failedBuild, err := common.GetRemoteFailedBuild()
   126  	assert.NoError(t, err)
   127  	build := &common.Build{
   128  		JobResponse: failedBuild,
   129  		Runner: &common.RunnerConfig{
   130  			RunnerSettings: common.RunnerSettings{
   131  				Executor: "docker",
   132  				Docker: &common.DockerConfig{
   133  					Image:      common.TestAlpineImage,
   134  					PullPolicy: common.PullPolicyIfNotPresent,
   135  				},
   136  			},
   137  		},
   138  	}
   139  
   140  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
   141  	require.Error(t, err, "error")
   142  	assert.IsType(t, err, &common.BuildError{})
   143  	assert.Contains(t, err.Error(), "exit code 1")
   144  }
   145  
   146  func TestDockerCommandWithAllowedImagesRun(t *testing.T) {
   147  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   148  		return
   149  	}
   150  
   151  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
   152  	successfulBuild.Image = common.Image{Name: "$IMAGE_NAME"}
   153  	successfulBuild.Variables = append(successfulBuild.Variables, common.JobVariable{
   154  		Key:      "IMAGE_NAME",
   155  		Value:    common.TestAlpineImage,
   156  		Public:   true,
   157  		Internal: false,
   158  		File:     false,
   159  	})
   160  	successfulBuild.Services = append(successfulBuild.Services, common.Image{Name: common.TestDockerDindImage})
   161  	assert.NoError(t, err)
   162  	build := &common.Build{
   163  		JobResponse: successfulBuild,
   164  		Runner: &common.RunnerConfig{
   165  			RunnerSettings: common.RunnerSettings{
   166  				Executor: "docker",
   167  				Docker: &common.DockerConfig{
   168  					AllowedImages:   []string{common.TestAlpineImage},
   169  					AllowedServices: []string{common.TestDockerDindImage},
   170  					Privileged:      true,
   171  					PullPolicy:      common.PullPolicyIfNotPresent,
   172  				},
   173  			},
   174  		},
   175  	}
   176  
   177  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
   178  	assert.NoError(t, err)
   179  }
   180  
   181  func TestDockerCommandDisableEntrypointOverwrite(t *testing.T) {
   182  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   183  		return
   184  	}
   185  
   186  	tests := []struct {
   187  		name     string
   188  		services bool
   189  		disabled bool
   190  	}{
   191  		{
   192  			name:     "Disabled - no services",
   193  			disabled: true,
   194  		},
   195  		{
   196  			name:     "Disabled - services",
   197  			disabled: true,
   198  			services: true,
   199  		},
   200  		{
   201  			name: "Enabled - no services",
   202  		},
   203  		{
   204  			name:     "Enabled - services",
   205  			services: true,
   206  		},
   207  	}
   208  
   209  	for _, test := range tests {
   210  		t.Run(test.name, func(t *testing.T) {
   211  			successfulBuild, err := common.GetRemoteSuccessfulBuild()
   212  			require.NoError(t, err)
   213  
   214  			successfulBuild.Image.Entrypoint = []string{"/bin/sh", "-c", "echo 'image overwritten'"}
   215  
   216  			if test.services {
   217  				successfulBuild.Services = common.Services{
   218  					common.Image{
   219  						Name:       common.TestDockerDindImage,
   220  						Entrypoint: []string{"/bin/sh", "-c", "echo 'service overwritten'"},
   221  					},
   222  				}
   223  			}
   224  
   225  			build := &common.Build{
   226  				JobResponse: successfulBuild,
   227  				Runner: &common.RunnerConfig{
   228  					RunnerSettings: common.RunnerSettings{
   229  						Executor: "docker",
   230  						Docker: &common.DockerConfig{
   231  							Privileged:                 true,
   232  							Image:                      common.TestAlpineImage,
   233  							PullPolicy:                 common.PullPolicyIfNotPresent,
   234  							DisableEntrypointOverwrite: test.disabled,
   235  						},
   236  					},
   237  				},
   238  			}
   239  
   240  			var buffer bytes.Buffer
   241  			err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer})
   242  			assert.NoError(t, err)
   243  			out := buffer.String()
   244  			if test.disabled {
   245  				assert.NotContains(t, out, "image overwritten")
   246  				assert.NotContains(t, out, "service overwritten")
   247  				assert.Contains(t, out, "Entrypoint override disabled")
   248  			} else {
   249  				assert.Contains(t, out, "image overwritten")
   250  				if test.services {
   251  					assert.Contains(t, out, "service overwritten")
   252  				}
   253  			}
   254  		})
   255  	}
   256  }
   257  
   258  func isDockerOlderThan17_07(t *testing.T) bool {
   259  	client, err := docker_helpers.New(docker_helpers.DockerCredentials{}, "")
   260  	require.NoError(t, err, "should be able to connect to docker")
   261  
   262  	types, err := client.Info(context.Background())
   263  	require.NoError(t, err, "should be able to get docker info")
   264  
   265  	localVersion, err := version.NewVersion(types.ServerVersion)
   266  	require.NoError(t, err)
   267  
   268  	checkedVersion, err := version.NewVersion("17.07.0-ce")
   269  	require.NoError(t, err)
   270  
   271  	return localVersion.LessThan(checkedVersion)
   272  }
   273  
   274  func TestDockerCommandMissingImage(t *testing.T) {
   275  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   276  		return
   277  	}
   278  
   279  	build := &common.Build{
   280  		Runner: &common.RunnerConfig{
   281  			RunnerSettings: common.RunnerSettings{
   282  				Executor: "docker",
   283  				Docker: &common.DockerConfig{
   284  					Image: "some/non-existing/image",
   285  				},
   286  			},
   287  		},
   288  	}
   289  
   290  	err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
   291  	require.Error(t, err)
   292  	assert.IsType(t, &common.BuildError{}, err)
   293  
   294  	contains := "repository does not exist"
   295  	if isDockerOlderThan17_07(t) {
   296  		contains = "not found"
   297  	}
   298  
   299  	assert.Contains(t, err.Error(), contains)
   300  }
   301  
   302  func TestDockerCommandMissingTag(t *testing.T) {
   303  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   304  		return
   305  	}
   306  
   307  	build := &common.Build{
   308  		Runner: &common.RunnerConfig{
   309  			RunnerSettings: common.RunnerSettings{
   310  				Executor: "docker",
   311  				Docker: &common.DockerConfig{
   312  					Image: "docker:missing-tag",
   313  				},
   314  			},
   315  		},
   316  	}
   317  
   318  	err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
   319  	require.Error(t, err)
   320  	assert.IsType(t, &common.BuildError{}, err)
   321  	assert.Contains(t, err.Error(), "not found")
   322  }
   323  
   324  func TestDockerCommandBuildAbort(t *testing.T) {
   325  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   326  		return
   327  	}
   328  
   329  	longRunningBuild, err := common.GetRemoteLongRunningBuild()
   330  	assert.NoError(t, err)
   331  	build := &common.Build{
   332  		JobResponse: longRunningBuild,
   333  		Runner: &common.RunnerConfig{
   334  			RunnerSettings: common.RunnerSettings{
   335  				Executor: "docker",
   336  				Docker: &common.DockerConfig{
   337  					Image:      common.TestAlpineImage,
   338  					PullPolicy: common.PullPolicyIfNotPresent,
   339  				},
   340  			},
   341  		},
   342  		SystemInterrupt: make(chan os.Signal, 1),
   343  	}
   344  
   345  	abortTimer := time.AfterFunc(time.Second, func() {
   346  		t.Log("Interrupt")
   347  		build.SystemInterrupt <- os.Interrupt
   348  	})
   349  	defer abortTimer.Stop()
   350  
   351  	timeoutTimer := time.AfterFunc(time.Minute, func() {
   352  		t.Log("Timedout")
   353  		t.FailNow()
   354  	})
   355  	defer timeoutTimer.Stop()
   356  
   357  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
   358  	assert.EqualError(t, err, "aborted: interrupt")
   359  }
   360  
   361  func TestDockerCommandBuildCancel(t *testing.T) {
   362  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   363  		return
   364  	}
   365  
   366  	longRunningBuild, err := common.GetRemoteLongRunningBuild()
   367  	assert.NoError(t, err)
   368  	build := &common.Build{
   369  		JobResponse: longRunningBuild,
   370  		Runner: &common.RunnerConfig{
   371  			RunnerSettings: common.RunnerSettings{
   372  				Executor: "docker",
   373  				Docker: &common.DockerConfig{
   374  					Image:      common.TestAlpineImage,
   375  					PullPolicy: common.PullPolicyIfNotPresent,
   376  				},
   377  			},
   378  		},
   379  	}
   380  
   381  	trace := &common.Trace{Writer: os.Stdout}
   382  
   383  	abortTimer := time.AfterFunc(time.Second, func() {
   384  		t.Log("Interrupt")
   385  		trace.CancelFunc()
   386  	})
   387  	defer abortTimer.Stop()
   388  
   389  	timeoutTimer := time.AfterFunc(time.Minute, func() {
   390  		t.Log("Timedout")
   391  		t.FailNow()
   392  	})
   393  	defer timeoutTimer.Stop()
   394  
   395  	err = build.Run(&common.Config{}, trace)
   396  	assert.IsType(t, err, &common.BuildError{})
   397  	assert.Contains(t, err.Error(), "canceled")
   398  }
   399  
   400  func TestDockerCommandTwoServicesFromOneImage(t *testing.T) {
   401  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   402  		return
   403  	}
   404  
   405  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
   406  	successfulBuild.Services = common.Services{
   407  		{Name: common.TestAlpineImage, Alias: "service-1"},
   408  		{Name: common.TestAlpineImage, Alias: "service-2"},
   409  	}
   410  	assert.NoError(t, err)
   411  	build := &common.Build{
   412  		JobResponse: successfulBuild,
   413  		Runner: &common.RunnerConfig{
   414  			RunnerSettings: common.RunnerSettings{
   415  				Executor: "docker",
   416  				Docker: &common.DockerConfig{
   417  					Image:      common.TestAlpineImage,
   418  					PullPolicy: common.PullPolicyIfNotPresent,
   419  				},
   420  			},
   421  		},
   422  	}
   423  
   424  	var buffer bytes.Buffer
   425  
   426  	err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer})
   427  	assert.NoError(t, err)
   428  	str := buffer.String()
   429  
   430  	re, err := regexp.Compile("(?m)Conflict. The container name [^ ]+ is already in use by container")
   431  	require.NoError(t, err)
   432  	assert.NotRegexp(t, re, str, "Both service containers should be started and use different name")
   433  }
   434  
   435  func TestDockerCommandOutput(t *testing.T) {
   436  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   437  		return
   438  	}
   439  
   440  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
   441  	assert.NoError(t, err)
   442  	build := &common.Build{
   443  		JobResponse: successfulBuild,
   444  		Runner: &common.RunnerConfig{
   445  			RunnerSettings: common.RunnerSettings{
   446  				Executor: "docker",
   447  				Docker: &common.DockerConfig{
   448  					Image:      common.TestAlpineImage,
   449  					PullPolicy: common.PullPolicyIfNotPresent,
   450  				},
   451  			},
   452  		},
   453  	}
   454  
   455  	var buffer bytes.Buffer
   456  
   457  	err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer})
   458  	assert.NoError(t, err)
   459  
   460  	re, err := regexp.Compile("(?m)^Initialized empty Git repository in /builds/gitlab-org/ci-cd/tests/gitlab-test/.git/")
   461  	assert.NoError(t, err)
   462  	assert.Regexp(t, re, buffer.String())
   463  }
   464  
   465  func TestDockerPrivilegedServiceAccessingBuildsFolder(t *testing.T) {
   466  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   467  		return
   468  	}
   469  
   470  	commands := []string{
   471  		"docker info",
   472  		"docker run -v $(pwd):$(pwd) -w $(pwd) busybox touch test",
   473  		"cat test",
   474  	}
   475  
   476  	strategies := []string{
   477  		"fetch",
   478  		"clone",
   479  	}
   480  
   481  	for _, strategy := range strategies {
   482  		t.Log("Testing", strategy, "strategy...")
   483  		longRunningBuild, err := common.GetRemoteLongRunningBuild()
   484  		assert.NoError(t, err)
   485  		build := &common.Build{
   486  			JobResponse: longRunningBuild,
   487  			Runner: &common.RunnerConfig{
   488  				RunnerSettings: common.RunnerSettings{
   489  					Executor: "docker",
   490  					Docker: &common.DockerConfig{
   491  						Image:      common.TestAlpineImage,
   492  						PullPolicy: common.PullPolicyIfNotPresent,
   493  						Privileged: true,
   494  					},
   495  				},
   496  			},
   497  		}
   498  		build.Steps = common.Steps{
   499  			common.Step{
   500  				Name:         common.StepNameScript,
   501  				Script:       common.StepScript(commands),
   502  				When:         common.StepWhenOnSuccess,
   503  				AllowFailure: false,
   504  			},
   505  		}
   506  		build.Image.Name = common.TestDockerGitImage
   507  		build.Services = common.Services{
   508  			common.Image{
   509  				Name: common.TestDockerDindImage,
   510  			},
   511  		}
   512  		build.Variables = append(build.Variables, common.JobVariable{
   513  			Key: "GIT_STRATEGY", Value: strategy,
   514  		})
   515  
   516  		err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
   517  		assert.NoError(t, err)
   518  	}
   519  }
   520  
   521  func getTestDockerJob(t *testing.T) *common.Build {
   522  	commands := []string{
   523  		"docker info",
   524  	}
   525  
   526  	longRunningBuild, err := common.GetRemoteLongRunningBuild()
   527  	assert.NoError(t, err)
   528  
   529  	build := &common.Build{
   530  		JobResponse: longRunningBuild,
   531  		Runner: &common.RunnerConfig{
   532  			RunnerSettings: common.RunnerSettings{
   533  				Executor: "docker",
   534  				Docker: &common.DockerConfig{
   535  					Image:      common.TestAlpineImage,
   536  					PullPolicy: common.PullPolicyIfNotPresent,
   537  					Privileged: true,
   538  				},
   539  			},
   540  		},
   541  	}
   542  	build.Steps = common.Steps{
   543  		common.Step{
   544  			Name:         common.StepNameScript,
   545  			Script:       common.StepScript(commands),
   546  			When:         common.StepWhenOnSuccess,
   547  			AllowFailure: false,
   548  		},
   549  	}
   550  
   551  	return build
   552  }
   553  
   554  func TestDockerExtendedConfigurationFromJob(t *testing.T) {
   555  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   556  		return
   557  	}
   558  
   559  	examples := []struct {
   560  		image     common.Image
   561  		services  common.Services
   562  		variables common.JobVariables
   563  	}{
   564  		{
   565  			image: common.Image{
   566  				Name:       "$IMAGE_NAME",
   567  				Entrypoint: []string{"sh", "-c"},
   568  			},
   569  			services: common.Services{
   570  				common.Image{
   571  					Name:       "$SERVICE_NAME",
   572  					Entrypoint: []string{"sh", "-c"},
   573  					Command:    []string{"dockerd-entrypoint.sh"},
   574  					Alias:      "my-docker-service",
   575  				},
   576  			},
   577  			variables: common.JobVariables{
   578  				{Key: "DOCKER_HOST", Value: "tcp://my-docker-service:2375"},
   579  				{Key: "IMAGE_NAME", Value: common.TestDockerGitImage},
   580  				{Key: "SERVICE_NAME", Value: common.TestDockerDindImage},
   581  			},
   582  		},
   583  		{
   584  			image: common.Image{
   585  				Name: "$IMAGE_NAME",
   586  			},
   587  			services: common.Services{
   588  				common.Image{
   589  					Name: "$SERVICE_NAME",
   590  				},
   591  			},
   592  			variables: common.JobVariables{
   593  				{Key: "DOCKER_HOST", Value: "tcp://docker:2375"},
   594  				{Key: "IMAGE_NAME", Value: common.TestDockerGitImage},
   595  				{Key: "SERVICE_NAME", Value: common.TestDockerDindImage},
   596  			},
   597  		},
   598  	}
   599  
   600  	for exampleID, example := range examples {
   601  		t.Run(fmt.Sprintf("example-%d", exampleID), func(t *testing.T) {
   602  			build := getTestDockerJob(t)
   603  			build.Image = example.image
   604  			build.Services = example.services
   605  			build.Variables = append(build.Variables, example.variables...)
   606  
   607  			err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
   608  			assert.NoError(t, err)
   609  		})
   610  	}
   611  }
   612  
   613  func runTestJobWithOutput(t *testing.T, build *common.Build) (output string) {
   614  	var buffer bytes.Buffer
   615  
   616  	err := build.Run(&common.Config{}, &common.Trace{Writer: &buffer})
   617  	assert.NoError(t, err)
   618  
   619  	output = buffer.String()
   620  	return
   621  }
   622  
   623  func TestCacheInContainer(t *testing.T) {
   624  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   625  		return
   626  	}
   627  
   628  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
   629  	assert.NoError(t, err)
   630  
   631  	successfulBuild.JobInfo.ProjectID = int(time.Now().Unix())
   632  	successfulBuild.Steps[0].Script = common.StepScript{
   633  		"(test -d cached/ && ls -lh cached/) || echo \"no cached directory\"",
   634  		"(test -f cached/date && cat cached/date) || echo \"no cached date\"",
   635  		"mkdir -p cached",
   636  		"date > cached/date",
   637  	}
   638  	successfulBuild.Cache = common.Caches{
   639  		common.Cache{
   640  			Key:    "key",
   641  			Paths:  common.ArtifactPaths{"cached/*"},
   642  			Policy: common.CachePolicyPullPush,
   643  		},
   644  	}
   645  
   646  	build := &common.Build{
   647  		JobResponse: successfulBuild,
   648  		Runner: &common.RunnerConfig{
   649  			RunnerSettings: common.RunnerSettings{
   650  				Executor: "docker",
   651  				Docker: &common.DockerConfig{
   652  					Image:      common.TestAlpineImage,
   653  					PullPolicy: common.PullPolicyIfNotPresent,
   654  					Volumes:    []string{"/cache"},
   655  				},
   656  			},
   657  		},
   658  	}
   659  
   660  	cacheNotPresentRE := regexp.MustCompile("(?m)^no cached directory")
   661  	skipCacheDownload := "Not downloading cache key due to policy"
   662  	skipCacheUpload := "Not uploading cache key due to policy"
   663  
   664  	// The first job lacks any cache to pull, but tries to both pull and push
   665  	output := runTestJobWithOutput(t, build)
   666  	assert.Regexp(t, cacheNotPresentRE, output, "First job execution should not have cached data")
   667  	assert.NotContains(t, output, skipCacheDownload, "Cache download should be performed with policy: %s", common.CachePolicyPullPush)
   668  	assert.NotContains(t, output, skipCacheUpload, "Cache upload should be performed with policy: %s", common.CachePolicyPullPush)
   669  
   670  	// pull-only jobs should skip the push step
   671  	build.JobResponse.Cache[0].Policy = common.CachePolicyPull
   672  	output = runTestJobWithOutput(t, build)
   673  	assert.NotRegexp(t, cacheNotPresentRE, output, "Second job execution should have cached data")
   674  	assert.NotContains(t, output, skipCacheDownload, "Cache download should be performed with policy: %s", common.CachePolicyPull)
   675  	assert.Contains(t, output, skipCacheUpload, "Cache upload should be skipped with policy: %s", common.CachePolicyPull)
   676  
   677  	// push-only jobs should skip the pull step
   678  	build.JobResponse.Cache[0].Policy = common.CachePolicyPush
   679  	output = runTestJobWithOutput(t, build)
   680  	assert.Regexp(t, cacheNotPresentRE, output, "Third job execution should not have cached data")
   681  	assert.Contains(t, output, skipCacheDownload, "Cache download be skipped with policy: push")
   682  	assert.NotContains(t, output, skipCacheUpload, "Cache upload should be performed with policy: push")
   683  }
   684  
   685  func TestDockerImageNameFromVariable(t *testing.T) {
   686  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   687  		return
   688  	}
   689  
   690  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
   691  	successfulBuild.Variables = append(successfulBuild.Variables, common.JobVariable{
   692  		Key:   "CI_REGISTRY_IMAGE",
   693  		Value: common.TestAlpineImage,
   694  	})
   695  	successfulBuild.Image = common.Image{
   696  		Name: "$CI_REGISTRY_IMAGE",
   697  	}
   698  	assert.NoError(t, err)
   699  	build := &common.Build{
   700  		JobResponse: successfulBuild,
   701  		Runner: &common.RunnerConfig{
   702  			RunnerSettings: common.RunnerSettings{
   703  				Executor: "docker",
   704  				Docker: &common.DockerConfig{
   705  					Image:           common.TestAlpineImage,
   706  					PullPolicy:      common.PullPolicyIfNotPresent,
   707  					AllowedServices: []string{common.TestAlpineImage},
   708  				},
   709  			},
   710  		},
   711  	}
   712  
   713  	re := regexp.MustCompile("(?m)^ERROR: The [^ ]+ is not present on list of allowed images")
   714  
   715  	output := runTestJobWithOutput(t, build)
   716  	assert.NotRegexp(t, re, output, "Image's name should be expanded from variable")
   717  }
   718  
   719  func TestDockerServiceNameFromVariable(t *testing.T) {
   720  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   721  		return
   722  	}
   723  
   724  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
   725  	successfulBuild.Variables = append(successfulBuild.Variables, common.JobVariable{
   726  		Key:   "CI_REGISTRY_IMAGE",
   727  		Value: common.TestAlpineImage,
   728  	})
   729  	successfulBuild.Services = append(successfulBuild.Services, common.Image{
   730  		Name: "$CI_REGISTRY_IMAGE",
   731  	})
   732  	assert.NoError(t, err)
   733  	build := &common.Build{
   734  		JobResponse: successfulBuild,
   735  		Runner: &common.RunnerConfig{
   736  			RunnerSettings: common.RunnerSettings{
   737  				Executor: "docker",
   738  				Docker: &common.DockerConfig{
   739  					Image:           common.TestAlpineImage,
   740  					PullPolicy:      common.PullPolicyIfNotPresent,
   741  					AllowedServices: []string{common.TestAlpineImage},
   742  				},
   743  			},
   744  		},
   745  	}
   746  
   747  	re := regexp.MustCompile("(?m)^ERROR: The [^ ]+ is not present on list of allowed services")
   748  
   749  	output := runTestJobWithOutput(t, build)
   750  	assert.NotRegexp(t, re, output, "Service's name should be expanded from variable")
   751  }
   752  
   753  func runDockerInDocker(version string) (id string, err error) {
   754  	cmd := exec.Command("docker", "run", "--detach", "--privileged", "-p", "2375", "docker:"+version+"-dind")
   755  	cmd.Stderr = os.Stderr
   756  	data, err := cmd.Output()
   757  	if err != nil {
   758  		return
   759  	}
   760  	id = strings.TrimSpace(string(data))
   761  	return
   762  }
   763  
   764  func getDockerCredentials(id string) (credentials docker_helpers.DockerCredentials, err error) {
   765  	cmd := exec.Command("docker", "port", id, "2375")
   766  	cmd.Stderr = os.Stderr
   767  	data, err := cmd.Output()
   768  	if err != nil {
   769  		return
   770  	}
   771  
   772  	hostPort := strings.Split(strings.TrimSpace(string(data)), ":")
   773  	if dockerHost, err := url.Parse(os.Getenv("DOCKER_HOST")); err == nil {
   774  		dockerHostPort := strings.Split(dockerHost.Host, ":")
   775  		hostPort[0] = dockerHostPort[0]
   776  	} else if hostPort[0] == "0.0.0.0" {
   777  		hostPort[0] = "localhost"
   778  	}
   779  	credentials.Host = "tcp://" + hostPort[0] + ":" + hostPort[1]
   780  	return
   781  }
   782  
   783  func waitForDocker(credentials docker_helpers.DockerCredentials) error {
   784  	client, err := docker_helpers.New(credentials, "")
   785  	if err != nil {
   786  		return err
   787  	}
   788  
   789  	for i := 0; i < 20; i++ {
   790  		_, err = client.Info(context.Background())
   791  		if err == nil {
   792  			break
   793  		}
   794  		time.Sleep(time.Second)
   795  	}
   796  	return err
   797  }
   798  
   799  func testDockerVersion(t *testing.T, version string) {
   800  	t.Log("Running docker", version, "...")
   801  	id, err := runDockerInDocker(version)
   802  	if err != nil {
   803  		t.Error("Docker run:", err)
   804  		return
   805  	}
   806  
   807  	defer func() {
   808  		exec.Command("docker", "rm", "-f", "-v", id).Run()
   809  	}()
   810  
   811  	t.Log("Getting address of", version, "...")
   812  	credentials, err := getDockerCredentials(id)
   813  	if err != nil {
   814  		t.Error("Docker credentials:", err)
   815  		return
   816  	}
   817  
   818  	t.Log("Connecting to", credentials.Host, "...")
   819  	err = waitForDocker(credentials)
   820  	if err != nil {
   821  		t.Error("Wait for docker:", err)
   822  		return
   823  	}
   824  
   825  	t.Log("Docker", version, "is running at", credentials.Host)
   826  
   827  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
   828  	assert.NoError(t, err)
   829  	build := &common.Build{
   830  		JobResponse: successfulBuild,
   831  		Runner: &common.RunnerConfig{
   832  			RunnerSettings: common.RunnerSettings{
   833  				Executor: "docker",
   834  				Docker: &common.DockerConfig{
   835  					Image:             common.TestAlpineImage,
   836  					PullPolicy:        common.PullPolicyIfNotPresent,
   837  					DockerCredentials: credentials,
   838  					CPUS:              "0.1",
   839  				},
   840  			},
   841  		},
   842  	}
   843  
   844  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
   845  	assert.NoError(t, err)
   846  }
   847  
   848  func TestDocker1_8Compatibility(t *testing.T) {
   849  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   850  		return
   851  	}
   852  	if os.Getenv("CI") != "" {
   853  		t.Skip("This test doesn't work in nested dind")
   854  		return
   855  	}
   856  
   857  	testDockerVersion(t, "1.8")
   858  }
   859  
   860  func TestDocker1_9Compatibility(t *testing.T) {
   861  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   862  		return
   863  	}
   864  	if os.Getenv("CI") != "" {
   865  		t.Skip("This test doesn't work in nested dind")
   866  		return
   867  	}
   868  
   869  	testDockerVersion(t, "1.9")
   870  }
   871  
   872  func TestDocker1_10Compatibility(t *testing.T) {
   873  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   874  		return
   875  	}
   876  	if os.Getenv("CI") != "" {
   877  		t.Skip("This test doesn't work in nested dind")
   878  		return
   879  	}
   880  
   881  	testDockerVersion(t, "1.10")
   882  }
   883  
   884  func TestDocker1_11Compatibility(t *testing.T) {
   885  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   886  		return
   887  	}
   888  	if os.Getenv("CI") != "" {
   889  		t.Skip("This test doesn't work in nested dind")
   890  		return
   891  	}
   892  
   893  	testDockerVersion(t, "1.11")
   894  }
   895  
   896  func TestDocker1_12Compatibility(t *testing.T) {
   897  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   898  		return
   899  	}
   900  	if os.Getenv("CI") != "" {
   901  		t.Skip("This test doesn't work in nested dind")
   902  		return
   903  	}
   904  
   905  	testDockerVersion(t, "1.12")
   906  }
   907  
   908  func TestDocker1_13Compatibility(t *testing.T) {
   909  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   910  		return
   911  	}
   912  	if os.Getenv("CI") != "" {
   913  		t.Skip("This test doesn't work in nested dind")
   914  		return
   915  	}
   916  
   917  	testDockerVersion(t, "1.13")
   918  }
   919  
   920  func TestDockerCommandWithBrokenGitSSLCAInfo(t *testing.T) {
   921  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   922  		return
   923  	}
   924  
   925  	successfulBuild, err := common.GetRemoteBrokenTLSBuild()
   926  	assert.NoError(t, err)
   927  	build := &common.Build{
   928  		JobResponse: successfulBuild,
   929  		Runner: &common.RunnerConfig{
   930  			RunnerCredentials: common.RunnerCredentials{
   931  				URL: "https://gitlab.com",
   932  			},
   933  			RunnerSettings: common.RunnerSettings{
   934  				Executor: "docker",
   935  				Docker: &common.DockerConfig{
   936  					Image:      common.TestAlpineImage,
   937  					PullPolicy: common.PullPolicyIfNotPresent,
   938  				},
   939  			},
   940  		},
   941  	}
   942  
   943  	var buffer bytes.Buffer
   944  
   945  	err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer})
   946  	assert.Error(t, err)
   947  	out := buffer.String()
   948  	assert.Contains(t, out, "Created fresh repository")
   949  	assert.NotContains(t, out, "Updating/initializing submodules")
   950  }
   951  
   952  func TestDockerCommandWithGitSSLCAInfo(t *testing.T) {
   953  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   954  		return
   955  	}
   956  
   957  	successfulBuild, err := common.GetRemoteGitLabComTLSBuild()
   958  	assert.NoError(t, err)
   959  	build := &common.Build{
   960  		JobResponse: successfulBuild,
   961  		Runner: &common.RunnerConfig{
   962  			RunnerCredentials: common.RunnerCredentials{
   963  				URL: "https://gitlab.com",
   964  			},
   965  			RunnerSettings: common.RunnerSettings{
   966  				Executor: "docker",
   967  				Docker: &common.DockerConfig{
   968  					Image:      common.TestAlpineImage,
   969  					PullPolicy: common.PullPolicyIfNotPresent,
   970  				},
   971  			},
   972  		},
   973  	}
   974  
   975  	var buffer bytes.Buffer
   976  
   977  	err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer})
   978  	assert.NoError(t, err)
   979  	out := buffer.String()
   980  	assert.Contains(t, out, "Created fresh repository")
   981  	assert.Contains(t, out, "Updating/initializing submodules")
   982  }
   983  
   984  func TestDockerCommandWithHelperImageConfig(t *testing.T) {
   985  	if helpers.SkipIntegrationTests(t, "docker", "info") {
   986  		return
   987  	}
   988  
   989  	helperImageConfig := "gitlab/gitlab-runner-helper:x86_64-5a147c92"
   990  
   991  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
   992  	assert.NoError(t, err)
   993  	build := &common.Build{
   994  		JobResponse: successfulBuild,
   995  		Runner: &common.RunnerConfig{
   996  			RunnerSettings: common.RunnerSettings{
   997  				Executor: "docker",
   998  				Docker: &common.DockerConfig{
   999  					Image:       common.TestAlpineImage,
  1000  					HelperImage: helperImageConfig,
  1001  					PullPolicy:  common.PullPolicyIfNotPresent,
  1002  				},
  1003  			},
  1004  		},
  1005  	}
  1006  
  1007  	var buffer bytes.Buffer
  1008  	err = build.Run(&common.Config{}, &common.Trace{Writer: &buffer})
  1009  	assert.NoError(t, err)
  1010  	out := buffer.String()
  1011  	assert.Contains(t, out, "Using docker image sha256:3cf24b1b62b6a4c55c5de43db4f50c0ff8b455238c836945d4b5c645411bfc77 for gitlab/gitlab-runner-helper:x86_64-5a147c92 ...")
  1012  }
  1013  
  1014  func TestDockerCommandWithDoingPruneAndAfterScript(t *testing.T) {
  1015  	if helpers.SkipIntegrationTests(t, "docker", "info") {
  1016  		return
  1017  	}
  1018  
  1019  	successfulBuild, err := common.GetRemoteSuccessfulBuildWithAfterScript()
  1020  
  1021  	// This scripts removes self-created containers that do exit
  1022  	// It will fail if: cannot be removed, or no containers is found
  1023  	// It is assuming that name of each runner created container starts
  1024  	// with `runner-doprune-`
  1025  	successfulBuild.Steps[0].Script = common.StepScript{
  1026  		"docker ps -a -f status=exited | grep runner-doprune-",
  1027  		"docker rm $(docker ps -a -f status=exited | grep runner-doprune- | awk '{print $1}')",
  1028  	}
  1029  
  1030  	assert.NoError(t, err)
  1031  	build := &common.Build{
  1032  		JobResponse: successfulBuild,
  1033  		Runner: &common.RunnerConfig{
  1034  			RunnerCredentials: common.RunnerCredentials{
  1035  				Token: "doprune",
  1036  			},
  1037  			RunnerSettings: common.RunnerSettings{
  1038  				Executor: "docker",
  1039  				Docker: &common.DockerConfig{
  1040  					Image:      common.TestDockerGitImage,
  1041  					PullPolicy: common.PullPolicyIfNotPresent,
  1042  					Volumes: []string{
  1043  						"/var/run/docker.sock:/var/run/docker.sock",
  1044  					},
  1045  				},
  1046  			},
  1047  		},
  1048  	}
  1049  
  1050  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1051  	assert.NoError(t, err)
  1052  }
  1053  
  1054  func TestDockerCommandUsingBuildsVolume(t *testing.T) {
  1055  	if helpers.SkipIntegrationTests(t, "docker", "info") {
  1056  		return
  1057  	}
  1058  
  1059  	const buildsPath = "/builds"
  1060  	// the path is taken from `repoRemoteURL`
  1061  	const buildsGroupPath = "/builds/gitlab-org/ci-cd/tests"
  1062  
  1063  	tests := map[string]struct {
  1064  		validPath   string
  1065  		invalidPath string
  1066  		variable    string
  1067  	}{
  1068  		"uses default state of FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER": {
  1069  			validPath:   buildsPath,
  1070  			invalidPath: buildsGroupPath,
  1071  			variable:    "",
  1072  		},
  1073  		"disables FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER": {
  1074  			validPath:   buildsPath,
  1075  			invalidPath: buildsGroupPath,
  1076  			variable:    "FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER=false",
  1077  		},
  1078  		"enables FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER": {
  1079  			validPath:   buildsGroupPath,
  1080  			invalidPath: buildsPath,
  1081  			variable:    "FF_USE_LEGACY_BUILDS_DIR_FOR_DOCKER=true",
  1082  		},
  1083  	}
  1084  
  1085  	for name, test := range tests {
  1086  		t.Run(name, func(t *testing.T) {
  1087  			jobResponse, err := common.GetRemoteBuildResponse(
  1088  				"mountpoint "+test.validPath,
  1089  				"! mountpoint "+test.invalidPath,
  1090  			)
  1091  			require.NoError(t, err)
  1092  
  1093  			build := &common.Build{
  1094  				JobResponse: jobResponse,
  1095  				Runner: &common.RunnerConfig{
  1096  					RunnerSettings: common.RunnerSettings{
  1097  						Executor: "docker",
  1098  						Docker: &common.DockerConfig{
  1099  							Image:      common.TestAlpineImage,
  1100  							PullPolicy: common.PullPolicyIfNotPresent,
  1101  						},
  1102  						Environment: []string{
  1103  							"GIT_STRATEGY=none",
  1104  							test.variable,
  1105  						},
  1106  					},
  1107  				},
  1108  			}
  1109  
  1110  			err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1111  			assert.NoError(t, err)
  1112  		})
  1113  	}
  1114  }