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

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