From c55eaaa9788ff38e146e2b638e04483ff514272c Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin <tomasz@maczukin.pl> Date: Fri, 14 Jul 2017 21:32:59 +0200 Subject: [PATCH] Support extended docker configuration with Kubernetes executor --- common/network.go | 4 +- executors/kubernetes/executor_kubernetes.go | 45 ++++--- .../kubernetes/executor_kubernetes_test.go | 124 +++++++++++++++++- 3 files changed, 147 insertions(+), 26 deletions(-) diff --git a/common/network.go b/common/network.go index 3f9f52a5c..462403a24 100644 --- a/common/network.go +++ b/common/network.go @@ -150,8 +150,8 @@ type Step struct { type Steps []Step type Image struct { - Name string `json:"name"` - Alias string `json:"alias,omitempty"` + Name string `json:"name"` + Alias string `json:"alias,omitempty"` Command []string `json:"command,omitempty"` Entrypoint []string `json:"entrypoint,omitempty"` } diff --git a/executors/kubernetes/executor_kubernetes.go b/executors/kubernetes/executor_kubernetes.go index 704da9f96..e021ccfa4 100644 --- a/executors/kubernetes/executor_kubernetes.go +++ b/executors/kubernetes/executor_kubernetes.go @@ -28,8 +28,8 @@ var ( ) type kubernetesOptions struct { - Image string `json:"image"` - Services []string `json:"services"` + Image common.Image + Services common.Services } type executor struct { @@ -117,7 +117,7 @@ func (s *executor) Prepare(options common.ExecutorPrepareOptions) (err error) { return err } - s.Println("Using Kubernetes executor with image", s.options.Image, "...") + s.Println("Using Kubernetes executor with image", s.options.Image.Name, "...") return nil } @@ -174,17 +174,28 @@ func (s *executor) Cleanup() { s.AbstractExecutor.Cleanup() } -func (s *executor) buildContainer(name, image string, requests, limits api.ResourceList, command ...string) api.Container { +func (s *executor) buildContainer(name, image string, imageDefinition common.Image, requests, limits api.ResourceList, command ...string) api.Container { privileged := false if s.Config.Kubernetes != nil { privileged = s.Config.Kubernetes.Privileged } + if len(command) == 0 && len(imageDefinition.Command) > 0 { + command = imageDefinition.Command + } + + var args []string + if len(imageDefinition.Entrypoint) > 0 { + args = command + command = imageDefinition.Entrypoint + } + return api.Container{ Name: name, Image: image, ImagePullPolicy: api.PullPolicy(s.pullPolicy), Command: command, + Args: args, Env: buildVariables(s.Build.GetAllVariables().PublicOrInternal()), Resources: api.ResourceRequirements{ Limits: limits, @@ -357,9 +368,9 @@ func (s *executor) setupCredentials() error { func (s *executor) setupBuildPod() error { services := make([]api.Container, len(s.options.Services)) - for i, image := range s.options.Services { - resolvedImage := s.Build.GetAllVariables().ExpandValue(image) - services[i] = s.buildContainer(fmt.Sprintf("svc-%d", i), resolvedImage, s.serviceRequests, s.serviceLimits) + for i, service := range s.options.Services { + resolvedImage := s.Build.GetAllVariables().ExpandValue(service.Name) + services[i] = s.buildContainer(fmt.Sprintf("svc-%d", i), resolvedImage, service, s.serviceRequests, s.serviceLimits) } labels := make(map[string]string) for k, v := range s.Build.Runner.Kubernetes.PodLabels { @@ -375,7 +386,7 @@ func (s *executor) setupBuildPod() error { imagePullSecrets = append(imagePullSecrets, api.LocalObjectReference{Name: s.credentials.Name}) } - buildImage := s.Build.GetAllVariables().ExpandValue(s.options.Image) + buildImage := s.Build.GetAllVariables().ExpandValue(s.options.Image.Name) pod, err := s.kubeClient.Pods(s.Config.Kubernetes.Namespace).Create(&api.Pod{ ObjectMeta: api.ObjectMeta{ GenerateName: s.Build.ProjectUniqueName(), @@ -389,8 +400,8 @@ func (s *executor) setupBuildPod() error { NodeSelector: s.Config.Kubernetes.NodeSelector, Containers: append([]api.Container{ // TODO use the build and helper template here - s.buildContainer("build", buildImage, s.buildRequests, s.buildLimits, s.BuildShell.DockerCommand...), - s.buildContainer("helper", s.Config.Kubernetes.GetHelperImage(), s.helperRequests, s.helperLimits, s.BuildShell.DockerCommand...), + s.buildContainer("build", buildImage, s.options.Image, s.buildRequests, s.buildLimits, s.BuildShell.DockerCommand...), + s.buildContainer("helper", s.Config.Kubernetes.GetHelperImage(), common.Image{}, s.helperRequests, s.helperLimits, s.BuildShell.DockerCommand...), }, services...), TerminationGracePeriodSeconds: &s.Config.Kubernetes.TerminationGracePeriodSeconds, ImagePullSecrets: imagePullSecrets, @@ -452,25 +463,25 @@ func (s *executor) runInContainer(ctx context.Context, name, command string) <-c func (s *executor) prepareOptions(job *common.Build) { s.options = &kubernetesOptions{} - s.options.Image = job.Image.Name + s.options.Image = job.Image for _, service := range job.Services { - serviceName := service.Name - if serviceName == "" { + if service.Name == "" { continue } - - s.options.Services = append(s.options.Services, serviceName) + s.options.Services = append(s.options.Services, service) } } // checkDefaults Defines the configuration for the Pod on Kubernetes func (s *executor) checkDefaults() error { - if s.options.Image == "" { + if s.options.Image.Name == "" { if s.Config.Kubernetes.Image == "" { return fmt.Errorf("no image specified and no default set in config") } - s.options.Image = s.Config.Kubernetes.Image + s.options.Image = common.Image{ + Name: s.Config.Kubernetes.Image, + } } if s.Config.Kubernetes.Namespace == "" { diff --git a/executors/kubernetes/executor_kubernetes_test.go b/executors/kubernetes/executor_kubernetes_test.go index f8392c26b..6d7a89dee 100644 --- a/executors/kubernetes/executor_kubernetes_test.go +++ b/executors/kubernetes/executor_kubernetes_test.go @@ -385,7 +385,9 @@ func TestPrepare(t *testing.T) { }, Expected: &executor{ options: &kubernetesOptions{ - Image: "test-image", + Image: common.Image{ + Name: "test-image", + }, }, namespaceOverwrite: "", serviceLimits: api.ResourceList{ @@ -446,7 +448,9 @@ func TestPrepare(t *testing.T) { }, Expected: &executor{ options: &kubernetesOptions{ - Image: "test-image", + Image: common.Image{ + Name: "test-image", + }, }, serviceAccountOverwrite: "not-default", serviceLimits: api.ResourceList{ @@ -517,7 +521,9 @@ func TestPrepare(t *testing.T) { }, Expected: &executor{ options: &kubernetesOptions{ - Image: "test-image", + Image: common.Image{ + Name: "test-image", + }, }, namespaceOverwrite: "namespacee", serviceLimits: api.ResourceList{ @@ -589,7 +595,9 @@ func TestPrepare(t *testing.T) { }, Expected: &executor{ options: &kubernetesOptions{ - Image: "test-image", + Image: common.Image{ + Name: "test-image", + }, }, namespaceOverwrite: "namespacee", serviceLimits: api.ResourceList{ @@ -645,7 +653,9 @@ func TestPrepare(t *testing.T) { }, Expected: &executor{ options: &kubernetesOptions{ - Image: "test-image", + Image: common.Image{ + Name: "test-image", + }, }, namespaceOverwrite: "", serviceLimits: api.ResourceList{}, @@ -676,7 +686,60 @@ func TestPrepare(t *testing.T) { }, Expected: &executor{ options: &kubernetesOptions{ - Image: "test-image", + Image: common.Image{ + Name: "test-image", + }, + }, + namespaceOverwrite: "", + serviceLimits: api.ResourceList{}, + buildLimits: api.ResourceList{}, + helperLimits: api.ResourceList{}, + serviceRequests: api.ResourceList{}, + buildRequests: api.ResourceList{}, + helperRequests: api.ResourceList{}, + }, + }, + { + GlobalConfig: &common.Config{}, + RunnerConfig: &common.RunnerConfig{ + RunnerSettings: common.RunnerSettings{ + Kubernetes: &common.KubernetesConfig{ + Host: "test-server", + }, + }, + }, + Build: &common.Build{ + JobResponse: common.JobResponse{ + GitInfo: common.GitInfo{ + Sha: "1234567890", + }, + Image: common.Image{ + Name: "test-image", + Entrypoint: []string{"/init", "run"}, + }, + Services: common.Services{ + { + Name: "test-service", + Entrypoint: []string{"/init", "run"}, + Command: []string{"application", "--debug"}, + }, + }, + }, + Runner: &common.RunnerConfig{}, + }, + Expected: &executor{ + options: &kubernetesOptions{ + Image: common.Image{ + Name: "test-image", + Entrypoint: []string{"/init", "run"}, + }, + Services: common.Services{ + { + Name: "test-service", + Entrypoint: []string{"/init", "run"}, + Command: []string{"application", "--debug"}, + }, + }, }, namespaceOverwrite: "", serviceLimits: api.ResourceList{}, @@ -852,6 +915,7 @@ func TestSetupBuildPod(t *testing.T) { type testDef struct { RunnerConfig common.RunnerConfig + Options *kubernetesOptions PrepareFn func(*testing.T, testDef, *executor) VerifyFn func(*testing.T, testDef, *api.Pod) Variables []common.JobVariable @@ -968,6 +1032,47 @@ func TestSetupBuildPod(t *testing.T) { {Key: "test", Value: "sometestvar"}, }, }, + { + RunnerConfig: common.RunnerConfig{ + RunnerSettings: common.RunnerSettings{ + Kubernetes: &common.KubernetesConfig{ + Namespace: "default", + HelperImage: "custom/helper-image", + }, + }, + }, + Options: &kubernetesOptions{ + Image: common.Image{ + Name: "test-image", + Entrypoint: []string{"/init", "run"}, + }, + Services: common.Services{ + { + Name: "test-service", + Entrypoint: []string{"/init", "run"}, + Command: []string{"application", "--debug"}, + }, + }, + }, + VerifyFn: func(t *testing.T, test testDef, pod *api.Pod) { + require.Len(t, pod.Spec.Containers, 3) + + assert.Equal(t, pod.Spec.Containers[0].Name, "build") + assert.Equal(t, pod.Spec.Containers[0].Image, "test-image") + assert.Equal(t, pod.Spec.Containers[0].Command, []string{"/init", "run"}) + assert.Empty(t, pod.Spec.Containers[0].Args, "Build container args should be empty") + + assert.Equal(t, pod.Spec.Containers[1].Name, "helper") + assert.Equal(t, pod.Spec.Containers[1].Image, "custom/helper-image") + assert.Empty(t, pod.Spec.Containers[1].Command, "Helper container command should be empty") + assert.Empty(t, pod.Spec.Containers[1].Args, "Helper container args should be empty") + + assert.Equal(t, pod.Spec.Containers[2].Name, "svc-0") + assert.Equal(t, pod.Spec.Containers[2].Image, "test-service") + assert.Equal(t, pod.Spec.Containers[2].Command, []string{"/init", "run"}) + assert.Equal(t, pod.Spec.Containers[2].Args, []string{"application", "--debug"}) + }, + }, } executed := false @@ -1014,9 +1119,14 @@ func TestSetupBuildPod(t *testing.T) { if vars == nil { vars = []common.JobVariable{} } + + options := test.Options + if options == nil { + options = &kubernetesOptions{} + } ex := executor{ kubeClient: c, - options: &kubernetesOptions{}, + options: options, AbstractExecutor: executors.AbstractExecutor{ Config: test.RunnerConfig, BuildShell: &common.ShellConfiguration{}, -- GitLab