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

     1  package kubernetes
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  
    11  	"github.com/sirupsen/logrus"
    12  	terminal "gitlab.com/gitlab-org/gitlab-terminal"
    13  	"golang.org/x/net/context"
    14  	api "k8s.io/api/core/v1"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/client-go/kubernetes"
    17  	"k8s.io/client-go/kubernetes/scheme"
    18  	_ "k8s.io/client-go/plugin/pkg/client/auth" // Register all available authentication methods
    19  	restclient "k8s.io/client-go/rest"
    20  
    21  	"gitlab.com/gitlab-org/gitlab-runner/common"
    22  	"gitlab.com/gitlab-org/gitlab-runner/executors"
    23  	"gitlab.com/gitlab-org/gitlab-runner/helpers/dns"
    24  	"gitlab.com/gitlab-org/gitlab-runner/helpers/docker/helperimage"
    25  	"gitlab.com/gitlab-org/gitlab-runner/helpers/featureflags"
    26  	terminalsession "gitlab.com/gitlab-org/gitlab-runner/session/terminal"
    27  )
    28  
    29  var (
    30  	executorOptions = executors.ExecutorOptions{
    31  		DefaultCustomBuildsDirEnabled: true,
    32  		DefaultBuildsDir:              "/builds",
    33  		DefaultCacheDir:               "/cache",
    34  		SharedBuildsDir:               false,
    35  		Shell: common.ShellScriptInfo{
    36  			Shell:         "bash",
    37  			Type:          common.NormalShell,
    38  			RunnerCommand: "/usr/bin/gitlab-runner-helper",
    39  		},
    40  		ShowHostname: true,
    41  	}
    42  )
    43  
    44  type kubernetesOptions struct {
    45  	Image    common.Image
    46  	Services common.Services
    47  }
    48  
    49  type executor struct {
    50  	executors.AbstractExecutor
    51  
    52  	kubeClient  *kubernetes.Clientset
    53  	pod         *api.Pod
    54  	credentials *api.Secret
    55  	options     *kubernetesOptions
    56  
    57  	configurationOverwrites *overwrites
    58  	buildLimits             api.ResourceList
    59  	serviceLimits           api.ResourceList
    60  	helperLimits            api.ResourceList
    61  	buildRequests           api.ResourceList
    62  	serviceRequests         api.ResourceList
    63  	helperRequests          api.ResourceList
    64  	pullPolicy              common.KubernetesPullPolicy
    65  
    66  	helperImageInfo helperimage.Info
    67  }
    68  
    69  func (s *executor) setupResources() error {
    70  	var err error
    71  
    72  	// Limit
    73  	if s.buildLimits, err = limits(s.Config.Kubernetes.CPULimit, s.Config.Kubernetes.MemoryLimit); err != nil {
    74  		return fmt.Errorf("invalid build limits specified: %s", err.Error())
    75  	}
    76  
    77  	if s.serviceLimits, err = limits(s.Config.Kubernetes.ServiceCPULimit, s.Config.Kubernetes.ServiceMemoryLimit); err != nil {
    78  		return fmt.Errorf("invalid service limits specified: %s", err.Error())
    79  	}
    80  
    81  	if s.helperLimits, err = limits(s.Config.Kubernetes.HelperCPULimit, s.Config.Kubernetes.HelperMemoryLimit); err != nil {
    82  		return fmt.Errorf("invalid helper limits specified: %s", err.Error())
    83  	}
    84  
    85  	// Requests
    86  	if s.buildRequests, err = limits(s.Config.Kubernetes.CPURequest, s.Config.Kubernetes.MemoryRequest); err != nil {
    87  		return fmt.Errorf("invalid build requests specified: %s", err.Error())
    88  	}
    89  
    90  	if s.serviceRequests, err = limits(s.Config.Kubernetes.ServiceCPURequest, s.Config.Kubernetes.ServiceMemoryRequest); err != nil {
    91  		return fmt.Errorf("invalid service requests specified: %s", err.Error())
    92  	}
    93  
    94  	if s.helperRequests, err = limits(s.Config.Kubernetes.HelperCPURequest, s.Config.Kubernetes.HelperMemoryRequest); err != nil {
    95  		return fmt.Errorf("invalid helper requests specified: %s", err.Error())
    96  	}
    97  	return nil
    98  }
    99  
   100  func (s *executor) Prepare(options common.ExecutorPrepareOptions) (err error) {
   101  	if err = s.AbstractExecutor.Prepare(options); err != nil {
   102  		return err
   103  	}
   104  
   105  	if s.BuildShell.PassFile {
   106  		return fmt.Errorf("kubernetes doesn't support shells that require script file")
   107  	}
   108  
   109  	if err = s.setupResources(); err != nil {
   110  		return err
   111  	}
   112  
   113  	if s.pullPolicy, err = s.Config.Kubernetes.PullPolicy.Get(); err != nil {
   114  		return err
   115  	}
   116  
   117  	if err = s.prepareOverwrites(options.Build.Variables); err != nil {
   118  		return err
   119  	}
   120  
   121  	s.prepareOptions(options.Build)
   122  
   123  	if err = s.checkDefaults(); err != nil {
   124  		return err
   125  	}
   126  
   127  	if s.kubeClient, err = getKubeClient(options.Config.Kubernetes, s.configurationOverwrites); err != nil {
   128  		return fmt.Errorf("error connecting to Kubernetes: %s", err.Error())
   129  	}
   130  
   131  	s.Println("Using Kubernetes executor with image", s.options.Image.Name, "...")
   132  
   133  	return nil
   134  }
   135  
   136  func (s *executor) Run(cmd common.ExecutorCommand) error {
   137  	s.Debugln("Starting Kubernetes command...")
   138  
   139  	if s.pod == nil {
   140  		err := s.setupCredentials()
   141  		if err != nil {
   142  			return err
   143  		}
   144  
   145  		err = s.setupBuildPod()
   146  		if err != nil {
   147  			return err
   148  		}
   149  	}
   150  
   151  	containerName := "build"
   152  	containerCommand := s.BuildShell.DockerCommand
   153  	if cmd.Predefined {
   154  		containerName = "helper"
   155  		containerCommand = s.helperImageInfo.Cmd
   156  	}
   157  
   158  	ctx, cancel := context.WithCancel(context.Background())
   159  	defer cancel()
   160  
   161  	s.Debugln(fmt.Sprintf(
   162  		"Starting in container %q the command %q with script: %s",
   163  		containerName,
   164  		containerCommand,
   165  		cmd.Script,
   166  	))
   167  
   168  	select {
   169  	case err := <-s.runInContainer(ctx, containerName, containerCommand, cmd.Script):
   170  		s.Debugln(fmt.Sprintf("Container %q exited with error: %v", containerName, err))
   171  		if err != nil && strings.Contains(err.Error(), "command terminated with exit code") {
   172  			return &common.BuildError{Inner: err}
   173  		}
   174  		return err
   175  
   176  	case <-cmd.Context.Done():
   177  		return fmt.Errorf("build aborted")
   178  	}
   179  }
   180  
   181  func (s *executor) Cleanup() {
   182  	if s.pod != nil {
   183  		err := s.kubeClient.CoreV1().Pods(s.pod.Namespace).Delete(s.pod.Name, &metav1.DeleteOptions{})
   184  		if err != nil {
   185  			s.Errorln(fmt.Sprintf("Error cleaning up pod: %s", err.Error()))
   186  		}
   187  	}
   188  	if s.credentials != nil {
   189  		err := s.kubeClient.CoreV1().Secrets(s.configurationOverwrites.namespace).Delete(s.credentials.Name, &metav1.DeleteOptions{})
   190  		if err != nil {
   191  			s.Errorln(fmt.Sprintf("Error cleaning up secrets: %s", err.Error()))
   192  		}
   193  	}
   194  	closeKubeClient(s.kubeClient)
   195  	s.AbstractExecutor.Cleanup()
   196  }
   197  
   198  func (s *executor) buildContainer(name, image string, imageDefinition common.Image, requests, limits api.ResourceList, containerCommand ...string) api.Container {
   199  	privileged := false
   200  	if s.Config.Kubernetes != nil {
   201  		privileged = s.Config.Kubernetes.Privileged
   202  	}
   203  
   204  	command, args := s.getCommandAndArgs(imageDefinition, containerCommand...)
   205  
   206  	return api.Container{
   207  		Name:            name,
   208  		Image:           image,
   209  		ImagePullPolicy: api.PullPolicy(s.pullPolicy),
   210  		Command:         command,
   211  		Args:            args,
   212  		Env:             buildVariables(s.Build.GetAllVariables().PublicOrInternal()),
   213  		Resources: api.ResourceRequirements{
   214  			Limits:   limits,
   215  			Requests: requests,
   216  		},
   217  		VolumeMounts: s.getVolumeMounts(),
   218  		SecurityContext: &api.SecurityContext{
   219  			Privileged: &privileged,
   220  		},
   221  		Stdin: true,
   222  	}
   223  }
   224  
   225  func (s *executor) getCommandAndArgs(imageDefinition common.Image, command ...string) ([]string, []string) {
   226  	if s.Build.IsFeatureFlagOn(featureflags.K8sEntrypointOverCommand) {
   227  		return s.getCommandsAndArgsV2(imageDefinition, command...)
   228  	}
   229  
   230  	return s.getCommandsAndArgsV1(imageDefinition, command...)
   231  }
   232  
   233  // TODO: Remove in 12.0
   234  func (s *executor) getCommandsAndArgsV1(imageDefinition common.Image, command ...string) ([]string, []string) {
   235  	if len(command) == 0 && len(imageDefinition.Command) > 0 {
   236  		command = imageDefinition.Command
   237  	}
   238  
   239  	var args []string
   240  	if len(imageDefinition.Entrypoint) > 0 {
   241  		args = command
   242  		command = imageDefinition.Entrypoint
   243  	}
   244  
   245  	return command, args
   246  }
   247  
   248  // TODO: Make this the only proper way to setup command and args in 12.0
   249  func (s *executor) getCommandsAndArgsV2(imageDefinition common.Image, command ...string) ([]string, []string) {
   250  	if len(command) == 0 && len(imageDefinition.Entrypoint) > 0 {
   251  		command = imageDefinition.Entrypoint
   252  	}
   253  
   254  	var args []string
   255  	if len(imageDefinition.Command) > 0 {
   256  		args = imageDefinition.Command
   257  	}
   258  
   259  	return command, args
   260  }
   261  
   262  func (s *executor) getVolumeMounts() (mounts []api.VolumeMount) {
   263  	mounts = append(mounts, api.VolumeMount{
   264  		Name:      "repo",
   265  		MountPath: s.Build.RootDir,
   266  	})
   267  
   268  	for _, mount := range s.Config.Kubernetes.Volumes.HostPaths {
   269  		mounts = append(mounts, api.VolumeMount{
   270  			Name:      mount.Name,
   271  			MountPath: mount.MountPath,
   272  			ReadOnly:  mount.ReadOnly,
   273  		})
   274  	}
   275  
   276  	for _, mount := range s.Config.Kubernetes.Volumes.Secrets {
   277  		mounts = append(mounts, api.VolumeMount{
   278  			Name:      mount.Name,
   279  			MountPath: mount.MountPath,
   280  			ReadOnly:  mount.ReadOnly,
   281  		})
   282  	}
   283  
   284  	for _, mount := range s.Config.Kubernetes.Volumes.PVCs {
   285  		mounts = append(mounts, api.VolumeMount{
   286  			Name:      mount.Name,
   287  			MountPath: mount.MountPath,
   288  			ReadOnly:  mount.ReadOnly,
   289  		})
   290  	}
   291  
   292  	for _, mount := range s.Config.Kubernetes.Volumes.ConfigMaps {
   293  		mounts = append(mounts, api.VolumeMount{
   294  			Name:      mount.Name,
   295  			MountPath: mount.MountPath,
   296  			ReadOnly:  mount.ReadOnly,
   297  		})
   298  	}
   299  
   300  	for _, mount := range s.Config.Kubernetes.Volumes.EmptyDirs {
   301  		mounts = append(mounts, api.VolumeMount{
   302  			Name:      mount.Name,
   303  			MountPath: mount.MountPath,
   304  		})
   305  	}
   306  
   307  	return
   308  }
   309  
   310  func (s *executor) getVolumes() (volumes []api.Volume) {
   311  	volumes = append(volumes, api.Volume{
   312  		Name: "repo",
   313  		VolumeSource: api.VolumeSource{
   314  			EmptyDir: &api.EmptyDirVolumeSource{},
   315  		},
   316  	})
   317  
   318  	for _, volume := range s.Config.Kubernetes.Volumes.HostPaths {
   319  		path := volume.HostPath
   320  		// Make backward compatible with syntax introduced in version 9.3.0
   321  		if path == "" {
   322  			path = volume.MountPath
   323  		}
   324  
   325  		volumes = append(volumes, api.Volume{
   326  			Name: volume.Name,
   327  			VolumeSource: api.VolumeSource{
   328  				HostPath: &api.HostPathVolumeSource{
   329  					Path: path,
   330  				},
   331  			},
   332  		})
   333  	}
   334  
   335  	for _, volume := range s.Config.Kubernetes.Volumes.Secrets {
   336  		items := []api.KeyToPath{}
   337  		for key, path := range volume.Items {
   338  			items = append(items, api.KeyToPath{Key: key, Path: path})
   339  		}
   340  
   341  		volumes = append(volumes, api.Volume{
   342  			Name: volume.Name,
   343  			VolumeSource: api.VolumeSource{
   344  				Secret: &api.SecretVolumeSource{
   345  					SecretName: volume.Name,
   346  					Items:      items,
   347  				},
   348  			},
   349  		})
   350  	}
   351  
   352  	for _, volume := range s.Config.Kubernetes.Volumes.PVCs {
   353  		volumes = append(volumes, api.Volume{
   354  			Name: volume.Name,
   355  			VolumeSource: api.VolumeSource{
   356  				PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
   357  					ClaimName: volume.Name,
   358  					ReadOnly:  volume.ReadOnly,
   359  				},
   360  			},
   361  		})
   362  	}
   363  
   364  	for _, volume := range s.Config.Kubernetes.Volumes.ConfigMaps {
   365  		items := []api.KeyToPath{}
   366  		for key, path := range volume.Items {
   367  			items = append(items, api.KeyToPath{Key: key, Path: path})
   368  		}
   369  
   370  		volumes = append(volumes, api.Volume{
   371  			Name: volume.Name,
   372  			VolumeSource: api.VolumeSource{
   373  				ConfigMap: &api.ConfigMapVolumeSource{
   374  					LocalObjectReference: api.LocalObjectReference{
   375  						Name: volume.Name,
   376  					},
   377  					Items: items,
   378  				},
   379  			},
   380  		})
   381  	}
   382  
   383  	for _, volume := range s.Config.Kubernetes.Volumes.EmptyDirs {
   384  		volumes = append(volumes, api.Volume{
   385  			Name: volume.Name,
   386  			VolumeSource: api.VolumeSource{
   387  				EmptyDir: &api.EmptyDirVolumeSource{
   388  					Medium: api.StorageMedium(volume.Medium),
   389  				},
   390  			},
   391  		})
   392  	}
   393  
   394  	return
   395  }
   396  
   397  type dockerConfigEntry struct {
   398  	Username, Password string
   399  }
   400  
   401  func (s *executor) projectUniqueName() string {
   402  	return dns.MakeRFC1123Compatible(s.Build.ProjectUniqueName())
   403  }
   404  
   405  func (s *executor) setupCredentials() error {
   406  	authConfigs := make(map[string]dockerConfigEntry)
   407  
   408  	for _, credentials := range s.Build.Credentials {
   409  		if credentials.Type != "registry" {
   410  			continue
   411  		}
   412  
   413  		authConfigs[credentials.URL] = dockerConfigEntry{
   414  			Username: credentials.Username,
   415  			Password: credentials.Password,
   416  		}
   417  	}
   418  
   419  	if len(authConfigs) == 0 {
   420  		return nil
   421  	}
   422  
   423  	dockerCfgContent, err := json.Marshal(authConfigs)
   424  	if err != nil {
   425  		return err
   426  	}
   427  
   428  	secret := api.Secret{}
   429  	secret.GenerateName = s.projectUniqueName()
   430  	secret.Namespace = s.configurationOverwrites.namespace
   431  	secret.Type = api.SecretTypeDockercfg
   432  	secret.Data = map[string][]byte{}
   433  	secret.Data[api.DockerConfigKey] = dockerCfgContent
   434  
   435  	s.credentials, err = s.kubeClient.CoreV1().Secrets(s.configurationOverwrites.namespace).Create(&secret)
   436  	if err != nil {
   437  		return err
   438  	}
   439  	return nil
   440  }
   441  
   442  func (s *executor) setupBuildPod() error {
   443  	services := make([]api.Container, len(s.options.Services))
   444  	for i, service := range s.options.Services {
   445  		resolvedImage := s.Build.GetAllVariables().ExpandValue(service.Name)
   446  		services[i] = s.buildContainer(fmt.Sprintf("svc-%d", i), resolvedImage, service, s.serviceRequests, s.serviceLimits)
   447  	}
   448  
   449  	labels := make(map[string]string)
   450  	for k, v := range s.Build.Runner.Kubernetes.PodLabels {
   451  		labels[k] = s.Build.Variables.ExpandValue(v)
   452  	}
   453  
   454  	annotations := make(map[string]string)
   455  	for key, val := range s.configurationOverwrites.podAnnotations {
   456  		annotations[key] = s.Build.Variables.ExpandValue(val)
   457  	}
   458  
   459  	var imagePullSecrets []api.LocalObjectReference
   460  	for _, imagePullSecret := range s.Config.Kubernetes.ImagePullSecrets {
   461  		imagePullSecrets = append(imagePullSecrets, api.LocalObjectReference{Name: imagePullSecret})
   462  	}
   463  
   464  	if s.credentials != nil {
   465  		imagePullSecrets = append(imagePullSecrets, api.LocalObjectReference{Name: s.credentials.Name})
   466  	}
   467  
   468  	buildImage := s.Build.GetAllVariables().ExpandValue(s.options.Image.Name)
   469  
   470  	pod, err := s.kubeClient.CoreV1().Pods(s.configurationOverwrites.namespace).Create(&api.Pod{
   471  		ObjectMeta: metav1.ObjectMeta{
   472  			GenerateName: s.projectUniqueName(),
   473  			Namespace:    s.configurationOverwrites.namespace,
   474  			Labels:       labels,
   475  			Annotations:  annotations,
   476  		},
   477  		Spec: api.PodSpec{
   478  			Volumes:            s.getVolumes(),
   479  			ServiceAccountName: s.configurationOverwrites.serviceAccount,
   480  			RestartPolicy:      api.RestartPolicyNever,
   481  			NodeSelector:       s.Config.Kubernetes.NodeSelector,
   482  			Tolerations:        s.Config.Kubernetes.GetNodeTolerations(),
   483  			Containers: append([]api.Container{
   484  				// TODO use the build and helper template here
   485  				s.buildContainer("build", buildImage, s.options.Image, s.buildRequests, s.buildLimits, s.BuildShell.DockerCommand...),
   486  				s.buildContainer("helper", s.getHelperImage(), common.Image{}, s.helperRequests, s.helperLimits, s.BuildShell.DockerCommand...),
   487  			}, services...),
   488  			TerminationGracePeriodSeconds: &s.Config.Kubernetes.TerminationGracePeriodSeconds,
   489  			ImagePullSecrets:              imagePullSecrets,
   490  		},
   491  	})
   492  
   493  	if err != nil {
   494  		return err
   495  	}
   496  
   497  	s.pod = pod
   498  
   499  	return nil
   500  }
   501  
   502  func (s *executor) getHelperImage() string {
   503  	if len(s.Config.Kubernetes.HelperImage) > 0 {
   504  		return common.AppVersion.Variables().ExpandValue(s.Config.Kubernetes.HelperImage)
   505  	}
   506  
   507  	return s.helperImageInfo.String()
   508  }
   509  
   510  func (s *executor) runInContainer(ctx context.Context, name string, command []string, script string) <-chan error {
   511  	errc := make(chan error, 1)
   512  	go func() {
   513  		defer close(errc)
   514  
   515  		status, err := waitForPodRunning(ctx, s.kubeClient, s.pod, s.Trace, s.Config.Kubernetes)
   516  
   517  		if err != nil {
   518  			errc <- err
   519  			return
   520  		}
   521  
   522  		if status != api.PodRunning {
   523  			errc <- fmt.Errorf("pod failed to enter running state: %s", status)
   524  			return
   525  		}
   526  
   527  		config, err := getKubeClientConfig(s.Config.Kubernetes, s.configurationOverwrites)
   528  
   529  		if err != nil {
   530  			errc <- err
   531  			return
   532  		}
   533  
   534  		exec := ExecOptions{
   535  			PodName:       s.pod.Name,
   536  			Namespace:     s.pod.Namespace,
   537  			ContainerName: name,
   538  			Command:       command,
   539  			In:            strings.NewReader(script),
   540  			Out:           s.Trace,
   541  			Err:           s.Trace,
   542  			Stdin:         true,
   543  			Config:        config,
   544  			Client:        s.kubeClient,
   545  			Executor:      &DefaultRemoteExecutor{},
   546  		}
   547  
   548  		errc <- exec.Run()
   549  	}()
   550  
   551  	return errc
   552  }
   553  
   554  func (s *executor) Connect() (terminalsession.Conn, error) {
   555  	settings, err := s.getTerminalSettings()
   556  	if err != nil {
   557  		return nil, err
   558  	}
   559  
   560  	return terminalConn{settings: settings}, nil
   561  }
   562  
   563  type terminalConn struct {
   564  	settings *terminal.TerminalSettings
   565  }
   566  
   567  func (t terminalConn) Start(w http.ResponseWriter, r *http.Request, timeoutCh, disconnectCh chan error) {
   568  	proxy := terminal.NewWebSocketProxy(1) // one stopper: terminal exit handler
   569  
   570  	terminalsession.ProxyTerminal(
   571  		timeoutCh,
   572  		disconnectCh,
   573  		proxy.StopCh,
   574  		func() {
   575  			terminal.ProxyWebSocket(w, r, t.settings, proxy)
   576  		},
   577  	)
   578  }
   579  
   580  func (t terminalConn) Close() error {
   581  	return nil
   582  }
   583  
   584  func (s *executor) getTerminalSettings() (*terminal.TerminalSettings, error) {
   585  	config, err := getKubeClientConfig(s.Config.Kubernetes, s.configurationOverwrites)
   586  	if err != nil {
   587  		return nil, err
   588  	}
   589  
   590  	wsURL, err := s.getTerminalWebSocketURL(config)
   591  	if err != nil {
   592  		return nil, err
   593  	}
   594  
   595  	caCert := ""
   596  	if len(config.CAFile) > 0 {
   597  		buf, err := ioutil.ReadFile(config.CAFile)
   598  		if err != nil {
   599  			return nil, err
   600  		}
   601  		caCert = string(buf)
   602  	}
   603  
   604  	term := &terminal.TerminalSettings{
   605  		Subprotocols:   []string{"channel.k8s.io"},
   606  		Url:            wsURL.String(),
   607  		Header:         http.Header{"Authorization": []string{"Bearer " + config.BearerToken}},
   608  		CAPem:          caCert,
   609  		MaxSessionTime: 0,
   610  	}
   611  
   612  	return term, nil
   613  }
   614  
   615  func (s *executor) getTerminalWebSocketURL(config *restclient.Config) (*url.URL, error) {
   616  	wsURL := s.kubeClient.CoreV1().RESTClient().Post().
   617  		Namespace(s.pod.Namespace).
   618  		Resource("pods").
   619  		Name(s.pod.Name).
   620  		SubResource("exec").
   621  		VersionedParams(&api.PodExecOptions{
   622  			Stdin:     true,
   623  			Stdout:    true,
   624  			Stderr:    true,
   625  			TTY:       true,
   626  			Container: "build",
   627  			Command:   []string{"sh", "-c", "bash || sh"},
   628  		}, scheme.ParameterCodec).URL()
   629  
   630  	if wsURL.Scheme == "https" {
   631  		wsURL.Scheme = "wss"
   632  	} else if wsURL.Scheme == "http" {
   633  		wsURL.Scheme = "ws"
   634  	}
   635  
   636  	return wsURL, nil
   637  }
   638  
   639  func (s *executor) prepareOverwrites(variables common.JobVariables) error {
   640  	values, err := createOverwrites(s.Config.Kubernetes, variables, s.BuildLogger)
   641  	if err != nil {
   642  		return err
   643  	}
   644  
   645  	s.configurationOverwrites = values
   646  	return nil
   647  }
   648  
   649  func (s *executor) prepareOptions(job *common.Build) {
   650  	s.options = &kubernetesOptions{}
   651  	s.options.Image = job.Image
   652  	for _, service := range job.Services {
   653  		if service.Name == "" {
   654  			continue
   655  		}
   656  		s.options.Services = append(s.options.Services, service)
   657  	}
   658  }
   659  
   660  // checkDefaults Defines the configuration for the Pod on Kubernetes
   661  func (s *executor) checkDefaults() error {
   662  	if s.options.Image.Name == "" {
   663  		if s.Config.Kubernetes.Image == "" {
   664  			return fmt.Errorf("no image specified and no default set in config")
   665  		}
   666  
   667  		s.options.Image = common.Image{
   668  			Name: s.Config.Kubernetes.Image,
   669  		}
   670  	}
   671  
   672  	if s.configurationOverwrites.namespace == "" {
   673  		s.Warningln("Namespace is empty, therefore assuming 'default'.")
   674  		s.configurationOverwrites.namespace = "default"
   675  	}
   676  
   677  	s.Println("Using Kubernetes namespace:", s.configurationOverwrites.namespace)
   678  
   679  	return nil
   680  }
   681  
   682  func createFn() common.Executor {
   683  	helperImageInfo, err := helperimage.Get(common.REVISION, helperimage.Config{
   684  		OSType:       helperimage.OSTypeLinux,
   685  		Architecture: "amd64",
   686  	})
   687  	if err != nil {
   688  		logrus.WithError(err).Fatal("Failed to set up helper image for kubernetes executor")
   689  	}
   690  
   691  	return &executor{
   692  		AbstractExecutor: executors.AbstractExecutor{
   693  			ExecutorOptions: executorOptions,
   694  		},
   695  		helperImageInfo: helperImageInfo,
   696  	}
   697  }
   698  
   699  func featuresFn(features *common.FeaturesInfo) {
   700  	features.Variables = true
   701  	features.Image = true
   702  	features.Services = true
   703  	features.Artifacts = true
   704  	features.Cache = true
   705  	features.Session = true
   706  	features.Terminal = true
   707  }
   708  
   709  func init() {
   710  	common.RegisterExecutor("kubernetes", executors.DefaultExecutorProvider{
   711  		Creator:          createFn,
   712  		FeaturesUpdater:  featuresFn,
   713  		DefaultShellName: executorOptions.Shell.Shell,
   714  	})
   715  }