From 253d702bc77079eb8c1c0ccf11736dc37225d06b Mon Sep 17 00:00:00 2001 From: Thomas Anderson <127358482+zc-devs@users.noreply.github.com> Date: Sat, 23 Dec 2023 02:42:30 +0300 Subject: [PATCH] Fix IPv6 host aliases for kubernetes (#2992) Closes #2991 [Tests](https://github.com/woodpecker-ci/woodpecker/pull/2993#issuecomment-1868048169) --------- Co-authored-by: 6543 <6543@obermui.de> --- pipeline/backend/docker/convert.go | 6 +- pipeline/backend/kubernetes/kubernetes.go | 10 +- pipeline/backend/kubernetes/pod.go | 23 +++-- pipeline/backend/kubernetes/pod_test.go | 18 +++- pipeline/backend/types/network.go | 5 + pipeline/backend/types/step.go | 2 +- .../frontend/yaml/compiler/compiler_test.go | 91 ++++++++++--------- pipeline/frontend/yaml/compiler/convert.go | 12 ++- pipeline/frontend/yaml/compiler/errors.go | 30 ++++++ .../frontend/yaml/types/container_test.go | 3 +- 10 files changed, 131 insertions(+), 69 deletions(-) create mode 100644 pipeline/frontend/yaml/compiler/errors.go diff --git a/pipeline/backend/docker/convert.go b/pipeline/backend/docker/convert.go index f841bff1e..cfe5d55e5 100644 --- a/pipeline/backend/docker/convert.go +++ b/pipeline/backend/docker/convert.go @@ -88,8 +88,12 @@ func toHostConfig(step *types.Step) *container.HostConfig { if len(step.DNSSearch) != 0 { config.DNSSearch = step.DNSSearch } + extraHosts := []string{} + for _, hostAlias := range step.ExtraHosts { + extraHosts = append(extraHosts, hostAlias.Name+":"+hostAlias.IP) + } if len(step.ExtraHosts) != 0 { - config.ExtraHosts = step.ExtraHosts + config.ExtraHosts = extraHosts } if len(step.Devices) != 0 { config.Devices = toDev(step.Devices) diff --git a/pipeline/backend/kubernetes/kubernetes.go b/pipeline/backend/kubernetes/kubernetes.go index c8accfb69..e8fb6820d 100644 --- a/pipeline/backend/kubernetes/kubernetes.go +++ b/pipeline/backend/kubernetes/kubernetes.go @@ -20,7 +20,6 @@ import ( "io" "os" "runtime" - "strings" "time" "github.com/rs/zerolog/log" @@ -166,8 +165,7 @@ func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID s } } - extraHosts := []string{} - + extraHosts := []types.HostAlias{} for _, stage := range conf.Stages { if stage.Alias == "services" { for _, step := range stage.Steps { @@ -175,12 +173,12 @@ func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID s if err != nil { return err } - extraHosts = append(extraHosts, step.Networks[0].Aliases[0]+":"+svc.Spec.ClusterIP) + hostAlias := types.HostAlias{Name: step.Networks[0].Aliases[0], IP: svc.Spec.ClusterIP} + extraHosts = append(extraHosts, hostAlias) } } } - - log.Trace().Msgf("Adding extra hosts: %s", strings.Join(extraHosts, ", ")) + log.Trace().Msgf("Adding extra hosts: %v", extraHosts) for _, stage := range conf.Stages { for _, step := range stage.Steps { step.ExtraHosts = extraHosts diff --git a/pipeline/backend/kubernetes/pod.go b/pipeline/backend/kubernetes/pod.go index 5f62bc8b2..d0adb5cc3 100644 --- a/pipeline/backend/kubernetes/pod.go +++ b/pipeline/backend/kubernetes/pod.go @@ -36,17 +36,16 @@ const ( func mkPod(namespace, name, image, workDir, goos, serviceAccountName string, pool, privileged bool, - commands, vols, extraHosts []string, + commands, vols []string, labels, annotations, env, nodeSelector map[string]string, - tolerations []types.Toleration, resources types.Resources, + extraHosts []types.HostAlias, tolerations []types.Toleration, resources types.Resources, securityContext *types.SecurityContext, securityContextConfig SecurityContextConfig, ) (*v1.Pod, error) { var err error meta := podMeta(name, namespace, labels, annotations) - spec, err := podSpec(serviceAccountName, vols, extraHosts, env, - nodeSelector, tolerations, securityContext, securityContextConfig) + spec, err := podSpec(serviceAccountName, vols, env, nodeSelector, extraHosts, tolerations, securityContext, securityContextConfig) if err != nil { return nil, err } @@ -86,7 +85,8 @@ func podMeta(name, namespace string, labels, annotations map[string]string) meta return meta } -func podSpec(serviceAccountName string, vols, extraHosts []string, env, backendNodeSelector map[string]string, backendTolerations []types.Toleration, +func podSpec(serviceAccountName string, vols []string, env, backendNodeSelector map[string]string, + extraHosts []types.HostAlias, backendTolerations []types.Toleration, securityContext *types.SecurityContext, securityContextConfig SecurityContextConfig, ) (v1.PodSpec, error) { var err error @@ -195,7 +195,7 @@ func volumeMount(name, path string) v1.VolumeMount { } // Here is the service IPs (placed in /etc/hosts in the Pod) -func hostAliases(extraHosts []string) []v1.HostAlias { +func hostAliases(extraHosts []types.HostAlias) []v1.HostAlias { hostAliases := []v1.HostAlias{} for _, extraHost := range extraHosts { hostAlias := hostAlias(extraHost) @@ -204,11 +204,10 @@ func hostAliases(extraHosts []string) []v1.HostAlias { return hostAliases } -func hostAlias(extraHost string) v1.HostAlias { - host := strings.Split(extraHost, ":") +func hostAlias(extraHost types.HostAlias) v1.HostAlias { return v1.HostAlias{ - IP: host[1], - Hostnames: []string{host[0]}, + IP: extraHost.IP, + Hostnames: []string{extraHost.Name}, } } @@ -358,9 +357,9 @@ func startPod(ctx context.Context, engine *kube, step *types.Step) (*v1.Pod, err pod, err := mkPod(engine.config.Namespace, podName, step.Image, step.WorkingDir, engine.goos, step.BackendOptions.Kubernetes.ServiceAccountName, step.Pull, step.Privileged, - step.Commands, step.Volumes, step.ExtraHosts, + step.Commands, step.Volumes, engine.config.PodLabels, engine.config.PodAnnotations, step.Environment, step.BackendOptions.Kubernetes.NodeSelector, - step.BackendOptions.Kubernetes.Tolerations, step.BackendOptions.Kubernetes.Resources, step.BackendOptions.Kubernetes.SecurityContext, engine.config.SecurityContext) + step.ExtraHosts, step.BackendOptions.Kubernetes.Tolerations, step.BackendOptions.Kubernetes.Resources, step.BackendOptions.Kubernetes.SecurityContext, engine.config.SecurityContext) if err != nil { return nil, err } diff --git a/pipeline/backend/kubernetes/pod_test.go b/pipeline/backend/kubernetes/pod_test.go index 24f7d64b7..b05b2451e 100644 --- a/pipeline/backend/kubernetes/pod_test.go +++ b/pipeline/backend/kubernetes/pod_test.go @@ -108,9 +108,9 @@ func TestTinyPod(t *testing.T) { pod, err := mkPod("woodpecker", "wp-01he8bebctabr3kgk0qj36d2me-0", "gradle:8.4.0-jdk21", "/woodpecker/src", "linux/amd64", "", false, false, - []string{"gradle build"}, []string{"workspace:/woodpecker/src"}, nil, + []string{"gradle build"}, []string{"workspace:/woodpecker/src"}, nil, nil, map[string]string{"CI": "woodpecker"}, nil, - nil, + nil, nil, types.Resources{Requests: nil, Limits: nil}, nil, SecurityContextConfig{}, ) assert.NoError(t, err) @@ -228,17 +228,27 @@ func TestFullPod(t *testing.T) { "hostnames": [ "cloudflare" ] + }, + { + "ip": "2606:4700:4700::64", + "hostnames": [ + "cf.v6" + ] } ] }, "status": {} }` + hostAliases := []types.HostAlias{ + {Name: "cloudflare", IP: "1.1.1.1"}, + {Name: "cf.v6", IP: "2606:4700:4700::64"}, + } pod, err := mkPod("woodpecker", "wp-01he8bebctabr3kgk0qj36d2me-0", "meltwater/drone-cache", "/woodpecker/src", "linux/amd64", "wp-svc-acc", true, true, - []string{"go get", "go test"}, []string{"woodpecker-cache:/woodpecker/src/cache"}, []string{"cloudflare:1.1.1.1"}, + []string{"go get", "go test"}, []string{"woodpecker-cache:/woodpecker/src/cache"}, map[string]string{"app": "test"}, map[string]string{"apparmor.security": "runtime/default"}, map[string]string{"CGO": "0"}, map[string]string{"storage": "ssd"}, - []types.Toleration{{Key: "net-port", Value: "100Mbit", Effect: types.TaintEffectNoSchedule}}, + hostAliases, []types.Toleration{{Key: "net-port", Value: "100Mbit", Effect: types.TaintEffectNoSchedule}}, types.Resources{Requests: map[string]string{"memory": "128Mi", "cpu": "1000m"}, Limits: map[string]string{"memory": "256Mi", "cpu": "2"}}, &types.SecurityContext{Privileged: newBool(true), RunAsNonRoot: newBool(true), RunAsUser: newInt64(101), RunAsGroup: newInt64(101), FSGroup: newInt64(101)}, SecurityContextConfig{RunAsNonRoot: false}, diff --git a/pipeline/backend/types/network.go b/pipeline/backend/types/network.go index 8aa027ae4..88af6ef41 100644 --- a/pipeline/backend/types/network.go +++ b/pipeline/backend/types/network.go @@ -18,3 +18,8 @@ package types type Network struct { Name string `json:"name,omitempty"` } + +type HostAlias struct { + Name string `json:"name,omitempty"` + IP string `json:"ip,omitempty"` +} diff --git a/pipeline/backend/types/step.go b/pipeline/backend/types/step.go index 71bd5a66d..0d1b600bf 100644 --- a/pipeline/backend/types/step.go +++ b/pipeline/backend/types/step.go @@ -28,7 +28,7 @@ type Step struct { Environment map[string]string `json:"environment,omitempty"` Entrypoint []string `json:"entrypoint,omitempty"` Commands []string `json:"commands,omitempty"` - ExtraHosts []string `json:"extra_hosts,omitempty"` + ExtraHosts []HostAlias `json:"extra_hosts,omitempty"` Volumes []string `json:"volumes,omitempty"` Tmpfs []string `json:"tmpfs,omitempty"` Devices []string `json:"devices,omitempty"` diff --git a/pipeline/frontend/yaml/compiler/compiler_test.go b/pipeline/frontend/yaml/compiler/compiler_test.go index 2d5ebeafd..a890044f8 100644 --- a/pipeline/frontend/yaml/compiler/compiler_test.go +++ b/pipeline/frontend/yaml/compiler/compiler_test.go @@ -82,14 +82,15 @@ func TestCompilerCompile(t *testing.T) { Name: "test_clone", Alias: "clone", Steps: []*backend_types.Step{{ - Name: "test_clone", - Alias: "clone", - Type: backend_types.StepTypeClone, - Image: constant.DefaultCloneImage, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"clone"}}}, + Name: "test_clone", + Alias: "clone", + Type: backend_types.StepTypeClone, + Image: constant.DefaultCloneImage, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolumes[0].Name + ":"}, + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"clone"}}}, + ExtraHosts: []backend_types.HostAlias{}, }}, } @@ -126,14 +127,15 @@ func TestCompilerCompile(t *testing.T) { Name: "test_stage_0", Alias: "dummy", Steps: []*backend_types.Step{{ - Name: "test_step_0", - Alias: "dummy", - Type: backend_types.StepTypePlugin, - Image: "dummy_img", - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"dummy"}}}, + Name: "test_step_0", + Alias: "dummy", + Type: backend_types.StepTypePlugin, + Image: "dummy_img", + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolumes[0].Name + ":"}, + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"dummy"}}}, + ExtraHosts: []backend_types.HostAlias{}, }}, }}, }, @@ -161,39 +163,42 @@ func TestCompilerCompile(t *testing.T) { Name: "test_stage_0", Alias: "echo env", Steps: []*backend_types.Step{{ - Name: "test_step_0", - Alias: "echo env", - Type: backend_types.StepTypeCommands, - Image: "bash", - Commands: []string{"env"}, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}}, + Name: "test_step_0", + Alias: "echo env", + Type: backend_types.StepTypeCommands, + Image: "bash", + Commands: []string{"env"}, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolumes[0].Name + ":"}, + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}}, + ExtraHosts: []backend_types.HostAlias{}, }}, }, { Name: "test_stage_1", Alias: "parallel echo 1", Steps: []*backend_types.Step{{ - Name: "test_step_1", - Alias: "parallel echo 1", - Type: backend_types.StepTypeCommands, - Image: "bash", - Commands: []string{"echo 1"}, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 1"}}}, + Name: "test_step_1", + Alias: "parallel echo 1", + Type: backend_types.StepTypeCommands, + Image: "bash", + Commands: []string{"echo 1"}, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolumes[0].Name + ":"}, + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 1"}}}, + ExtraHosts: []backend_types.HostAlias{}, }, { - Name: "test_step_2", - Alias: "parallel echo 2", - Type: backend_types.StepTypeCommands, - Image: "bash", - Commands: []string{"echo 2"}, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 2"}}}, + Name: "test_step_2", + Alias: "parallel echo 2", + Type: backend_types.StepTypeCommands, + Image: "bash", + Commands: []string{"echo 2"}, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolumes[0].Name + ":"}, + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 2"}}}, + ExtraHosts: []backend_types.HostAlias{}, }}, }}, }, diff --git a/pipeline/frontend/yaml/compiler/convert.go b/pipeline/frontend/yaml/compiler/convert.go index 07b1eec2b..186ca8f1d 100644 --- a/pipeline/frontend/yaml/compiler/convert.go +++ b/pipeline/frontend/yaml/compiler/convert.go @@ -55,6 +55,16 @@ func (c *Compiler) createProcess(name string, container *yaml_types.Container, s }) } + extraHosts := make([]backend_types.HostAlias, len(container.ExtraHosts)) + for i, extraHost := range container.ExtraHosts { + name, ip, ok := strings.Cut(extraHost, ":") + if !ok { + return nil, &ErrExtraHostFormat{host: extraHost} + } + extraHosts[i].Name = name + extraHosts[i].IP = ip + } + var volumes []string if !c.local { volumes = append(volumes, workspace) @@ -173,7 +183,7 @@ func (c *Compiler) createProcess(name string, container *yaml_types.Container, s WorkingDir: workingdir, Environment: environment, Commands: container.Commands, - ExtraHosts: container.ExtraHosts, + ExtraHosts: extraHosts, Volumes: volumes, Tmpfs: container.Tmpfs, Devices: container.Devices, diff --git a/pipeline/frontend/yaml/compiler/errors.go b/pipeline/frontend/yaml/compiler/errors.go new file mode 100644 index 000000000..c76091e33 --- /dev/null +++ b/pipeline/frontend/yaml/compiler/errors.go @@ -0,0 +1,30 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compiler + +import "fmt" + +type ErrExtraHostFormat struct { + host string +} + +func (err *ErrExtraHostFormat) Error() string { + return fmt.Sprintf("extra host %s is in wrong format", err.host) +} + +func (*ErrExtraHostFormat) Is(target error) bool { + _, ok := target.(*ErrExtraHostFormat) //nolint:errorlint + return ok +} diff --git a/pipeline/frontend/yaml/types/container_test.go b/pipeline/frontend/yaml/types/container_test.go index 429e5dbe6..7e14369f2 100644 --- a/pipeline/frontend/yaml/types/container_test.go +++ b/pipeline/frontend/yaml/types/container_test.go @@ -45,6 +45,7 @@ environment: extra_hosts: - somehost:162.242.195.82 - otherhost:50.31.209.229 + - ipv6:2001:db8::10 name: my-build-container network_mode: bridge networks: @@ -84,7 +85,7 @@ func TestUnmarshalContainer(t *testing.T) { DNS: base.StringOrSlice{"8.8.8.8"}, DNSSearch: base.StringOrSlice{"example.com"}, Environment: base.SliceOrMap{"RACK_ENV": "development", "SHOW": "true"}, - ExtraHosts: []string{"somehost:162.242.195.82", "otherhost:50.31.209.229"}, + ExtraHosts: []string{"somehost:162.242.195.82", "otherhost:50.31.209.229", "ipv6:2001:db8::10"}, Image: "golang:latest", MemLimit: base.MemStringOrInt(1024), MemSwapLimit: base.MemStringOrInt(1024),