Skip to content
Snippets Groups Projects
Unverified Commit c55eaaa9 authored by Tomasz Maczukin's avatar Tomasz Maczukin
Browse files

Support extended docker configuration with Kubernetes executor

parent 3144a467
Branches
Tags
No related merge requests found
...@@ -150,8 +150,8 @@ type Step struct { ...@@ -150,8 +150,8 @@ type Step struct {
type Steps []Step type Steps []Step
type Image struct { type Image struct {
Name string `json:"name"` Name string `json:"name"`
Alias string `json:"alias,omitempty"` Alias string `json:"alias,omitempty"`
Command []string `json:"command,omitempty"` Command []string `json:"command,omitempty"`
Entrypoint []string `json:"entrypoint,omitempty"` Entrypoint []string `json:"entrypoint,omitempty"`
} }
......
...@@ -28,8 +28,8 @@ var ( ...@@ -28,8 +28,8 @@ var (
) )
type kubernetesOptions struct { type kubernetesOptions struct {
Image string `json:"image"` Image common.Image
Services []string `json:"services"` Services common.Services
} }
type executor struct { type executor struct {
...@@ -117,7 +117,7 @@ func (s *executor) Prepare(options common.ExecutorPrepareOptions) (err error) { ...@@ -117,7 +117,7 @@ func (s *executor) Prepare(options common.ExecutorPrepareOptions) (err error) {
return err 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 return nil
} }
...@@ -174,17 +174,28 @@ func (s *executor) Cleanup() { ...@@ -174,17 +174,28 @@ func (s *executor) Cleanup() {
s.AbstractExecutor.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 privileged := false
if s.Config.Kubernetes != nil { if s.Config.Kubernetes != nil {
privileged = s.Config.Kubernetes.Privileged 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{ return api.Container{
Name: name, Name: name,
Image: image, Image: image,
ImagePullPolicy: api.PullPolicy(s.pullPolicy), ImagePullPolicy: api.PullPolicy(s.pullPolicy),
Command: command, Command: command,
Args: args,
Env: buildVariables(s.Build.GetAllVariables().PublicOrInternal()), Env: buildVariables(s.Build.GetAllVariables().PublicOrInternal()),
Resources: api.ResourceRequirements{ Resources: api.ResourceRequirements{
Limits: limits, Limits: limits,
...@@ -357,9 +368,9 @@ func (s *executor) setupCredentials() error { ...@@ -357,9 +368,9 @@ func (s *executor) setupCredentials() error {
func (s *executor) setupBuildPod() error { func (s *executor) setupBuildPod() error {
services := make([]api.Container, len(s.options.Services)) services := make([]api.Container, len(s.options.Services))
for i, image := range s.options.Services { for i, service := range s.options.Services {
resolvedImage := s.Build.GetAllVariables().ExpandValue(image) resolvedImage := s.Build.GetAllVariables().ExpandValue(service.Name)
services[i] = s.buildContainer(fmt.Sprintf("svc-%d", i), resolvedImage, s.serviceRequests, s.serviceLimits) services[i] = s.buildContainer(fmt.Sprintf("svc-%d", i), resolvedImage, service, s.serviceRequests, s.serviceLimits)
} }
labels := make(map[string]string) labels := make(map[string]string)
for k, v := range s.Build.Runner.Kubernetes.PodLabels { for k, v := range s.Build.Runner.Kubernetes.PodLabels {
...@@ -375,7 +386,7 @@ func (s *executor) setupBuildPod() error { ...@@ -375,7 +386,7 @@ func (s *executor) setupBuildPod() error {
imagePullSecrets = append(imagePullSecrets, api.LocalObjectReference{Name: s.credentials.Name}) 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{ pod, err := s.kubeClient.Pods(s.Config.Kubernetes.Namespace).Create(&api.Pod{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
GenerateName: s.Build.ProjectUniqueName(), GenerateName: s.Build.ProjectUniqueName(),
...@@ -389,8 +400,8 @@ func (s *executor) setupBuildPod() error { ...@@ -389,8 +400,8 @@ func (s *executor) setupBuildPod() error {
NodeSelector: s.Config.Kubernetes.NodeSelector, NodeSelector: s.Config.Kubernetes.NodeSelector,
Containers: append([]api.Container{ Containers: append([]api.Container{
// TODO use the build and helper template here // TODO use the build and helper template here
s.buildContainer("build", buildImage, s.buildRequests, s.buildLimits, s.BuildShell.DockerCommand...), s.buildContainer("build", buildImage, s.options.Image, s.buildRequests, s.buildLimits, s.BuildShell.DockerCommand...),
s.buildContainer("helper", s.Config.Kubernetes.GetHelperImage(), s.helperRequests, s.helperLimits, s.BuildShell.DockerCommand...), s.buildContainer("helper", s.Config.Kubernetes.GetHelperImage(), common.Image{}, s.helperRequests, s.helperLimits, s.BuildShell.DockerCommand...),
}, services...), }, services...),
TerminationGracePeriodSeconds: &s.Config.Kubernetes.TerminationGracePeriodSeconds, TerminationGracePeriodSeconds: &s.Config.Kubernetes.TerminationGracePeriodSeconds,
ImagePullSecrets: imagePullSecrets, ImagePullSecrets: imagePullSecrets,
...@@ -452,25 +463,25 @@ func (s *executor) runInContainer(ctx context.Context, name, command string) <-c ...@@ -452,25 +463,25 @@ func (s *executor) runInContainer(ctx context.Context, name, command string) <-c
func (s *executor) prepareOptions(job *common.Build) { func (s *executor) prepareOptions(job *common.Build) {
s.options = &kubernetesOptions{} s.options = &kubernetesOptions{}
s.options.Image = job.Image.Name s.options.Image = job.Image
for _, service := range job.Services { for _, service := range job.Services {
serviceName := service.Name if service.Name == "" {
if serviceName == "" {
continue continue
} }
s.options.Services = append(s.options.Services, service)
s.options.Services = append(s.options.Services, serviceName)
} }
} }
// checkDefaults Defines the configuration for the Pod on Kubernetes // checkDefaults Defines the configuration for the Pod on Kubernetes
func (s *executor) checkDefaults() error { func (s *executor) checkDefaults() error {
if s.options.Image == "" { if s.options.Image.Name == "" {
if s.Config.Kubernetes.Image == "" { if s.Config.Kubernetes.Image == "" {
return fmt.Errorf("no image specified and no default set in config") 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 == "" { if s.Config.Kubernetes.Namespace == "" {
......
...@@ -385,7 +385,9 @@ func TestPrepare(t *testing.T) { ...@@ -385,7 +385,9 @@ func TestPrepare(t *testing.T) {
}, },
Expected: &executor{ Expected: &executor{
options: &kubernetesOptions{ options: &kubernetesOptions{
Image: "test-image", Image: common.Image{
Name: "test-image",
},
}, },
namespaceOverwrite: "", namespaceOverwrite: "",
serviceLimits: api.ResourceList{ serviceLimits: api.ResourceList{
...@@ -446,7 +448,9 @@ func TestPrepare(t *testing.T) { ...@@ -446,7 +448,9 @@ func TestPrepare(t *testing.T) {
}, },
Expected: &executor{ Expected: &executor{
options: &kubernetesOptions{ options: &kubernetesOptions{
Image: "test-image", Image: common.Image{
Name: "test-image",
},
}, },
serviceAccountOverwrite: "not-default", serviceAccountOverwrite: "not-default",
serviceLimits: api.ResourceList{ serviceLimits: api.ResourceList{
...@@ -517,7 +521,9 @@ func TestPrepare(t *testing.T) { ...@@ -517,7 +521,9 @@ func TestPrepare(t *testing.T) {
}, },
Expected: &executor{ Expected: &executor{
options: &kubernetesOptions{ options: &kubernetesOptions{
Image: "test-image", Image: common.Image{
Name: "test-image",
},
}, },
namespaceOverwrite: "namespacee", namespaceOverwrite: "namespacee",
serviceLimits: api.ResourceList{ serviceLimits: api.ResourceList{
...@@ -589,7 +595,9 @@ func TestPrepare(t *testing.T) { ...@@ -589,7 +595,9 @@ func TestPrepare(t *testing.T) {
}, },
Expected: &executor{ Expected: &executor{
options: &kubernetesOptions{ options: &kubernetesOptions{
Image: "test-image", Image: common.Image{
Name: "test-image",
},
}, },
namespaceOverwrite: "namespacee", namespaceOverwrite: "namespacee",
serviceLimits: api.ResourceList{ serviceLimits: api.ResourceList{
...@@ -645,7 +653,9 @@ func TestPrepare(t *testing.T) { ...@@ -645,7 +653,9 @@ func TestPrepare(t *testing.T) {
}, },
Expected: &executor{ Expected: &executor{
options: &kubernetesOptions{ options: &kubernetesOptions{
Image: "test-image", Image: common.Image{
Name: "test-image",
},
}, },
namespaceOverwrite: "", namespaceOverwrite: "",
serviceLimits: api.ResourceList{}, serviceLimits: api.ResourceList{},
...@@ -676,7 +686,60 @@ func TestPrepare(t *testing.T) { ...@@ -676,7 +686,60 @@ func TestPrepare(t *testing.T) {
}, },
Expected: &executor{ Expected: &executor{
options: &kubernetesOptions{ 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: "", namespaceOverwrite: "",
serviceLimits: api.ResourceList{}, serviceLimits: api.ResourceList{},
...@@ -852,6 +915,7 @@ func TestSetupBuildPod(t *testing.T) { ...@@ -852,6 +915,7 @@ func TestSetupBuildPod(t *testing.T) {
type testDef struct { type testDef struct {
RunnerConfig common.RunnerConfig RunnerConfig common.RunnerConfig
Options *kubernetesOptions
PrepareFn func(*testing.T, testDef, *executor) PrepareFn func(*testing.T, testDef, *executor)
VerifyFn func(*testing.T, testDef, *api.Pod) VerifyFn func(*testing.T, testDef, *api.Pod)
Variables []common.JobVariable Variables []common.JobVariable
...@@ -968,6 +1032,47 @@ func TestSetupBuildPod(t *testing.T) { ...@@ -968,6 +1032,47 @@ func TestSetupBuildPod(t *testing.T) {
{Key: "test", Value: "sometestvar"}, {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 executed := false
...@@ -1014,9 +1119,14 @@ func TestSetupBuildPod(t *testing.T) { ...@@ -1014,9 +1119,14 @@ func TestSetupBuildPod(t *testing.T) {
if vars == nil { if vars == nil {
vars = []common.JobVariable{} vars = []common.JobVariable{}
} }
options := test.Options
if options == nil {
options = &kubernetesOptions{}
}
ex := executor{ ex := executor{
kubeClient: c, kubeClient: c,
options: &kubernetesOptions{}, options: options,
AbstractExecutor: executors.AbstractExecutor{ AbstractExecutor: executors.AbstractExecutor{
Config: test.RunnerConfig, Config: test.RunnerConfig,
BuildShell: &common.ShellConfiguration{}, BuildShell: &common.ShellConfiguration{},
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment