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

     1  package kubernetes
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/url"
    13  	"os"
    14  	"strconv"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/gorilla/websocket"
    20  	"github.com/sirupsen/logrus"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  
    24  	api "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/api/resource"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/client-go/rest/fake"
    28  
    29  	"gitlab.com/gitlab-org/gitlab-runner/common"
    30  	"gitlab.com/gitlab-org/gitlab-runner/executors"
    31  	"gitlab.com/gitlab-org/gitlab-runner/helpers"
    32  	dns_test "gitlab.com/gitlab-org/gitlab-runner/helpers/dns/test"
    33  	"gitlab.com/gitlab-org/gitlab-runner/helpers/docker/helperimage"
    34  	"gitlab.com/gitlab-org/gitlab-runner/helpers/featureflags"
    35  	"gitlab.com/gitlab-org/gitlab-runner/session"
    36  )
    37  
    38  var (
    39  	TRUE = true
    40  )
    41  
    42  const (
    43  	TestTimeout = 15 * time.Second
    44  )
    45  
    46  func TestLimits(t *testing.T) {
    47  	tests := []struct {
    48  		CPU, Memory string
    49  		Expected    api.ResourceList
    50  	}{
    51  		{
    52  			CPU:    "100m",
    53  			Memory: "100Mi",
    54  			Expected: api.ResourceList{
    55  				api.ResourceCPU:    resource.MustParse("100m"),
    56  				api.ResourceMemory: resource.MustParse("100Mi"),
    57  			},
    58  		},
    59  		{
    60  			CPU: "100m",
    61  			Expected: api.ResourceList{
    62  				api.ResourceCPU: resource.MustParse("100m"),
    63  			},
    64  		},
    65  		{
    66  			Memory: "100Mi",
    67  			Expected: api.ResourceList{
    68  				api.ResourceMemory: resource.MustParse("100Mi"),
    69  			},
    70  		},
    71  		{
    72  			CPU:      "100j",
    73  			Expected: api.ResourceList{},
    74  		},
    75  		{
    76  			Memory:   "100j",
    77  			Expected: api.ResourceList{},
    78  		},
    79  		{
    80  			Expected: api.ResourceList{},
    81  		},
    82  	}
    83  
    84  	for _, test := range tests {
    85  		res, _ := limits(test.CPU, test.Memory)
    86  		assert.Equal(t, test.Expected, res)
    87  	}
    88  }
    89  
    90  func TestVolumeMounts(t *testing.T) {
    91  	tests := []struct {
    92  		GlobalConfig *common.Config
    93  		RunnerConfig common.RunnerConfig
    94  		Build        *common.Build
    95  
    96  		Expected []api.VolumeMount
    97  	}{
    98  		{
    99  			GlobalConfig: &common.Config{},
   100  			RunnerConfig: common.RunnerConfig{
   101  				RunnerSettings: common.RunnerSettings{
   102  					Kubernetes: &common.KubernetesConfig{},
   103  				},
   104  			},
   105  			Build: &common.Build{
   106  				Runner: &common.RunnerConfig{},
   107  			},
   108  			Expected: []api.VolumeMount{
   109  				{Name: "repo"},
   110  			},
   111  		},
   112  		{
   113  			GlobalConfig: &common.Config{},
   114  			RunnerConfig: common.RunnerConfig{
   115  				RunnerSettings: common.RunnerSettings{
   116  					Kubernetes: &common.KubernetesConfig{
   117  						Volumes: common.KubernetesVolumes{
   118  							HostPaths: []common.KubernetesHostPath{
   119  								{Name: "docker", MountPath: "/var/run/docker.sock", HostPath: "/var/run/docker.sock"},
   120  							},
   121  							PVCs: []common.KubernetesPVC{
   122  								{Name: "PVC", MountPath: "/path/to/whatever"},
   123  							},
   124  							EmptyDirs: []common.KubernetesEmptyDir{
   125  								{Name: "emptyDir", MountPath: "/path/to/empty/dir"},
   126  							},
   127  						},
   128  					},
   129  				},
   130  			},
   131  			Build: &common.Build{
   132  				Runner: &common.RunnerConfig{},
   133  			},
   134  			Expected: []api.VolumeMount{
   135  				{Name: "repo"},
   136  				{Name: "docker", MountPath: "/var/run/docker.sock"},
   137  				{Name: "PVC", MountPath: "/path/to/whatever"},
   138  				{Name: "emptyDir", MountPath: "/path/to/empty/dir"},
   139  			},
   140  		},
   141  		{
   142  			GlobalConfig: &common.Config{},
   143  			RunnerConfig: common.RunnerConfig{
   144  				RunnerSettings: common.RunnerSettings{
   145  					Kubernetes: &common.KubernetesConfig{
   146  						Volumes: common.KubernetesVolumes{
   147  							HostPaths: []common.KubernetesHostPath{
   148  								{Name: "test", MountPath: "/opt/test/readonly", ReadOnly: true, HostPath: "/opt/test/rw"},
   149  								{Name: "docker", MountPath: "/var/run/docker.sock"},
   150  							},
   151  							ConfigMaps: []common.KubernetesConfigMap{
   152  								{Name: "configMap", MountPath: "/path/to/configmap", ReadOnly: true},
   153  							},
   154  							Secrets: []common.KubernetesSecret{
   155  								{Name: "secret", MountPath: "/path/to/secret", ReadOnly: true},
   156  							},
   157  						},
   158  					},
   159  				},
   160  			},
   161  			Build: &common.Build{
   162  				Runner: &common.RunnerConfig{},
   163  			},
   164  			Expected: []api.VolumeMount{
   165  				{Name: "repo"},
   166  				{Name: "test", MountPath: "/opt/test/readonly", ReadOnly: true},
   167  				{Name: "docker", MountPath: "/var/run/docker.sock"},
   168  				{Name: "secret", MountPath: "/path/to/secret", ReadOnly: true},
   169  				{Name: "configMap", MountPath: "/path/to/configmap", ReadOnly: true},
   170  			},
   171  		},
   172  	}
   173  
   174  	for _, test := range tests {
   175  		e := &executor{
   176  			AbstractExecutor: executors.AbstractExecutor{
   177  				ExecutorOptions: executorOptions,
   178  				Build:           test.Build,
   179  				Config:          test.RunnerConfig,
   180  			},
   181  		}
   182  
   183  		mounts := e.getVolumeMounts()
   184  		for _, expected := range test.Expected {
   185  			assert.Contains(t, mounts, expected, "Expected volumeMount definition for %s was not found", expected.Name)
   186  		}
   187  	}
   188  }
   189  
   190  func TestVolumes(t *testing.T) {
   191  	tests := []struct {
   192  		GlobalConfig *common.Config
   193  		RunnerConfig common.RunnerConfig
   194  		Build        *common.Build
   195  
   196  		Expected []api.Volume
   197  	}{
   198  		{
   199  			GlobalConfig: &common.Config{},
   200  			RunnerConfig: common.RunnerConfig{
   201  				RunnerSettings: common.RunnerSettings{
   202  					Kubernetes: &common.KubernetesConfig{},
   203  				},
   204  			},
   205  			Build: &common.Build{
   206  				Runner: &common.RunnerConfig{},
   207  			},
   208  			Expected: []api.Volume{
   209  				{Name: "repo", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
   210  			},
   211  		},
   212  		{
   213  			GlobalConfig: &common.Config{},
   214  			RunnerConfig: common.RunnerConfig{
   215  				RunnerSettings: common.RunnerSettings{
   216  					Kubernetes: &common.KubernetesConfig{
   217  						Volumes: common.KubernetesVolumes{
   218  							HostPaths: []common.KubernetesHostPath{
   219  								{Name: "docker", MountPath: "/var/run/docker.sock"},
   220  								{Name: "host-path", MountPath: "/path/two", HostPath: "/path/one"},
   221  							},
   222  							PVCs: []common.KubernetesPVC{
   223  								{Name: "PVC", MountPath: "/path/to/whatever"},
   224  							},
   225  							ConfigMaps: []common.KubernetesConfigMap{
   226  								{Name: "ConfigMap", MountPath: "/path/to/config", Items: map[string]string{"key_1": "/path/to/key_1"}},
   227  							},
   228  							Secrets: []common.KubernetesSecret{
   229  								{Name: "secret", MountPath: "/path/to/secret", ReadOnly: true, Items: map[string]string{"secret_1": "/path/to/secret_1"}},
   230  							},
   231  							EmptyDirs: []common.KubernetesEmptyDir{
   232  								{Name: "emptyDir", MountPath: "/path/to/empty/dir", Medium: "Memory"},
   233  							},
   234  						},
   235  					},
   236  				},
   237  			},
   238  			Build: &common.Build{
   239  				Runner: &common.RunnerConfig{},
   240  			},
   241  			Expected: []api.Volume{
   242  				{Name: "repo", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
   243  				{Name: "docker", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: "/var/run/docker.sock"}}},
   244  				{Name: "host-path", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: "/path/one"}}},
   245  				{Name: "PVC", VolumeSource: api.VolumeSource{PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ClaimName: "PVC"}}},
   246  				{Name: "emptyDir", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{Medium: "Memory"}}},
   247  				{
   248  					Name: "ConfigMap",
   249  					VolumeSource: api.VolumeSource{
   250  						ConfigMap: &api.ConfigMapVolumeSource{
   251  							LocalObjectReference: api.LocalObjectReference{Name: "ConfigMap"},
   252  							Items:                []api.KeyToPath{{Key: "key_1", Path: "/path/to/key_1"}},
   253  						},
   254  					},
   255  				},
   256  				{
   257  					Name: "secret",
   258  					VolumeSource: api.VolumeSource{
   259  						Secret: &api.SecretVolumeSource{
   260  							SecretName: "secret",
   261  							Items:      []api.KeyToPath{{Key: "secret_1", Path: "/path/to/secret_1"}},
   262  						},
   263  					},
   264  				},
   265  			},
   266  		},
   267  	}
   268  
   269  	for _, test := range tests {
   270  		e := &executor{
   271  			AbstractExecutor: executors.AbstractExecutor{
   272  				ExecutorOptions: executorOptions,
   273  				Build:           test.Build,
   274  				Config:          test.RunnerConfig,
   275  			},
   276  		}
   277  
   278  		volumes := e.getVolumes()
   279  		for _, expected := range test.Expected {
   280  			assert.Contains(t, volumes, expected, "Expected volume definition for %s was not found", expected.Name)
   281  		}
   282  	}
   283  }
   284  
   285  func fakeKubeDeleteResponse(status int) *http.Response {
   286  	_, codec := testVersionAndCodec()
   287  
   288  	body := objBody(codec, &metav1.Status{Code: int32(status)})
   289  	return &http.Response{StatusCode: status, Body: body, Header: map[string][]string{
   290  		"Content-Type": {"application/json"},
   291  	}}
   292  }
   293  
   294  func TestCleanup(t *testing.T) {
   295  	version, _ := testVersionAndCodec()
   296  
   297  	objectMeta := metav1.ObjectMeta{Name: "test-resource", Namespace: "test-ns"}
   298  
   299  	tests := []struct {
   300  		Name        string
   301  		Pod         *api.Pod
   302  		Credentials *api.Secret
   303  		ClientFunc  func(*http.Request) (*http.Response, error)
   304  		Error       bool
   305  	}{
   306  		{
   307  			Name: "Proper Cleanup",
   308  			Pod:  &api.Pod{ObjectMeta: objectMeta},
   309  			ClientFunc: func(req *http.Request) (*http.Response, error) {
   310  				switch p, m := req.URL.Path, req.Method; {
   311  				case m == http.MethodDelete && p == "/api/"+version+"/namespaces/test-ns/pods/test-resource":
   312  					return fakeKubeDeleteResponse(http.StatusOK), nil
   313  				default:
   314  					return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p)
   315  				}
   316  			},
   317  		},
   318  		{
   319  			Name: "Delete failure",
   320  			Pod:  &api.Pod{ObjectMeta: objectMeta},
   321  			ClientFunc: func(req *http.Request) (*http.Response, error) {
   322  				return nil, fmt.Errorf("delete failed")
   323  			},
   324  			Error: true,
   325  		},
   326  		{
   327  			Name: "POD already deleted",
   328  			Pod:  &api.Pod{ObjectMeta: objectMeta},
   329  			ClientFunc: func(req *http.Request) (*http.Response, error) {
   330  				switch p, m := req.URL.Path, req.Method; {
   331  				case m == http.MethodDelete && p == "/api/"+version+"/namespaces/test-ns/pods/test-resource":
   332  					return fakeKubeDeleteResponse(http.StatusNotFound), nil
   333  				default:
   334  					return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p)
   335  				}
   336  			},
   337  			Error: true,
   338  		},
   339  		{
   340  			Name:        "POD creation failed, Secretes provided",
   341  			Pod:         nil, // a failed POD create request will cause a nil Pod
   342  			Credentials: &api.Secret{ObjectMeta: objectMeta},
   343  			ClientFunc: func(req *http.Request) (*http.Response, error) {
   344  				switch p, m := req.URL.Path, req.Method; {
   345  				case m == http.MethodDelete && p == "/api/"+version+"/namespaces/test-ns/secrets/test-resource":
   346  					return fakeKubeDeleteResponse(http.StatusNotFound), nil
   347  				default:
   348  					return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p)
   349  				}
   350  			},
   351  			Error: true,
   352  		},
   353  	}
   354  
   355  	for _, test := range tests {
   356  		t.Run(test.Name, func(t *testing.T) {
   357  			ex := executor{
   358  				kubeClient:  testKubernetesClient(version, fake.CreateHTTPClient(test.ClientFunc)),
   359  				pod:         test.Pod,
   360  				credentials: test.Credentials,
   361  			}
   362  			ex.configurationOverwrites = &overwrites{namespace: "test-ns"}
   363  			errored := false
   364  			buildTrace := FakeBuildTrace{
   365  				testWriter{
   366  					call: func(b []byte) (int, error) {
   367  						if !errored {
   368  							if s := string(b); strings.Contains(s, "Error cleaning up") {
   369  								errored = true
   370  							} else if test.Error {
   371  								t.Errorf("expected failure. got: '%s'", string(b))
   372  							}
   373  						}
   374  						return len(b), nil
   375  					},
   376  				},
   377  			}
   378  			ex.AbstractExecutor.Trace = buildTrace
   379  			ex.AbstractExecutor.BuildLogger = common.NewBuildLogger(buildTrace, logrus.WithFields(logrus.Fields{}))
   380  
   381  			ex.Cleanup()
   382  
   383  			if test.Error && !errored {
   384  				t.Errorf("expected cleanup to fail but it didn't")
   385  			} else if !test.Error && errored {
   386  				t.Errorf("expected cleanup not to fail but it did")
   387  			}
   388  		})
   389  	}
   390  }
   391  
   392  func TestPrepare(t *testing.T) {
   393  	tests := []struct {
   394  		GlobalConfig *common.Config
   395  		RunnerConfig *common.RunnerConfig
   396  		Build        *common.Build
   397  
   398  		Expected *executor
   399  		Error    bool
   400  	}{
   401  		{
   402  			GlobalConfig: &common.Config{},
   403  			RunnerConfig: &common.RunnerConfig{
   404  				RunnerSettings: common.RunnerSettings{
   405  					Kubernetes: &common.KubernetesConfig{
   406  						Host:               "test-server",
   407  						ServiceCPULimit:    "100m",
   408  						ServiceMemoryLimit: "200Mi",
   409  						CPULimit:           "1.5",
   410  						MemoryLimit:        "4Gi",
   411  						HelperCPULimit:     "50m",
   412  						HelperMemoryLimit:  "100Mi",
   413  						Privileged:         true,
   414  						PullPolicy:         "if-not-present",
   415  					},
   416  				},
   417  			},
   418  			Build: &common.Build{
   419  				JobResponse: common.JobResponse{
   420  					GitInfo: common.GitInfo{
   421  						Sha: "1234567890",
   422  					},
   423  					Image: common.Image{
   424  						Name: "test-image",
   425  					},
   426  					Variables: []common.JobVariable{
   427  						{Key: "privileged", Value: "true"},
   428  					},
   429  				},
   430  				Runner: &common.RunnerConfig{},
   431  			},
   432  			Expected: &executor{
   433  				options: &kubernetesOptions{
   434  					Image: common.Image{
   435  						Name: "test-image",
   436  					},
   437  				},
   438  				configurationOverwrites: &overwrites{namespace: "default"},
   439  				serviceLimits: api.ResourceList{
   440  					api.ResourceCPU:    resource.MustParse("100m"),
   441  					api.ResourceMemory: resource.MustParse("200Mi"),
   442  				},
   443  				buildLimits: api.ResourceList{
   444  					api.ResourceCPU:    resource.MustParse("1.5"),
   445  					api.ResourceMemory: resource.MustParse("4Gi"),
   446  				},
   447  				helperLimits: api.ResourceList{
   448  					api.ResourceCPU:    resource.MustParse("50m"),
   449  					api.ResourceMemory: resource.MustParse("100Mi"),
   450  				},
   451  				serviceRequests: api.ResourceList{},
   452  				buildRequests:   api.ResourceList{},
   453  				helperRequests:  api.ResourceList{},
   454  				pullPolicy:      "IfNotPresent",
   455  			},
   456  		},
   457  		{
   458  			GlobalConfig: &common.Config{},
   459  			RunnerConfig: &common.RunnerConfig{
   460  				RunnerSettings: common.RunnerSettings{
   461  					Kubernetes: &common.KubernetesConfig{
   462  						Host:                           "test-server",
   463  						ServiceAccount:                 "default",
   464  						ServiceAccountOverwriteAllowed: ".*",
   465  						BearerTokenOverwriteAllowed:    true,
   466  						ServiceCPULimit:                "100m",
   467  						ServiceMemoryLimit:             "200Mi",
   468  						CPULimit:                       "1.5",
   469  						MemoryLimit:                    "4Gi",
   470  						HelperCPULimit:                 "50m",
   471  						HelperMemoryLimit:              "100Mi",
   472  						ServiceCPURequest:              "99m",
   473  						ServiceMemoryRequest:           "5Mi",
   474  						CPURequest:                     "1",
   475  						MemoryRequest:                  "1.5Gi",
   476  						HelperCPURequest:               "0.5m",
   477  						HelperMemoryRequest:            "42Mi",
   478  						Privileged:                     false,
   479  					},
   480  				},
   481  			},
   482  			Build: &common.Build{
   483  				JobResponse: common.JobResponse{
   484  					GitInfo: common.GitInfo{
   485  						Sha: "1234567890",
   486  					},
   487  					Image: common.Image{
   488  						Name: "test-image",
   489  					},
   490  					Variables: []common.JobVariable{
   491  						{Key: ServiceAccountOverwriteVariableName, Value: "not-default"},
   492  					},
   493  				},
   494  				Runner: &common.RunnerConfig{},
   495  			},
   496  			Expected: &executor{
   497  				options: &kubernetesOptions{
   498  					Image: common.Image{
   499  						Name: "test-image",
   500  					},
   501  				},
   502  				configurationOverwrites: &overwrites{namespace: "default", serviceAccount: "not-default"},
   503  				serviceLimits: api.ResourceList{
   504  					api.ResourceCPU:    resource.MustParse("100m"),
   505  					api.ResourceMemory: resource.MustParse("200Mi"),
   506  				},
   507  				buildLimits: api.ResourceList{
   508  					api.ResourceCPU:    resource.MustParse("1.5"),
   509  					api.ResourceMemory: resource.MustParse("4Gi"),
   510  				},
   511  				helperLimits: api.ResourceList{
   512  					api.ResourceCPU:    resource.MustParse("50m"),
   513  					api.ResourceMemory: resource.MustParse("100Mi"),
   514  				},
   515  				serviceRequests: api.ResourceList{
   516  					api.ResourceCPU:    resource.MustParse("99m"),
   517  					api.ResourceMemory: resource.MustParse("5Mi"),
   518  				},
   519  				buildRequests: api.ResourceList{
   520  					api.ResourceCPU:    resource.MustParse("1"),
   521  					api.ResourceMemory: resource.MustParse("1.5Gi"),
   522  				},
   523  				helperRequests: api.ResourceList{
   524  					api.ResourceCPU:    resource.MustParse("0.5m"),
   525  					api.ResourceMemory: resource.MustParse("42Mi"),
   526  				},
   527  			},
   528  			Error: false,
   529  		},
   530  
   531  		{
   532  			GlobalConfig: &common.Config{},
   533  			RunnerConfig: &common.RunnerConfig{
   534  				RunnerSettings: common.RunnerSettings{
   535  					Kubernetes: &common.KubernetesConfig{
   536  						Host:                           "test-server",
   537  						ServiceAccount:                 "default",
   538  						ServiceAccountOverwriteAllowed: "allowed-.*",
   539  						ServiceCPULimit:                "100m",
   540  						ServiceMemoryLimit:             "200Mi",
   541  						CPULimit:                       "1.5",
   542  						MemoryLimit:                    "4Gi",
   543  						HelperCPULimit:                 "50m",
   544  						HelperMemoryLimit:              "100Mi",
   545  						ServiceCPURequest:              "99m",
   546  						ServiceMemoryRequest:           "5Mi",
   547  						CPURequest:                     "1",
   548  						MemoryRequest:                  "1.5Gi",
   549  						HelperCPURequest:               "0.5m",
   550  						HelperMemoryRequest:            "42Mi",
   551  						Privileged:                     false,
   552  					},
   553  				},
   554  			},
   555  			Build: &common.Build{
   556  				JobResponse: common.JobResponse{
   557  					GitInfo: common.GitInfo{
   558  						Sha: "1234567890",
   559  					},
   560  					Image: common.Image{
   561  						Name: "test-image",
   562  					},
   563  					Variables: []common.JobVariable{
   564  						{Key: ServiceAccountOverwriteVariableName, Value: "not-default"},
   565  					},
   566  				},
   567  				Runner: &common.RunnerConfig{},
   568  			},
   569  			Expected: &executor{
   570  				options: &kubernetesOptions{
   571  					Image: common.Image{
   572  						Name: "test-image",
   573  					},
   574  				},
   575  				configurationOverwrites: &overwrites{namespace: "namespacee"},
   576  				serviceLimits: api.ResourceList{
   577  					api.ResourceCPU:    resource.MustParse("100m"),
   578  					api.ResourceMemory: resource.MustParse("200Mi"),
   579  				},
   580  				buildLimits: api.ResourceList{
   581  					api.ResourceCPU:    resource.MustParse("1.5"),
   582  					api.ResourceMemory: resource.MustParse("4Gi"),
   583  				},
   584  				helperLimits: api.ResourceList{
   585  					api.ResourceCPU:    resource.MustParse("50m"),
   586  					api.ResourceMemory: resource.MustParse("100Mi"),
   587  				},
   588  				serviceRequests: api.ResourceList{
   589  					api.ResourceCPU:    resource.MustParse("99m"),
   590  					api.ResourceMemory: resource.MustParse("5Mi"),
   591  				},
   592  				buildRequests: api.ResourceList{
   593  					api.ResourceCPU:    resource.MustParse("1"),
   594  					api.ResourceMemory: resource.MustParse("1.5Gi"),
   595  				},
   596  				helperRequests: api.ResourceList{
   597  					api.ResourceCPU:    resource.MustParse("0.5m"),
   598  					api.ResourceMemory: resource.MustParse("42Mi"),
   599  				},
   600  			},
   601  			Error: true,
   602  		},
   603  		{
   604  			GlobalConfig: &common.Config{},
   605  			RunnerConfig: &common.RunnerConfig{
   606  				RunnerSettings: common.RunnerSettings{
   607  					Kubernetes: &common.KubernetesConfig{
   608  						Host:                           "test-server",
   609  						Namespace:                      "namespace",
   610  						ServiceAccount:                 "a_service_account",
   611  						ServiceAccountOverwriteAllowed: ".*",
   612  						NamespaceOverwriteAllowed:      "^n.*?e$",
   613  						ServiceCPULimit:                "100m",
   614  						ServiceMemoryLimit:             "200Mi",
   615  						CPULimit:                       "1.5",
   616  						MemoryLimit:                    "4Gi",
   617  						HelperCPULimit:                 "50m",
   618  						HelperMemoryLimit:              "100Mi",
   619  						ServiceCPURequest:              "99m",
   620  						ServiceMemoryRequest:           "5Mi",
   621  						CPURequest:                     "1",
   622  						MemoryRequest:                  "1.5Gi",
   623  						HelperCPURequest:               "0.5m",
   624  						HelperMemoryRequest:            "42Mi",
   625  						Privileged:                     false,
   626  					},
   627  				},
   628  			},
   629  			Build: &common.Build{
   630  				JobResponse: common.JobResponse{
   631  					GitInfo: common.GitInfo{
   632  						Sha: "1234567890",
   633  					},
   634  					Image: common.Image{
   635  						Name: "test-image",
   636  					},
   637  					Variables: []common.JobVariable{
   638  						{Key: NamespaceOverwriteVariableName, Value: "namespacee"},
   639  					},
   640  				},
   641  				Runner: &common.RunnerConfig{},
   642  			},
   643  			Expected: &executor{
   644  				options: &kubernetesOptions{
   645  					Image: common.Image{
   646  						Name: "test-image",
   647  					},
   648  				},
   649  				configurationOverwrites: &overwrites{namespace: "namespacee", serviceAccount: "a_service_account"},
   650  				serviceLimits: api.ResourceList{
   651  					api.ResourceCPU:    resource.MustParse("100m"),
   652  					api.ResourceMemory: resource.MustParse("200Mi"),
   653  				},
   654  				buildLimits: api.ResourceList{
   655  					api.ResourceCPU:    resource.MustParse("1.5"),
   656  					api.ResourceMemory: resource.MustParse("4Gi"),
   657  				},
   658  				helperLimits: api.ResourceList{
   659  					api.ResourceCPU:    resource.MustParse("50m"),
   660  					api.ResourceMemory: resource.MustParse("100Mi"),
   661  				},
   662  				serviceRequests: api.ResourceList{
   663  					api.ResourceCPU:    resource.MustParse("99m"),
   664  					api.ResourceMemory: resource.MustParse("5Mi"),
   665  				},
   666  				buildRequests: api.ResourceList{
   667  					api.ResourceCPU:    resource.MustParse("1"),
   668  					api.ResourceMemory: resource.MustParse("1.5Gi"),
   669  				},
   670  				helperRequests: api.ResourceList{
   671  					api.ResourceCPU:    resource.MustParse("0.5m"),
   672  					api.ResourceMemory: resource.MustParse("42Mi"),
   673  				},
   674  			},
   675  			Error: true,
   676  		},
   677  		{
   678  			GlobalConfig: &common.Config{},
   679  			RunnerConfig: &common.RunnerConfig{
   680  				RunnerSettings: common.RunnerSettings{
   681  					Kubernetes: &common.KubernetesConfig{
   682  						Namespace: "namespace",
   683  						Host:      "test-server",
   684  					},
   685  				},
   686  			},
   687  			Build: &common.Build{
   688  				JobResponse: common.JobResponse{
   689  					GitInfo: common.GitInfo{
   690  						Sha: "1234567890",
   691  					},
   692  					Image: common.Image{
   693  						Name: "test-image",
   694  					},
   695  					Variables: []common.JobVariable{
   696  						{Key: NamespaceOverwriteVariableName, Value: "namespace"},
   697  					},
   698  				},
   699  				Runner: &common.RunnerConfig{},
   700  			},
   701  			Expected: &executor{
   702  				options: &kubernetesOptions{
   703  					Image: common.Image{
   704  						Name: "test-image",
   705  					},
   706  				},
   707  				configurationOverwrites: &overwrites{namespace: "namespace"},
   708  				serviceLimits:           api.ResourceList{},
   709  				buildLimits:             api.ResourceList{},
   710  				helperLimits:            api.ResourceList{},
   711  				serviceRequests:         api.ResourceList{},
   712  				buildRequests:           api.ResourceList{},
   713  				helperRequests:          api.ResourceList{},
   714  			},
   715  		},
   716  		{
   717  			GlobalConfig: &common.Config{},
   718  			RunnerConfig: &common.RunnerConfig{
   719  				RunnerSettings: common.RunnerSettings{
   720  					Kubernetes: &common.KubernetesConfig{
   721  						Image: "test-image",
   722  						Host:  "test-server",
   723  					},
   724  				},
   725  			},
   726  			Build: &common.Build{
   727  				JobResponse: common.JobResponse{
   728  					GitInfo: common.GitInfo{
   729  						Sha: "1234567890",
   730  					},
   731  				},
   732  				Runner: &common.RunnerConfig{},
   733  			},
   734  			Expected: &executor{
   735  				options: &kubernetesOptions{
   736  					Image: common.Image{
   737  						Name: "test-image",
   738  					},
   739  				},
   740  				configurationOverwrites: &overwrites{namespace: "default"},
   741  				serviceLimits:           api.ResourceList{},
   742  				buildLimits:             api.ResourceList{},
   743  				helperLimits:            api.ResourceList{},
   744  				serviceRequests:         api.ResourceList{},
   745  				buildRequests:           api.ResourceList{},
   746  				helperRequests:          api.ResourceList{},
   747  			},
   748  		},
   749  		{
   750  			GlobalConfig: &common.Config{},
   751  			RunnerConfig: &common.RunnerConfig{
   752  				RunnerSettings: common.RunnerSettings{
   753  					Kubernetes: &common.KubernetesConfig{
   754  						Host: "test-server",
   755  					},
   756  				},
   757  			},
   758  			Build: &common.Build{
   759  				JobResponse: common.JobResponse{
   760  					GitInfo: common.GitInfo{
   761  						Sha: "1234567890",
   762  					},
   763  					Image: common.Image{
   764  						Name:       "test-image",
   765  						Entrypoint: []string{"/init", "run"},
   766  					},
   767  					Services: common.Services{
   768  						{
   769  							Name:       "test-service",
   770  							Entrypoint: []string{"/init", "run"},
   771  							Command:    []string{"application", "--debug"},
   772  						},
   773  					},
   774  				},
   775  				Runner: &common.RunnerConfig{},
   776  			},
   777  			Expected: &executor{
   778  				options: &kubernetesOptions{
   779  					Image: common.Image{
   780  						Name:       "test-image",
   781  						Entrypoint: []string{"/init", "run"},
   782  					},
   783  					Services: common.Services{
   784  						{
   785  							Name:       "test-service",
   786  							Entrypoint: []string{"/init", "run"},
   787  							Command:    []string{"application", "--debug"},
   788  						},
   789  					},
   790  				},
   791  				configurationOverwrites: &overwrites{namespace: "default"},
   792  				serviceLimits:           api.ResourceList{},
   793  				buildLimits:             api.ResourceList{},
   794  				helperLimits:            api.ResourceList{},
   795  				serviceRequests:         api.ResourceList{},
   796  				buildRequests:           api.ResourceList{},
   797  				helperRequests:          api.ResourceList{},
   798  			},
   799  		},
   800  	}
   801  
   802  	for index, test := range tests {
   803  		t.Run(strconv.Itoa(index), func(t *testing.T) {
   804  			e := &executor{
   805  				AbstractExecutor: executors.AbstractExecutor{
   806  					ExecutorOptions: executorOptions,
   807  				},
   808  			}
   809  
   810  			prepareOptions := common.ExecutorPrepareOptions{
   811  				Config:  test.RunnerConfig,
   812  				Build:   test.Build,
   813  				Context: context.TODO(),
   814  			}
   815  
   816  			err := e.Prepare(prepareOptions)
   817  
   818  			if err != nil {
   819  				assert.False(t, test.Build.IsSharedEnv())
   820  				if test.Error {
   821  					assert.Error(t, err)
   822  				} else {
   823  					assert.NoError(t, err)
   824  				}
   825  				if !test.Error {
   826  					t.Errorf("Got error. Expected: %v", test.Expected)
   827  				}
   828  				return
   829  			}
   830  
   831  			// Set this to nil so we aren't testing the functionality of the
   832  			// base AbstractExecutor's Prepare method
   833  			e.AbstractExecutor = executors.AbstractExecutor{}
   834  
   835  			// TODO: Improve this so we don't have to nil-ify the kubeClient.
   836  			// It currently contains some moving parts that are failing, meaning
   837  			// we'll need to mock _something_
   838  			e.kubeClient = nil
   839  			assert.Equal(t, test.Expected, e)
   840  		})
   841  	}
   842  }
   843  
   844  // This test reproduces the bug reported in https://gitlab.com/gitlab-org/gitlab-runner/issues/2583
   845  func TestPrepareIssue2583(t *testing.T) {
   846  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
   847  		return
   848  	}
   849  
   850  	namespace := "my_namespace"
   851  	serviceAccount := "my_account"
   852  
   853  	runnerConfig := &common.RunnerConfig{
   854  		RunnerSettings: common.RunnerSettings{
   855  			Executor: "kubernetes",
   856  			Kubernetes: &common.KubernetesConfig{
   857  				Image:                          "an/image:latest",
   858  				Namespace:                      namespace,
   859  				NamespaceOverwriteAllowed:      ".*",
   860  				ServiceAccount:                 serviceAccount,
   861  				ServiceAccountOverwriteAllowed: ".*",
   862  			},
   863  		},
   864  	}
   865  
   866  	build := &common.Build{
   867  		JobResponse: common.JobResponse{
   868  			Variables: []common.JobVariable{
   869  				{Key: NamespaceOverwriteVariableName, Value: "namespace"},
   870  				{Key: ServiceAccountOverwriteVariableName, Value: "sa"},
   871  			},
   872  		},
   873  		Runner: &common.RunnerConfig{},
   874  	}
   875  
   876  	e := &executor{
   877  		AbstractExecutor: executors.AbstractExecutor{
   878  			ExecutorOptions: executorOptions,
   879  		},
   880  	}
   881  
   882  	prepareOptions := common.ExecutorPrepareOptions{
   883  		Config:  runnerConfig,
   884  		Build:   build,
   885  		Context: context.TODO(),
   886  	}
   887  
   888  	err := e.Prepare(prepareOptions)
   889  	assert.NoError(t, err)
   890  	assert.Equal(t, namespace, runnerConfig.Kubernetes.Namespace)
   891  	assert.Equal(t, serviceAccount, runnerConfig.Kubernetes.ServiceAccount)
   892  }
   893  
   894  func TestSetupCredentials(t *testing.T) {
   895  	version, _ := testVersionAndCodec()
   896  
   897  	type testDef struct {
   898  		RunnerCredentials *common.RunnerCredentials
   899  		Credentials       []common.Credentials
   900  		VerifyFn          func(*testing.T, testDef, *api.Secret)
   901  	}
   902  	tests := map[string]testDef{
   903  		"no credentials": {
   904  			// don't execute VerifyFn
   905  			VerifyFn: nil,
   906  		},
   907  		"registry credentials": {
   908  			Credentials: []common.Credentials{
   909  				{
   910  					Type:     "registry",
   911  					URL:      "http://example.com",
   912  					Username: "user",
   913  					Password: "password",
   914  				},
   915  			},
   916  			VerifyFn: func(t *testing.T, test testDef, secret *api.Secret) {
   917  				assert.Equal(t, api.SecretTypeDockercfg, secret.Type)
   918  				assert.NotEmpty(t, secret.Data[api.DockerConfigKey])
   919  			},
   920  		},
   921  		"other credentials": {
   922  			Credentials: []common.Credentials{
   923  				{
   924  					Type:     "other",
   925  					URL:      "http://example.com",
   926  					Username: "user",
   927  					Password: "password",
   928  				},
   929  			},
   930  			// don't execute VerifyFn
   931  			VerifyFn: nil,
   932  		},
   933  		"non-DNS-1123-compatible-token": {
   934  			RunnerCredentials: &common.RunnerCredentials{
   935  				Token: "ToK3_?OF",
   936  			},
   937  			Credentials: []common.Credentials{
   938  				{
   939  					Type:     "registry",
   940  					URL:      "http://example.com",
   941  					Username: "user",
   942  					Password: "password",
   943  				},
   944  			},
   945  			VerifyFn: func(t *testing.T, test testDef, secret *api.Secret) {
   946  				dns_test.AssertRFC1123Compatibility(t, secret.GetGenerateName())
   947  			},
   948  		},
   949  	}
   950  
   951  	executed := false
   952  	fakeClientRoundTripper := func(test testDef) func(req *http.Request) (*http.Response, error) {
   953  		return func(req *http.Request) (resp *http.Response, err error) {
   954  			podBytes, err := ioutil.ReadAll(req.Body)
   955  			executed = true
   956  
   957  			if err != nil {
   958  				t.Errorf("failed to read request body: %s", err.Error())
   959  				return
   960  			}
   961  
   962  			p := new(api.Secret)
   963  
   964  			err = json.Unmarshal(podBytes, p)
   965  
   966  			if err != nil {
   967  				t.Errorf("error decoding pod: %s", err.Error())
   968  				return
   969  			}
   970  
   971  			if test.VerifyFn != nil {
   972  				test.VerifyFn(t, test, p)
   973  			}
   974  
   975  			resp = &http.Response{StatusCode: http.StatusOK, Body: FakeReadCloser{
   976  				Reader: bytes.NewBuffer(podBytes),
   977  			}}
   978  			resp.Header = make(http.Header)
   979  			resp.Header.Add("Content-Type", "application/json")
   980  
   981  			return
   982  		}
   983  	}
   984  
   985  	for testName, test := range tests {
   986  		t.Run(testName, func(t *testing.T) {
   987  			ex := executor{
   988  				kubeClient: testKubernetesClient(version, fake.CreateHTTPClient(fakeClientRoundTripper(test))),
   989  				options:    &kubernetesOptions{},
   990  				AbstractExecutor: executors.AbstractExecutor{
   991  					Config: common.RunnerConfig{
   992  						RunnerSettings: common.RunnerSettings{
   993  							Kubernetes: &common.KubernetesConfig{
   994  								Namespace: "default",
   995  							},
   996  						},
   997  					},
   998  					BuildShell: &common.ShellConfiguration{},
   999  					Build: &common.Build{
  1000  						JobResponse: common.JobResponse{
  1001  							Variables:   []common.JobVariable{},
  1002  							Credentials: test.Credentials,
  1003  						},
  1004  						Runner: &common.RunnerConfig{},
  1005  					},
  1006  				},
  1007  			}
  1008  
  1009  			if test.RunnerCredentials != nil {
  1010  				ex.Build.Runner = &common.RunnerConfig{
  1011  					RunnerCredentials: *test.RunnerCredentials,
  1012  				}
  1013  			}
  1014  
  1015  			executed = false
  1016  
  1017  			err := ex.prepareOverwrites(make(common.JobVariables, 0))
  1018  			assert.NoError(t, err)
  1019  
  1020  			err = ex.setupCredentials()
  1021  			assert.NoError(t, err)
  1022  
  1023  			if test.VerifyFn != nil {
  1024  				assert.True(t, executed)
  1025  			} else {
  1026  				assert.False(t, executed)
  1027  			}
  1028  		})
  1029  	}
  1030  }
  1031  
  1032  type setupBuildPodTestDef struct {
  1033  	RunnerConfig common.RunnerConfig
  1034  	Variables    []common.JobVariable
  1035  	Options      *kubernetesOptions
  1036  	PrepareFn    func(*testing.T, setupBuildPodTestDef, *executor)
  1037  	VerifyFn     func(*testing.T, setupBuildPodTestDef, *api.Pod)
  1038  }
  1039  
  1040  type setupBuildPodFakeRoundTripper struct {
  1041  	t        *testing.T
  1042  	test     setupBuildPodTestDef
  1043  	executed bool
  1044  }
  1045  
  1046  func (rt *setupBuildPodFakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
  1047  	rt.executed = true
  1048  	podBytes, err := ioutil.ReadAll(req.Body)
  1049  	if !assert.NoError(rt.t, err, "failed to read request body") {
  1050  		return nil, err
  1051  	}
  1052  
  1053  	p := new(api.Pod)
  1054  	err = json.Unmarshal(podBytes, p)
  1055  	if !assert.NoError(rt.t, err, "failed to read request body") {
  1056  		return nil, err
  1057  	}
  1058  
  1059  	rt.test.VerifyFn(rt.t, rt.test, p)
  1060  	resp := &http.Response{
  1061  		StatusCode: http.StatusOK,
  1062  		Body: FakeReadCloser{
  1063  			Reader: bytes.NewBuffer(podBytes),
  1064  		},
  1065  	}
  1066  	resp.Header = make(http.Header)
  1067  	resp.Header.Add("Content-Type", "application/json")
  1068  
  1069  	return resp, nil
  1070  }
  1071  
  1072  func TestSetupBuildPod(t *testing.T) {
  1073  	version, _ := testVersionAndCodec()
  1074  
  1075  	tests := map[string]setupBuildPodTestDef{
  1076  		"passes node selector setting": {
  1077  			RunnerConfig: common.RunnerConfig{
  1078  				RunnerSettings: common.RunnerSettings{
  1079  					Kubernetes: &common.KubernetesConfig{
  1080  						Namespace: "default",
  1081  						NodeSelector: map[string]string{
  1082  							"a-selector":       "first",
  1083  							"another-selector": "second",
  1084  						},
  1085  					},
  1086  				},
  1087  			},
  1088  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1089  				assert.Equal(t, test.RunnerConfig.RunnerSettings.Kubernetes.NodeSelector, pod.Spec.NodeSelector)
  1090  			},
  1091  		},
  1092  		"uses configured credentials": {
  1093  			RunnerConfig: common.RunnerConfig{
  1094  				RunnerSettings: common.RunnerSettings{
  1095  					Kubernetes: &common.KubernetesConfig{
  1096  						Namespace: "default",
  1097  					},
  1098  				},
  1099  			},
  1100  			PrepareFn: func(t *testing.T, test setupBuildPodTestDef, e *executor) {
  1101  				e.credentials = &api.Secret{
  1102  					ObjectMeta: metav1.ObjectMeta{
  1103  						Name: "job-credentials",
  1104  					},
  1105  				}
  1106  			},
  1107  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1108  				secrets := []api.LocalObjectReference{{Name: "job-credentials"}}
  1109  				assert.Equal(t, secrets, pod.Spec.ImagePullSecrets)
  1110  			},
  1111  		},
  1112  		"uses configured image pull secrets": {
  1113  			RunnerConfig: common.RunnerConfig{
  1114  				RunnerSettings: common.RunnerSettings{
  1115  					Kubernetes: &common.KubernetesConfig{
  1116  						Namespace: "default",
  1117  						ImagePullSecrets: []string{
  1118  							"docker-registry-credentials",
  1119  						},
  1120  					},
  1121  				},
  1122  			},
  1123  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1124  				secrets := []api.LocalObjectReference{{Name: "docker-registry-credentials"}}
  1125  				assert.Equal(t, secrets, pod.Spec.ImagePullSecrets)
  1126  			},
  1127  		},
  1128  		"configures helper container": {
  1129  			RunnerConfig: common.RunnerConfig{
  1130  				RunnerSettings: common.RunnerSettings{
  1131  					Kubernetes: &common.KubernetesConfig{
  1132  						Namespace: "default",
  1133  					},
  1134  				},
  1135  			},
  1136  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1137  				hasHelper := false
  1138  				for _, c := range pod.Spec.Containers {
  1139  					if c.Name == "helper" {
  1140  						hasHelper = true
  1141  					}
  1142  				}
  1143  				assert.True(t, hasHelper)
  1144  			},
  1145  		},
  1146  		"uses configured helper image": {
  1147  			RunnerConfig: common.RunnerConfig{
  1148  				RunnerSettings: common.RunnerSettings{
  1149  					Kubernetes: &common.KubernetesConfig{
  1150  						Namespace:   "default",
  1151  						HelperImage: "custom/helper-image",
  1152  					},
  1153  				},
  1154  			},
  1155  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1156  				for _, c := range pod.Spec.Containers {
  1157  					if c.Name == "helper" {
  1158  						assert.Equal(t, test.RunnerConfig.RunnerSettings.Kubernetes.HelperImage, c.Image)
  1159  					}
  1160  				}
  1161  			},
  1162  		},
  1163  		"expands variables for pod labels": {
  1164  			RunnerConfig: common.RunnerConfig{
  1165  				RunnerSettings: common.RunnerSettings{
  1166  					Kubernetes: &common.KubernetesConfig{
  1167  						Namespace: "default",
  1168  						PodLabels: map[string]string{
  1169  							"test":    "label",
  1170  							"another": "label",
  1171  							"var":     "$test",
  1172  						},
  1173  					},
  1174  				},
  1175  			},
  1176  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1177  				assert.Equal(t, map[string]string{
  1178  					"test":    "label",
  1179  					"another": "label",
  1180  					"var":     "sometestvar",
  1181  				}, pod.ObjectMeta.Labels)
  1182  			},
  1183  			Variables: []common.JobVariable{
  1184  				{Key: "test", Value: "sometestvar"},
  1185  			},
  1186  		},
  1187  		"expands variables for pod annotations": {
  1188  			RunnerConfig: common.RunnerConfig{
  1189  				RunnerSettings: common.RunnerSettings{
  1190  					Kubernetes: &common.KubernetesConfig{
  1191  						Namespace: "default",
  1192  						PodAnnotations: map[string]string{
  1193  							"test":    "annotation",
  1194  							"another": "annotation",
  1195  							"var":     "$test",
  1196  						},
  1197  					},
  1198  				},
  1199  			},
  1200  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1201  				assert.Equal(t, map[string]string{
  1202  					"test":    "annotation",
  1203  					"another": "annotation",
  1204  					"var":     "sometestvar",
  1205  				}, pod.ObjectMeta.Annotations)
  1206  			},
  1207  			Variables: []common.JobVariable{
  1208  				{Key: "test", Value: "sometestvar"},
  1209  			},
  1210  		},
  1211  		"expands variables for helper image": {
  1212  			RunnerConfig: common.RunnerConfig{
  1213  				RunnerSettings: common.RunnerSettings{
  1214  					Kubernetes: &common.KubernetesConfig{
  1215  						Namespace:   "default",
  1216  						HelperImage: "custom/helper-image:${CI_RUNNER_REVISION}",
  1217  					},
  1218  				},
  1219  			},
  1220  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1221  				for _, c := range pod.Spec.Containers {
  1222  					if c.Name == "helper" {
  1223  						assert.Equal(t, "custom/helper-image:HEAD", c.Image)
  1224  					}
  1225  				}
  1226  			},
  1227  		},
  1228  		"support setting kubernetes pod taint tolerations": {
  1229  			RunnerConfig: common.RunnerConfig{
  1230  				RunnerSettings: common.RunnerSettings{
  1231  					Kubernetes: &common.KubernetesConfig{
  1232  						Namespace: "default",
  1233  						NodeTolerations: map[string]string{
  1234  							"node-role.kubernetes.io/master": "NoSchedule",
  1235  							"custom.toleration=value":        "NoSchedule",
  1236  							"empty.value=":                   "PreferNoSchedule",
  1237  							"onlyKey":                        "",
  1238  						},
  1239  					},
  1240  				},
  1241  			},
  1242  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1243  				expectedTolerations := []api.Toleration{
  1244  					{
  1245  						Key:      "node-role.kubernetes.io/master",
  1246  						Operator: api.TolerationOpExists,
  1247  						Effect:   api.TaintEffectNoSchedule,
  1248  					},
  1249  					{
  1250  						Key:      "custom.toleration",
  1251  						Operator: api.TolerationOpEqual,
  1252  						Value:    "value",
  1253  						Effect:   api.TaintEffectNoSchedule,
  1254  					},
  1255  					{
  1256  
  1257  						Key:      "empty.value",
  1258  						Operator: api.TolerationOpEqual,
  1259  						Value:    "",
  1260  						Effect:   api.TaintEffectPreferNoSchedule,
  1261  					},
  1262  					{
  1263  						Key:      "onlyKey",
  1264  						Operator: api.TolerationOpExists,
  1265  						Effect:   "",
  1266  					},
  1267  				}
  1268  				assert.ElementsMatch(t, expectedTolerations, pod.Spec.Tolerations)
  1269  			},
  1270  		},
  1271  		"supports extended docker configuration for image and services": {
  1272  			RunnerConfig: common.RunnerConfig{
  1273  				RunnerSettings: common.RunnerSettings{
  1274  					Kubernetes: &common.KubernetesConfig{
  1275  						Namespace:   "default",
  1276  						HelperImage: "custom/helper-image",
  1277  					},
  1278  				},
  1279  			},
  1280  			Options: &kubernetesOptions{
  1281  				Image: common.Image{
  1282  					Name:       "test-image",
  1283  					Entrypoint: []string{"/init", "run"},
  1284  				},
  1285  				Services: common.Services{
  1286  					{
  1287  						Name:       "test-service",
  1288  						Entrypoint: []string{"/init", "run"},
  1289  						Command:    []string{"application", "--debug"},
  1290  					},
  1291  					{
  1292  						Name:    "test-service-2",
  1293  						Command: []string{"application", "--debug"},
  1294  					},
  1295  				},
  1296  			},
  1297  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1298  				require.Len(t, pod.Spec.Containers, 4)
  1299  
  1300  				assert.Equal(t, "build", pod.Spec.Containers[0].Name)
  1301  				assert.Equal(t, "test-image", pod.Spec.Containers[0].Image)
  1302  				assert.Equal(t, []string{"/init", "run"}, pod.Spec.Containers[0].Command)
  1303  				assert.Empty(t, pod.Spec.Containers[0].Args, "Build container args should be empty")
  1304  
  1305  				assert.Equal(t, "helper", pod.Spec.Containers[1].Name)
  1306  				assert.Equal(t, "custom/helper-image", pod.Spec.Containers[1].Image)
  1307  				assert.Empty(t, pod.Spec.Containers[1].Command, "Helper container command should be empty")
  1308  				assert.Empty(t, pod.Spec.Containers[1].Args, "Helper container args should be empty")
  1309  
  1310  				assert.Equal(t, "svc-0", pod.Spec.Containers[2].Name)
  1311  				assert.Equal(t, "test-service", pod.Spec.Containers[2].Image)
  1312  				assert.Equal(t, []string{"/init", "run"}, pod.Spec.Containers[2].Command)
  1313  				assert.Equal(t, []string{"application", "--debug"}, pod.Spec.Containers[2].Args)
  1314  
  1315  				assert.Equal(t, "svc-1", pod.Spec.Containers[3].Name)
  1316  				assert.Equal(t, "test-service-2", pod.Spec.Containers[3].Image)
  1317  				assert.Empty(t, pod.Spec.Containers[3].Command, "Service container command should be empty")
  1318  				assert.Equal(t, []string{"application", "--debug"}, pod.Spec.Containers[3].Args)
  1319  			},
  1320  		},
  1321  		// TODO: Remove the mention of Feature Flag in 12.0, make it the only proper test case.
  1322  		"properly sets command (entrypoint) and args when FF_K8S_USE_ENTRYPOINT_OVER_COMMAND is on": {
  1323  			RunnerConfig: common.RunnerConfig{
  1324  				RunnerSettings: common.RunnerSettings{
  1325  					Kubernetes: &common.KubernetesConfig{
  1326  						Namespace:   "default",
  1327  						HelperImage: "custom/helper-image",
  1328  					},
  1329  				},
  1330  			},
  1331  			Variables: []common.JobVariable{
  1332  				{Key: featureflags.K8sEntrypointOverCommand, Value: "true"},
  1333  			},
  1334  			Options: &kubernetesOptions{
  1335  				Image: common.Image{
  1336  					Name: "test-image",
  1337  				},
  1338  				Services: common.Services{
  1339  					{
  1340  						Name:    "test-service-0",
  1341  						Command: []string{"application", "--debug"},
  1342  					},
  1343  					{
  1344  						Name:       "test-service-1",
  1345  						Entrypoint: []string{"application", "--debug"},
  1346  					},
  1347  					{
  1348  						Name:       "test-service-2",
  1349  						Entrypoint: []string{"application", "--debug"},
  1350  						Command:    []string{"argument1", "argument2"},
  1351  					},
  1352  				},
  1353  			},
  1354  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1355  				require.Len(t, pod.Spec.Containers, 5)
  1356  
  1357  				assert.Equal(t, "build", pod.Spec.Containers[0].Name)
  1358  				assert.Equal(t, "test-image", pod.Spec.Containers[0].Image)
  1359  				assert.Empty(t, pod.Spec.Containers[0].Command, "Build container command should be empty")
  1360  				assert.Empty(t, pod.Spec.Containers[0].Args, "Build container args should be empty")
  1361  
  1362  				assert.Equal(t, "helper", pod.Spec.Containers[1].Name)
  1363  				assert.Equal(t, "custom/helper-image", pod.Spec.Containers[1].Image)
  1364  				assert.Empty(t, pod.Spec.Containers[1].Command, "Helper container command should be empty")
  1365  				assert.Empty(t, pod.Spec.Containers[1].Args, "Helper container args should be empty")
  1366  
  1367  				assert.Equal(t, "svc-0", pod.Spec.Containers[2].Name)
  1368  				assert.Equal(t, "test-service-0", pod.Spec.Containers[2].Image)
  1369  				assert.Empty(t, pod.Spec.Containers[2].Command, "Service container command should be empty")
  1370  				assert.Equal(t, []string{"application", "--debug"}, pod.Spec.Containers[2].Args)
  1371  
  1372  				assert.Equal(t, "svc-1", pod.Spec.Containers[3].Name)
  1373  				assert.Equal(t, "test-service-1", pod.Spec.Containers[3].Image)
  1374  				assert.Equal(t, []string{"application", "--debug"}, pod.Spec.Containers[3].Command)
  1375  				assert.Empty(t, pod.Spec.Containers[3].Args, "Service container args should be empty")
  1376  
  1377  				assert.Equal(t, "svc-2", pod.Spec.Containers[4].Name)
  1378  				assert.Equal(t, "test-service-2", pod.Spec.Containers[4].Image)
  1379  				assert.Equal(t, []string{"application", "--debug"}, pod.Spec.Containers[4].Command)
  1380  				assert.Equal(t, []string{"argument1", "argument2"}, pod.Spec.Containers[4].Args)
  1381  			},
  1382  		},
  1383  		// TODO: Remove in 12.0
  1384  		"sets command (entrypoint) and args in old way when FF_K8S_USE_ENTRYPOINT_OVER_COMMAND is off": {
  1385  			RunnerConfig: common.RunnerConfig{
  1386  				RunnerSettings: common.RunnerSettings{
  1387  					Kubernetes: &common.KubernetesConfig{
  1388  						Namespace:   "default",
  1389  						HelperImage: "custom/helper-image",
  1390  					},
  1391  				},
  1392  			},
  1393  			Variables: []common.JobVariable{
  1394  				{Key: featureflags.K8sEntrypointOverCommand, Value: "false"},
  1395  			},
  1396  			Options: &kubernetesOptions{
  1397  				Image: common.Image{
  1398  					Name: "test-image",
  1399  				},
  1400  				Services: common.Services{
  1401  					{
  1402  						Name:    "test-service-0",
  1403  						Command: []string{"application", "--debug"},
  1404  					},
  1405  					{
  1406  						Name:       "test-service-1",
  1407  						Entrypoint: []string{"application", "--debug"},
  1408  					},
  1409  					{
  1410  						Name:       "test-service-2",
  1411  						Entrypoint: []string{"application", "--debug"},
  1412  						Command:    []string{"argument1", "argument2"},
  1413  					},
  1414  				},
  1415  			},
  1416  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1417  				require.Len(t, pod.Spec.Containers, 5)
  1418  
  1419  				assert.Equal(t, "build", pod.Spec.Containers[0].Name)
  1420  				assert.Equal(t, "test-image", pod.Spec.Containers[0].Image)
  1421  				assert.Empty(t, pod.Spec.Containers[0].Command, "Build container command should be empty")
  1422  				assert.Empty(t, pod.Spec.Containers[0].Args, "Build container args should be empty")
  1423  
  1424  				assert.Equal(t, "helper", pod.Spec.Containers[1].Name)
  1425  				assert.Equal(t, "custom/helper-image", pod.Spec.Containers[1].Image)
  1426  				assert.Empty(t, pod.Spec.Containers[1].Command, "Helper container command should be empty")
  1427  				assert.Empty(t, pod.Spec.Containers[1].Args, "Helper container args should be empty")
  1428  
  1429  				assert.Equal(t, "svc-0", pod.Spec.Containers[2].Name)
  1430  				assert.Equal(t, "test-service-0", pod.Spec.Containers[2].Image)
  1431  				assert.Equal(t, []string{"application", "--debug"}, pod.Spec.Containers[2].Command)
  1432  				assert.Empty(t, pod.Spec.Containers[2].Args, "Service container command should be empty")
  1433  
  1434  				assert.Equal(t, "svc-1", pod.Spec.Containers[3].Name)
  1435  				assert.Equal(t, "test-service-1", pod.Spec.Containers[3].Image)
  1436  				assert.Equal(t, []string{"application", "--debug"}, pod.Spec.Containers[3].Command)
  1437  				assert.Empty(t, pod.Spec.Containers[3].Args, "Service container args should be empty")
  1438  
  1439  				assert.Equal(t, "svc-2", pod.Spec.Containers[4].Name)
  1440  				assert.Equal(t, "test-service-2", pod.Spec.Containers[4].Image)
  1441  				assert.Equal(t, []string{"application", "--debug"}, pod.Spec.Containers[4].Command)
  1442  				assert.Equal(t, []string{"argument1", "argument2"}, pod.Spec.Containers[4].Args)
  1443  			},
  1444  		},
  1445  		"non-DNS-1123-compatible-token": {
  1446  			RunnerConfig: common.RunnerConfig{
  1447  				RunnerCredentials: common.RunnerCredentials{
  1448  					Token: "ToK3_?OF",
  1449  				},
  1450  				RunnerSettings: common.RunnerSettings{
  1451  					Kubernetes: &common.KubernetesConfig{
  1452  						Namespace: "default",
  1453  					},
  1454  				},
  1455  			},
  1456  			VerifyFn: func(t *testing.T, test setupBuildPodTestDef, pod *api.Pod) {
  1457  				dns_test.AssertRFC1123Compatibility(t, pod.GetGenerateName())
  1458  			},
  1459  		},
  1460  	}
  1461  
  1462  	for testName, test := range tests {
  1463  		t.Run(testName, func(t *testing.T) {
  1464  			helperImageInfo, err := helperimage.Get(common.REVISION, helperimage.Config{
  1465  				OSType:       helperimage.OSTypeLinux,
  1466  				Architecture: "amd64",
  1467  			})
  1468  			require.NoError(t, err)
  1469  
  1470  			vars := test.Variables
  1471  			if vars == nil {
  1472  				vars = []common.JobVariable{}
  1473  			}
  1474  
  1475  			options := test.Options
  1476  			if options == nil {
  1477  				options = &kubernetesOptions{}
  1478  			}
  1479  
  1480  			rt := setupBuildPodFakeRoundTripper{
  1481  				t:    t,
  1482  				test: test,
  1483  			}
  1484  
  1485  			ex := executor{
  1486  				kubeClient: testKubernetesClient(version, fake.CreateHTTPClient(rt.RoundTrip)),
  1487  				options:    options,
  1488  				AbstractExecutor: executors.AbstractExecutor{
  1489  					Config:     test.RunnerConfig,
  1490  					BuildShell: &common.ShellConfiguration{},
  1491  					Build: &common.Build{
  1492  						JobResponse: common.JobResponse{
  1493  							Variables: vars,
  1494  						},
  1495  						Runner: &test.RunnerConfig,
  1496  					},
  1497  				},
  1498  				helperImageInfo: helperImageInfo,
  1499  			}
  1500  
  1501  			if test.PrepareFn != nil {
  1502  				test.PrepareFn(t, test, &ex)
  1503  			}
  1504  
  1505  			err = ex.prepareOverwrites(make(common.JobVariables, 0))
  1506  			assert.NoError(t, err, "error preparing overwrites")
  1507  
  1508  			err = ex.setupBuildPod()
  1509  			assert.NoError(t, err, "error setting up build pod")
  1510  
  1511  			assert.True(t, rt.executed, "RoundTrip for kubernetes client should be executed")
  1512  		})
  1513  	}
  1514  }
  1515  
  1516  func TestKubernetesSuccessRun(t *testing.T) {
  1517  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1518  		return
  1519  	}
  1520  
  1521  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
  1522  	assert.NoError(t, err)
  1523  	successfulBuild.Image.Name = common.TestDockerGitImage
  1524  	build := &common.Build{
  1525  		JobResponse: successfulBuild,
  1526  		Runner: &common.RunnerConfig{
  1527  			RunnerSettings: common.RunnerSettings{
  1528  				Executor: "kubernetes",
  1529  				Kubernetes: &common.KubernetesConfig{
  1530  					PullPolicy: common.PullPolicyIfNotPresent,
  1531  				},
  1532  			},
  1533  		},
  1534  	}
  1535  
  1536  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1537  	assert.NoError(t, err)
  1538  }
  1539  
  1540  func TestKubernetesNoRootImage(t *testing.T) {
  1541  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1542  		return
  1543  	}
  1544  
  1545  	successfulBuild, err := common.GetRemoteSuccessfulBuildWithDumpedVariables()
  1546  
  1547  	assert.NoError(t, err)
  1548  	successfulBuild.Image.Name = common.TestAlpineNoRootImage
  1549  	build := &common.Build{
  1550  		JobResponse: successfulBuild,
  1551  		Runner: &common.RunnerConfig{
  1552  			RunnerSettings: common.RunnerSettings{
  1553  				Executor: "kubernetes",
  1554  				Kubernetes: &common.KubernetesConfig{
  1555  					Image:      common.TestAlpineImage,
  1556  					PullPolicy: common.PullPolicyIfNotPresent,
  1557  				},
  1558  			},
  1559  		},
  1560  	}
  1561  
  1562  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1563  	assert.NoError(t, err)
  1564  }
  1565  
  1566  func TestKubernetesCustomClonePath(t *testing.T) {
  1567  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1568  		return
  1569  	}
  1570  
  1571  	jobResponse, err := common.GetRemoteBuildResponse(
  1572  		"ls -al $CI_BUILDS_DIR/go/src/gitlab.com/gitlab-org/repo")
  1573  	require.NoError(t, err)
  1574  
  1575  	tests := map[string]struct {
  1576  		clonePath         string
  1577  		expectedErrorType interface{}
  1578  	}{
  1579  		"uses custom clone path": {
  1580  			clonePath:         "$CI_BUILDS_DIR/go/src/gitlab.com/gitlab-org/repo",
  1581  			expectedErrorType: nil,
  1582  		},
  1583  		"path has to be within CI_BUILDS_DIR": {
  1584  			clonePath:         "/unknown/go/src/gitlab.com/gitlab-org/repo",
  1585  			expectedErrorType: &common.BuildError{},
  1586  		},
  1587  	}
  1588  
  1589  	for name, test := range tests {
  1590  		t.Run(name, func(t *testing.T) {
  1591  			build := &common.Build{
  1592  				JobResponse: jobResponse,
  1593  				Runner: &common.RunnerConfig{
  1594  					RunnerSettings: common.RunnerSettings{
  1595  						Executor: "kubernetes",
  1596  						Kubernetes: &common.KubernetesConfig{
  1597  							Image:      common.TestAlpineImage,
  1598  							PullPolicy: common.PullPolicyIfNotPresent,
  1599  						},
  1600  						Environment: []string{
  1601  							"GIT_CLONE_PATH=" + test.clonePath,
  1602  						},
  1603  					},
  1604  				},
  1605  			}
  1606  
  1607  			err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1608  			assert.IsType(t, test.expectedErrorType, err)
  1609  		})
  1610  	}
  1611  }
  1612  func TestKubernetesBuildFail(t *testing.T) {
  1613  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1614  		return
  1615  	}
  1616  
  1617  	failedBuild, err := common.GetRemoteFailedBuild()
  1618  	assert.NoError(t, err)
  1619  	build := &common.Build{
  1620  		JobResponse: failedBuild,
  1621  		Runner: &common.RunnerConfig{
  1622  			RunnerSettings: common.RunnerSettings{
  1623  				Executor: "kubernetes",
  1624  				Kubernetes: &common.KubernetesConfig{
  1625  					PullPolicy: common.PullPolicyIfNotPresent,
  1626  				},
  1627  			},
  1628  		},
  1629  	}
  1630  	build.Image.Name = common.TestDockerGitImage
  1631  
  1632  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1633  	require.Error(t, err, "error")
  1634  	assert.IsType(t, err, &common.BuildError{})
  1635  	assert.Contains(t, err.Error(), "command terminated with exit code")
  1636  }
  1637  
  1638  func TestKubernetesMissingImage(t *testing.T) {
  1639  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1640  		return
  1641  	}
  1642  
  1643  	failedBuild, err := common.GetRemoteFailedBuild()
  1644  	assert.NoError(t, err)
  1645  	build := &common.Build{
  1646  		JobResponse: failedBuild,
  1647  		Runner: &common.RunnerConfig{
  1648  			RunnerSettings: common.RunnerSettings{
  1649  				Executor:   "kubernetes",
  1650  				Kubernetes: &common.KubernetesConfig{},
  1651  			},
  1652  		},
  1653  	}
  1654  	build.Image.Name = "some/non-existing/image"
  1655  
  1656  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1657  	require.Error(t, err)
  1658  	assert.IsType(t, err, &common.BuildError{})
  1659  	assert.Contains(t, err.Error(), "image pull failed")
  1660  }
  1661  
  1662  func TestKubernetesMissingTag(t *testing.T) {
  1663  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1664  		return
  1665  	}
  1666  
  1667  	failedBuild, err := common.GetRemoteFailedBuild()
  1668  	assert.NoError(t, err)
  1669  	build := &common.Build{
  1670  		JobResponse: failedBuild,
  1671  		Runner: &common.RunnerConfig{
  1672  			RunnerSettings: common.RunnerSettings{
  1673  				Executor:   "kubernetes",
  1674  				Kubernetes: &common.KubernetesConfig{},
  1675  			},
  1676  		},
  1677  	}
  1678  	build.Image.Name = "docker:missing-tag"
  1679  
  1680  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1681  	require.Error(t, err)
  1682  	assert.IsType(t, err, &common.BuildError{})
  1683  	assert.Contains(t, err.Error(), "image pull failed")
  1684  }
  1685  
  1686  func TestKubernetesBuildAbort(t *testing.T) {
  1687  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1688  		return
  1689  	}
  1690  
  1691  	failedBuild, err := common.GetRemoteFailedBuild()
  1692  	assert.NoError(t, err)
  1693  	build := &common.Build{
  1694  		JobResponse: failedBuild,
  1695  		Runner: &common.RunnerConfig{
  1696  			RunnerSettings: common.RunnerSettings{
  1697  				Executor: "kubernetes",
  1698  				Kubernetes: &common.KubernetesConfig{
  1699  					PullPolicy: common.PullPolicyIfNotPresent,
  1700  				},
  1701  			},
  1702  		},
  1703  		SystemInterrupt: make(chan os.Signal, 1),
  1704  	}
  1705  	build.Image.Name = common.TestDockerGitImage
  1706  
  1707  	abortTimer := time.AfterFunc(time.Second, func() {
  1708  		t.Log("Interrupt")
  1709  		build.SystemInterrupt <- os.Interrupt
  1710  	})
  1711  	defer abortTimer.Stop()
  1712  
  1713  	timeoutTimer := time.AfterFunc(time.Minute, func() {
  1714  		t.Log("Timedout")
  1715  		t.FailNow()
  1716  	})
  1717  	defer timeoutTimer.Stop()
  1718  
  1719  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1720  	assert.EqualError(t, err, "aborted: interrupt")
  1721  }
  1722  
  1723  func TestKubernetesBuildCancel(t *testing.T) {
  1724  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1725  		return
  1726  	}
  1727  
  1728  	failedBuild, err := common.GetRemoteFailedBuild()
  1729  	assert.NoError(t, err)
  1730  	build := &common.Build{
  1731  		JobResponse: failedBuild,
  1732  		Runner: &common.RunnerConfig{
  1733  			RunnerSettings: common.RunnerSettings{
  1734  				Executor: "kubernetes",
  1735  				Kubernetes: &common.KubernetesConfig{
  1736  					PullPolicy: common.PullPolicyIfNotPresent,
  1737  				},
  1738  			},
  1739  		},
  1740  		SystemInterrupt: make(chan os.Signal, 1),
  1741  	}
  1742  	build.Image.Name = common.TestDockerGitImage
  1743  
  1744  	trace := &common.Trace{Writer: os.Stdout}
  1745  
  1746  	abortTimer := time.AfterFunc(time.Second, func() {
  1747  		t.Log("Interrupt")
  1748  		trace.CancelFunc()
  1749  	})
  1750  	defer abortTimer.Stop()
  1751  
  1752  	timeoutTimer := time.AfterFunc(time.Minute, func() {
  1753  		t.Log("Timedout")
  1754  		t.FailNow()
  1755  	})
  1756  	defer timeoutTimer.Stop()
  1757  
  1758  	err = build.Run(&common.Config{}, trace)
  1759  	assert.IsType(t, err, &common.BuildError{})
  1760  	assert.EqualError(t, err, "canceled")
  1761  }
  1762  
  1763  func TestOverwriteNamespaceNotMatch(t *testing.T) {
  1764  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1765  		return
  1766  	}
  1767  
  1768  	build := &common.Build{
  1769  		JobResponse: common.JobResponse{
  1770  			GitInfo: common.GitInfo{
  1771  				Sha: "1234567890",
  1772  			},
  1773  			Image: common.Image{
  1774  				Name: "test-image",
  1775  			},
  1776  			Variables: []common.JobVariable{
  1777  				{Key: NamespaceOverwriteVariableName, Value: "namespace"},
  1778  			},
  1779  		},
  1780  		Runner: &common.RunnerConfig{
  1781  			RunnerSettings: common.RunnerSettings{
  1782  				Executor: "kubernetes",
  1783  				Kubernetes: &common.KubernetesConfig{
  1784  					NamespaceOverwriteAllowed: "^not_a_match$",
  1785  					PullPolicy:                common.PullPolicyIfNotPresent,
  1786  				},
  1787  			},
  1788  		},
  1789  		SystemInterrupt: make(chan os.Signal, 1),
  1790  	}
  1791  	build.Image.Name = common.TestDockerGitImage
  1792  
  1793  	err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1794  	require.Error(t, err)
  1795  	assert.Contains(t, err.Error(), "does not match")
  1796  }
  1797  
  1798  func TestOverwriteServiceAccountNotMatch(t *testing.T) {
  1799  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1800  		return
  1801  	}
  1802  
  1803  	build := &common.Build{
  1804  		JobResponse: common.JobResponse{
  1805  			GitInfo: common.GitInfo{
  1806  				Sha: "1234567890",
  1807  			},
  1808  			Image: common.Image{
  1809  				Name: "test-image",
  1810  			},
  1811  			Variables: []common.JobVariable{
  1812  				{Key: ServiceAccountOverwriteVariableName, Value: "service-account"},
  1813  			},
  1814  		},
  1815  		Runner: &common.RunnerConfig{
  1816  			RunnerSettings: common.RunnerSettings{
  1817  				Executor: "kubernetes",
  1818  				Kubernetes: &common.KubernetesConfig{
  1819  					ServiceAccountOverwriteAllowed: "^not_a_match$",
  1820  					PullPolicy:                     common.PullPolicyIfNotPresent,
  1821  				},
  1822  			},
  1823  		},
  1824  		SystemInterrupt: make(chan os.Signal, 1),
  1825  	}
  1826  	build.Image.Name = common.TestDockerGitImage
  1827  
  1828  	err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1829  	require.Error(t, err)
  1830  	assert.Contains(t, err.Error(), "does not match")
  1831  }
  1832  
  1833  func TestInteractiveTerminal(t *testing.T) {
  1834  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1835  		return
  1836  	}
  1837  
  1838  	client, err := getKubeClient(&common.KubernetesConfig{}, &overwrites{})
  1839  	require.NoError(t, err)
  1840  	secrets, err := client.CoreV1().Secrets("default").List(metav1.ListOptions{})
  1841  	require.NoError(t, err)
  1842  
  1843  	successfulBuild, err := common.GetRemoteBuildResponse("sleep 5")
  1844  	require.NoError(t, err)
  1845  	successfulBuild.Image.Name = "docker:git"
  1846  	build := &common.Build{
  1847  		JobResponse: successfulBuild,
  1848  		Runner: &common.RunnerConfig{
  1849  			RunnerSettings: common.RunnerSettings{
  1850  				Executor: "kubernetes",
  1851  				Kubernetes: &common.KubernetesConfig{
  1852  					BearerToken: string(secrets.Items[0].Data["token"]),
  1853  				},
  1854  			},
  1855  		},
  1856  	}
  1857  
  1858  	sess, err := session.NewSession(nil)
  1859  	build.Session = sess
  1860  
  1861  	outBuffer := bytes.NewBuffer(nil)
  1862  	outCh := make(chan string)
  1863  
  1864  	go func() {
  1865  		err = build.Run(
  1866  			&common.Config{
  1867  				SessionServer: common.SessionServer{
  1868  					SessionTimeout: 2,
  1869  				},
  1870  			},
  1871  			&common.Trace{Writer: outBuffer},
  1872  		)
  1873  		require.NoError(t, err)
  1874  
  1875  		outCh <- outBuffer.String()
  1876  	}()
  1877  
  1878  	for build.Session.Mux() == nil {
  1879  		time.Sleep(10 * time.Millisecond)
  1880  	}
  1881  
  1882  	time.Sleep(5 * time.Second)
  1883  
  1884  	srv := httptest.NewServer(build.Session.Mux())
  1885  	defer srv.Close()
  1886  
  1887  	u := url.URL{
  1888  		Scheme: "ws",
  1889  		Host:   srv.Listener.Addr().String(),
  1890  		Path:   build.Session.Endpoint + "/exec",
  1891  	}
  1892  	headers := http.Header{
  1893  		"Authorization": []string{build.Session.Token},
  1894  	}
  1895  	conn, resp, err := websocket.DefaultDialer.Dial(u.String(), headers)
  1896  	defer func() {
  1897  		if conn != nil {
  1898  			_ = conn.Close()
  1899  		}
  1900  	}()
  1901  	require.NoError(t, err)
  1902  	assert.Equal(t, resp.StatusCode, http.StatusSwitchingProtocols)
  1903  
  1904  	out := <-outCh
  1905  	t.Log(out)
  1906  
  1907  	assert.Contains(t, out, "Terminal is connected, will time out in 2s...")
  1908  }
  1909  
  1910  type FakeReadCloser struct {
  1911  	io.Reader
  1912  }
  1913  
  1914  func (f FakeReadCloser) Close() error {
  1915  	return nil
  1916  }
  1917  
  1918  type FakeBuildTrace struct {
  1919  	testWriter
  1920  }
  1921  
  1922  func (f FakeBuildTrace) Success()                                              {}
  1923  func (f FakeBuildTrace) Fail(err error, failureReason common.JobFailureReason) {}
  1924  func (f FakeBuildTrace) Notify(func())                                         {}
  1925  func (f FakeBuildTrace) SetCancelFunc(cancelFunc context.CancelFunc)           {}
  1926  func (f FakeBuildTrace) SetFailuresCollector(fc common.FailuresCollector)      {}
  1927  func (f FakeBuildTrace) SetMasked(masked []string)                             {}
  1928  func (f FakeBuildTrace) IsStdout() bool {
  1929  	return false
  1930  }