github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/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  	"os"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/Sirupsen/logrus"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  
    21  	"k8s.io/kubernetes/pkg/api"
    22  	"k8s.io/kubernetes/pkg/api/resource"
    23  	"k8s.io/kubernetes/pkg/api/testapi"
    24  	"k8s.io/kubernetes/pkg/api/unversioned"
    25  	"k8s.io/kubernetes/pkg/client/restclient"
    26  	client "k8s.io/kubernetes/pkg/client/unversioned"
    27  	"k8s.io/kubernetes/pkg/client/unversioned/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  )
    33  
    34  var (
    35  	TRUE = true
    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 := testapi.Default.Codec()
   279  
   280  	body := objBody(codec, &unversioned.Status{Code: int32(status)})
   281  	return &http.Response{StatusCode: status, Body: body, Header: map[string][]string{
   282  		"Content-Type": []string{"application/json"},
   283  	}}
   284  }
   285  
   286  func TestCleanup(t *testing.T) {
   287  	version := testapi.Default.GroupVersion().Version
   288  	codec := testapi.Default.Codec()
   289  
   290  	objectMeta := api.ObjectMeta{Name: "test-resource", Namespace: "test-ns"}
   291  
   292  	tests := []struct {
   293  		Name        string
   294  		Pod         *api.Pod
   295  		Credentials *api.Secret
   296  		ClientFunc  func(*http.Request) (*http.Response, error)
   297  		Error       bool
   298  	}{
   299  		{
   300  			Name: "Proper Cleanup",
   301  			Pod:  &api.Pod{ObjectMeta: objectMeta},
   302  			ClientFunc: func(req *http.Request) (*http.Response, error) {
   303  				switch p, m := req.URL.Path, req.Method; {
   304  				case m == "DELETE" && p == "/api/"+version+"/namespaces/test-ns/pods/test-resource":
   305  					return fakeKubeDeleteResponse(http.StatusOK), nil
   306  				default:
   307  					return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p)
   308  				}
   309  			},
   310  		},
   311  		{
   312  			Name: "Delete failure",
   313  			Pod:  &api.Pod{ObjectMeta: objectMeta},
   314  			ClientFunc: func(req *http.Request) (*http.Response, error) {
   315  				return nil, fmt.Errorf("delete failed")
   316  			},
   317  			Error: true,
   318  		},
   319  		{
   320  			Name: "POD already deleted",
   321  			Pod:  &api.Pod{ObjectMeta: objectMeta},
   322  			ClientFunc: func(req *http.Request) (*http.Response, error) {
   323  				switch p, m := req.URL.Path, req.Method; {
   324  				case m == "DELETE" && p == "/api/"+version+"/namespaces/test-ns/pods/test-resource":
   325  					return fakeKubeDeleteResponse(http.StatusNotFound), nil
   326  				default:
   327  					return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p)
   328  				}
   329  			},
   330  			Error: true,
   331  		},
   332  		{
   333  			Name:        "POD creation failed, Secretes provided",
   334  			Pod:         nil, // a failed POD create request will cause a nil Pod
   335  			Credentials: &api.Secret{ObjectMeta: objectMeta},
   336  			ClientFunc: func(req *http.Request) (*http.Response, error) {
   337  				switch p, m := req.URL.Path, req.Method; {
   338  				case m == "DELETE" && p == "/api/"+version+"/namespaces/test-ns/secrets/test-resource":
   339  					return fakeKubeDeleteResponse(http.StatusNotFound), nil
   340  				default:
   341  					return nil, fmt.Errorf("unexpected request. method: %s, path: %s", m, p)
   342  				}
   343  			},
   344  			Error: true,
   345  		},
   346  	}
   347  
   348  	for _, test := range tests {
   349  		t.Run(test.Name, func(t *testing.T) {
   350  			c := client.NewOrDie(&restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}})
   351  			fakeClient := fake.RESTClient{
   352  				Codec:  codec,
   353  				Client: fake.CreateHTTPClient(test.ClientFunc),
   354  			}
   355  			c.Client = fakeClient.Client
   356  
   357  			ex := executor{
   358  				kubeClient:  c,
   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 := testapi.Default.GroupVersion().Version
   896  	codec := testapi.Default.Codec()
   897  
   898  	type testDef struct {
   899  		Credentials []common.Credentials
   900  		VerifyFn    func(*testing.T, testDef, *api.Secret)
   901  	}
   902  	tests := []testDef{
   903  		{
   904  			// don't execute VerifyFn
   905  			VerifyFn: nil,
   906  		},
   907  		{
   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  		{
   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  	}
   934  
   935  	executed := false
   936  	fakeClientRoundTripper := func(test testDef) func(req *http.Request) (*http.Response, error) {
   937  		return func(req *http.Request) (resp *http.Response, err error) {
   938  			podBytes, err := ioutil.ReadAll(req.Body)
   939  			executed = true
   940  
   941  			if err != nil {
   942  				t.Errorf("failed to read request body: %s", err.Error())
   943  				return
   944  			}
   945  
   946  			p := new(api.Secret)
   947  
   948  			err = json.Unmarshal(podBytes, p)
   949  
   950  			if err != nil {
   951  				t.Errorf("error decoding pod: %s", err.Error())
   952  				return
   953  			}
   954  
   955  			if test.VerifyFn != nil {
   956  				test.VerifyFn(t, test, p)
   957  			}
   958  
   959  			resp = &http.Response{StatusCode: http.StatusOK, Body: FakeReadCloser{
   960  				Reader: bytes.NewBuffer(podBytes),
   961  			}}
   962  			resp.Header = make(http.Header)
   963  			resp.Header.Add("Content-Type", "application/json")
   964  
   965  			return
   966  		}
   967  	}
   968  
   969  	for _, test := range tests {
   970  		c := client.NewOrDie(&restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}})
   971  		fakeClient := fake.RESTClient{
   972  			Codec:  codec,
   973  			Client: fake.CreateHTTPClient(fakeClientRoundTripper(test)),
   974  		}
   975  		c.Client = fakeClient.Client
   976  
   977  		ex := executor{
   978  			kubeClient: c,
   979  			options:    &kubernetesOptions{},
   980  			AbstractExecutor: executors.AbstractExecutor{
   981  				Config: common.RunnerConfig{
   982  					RunnerSettings: common.RunnerSettings{
   983  						Kubernetes: &common.KubernetesConfig{
   984  							Namespace: "default",
   985  						},
   986  					},
   987  				},
   988  				BuildShell: &common.ShellConfiguration{},
   989  				Build: &common.Build{
   990  					JobResponse: common.JobResponse{
   991  						Variables:   []common.JobVariable{},
   992  						Credentials: test.Credentials,
   993  					},
   994  					Runner: &common.RunnerConfig{},
   995  				},
   996  			},
   997  		}
   998  
   999  		executed = false
  1000  		err := ex.prepareOverwrites(make(common.JobVariables, 0))
  1001  		assert.NoError(t, err)
  1002  		err = ex.setupCredentials()
  1003  		assert.NoError(t, err)
  1004  		if test.VerifyFn != nil {
  1005  			assert.True(t, executed)
  1006  		} else {
  1007  			assert.False(t, executed)
  1008  		}
  1009  	}
  1010  }
  1011  
  1012  func TestSetupBuildPod(t *testing.T) {
  1013  	version := testapi.Default.GroupVersion().Version
  1014  	codec := testapi.Default.Codec()
  1015  
  1016  	type testDef struct {
  1017  		RunnerConfig common.RunnerConfig
  1018  		Options      *kubernetesOptions
  1019  		PrepareFn    func(*testing.T, testDef, *executor)
  1020  		VerifyFn     func(*testing.T, testDef, *api.Pod)
  1021  		Variables    []common.JobVariable
  1022  	}
  1023  	tests := []testDef{
  1024  		{
  1025  			RunnerConfig: common.RunnerConfig{
  1026  				RunnerSettings: common.RunnerSettings{
  1027  					Kubernetes: &common.KubernetesConfig{
  1028  						Namespace: "default",
  1029  						NodeSelector: map[string]string{
  1030  							"a-selector":       "first",
  1031  							"another-selector": "second",
  1032  						},
  1033  					},
  1034  				},
  1035  			},
  1036  			VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) {
  1037  				assert.Equal(t, test.RunnerConfig.RunnerSettings.Kubernetes.NodeSelector, pod.Spec.NodeSelector)
  1038  			},
  1039  		},
  1040  		{
  1041  			RunnerConfig: common.RunnerConfig{
  1042  				RunnerSettings: common.RunnerSettings{
  1043  					Kubernetes: &common.KubernetesConfig{
  1044  						Namespace: "default",
  1045  					},
  1046  				},
  1047  			},
  1048  			PrepareFn: func(t *testing.T, test testDef, e *executor) {
  1049  				e.credentials = &api.Secret{
  1050  					ObjectMeta: api.ObjectMeta{
  1051  						Name: "job-credentials",
  1052  					},
  1053  				}
  1054  			},
  1055  			VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) {
  1056  				secrets := []api.LocalObjectReference{{Name: "job-credentials"}}
  1057  				assert.Equal(t, secrets, pod.Spec.ImagePullSecrets)
  1058  			},
  1059  		},
  1060  		{
  1061  			RunnerConfig: common.RunnerConfig{
  1062  				RunnerSettings: common.RunnerSettings{
  1063  					Kubernetes: &common.KubernetesConfig{
  1064  						Namespace: "default",
  1065  						ImagePullSecrets: []string{
  1066  							"docker-registry-credentials",
  1067  						},
  1068  					},
  1069  				},
  1070  			},
  1071  			VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) {
  1072  				secrets := []api.LocalObjectReference{{Name: "docker-registry-credentials"}}
  1073  				assert.Equal(t, secrets, pod.Spec.ImagePullSecrets)
  1074  			},
  1075  		},
  1076  		{
  1077  			RunnerConfig: common.RunnerConfig{
  1078  				RunnerSettings: common.RunnerSettings{
  1079  					Kubernetes: &common.KubernetesConfig{
  1080  						Namespace: "default",
  1081  					},
  1082  				},
  1083  			},
  1084  			VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) {
  1085  				hasHelper := false
  1086  				for _, c := range pod.Spec.Containers {
  1087  					if c.Name == "helper" {
  1088  						hasHelper = true
  1089  					}
  1090  				}
  1091  				assert.True(t, hasHelper)
  1092  			},
  1093  		},
  1094  		{
  1095  			RunnerConfig: common.RunnerConfig{
  1096  				RunnerSettings: common.RunnerSettings{
  1097  					Kubernetes: &common.KubernetesConfig{
  1098  						Namespace:   "default",
  1099  						HelperImage: "custom/helper-image",
  1100  					},
  1101  				},
  1102  			},
  1103  			VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) {
  1104  				for _, c := range pod.Spec.Containers {
  1105  					if c.Name == "helper" {
  1106  						assert.Equal(t, test.RunnerConfig.RunnerSettings.Kubernetes.HelperImage, c.Image)
  1107  					}
  1108  				}
  1109  			},
  1110  		},
  1111  		{
  1112  			RunnerConfig: common.RunnerConfig{
  1113  				RunnerSettings: common.RunnerSettings{
  1114  					Kubernetes: &common.KubernetesConfig{
  1115  						Namespace: "default",
  1116  						PodLabels: map[string]string{
  1117  							"test":    "label",
  1118  							"another": "label",
  1119  							"var":     "$test",
  1120  						},
  1121  					},
  1122  				},
  1123  			},
  1124  			VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) {
  1125  				assert.Equal(t, map[string]string{
  1126  					"test":    "label",
  1127  					"another": "label",
  1128  					"var":     "sometestvar",
  1129  				}, pod.ObjectMeta.Labels)
  1130  			},
  1131  			Variables: []common.JobVariable{
  1132  				{Key: "test", Value: "sometestvar"},
  1133  			},
  1134  		},
  1135  		{
  1136  			RunnerConfig: common.RunnerConfig{
  1137  				RunnerSettings: common.RunnerSettings{
  1138  					Kubernetes: &common.KubernetesConfig{
  1139  						Namespace: "default",
  1140  						PodAnnotations: map[string]string{
  1141  							"test":    "annotation",
  1142  							"another": "annotation",
  1143  							"var":     "$test",
  1144  						},
  1145  					},
  1146  				},
  1147  			},
  1148  			VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) {
  1149  				assert.Equal(t, map[string]string{
  1150  					"test":    "annotation",
  1151  					"another": "annotation",
  1152  					"var":     "sometestvar",
  1153  				}, pod.ObjectMeta.Annotations)
  1154  			},
  1155  			Variables: []common.JobVariable{
  1156  				{Key: "test", Value: "sometestvar"},
  1157  			},
  1158  		},
  1159  		{
  1160  			RunnerConfig: common.RunnerConfig{
  1161  				RunnerSettings: common.RunnerSettings{
  1162  					Kubernetes: &common.KubernetesConfig{
  1163  						Namespace:   "default",
  1164  						HelperImage: "custom/helper-image",
  1165  					},
  1166  				},
  1167  			},
  1168  			Options: &kubernetesOptions{
  1169  				Image: common.Image{
  1170  					Name:       "test-image",
  1171  					Entrypoint: []string{"/init", "run"},
  1172  				},
  1173  				Services: common.Services{
  1174  					{
  1175  						Name:       "test-service",
  1176  						Entrypoint: []string{"/init", "run"},
  1177  						Command:    []string{"application", "--debug"},
  1178  					},
  1179  				},
  1180  			},
  1181  			VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) {
  1182  				require.Len(t, pod.Spec.Containers, 3)
  1183  
  1184  				assert.Equal(t, pod.Spec.Containers[0].Name, "build")
  1185  				assert.Equal(t, pod.Spec.Containers[0].Image, "test-image")
  1186  				assert.Equal(t, pod.Spec.Containers[0].Command, []string{"/init", "run"})
  1187  				assert.Empty(t, pod.Spec.Containers[0].Args, "Build container args should be empty")
  1188  
  1189  				assert.Equal(t, pod.Spec.Containers[1].Name, "helper")
  1190  				assert.Equal(t, pod.Spec.Containers[1].Image, "custom/helper-image")
  1191  				assert.Empty(t, pod.Spec.Containers[1].Command, "Helper container command should be empty")
  1192  				assert.Empty(t, pod.Spec.Containers[1].Args, "Helper container args should be empty")
  1193  
  1194  				assert.Equal(t, pod.Spec.Containers[2].Name, "svc-0")
  1195  				assert.Equal(t, pod.Spec.Containers[2].Image, "test-service")
  1196  				assert.Equal(t, pod.Spec.Containers[2].Command, []string{"/init", "run"})
  1197  				assert.Equal(t, pod.Spec.Containers[2].Args, []string{"application", "--debug"})
  1198  			},
  1199  		},
  1200  	}
  1201  
  1202  	executed := false
  1203  	fakeClientRoundTripper := func(test testDef) func(req *http.Request) (*http.Response, error) {
  1204  		return func(req *http.Request) (resp *http.Response, err error) {
  1205  			executed = true
  1206  			podBytes, err := ioutil.ReadAll(req.Body)
  1207  
  1208  			if err != nil {
  1209  				t.Errorf("failed to read request body: %s", err.Error())
  1210  				return
  1211  			}
  1212  
  1213  			p := new(api.Pod)
  1214  
  1215  			err = json.Unmarshal(podBytes, p)
  1216  
  1217  			if err != nil {
  1218  				t.Errorf("error decoding pod: %s", err.Error())
  1219  				return
  1220  			}
  1221  
  1222  			test.VerifyFn(t, test, p)
  1223  
  1224  			resp = &http.Response{StatusCode: http.StatusOK, Body: FakeReadCloser{
  1225  				Reader: bytes.NewBuffer(podBytes),
  1226  			}}
  1227  			resp.Header = make(http.Header)
  1228  			resp.Header.Add("Content-Type", "application/json")
  1229  
  1230  			return
  1231  		}
  1232  	}
  1233  
  1234  	for _, test := range tests {
  1235  		c := client.NewOrDie(&restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}})
  1236  		fakeClient := fake.RESTClient{
  1237  			Codec:  codec,
  1238  			Client: fake.CreateHTTPClient(fakeClientRoundTripper(test)),
  1239  		}
  1240  		c.Client = fakeClient.Client
  1241  
  1242  		vars := test.Variables
  1243  		if vars == nil {
  1244  			vars = []common.JobVariable{}
  1245  		}
  1246  
  1247  		options := test.Options
  1248  		if options == nil {
  1249  			options = &kubernetesOptions{}
  1250  		}
  1251  		ex := executor{
  1252  			kubeClient: c,
  1253  			options:    options,
  1254  			AbstractExecutor: executors.AbstractExecutor{
  1255  				Config:     test.RunnerConfig,
  1256  				BuildShell: &common.ShellConfiguration{},
  1257  				Build: &common.Build{
  1258  					JobResponse: common.JobResponse{
  1259  						Variables: vars,
  1260  					},
  1261  					Runner: &test.RunnerConfig,
  1262  				},
  1263  			},
  1264  		}
  1265  
  1266  		if test.PrepareFn != nil {
  1267  			test.PrepareFn(t, test, &ex)
  1268  		}
  1269  
  1270  		executed = false
  1271  		err := ex.prepareOverwrites(make(common.JobVariables, 0))
  1272  		assert.NoError(t, err, "error preparing overwrites: %s")
  1273  		err = ex.setupBuildPod()
  1274  		assert.NoError(t, err, "error setting up build pod: %s")
  1275  		assert.True(t, executed)
  1276  	}
  1277  }
  1278  
  1279  func TestKubernetesSuccessRun(t *testing.T) {
  1280  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1281  		return
  1282  	}
  1283  
  1284  	successfulBuild, err := common.GetRemoteSuccessfulBuild()
  1285  	assert.NoError(t, err)
  1286  	successfulBuild.Image.Name = "docker:git"
  1287  	build := &common.Build{
  1288  		JobResponse: successfulBuild,
  1289  		Runner: &common.RunnerConfig{
  1290  			RunnerSettings: common.RunnerSettings{
  1291  				Executor:   "kubernetes",
  1292  				Kubernetes: &common.KubernetesConfig{},
  1293  			},
  1294  		},
  1295  	}
  1296  
  1297  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1298  	assert.NoError(t, err)
  1299  }
  1300  
  1301  func TestKubernetesNoRootImage(t *testing.T) {
  1302  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1303  		return
  1304  	}
  1305  
  1306  	successfulBuild, err := common.GetRemoteSuccessfulBuildWithDumpedVariables()
  1307  
  1308  	assert.NoError(t, err)
  1309  	successfulBuild.Image.Name = "registry.gitlab.com/gitlab-org/gitlab-runner/alpine-no-root"
  1310  	build := &common.Build{
  1311  		JobResponse: successfulBuild,
  1312  		Runner: &common.RunnerConfig{
  1313  			RunnerSettings: common.RunnerSettings{
  1314  				Executor:   "kubernetes",
  1315  				Kubernetes: &common.KubernetesConfig{},
  1316  			},
  1317  		},
  1318  	}
  1319  
  1320  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1321  	assert.NoError(t, err)
  1322  }
  1323  
  1324  func TestKubernetesBuildFail(t *testing.T) {
  1325  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1326  		return
  1327  	}
  1328  
  1329  	failedBuild, err := common.GetRemoteFailedBuild()
  1330  	assert.NoError(t, err)
  1331  	build := &common.Build{
  1332  		JobResponse: failedBuild,
  1333  		Runner: &common.RunnerConfig{
  1334  			RunnerSettings: common.RunnerSettings{
  1335  				Executor:   "kubernetes",
  1336  				Kubernetes: &common.KubernetesConfig{},
  1337  			},
  1338  		},
  1339  	}
  1340  	build.Image.Name = "docker:git"
  1341  
  1342  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1343  	require.Error(t, err, "error")
  1344  	assert.IsType(t, err, &common.BuildError{})
  1345  	assert.Contains(t, err.Error(), "Error executing in Docker Container: 1")
  1346  }
  1347  
  1348  func TestKubernetesMissingImage(t *testing.T) {
  1349  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1350  		return
  1351  	}
  1352  
  1353  	failedBuild, err := common.GetRemoteFailedBuild()
  1354  	assert.NoError(t, err)
  1355  	build := &common.Build{
  1356  		JobResponse: failedBuild,
  1357  		Runner: &common.RunnerConfig{
  1358  			RunnerSettings: common.RunnerSettings{
  1359  				Executor:   "kubernetes",
  1360  				Kubernetes: &common.KubernetesConfig{},
  1361  			},
  1362  		},
  1363  	}
  1364  	build.Image.Name = "some/non-existing/image"
  1365  
  1366  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1367  	require.Error(t, err)
  1368  	assert.IsType(t, err, &common.BuildError{})
  1369  	assert.Contains(t, err.Error(), "image pull failed")
  1370  }
  1371  
  1372  func TestKubernetesMissingTag(t *testing.T) {
  1373  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1374  		return
  1375  	}
  1376  
  1377  	failedBuild, err := common.GetRemoteFailedBuild()
  1378  	assert.NoError(t, err)
  1379  	build := &common.Build{
  1380  		JobResponse: failedBuild,
  1381  		Runner: &common.RunnerConfig{
  1382  			RunnerSettings: common.RunnerSettings{
  1383  				Executor:   "kubernetes",
  1384  				Kubernetes: &common.KubernetesConfig{},
  1385  			},
  1386  		},
  1387  	}
  1388  	build.Image.Name = "docker:missing-tag"
  1389  
  1390  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1391  	require.Error(t, err)
  1392  	assert.IsType(t, err, &common.BuildError{})
  1393  	assert.Contains(t, err.Error(), "image pull failed")
  1394  }
  1395  
  1396  func TestKubernetesBuildAbort(t *testing.T) {
  1397  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1398  		return
  1399  	}
  1400  
  1401  	failedBuild, err := common.GetRemoteFailedBuild()
  1402  	assert.NoError(t, err)
  1403  	build := &common.Build{
  1404  		JobResponse: failedBuild,
  1405  		Runner: &common.RunnerConfig{
  1406  			RunnerSettings: common.RunnerSettings{
  1407  				Executor:   "kubernetes",
  1408  				Kubernetes: &common.KubernetesConfig{},
  1409  			},
  1410  		},
  1411  		SystemInterrupt: make(chan os.Signal, 1),
  1412  	}
  1413  	build.Image.Name = "docker:git"
  1414  
  1415  	abortTimer := time.AfterFunc(time.Second, func() {
  1416  		t.Log("Interrupt")
  1417  		build.SystemInterrupt <- os.Interrupt
  1418  	})
  1419  	defer abortTimer.Stop()
  1420  
  1421  	timeoutTimer := time.AfterFunc(time.Minute, func() {
  1422  		t.Log("Timedout")
  1423  		t.FailNow()
  1424  	})
  1425  	defer timeoutTimer.Stop()
  1426  
  1427  	err = build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1428  	assert.EqualError(t, err, "aborted: interrupt")
  1429  }
  1430  
  1431  func TestKubernetesBuildCancel(t *testing.T) {
  1432  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1433  		return
  1434  	}
  1435  
  1436  	failedBuild, err := common.GetRemoteFailedBuild()
  1437  	assert.NoError(t, err)
  1438  	build := &common.Build{
  1439  		JobResponse: failedBuild,
  1440  		Runner: &common.RunnerConfig{
  1441  			RunnerSettings: common.RunnerSettings{
  1442  				Executor:   "kubernetes",
  1443  				Kubernetes: &common.KubernetesConfig{},
  1444  			},
  1445  		},
  1446  		SystemInterrupt: make(chan os.Signal, 1),
  1447  	}
  1448  	build.Image.Name = "docker:git"
  1449  
  1450  	trace := &common.Trace{Writer: os.Stdout}
  1451  
  1452  	abortTimer := time.AfterFunc(time.Second, func() {
  1453  		t.Log("Interrupt")
  1454  		trace.CancelFunc()
  1455  	})
  1456  	defer abortTimer.Stop()
  1457  
  1458  	timeoutTimer := time.AfterFunc(time.Minute, func() {
  1459  		t.Log("Timedout")
  1460  		t.FailNow()
  1461  	})
  1462  	defer timeoutTimer.Stop()
  1463  
  1464  	err = build.Run(&common.Config{}, trace)
  1465  	assert.IsType(t, err, &common.BuildError{})
  1466  	assert.EqualError(t, err, "canceled")
  1467  }
  1468  
  1469  func TestOverwriteNamespaceNotMatch(t *testing.T) {
  1470  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1471  		return
  1472  	}
  1473  
  1474  	build := &common.Build{
  1475  		JobResponse: common.JobResponse{
  1476  			GitInfo: common.GitInfo{
  1477  				Sha: "1234567890",
  1478  			},
  1479  			Image: common.Image{
  1480  				Name: "test-image",
  1481  			},
  1482  			Variables: []common.JobVariable{
  1483  				{Key: NamespaceOverwriteVariableName, Value: "namespace"},
  1484  			},
  1485  		},
  1486  		Runner: &common.RunnerConfig{
  1487  			RunnerSettings: common.RunnerSettings{
  1488  				Executor: "kubernetes",
  1489  				Kubernetes: &common.KubernetesConfig{
  1490  					NamespaceOverwriteAllowed: "^not_a_match$",
  1491  				},
  1492  			},
  1493  		},
  1494  		SystemInterrupt: make(chan os.Signal, 1),
  1495  	}
  1496  	build.Image.Name = "docker:git"
  1497  
  1498  	err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1499  	require.Error(t, err)
  1500  	assert.Contains(t, err.Error(), "does not match")
  1501  }
  1502  
  1503  func TestOverwriteServiceAccountNotMatch(t *testing.T) {
  1504  	if helpers.SkipIntegrationTests(t, "kubectl", "cluster-info") {
  1505  		return
  1506  	}
  1507  
  1508  	build := &common.Build{
  1509  		JobResponse: common.JobResponse{
  1510  			GitInfo: common.GitInfo{
  1511  				Sha: "1234567890",
  1512  			},
  1513  			Image: common.Image{
  1514  				Name: "test-image",
  1515  			},
  1516  			Variables: []common.JobVariable{
  1517  				{Key: ServiceAccountOverwriteVariableName, Value: "service-account"},
  1518  			},
  1519  		},
  1520  		Runner: &common.RunnerConfig{
  1521  			RunnerSettings: common.RunnerSettings{
  1522  				Executor: "kubernetes",
  1523  				Kubernetes: &common.KubernetesConfig{
  1524  					ServiceAccountOverwriteAllowed: "^not_a_match$",
  1525  				},
  1526  			},
  1527  		},
  1528  		SystemInterrupt: make(chan os.Signal, 1),
  1529  	}
  1530  	build.Image.Name = "docker:git"
  1531  
  1532  	err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
  1533  	require.Error(t, err)
  1534  	assert.Contains(t, err.Error(), "does not match")
  1535  }
  1536  
  1537  type FakeReadCloser struct {
  1538  	io.Reader
  1539  }
  1540  
  1541  func (f FakeReadCloser) Close() error {
  1542  	return nil
  1543  }
  1544  
  1545  type FakeBuildTrace struct {
  1546  	testWriter
  1547  }
  1548  
  1549  func (f FakeBuildTrace) Success()                                              {}
  1550  func (f FakeBuildTrace) Fail(err error, failureReason common.JobFailureReason) {}
  1551  func (f FakeBuildTrace) Notify(func())                                         {}
  1552  func (f FakeBuildTrace) SetCancelFunc(cancelFunc context.CancelFunc)           {}
  1553  func (f FakeBuildTrace) SetFailuresCollector(fc common.FailuresCollector)      {}
  1554  func (f FakeBuildTrace) IsStdout() bool {
  1555  	return false
  1556  }