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 }