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