From 40121db7e767d11689e10bd81b21055e20f9dceb Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 1 Nov 2023 03:09:07 +0100 Subject: [PATCH 1/5] let engine report platform if you run woodpecker-agent on windows and connect it to an docker deamon, there could be two different platforms posible, as you can switch from linux to windows mode and visa versa --- cli/exec/exec.go | 2 +- cmd/agent/agent.go | 26 ++++++++--------- pipeline/backend/docker/convert.go | 3 -- pipeline/backend/docker/docker.go | 35 +++++++++++++++++------ pipeline/backend/kubernetes/kubernetes.go | 13 ++++++--- pipeline/backend/local/local.go | 7 +++-- pipeline/backend/types/engine.go | 8 +++++- 7 files changed, 60 insertions(+), 34 deletions(-) diff --git a/cli/exec/exec.go b/cli/exec/exec.go index de662f7625..7703d8f175 100644 --- a/cli/exec/exec.go +++ b/cli/exec/exec.go @@ -216,7 +216,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error return err } - if err = engine.Load(backendCtx); err != nil { + if _, err = engine.Load(backendCtx); err != nil { return err } diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index aac2a34533..20c7a31f35 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -22,7 +22,6 @@ import ( "fmt" "net/http" "os" - "runtime" "strings" "sync" "time" @@ -57,8 +56,6 @@ func run(c *cli.Context) error { hostname, _ = os.Hostname() } - platform := runtime.GOOS + "/" + runtime.GOARCH - counter.Polling = c.Int("max-workflows") counter.Running = 0 @@ -155,7 +152,15 @@ func run(c *cli.Context) error { return err } - agentConfig.AgentID, err = client.RegisterAgent(ctx, platform, engine.Name(), version.String(), parallel) + // load engine (e.g. init api client) + engInfo, err := engine.Load(backendCtx) + if err != nil { + log.Error().Err(err).Msg("cannot load backend engine") + return err + } + log.Debug().Msgf("loaded %s backend engine", engine.Name()) + + agentConfig.AgentID, err = client.RegisterAgent(ctx, engInfo.Platform, engInfo.Backend, version.String(), parallel) if err != nil { return err } @@ -164,8 +169,8 @@ func run(c *cli.Context) error { labels := map[string]string{ "hostname": hostname, - "platform": platform, - "backend": engine.Name(), + "platform": engInfo.Platform, + "backend": engInfo.Backend, "repo": "*", // allow all repos by default } @@ -195,13 +200,6 @@ func run(c *cli.Context) error { } }() - // load engine (e.g. init api client) - if err := engine.Load(backendCtx); err != nil { - log.Error().Err(err).Msg("cannot load backend engine") - return err - } - log.Debug().Msgf("loaded %s backend engine", engine.Name()) - for i := 0; i < parallel; i++ { i := i go func() { @@ -226,7 +224,7 @@ func run(c *cli.Context) error { log.Info().Msgf( "Starting Woodpecker agent with version '%s' and backend '%s' using platform '%s' running up to %d pipelines in parallel", - version.String(), engine.Name(), platform, parallel) + version.String(), engInfo.Backend, engInfo.Platform, parallel) wg.Wait() return nil diff --git a/pipeline/backend/docker/convert.go b/pipeline/backend/docker/convert.go index 815b61a7a3..2db4691142 100644 --- a/pipeline/backend/docker/convert.go +++ b/pipeline/backend/docker/convert.go @@ -76,9 +76,6 @@ func toHostConfig(step *types.Step) *container.HostConfig { Sysctls: step.Sysctls, } - // if len(step.VolumesFrom) != 0 { - // config.VolumesFrom = step.VolumesFrom - // } if len(step.NetworkMode) != 0 { config.NetworkMode = container.NetworkMode(step.NetworkMode) } diff --git a/pipeline/backend/docker/docker.go b/pipeline/backend/docker/docker.go index ba255d805c..e403e940cb 100644 --- a/pipeline/backend/docker/docker.go +++ b/pipeline/backend/docker/docker.go @@ -20,7 +20,6 @@ import ( "net/http" "os" "path/filepath" - "runtime" "strings" "github.com/docker/docker/api/types" @@ -43,6 +42,7 @@ type docker struct { enableIPv6 bool network string volumes []string + info types.Info } const ( @@ -94,10 +94,10 @@ func httpClientOfOpts(dockerCertPath string, verifyTLS bool) *http.Client { } // Load new client for Docker Engine using environment variables. -func (e *docker) Load(ctx context.Context) error { +func (e *docker) Load(ctx context.Context) (*backend.EngineInfo, error) { c, ok := ctx.Value(backend.CliContext).(*cli.Context) if !ok { - return backend.ErrNoCliContextFound + return nil, backend.ErrNoCliContextFound } var dockerClientOpts []client.Opt @@ -115,10 +115,15 @@ func (e *docker) Load(ctx context.Context) error { cl, err := client.NewClientWithOpts(dockerClientOpts...) if err != nil { - return err + return nil, err } e.client = cl + e.info, err = cl.Info(ctx) + if err != nil { + return nil, err + } + e.enableIPv6 = c.Bool("backend-docker-ipv6") e.network = c.String("backend-docker-network") @@ -137,7 +142,10 @@ func (e *docker) Load(ctx context.Context) error { e.volumes = append(e.volumes, strings.Join(parts, ":")) } - return nil + return &backend.EngineInfo{ + Backend: e.Name(), + Platform: e.info.OSType + "/" + normalizeArchType(e.info.Architecture), + }, nil } func (e *docker) SetupWorkflow(_ context.Context, conf *backend.Config, taskUUID string) error { @@ -154,7 +162,7 @@ func (e *docker) SetupWorkflow(_ context.Context, conf *backend.Config, taskUUID } networkDriver := networkDriverBridge - if runtime.GOOS == "windows" { + if e.info.OSType == "windows" { networkDriver = networkDriverNAT } for _, n := range conf.Networks { @@ -262,9 +270,6 @@ func (e *docker) WaitStep(ctx context.Context, step *backend.Step, taskUUID stri if err != nil { return nil, err } - // if info.State.Running { - // TODO - // } return &backend.State{ Exited: true, @@ -344,3 +349,15 @@ func isErrContainerNotFoundOrNotRunning(err error) bool { // Error: No such container: ... return err != nil && (strings.Contains(err.Error(), "No such container") || strings.Contains(err.Error(), "is not running")) } + +// normalizeArchType converts what docker info reports als arch +// and convert it into the runtime.GOARCH format +// TODO: find out if we we need to convert other arch types too +func normalizeArchType(s string) string { + switch s { + case "x86_64": + return "amd64" + default: + return s + } +} diff --git a/pipeline/backend/kubernetes/kubernetes.go b/pipeline/backend/kubernetes/kubernetes.go index 458a3f08c0..8002221d93 100644 --- a/pipeline/backend/kubernetes/kubernetes.go +++ b/pipeline/backend/kubernetes/kubernetes.go @@ -19,6 +19,7 @@ import ( "fmt" "io" "os" + "runtime" "strings" "time" @@ -104,10 +105,10 @@ func (e *kube) IsAvailable(context.Context) bool { return len(host) > 0 } -func (e *kube) Load(context.Context) error { +func (e *kube) Load(context.Context) (*types.EngineInfo, error) { config, err := configFromCliContext(e.ctx) if err != nil { - return err + return nil, err } e.config = config @@ -120,12 +121,16 @@ func (e *kube) Load(context.Context) error { } if err != nil { - return err + return nil, err } e.client = kubeClient - return nil + return &types.EngineInfo{ + Backend: e.Name(), + // TODO: use info resp of kubeClient to define platform var + Platform: runtime.GOOS + "/" + runtime.GOARCH, + }, nil } // Setup the pipeline environment. diff --git a/pipeline/backend/local/local.go b/pipeline/backend/local/local.go index f2a68eb7d0..7f7f66fa50 100644 --- a/pipeline/backend/local/local.go +++ b/pipeline/backend/local/local.go @@ -60,10 +60,13 @@ func (e *local) IsAvailable(context.Context) bool { return true } -func (e *local) Load(context.Context) error { +func (e *local) Load(context.Context) (*types.EngineInfo, error) { e.loadClone() - return nil + return &types.EngineInfo{ + Backend: e.Name(), + Platform: runtime.GOOS + "/" + runtime.GOARCH, + }, nil } // SetupWorkflow the pipeline environment. diff --git a/pipeline/backend/types/engine.go b/pipeline/backend/types/engine.go index 0c1a39dd5f..862ebca71c 100644 --- a/pipeline/backend/types/engine.go +++ b/pipeline/backend/types/engine.go @@ -29,7 +29,7 @@ type Engine interface { IsAvailable(ctx context.Context) bool // Load the backend engine. - Load(ctx context.Context) error + Load(ctx context.Context) (*EngineInfo, error) // SetupWorkflow the workflow environment. SetupWorkflow(ctx context.Context, conf *Config, taskUUID string) error @@ -47,3 +47,9 @@ type Engine interface { // DestroyWorkflow the workflow environment. DestroyWorkflow(ctx context.Context, conf *Config, taskUUID string) error } + +// EngineInfo represents the reported information of a loaded engine +type EngineInfo struct { + Platform string + Backend string +} From 4014e1d665c278aa53531867347b665b89d0aa62 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 1 Nov 2023 03:36:57 +0100 Subject: [PATCH 2/5] dont hardcode goos for generic script generator --- pipeline/backend/common/script.go | 5 ++--- pipeline/backend/docker/convert.go | 4 ++-- pipeline/backend/docker/docker.go | 2 +- pipeline/backend/kubernetes/kubernetes.go | 8 +++++--- pipeline/backend/kubernetes/pod.go | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pipeline/backend/common/script.go b/pipeline/backend/common/script.go index 8e955b19f9..1df8742dd2 100644 --- a/pipeline/backend/common/script.go +++ b/pipeline/backend/common/script.go @@ -16,12 +16,11 @@ package common import ( "encoding/base64" - "runtime" ) -func GenerateContainerConf(commands []string) (env map[string]string, entry, cmd []string) { +func GenerateContainerConf(commands []string, goos string) (env map[string]string, entry, cmd []string) { env = make(map[string]string) - if runtime.GOOS == "windows" { + if goos == "windows" { env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptWindows(commands))) env["HOME"] = "c:\\root" env["SHELL"] = "powershell.exe" diff --git a/pipeline/backend/docker/convert.go b/pipeline/backend/docker/convert.go index 2db4691142..2b097c2e78 100644 --- a/pipeline/backend/docker/convert.go +++ b/pipeline/backend/docker/convert.go @@ -27,7 +27,7 @@ import ( ) // returns a container configuration. -func toConfig(step *types.Step) *container.Config { +func (e *docker) toConfig(step *types.Step) *container.Config { config := &container.Config{ Image: step.Image, Labels: map[string]string{"wp_uuid": step.UUID}, @@ -37,7 +37,7 @@ func toConfig(step *types.Step) *container.Config { } if len(step.Commands) != 0 { - env, entry, cmd := common.GenerateContainerConf(step.Commands) + env, entry, cmd := common.GenerateContainerConf(step.Commands, e.info.OSType) for k, v := range env { step.Environment[k] = v } diff --git a/pipeline/backend/docker/docker.go b/pipeline/backend/docker/docker.go index e403e940cb..0c31ed8ea4 100644 --- a/pipeline/backend/docker/docker.go +++ b/pipeline/backend/docker/docker.go @@ -180,7 +180,7 @@ func (e *docker) SetupWorkflow(_ context.Context, conf *backend.Config, taskUUID func (e *docker) StartStep(ctx context.Context, step *backend.Step, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msgf("start step %s", step.Name) - config := toConfig(step) + config := e.toConfig(step) hostConfig := toHostConfig(step) containerName := toContainerName(step) diff --git a/pipeline/backend/kubernetes/kubernetes.go b/pipeline/backend/kubernetes/kubernetes.go index 8002221d93..23bf43985a 100644 --- a/pipeline/backend/kubernetes/kubernetes.go +++ b/pipeline/backend/kubernetes/kubernetes.go @@ -47,6 +47,7 @@ type kube struct { ctx context.Context client kubernetes.Interface config *Config + goos string } type Config struct { @@ -126,9 +127,10 @@ func (e *kube) Load(context.Context) (*types.EngineInfo, error) { e.client = kubeClient + // TODO: use info resp of kubeClient to define platform var + e.goos = runtime.GOOS return &types.EngineInfo{ - Backend: e.Name(), - // TODO: use info resp of kubeClient to define platform var + Backend: e.Name(), Platform: runtime.GOOS + "/" + runtime.GOARCH, }, nil } @@ -188,7 +190,7 @@ func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID s // Start the pipeline step. func (e *kube) StartStep(ctx context.Context, step *types.Step, taskUUID string) error { - pod, err := Pod(e.config.Namespace, step, e.config.PodLabels, e.config.PodAnnotations) + pod, err := Pod(e.config.Namespace, step, e.config.PodLabels, e.config.PodAnnotations, e.goos) if err != nil { return err } diff --git a/pipeline/backend/kubernetes/pod.go b/pipeline/backend/kubernetes/pod.go index 09e0b6d79f..6671d1dcdb 100644 --- a/pipeline/backend/kubernetes/pod.go +++ b/pipeline/backend/kubernetes/pod.go @@ -28,7 +28,7 @@ import ( "github.com/woodpecker-ci/woodpecker/pipeline/backend/types" ) -func Pod(namespace string, step *types.Step, labels, annotations map[string]string) (*v1.Pod, error) { +func Pod(namespace string, step *types.Step, labels, annotations map[string]string, goos string) (*v1.Pod, error) { var ( vols []v1.Volume volMounts []v1.VolumeMount @@ -66,7 +66,7 @@ func Pod(namespace string, step *types.Step, labels, annotations map[string]stri } if len(step.Commands) != 0 { - scriptEnv, entry, cmds := common.GenerateContainerConf(step.Commands) + scriptEnv, entry, cmds := common.GenerateContainerConf(step.Commands, goos) for k, v := range scriptEnv { step.Environment[k] = v } From 213fded3f32d4faf55d66185b58d117a3dc1b95a Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 1 Nov 2023 11:12:56 +0100 Subject: [PATCH 3/5] Update pipeline/backend/docker/docker.go Co-authored-by: Anbraten --- pipeline/backend/docker/docker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pipeline/backend/docker/docker.go b/pipeline/backend/docker/docker.go index 0c31ed8ea4..e8fd893686 100644 --- a/pipeline/backend/docker/docker.go +++ b/pipeline/backend/docker/docker.go @@ -350,8 +350,8 @@ func isErrContainerNotFoundOrNotRunning(err error) bool { return err != nil && (strings.Contains(err.Error(), "No such container") || strings.Contains(err.Error(), "is not running")) } -// normalizeArchType converts what docker info reports als arch -// and convert it into the runtime.GOARCH format +// normalizeArchType converts the arch type reported by docker info into +// the runtime.GOARCH format // TODO: find out if we we need to convert other arch types too func normalizeArchType(s string) string { switch s { From 597627a26c434a3c794d917538b9ac64afe87aa1 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 1 Nov 2023 13:34:42 +0100 Subject: [PATCH 4/5] revert and add a test --- cmd/agent/agent.go | 6 +-- pipeline/backend/common/script_win_test.go | 58 ++++++++++++++++++++++ pipeline/backend/docker/docker.go | 1 - pipeline/backend/kubernetes/kubernetes.go | 1 - pipeline/backend/local/local.go | 1 - pipeline/backend/types/engine.go | 1 - 6 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 pipeline/backend/common/script_win_test.go diff --git a/cmd/agent/agent.go b/cmd/agent/agent.go index 20c7a31f35..989e55c849 100644 --- a/cmd/agent/agent.go +++ b/cmd/agent/agent.go @@ -160,7 +160,7 @@ func run(c *cli.Context) error { } log.Debug().Msgf("loaded %s backend engine", engine.Name()) - agentConfig.AgentID, err = client.RegisterAgent(ctx, engInfo.Platform, engInfo.Backend, version.String(), parallel) + agentConfig.AgentID, err = client.RegisterAgent(ctx, engInfo.Platform, engine.Name(), version.String(), parallel) if err != nil { return err } @@ -170,7 +170,7 @@ func run(c *cli.Context) error { labels := map[string]string{ "hostname": hostname, "platform": engInfo.Platform, - "backend": engInfo.Backend, + "backend": engine.Name(), "repo": "*", // allow all repos by default } @@ -224,7 +224,7 @@ func run(c *cli.Context) error { log.Info().Msgf( "Starting Woodpecker agent with version '%s' and backend '%s' using platform '%s' running up to %d pipelines in parallel", - version.String(), engInfo.Backend, engInfo.Platform, parallel) + version.String(), engine.Name(), engInfo.Platform, parallel) wg.Wait() return nil diff --git a/pipeline/backend/common/script_win_test.go b/pipeline/backend/common/script_win_test.go new file mode 100644 index 0000000000..bbd1bfb41d --- /dev/null +++ b/pipeline/backend/common/script_win_test.go @@ -0,0 +1,58 @@ +// Copyright 2022 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 common + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerateScriptWin(t *testing.T) { + testdata := []struct { + from []string + want string + }{ + { + from: []string{"echo %PATH%", "go build", "go test"}, + want: ` +$ErrorActionPreference = 'Stop'; +&cmd /c "mkdir c:\root"; +if ($Env:CI_NETRC_MACHINE) { +$netrc=[string]::Format("{0}\_netrc",$Env:HOME); +"machine $Env:CI_NETRC_MACHINE" >> $netrc; +"login $Env:CI_NETRC_USERNAME" >> $netrc; +"password $Env:CI_NETRC_PASSWORD" >> $netrc; +}; +[Environment]::SetEnvironmentVariable("CI_NETRC_PASSWORD",$null); +[Environment]::SetEnvironmentVariable("CI_SCRIPT",$null); + +Write-Output ('+ "echo %PATH%"'); +& echo %PATH%; if ($LASTEXITCODE -ne 0) {exit $LASTEXITCODE} + +Write-Output ('+ "go build"'); +& go build; if ($LASTEXITCODE -ne 0) {exit $LASTEXITCODE} + +Write-Output ('+ "go test"'); +& go test; if ($LASTEXITCODE -ne 0) {exit $LASTEXITCODE} + +`, + }, + } + for _, test := range testdata { + script := generateScriptWindows(test.from) + assert.EqualValues(t, test.want, script, "Want encoded script for %s", test.from) + } +} diff --git a/pipeline/backend/docker/docker.go b/pipeline/backend/docker/docker.go index 8007be4dc5..4e180c7c66 100644 --- a/pipeline/backend/docker/docker.go +++ b/pipeline/backend/docker/docker.go @@ -143,7 +143,6 @@ func (e *docker) Load(ctx context.Context) (*backend.EngineInfo, error) { } return &backend.EngineInfo{ - Backend: e.Name(), Platform: e.info.OSType + "/" + normalizeArchType(e.info.Architecture), }, nil } diff --git a/pipeline/backend/kubernetes/kubernetes.go b/pipeline/backend/kubernetes/kubernetes.go index 821ed2bda8..520cbf6b6d 100644 --- a/pipeline/backend/kubernetes/kubernetes.go +++ b/pipeline/backend/kubernetes/kubernetes.go @@ -130,7 +130,6 @@ func (e *kube) Load(context.Context) (*types.EngineInfo, error) { // TODO: use info resp of kubeClient to define platform var e.goos = runtime.GOOS return &types.EngineInfo{ - Backend: e.Name(), Platform: runtime.GOOS + "/" + runtime.GOARCH, }, nil } diff --git a/pipeline/backend/local/local.go b/pipeline/backend/local/local.go index 17c80bcb43..5543fed0ec 100644 --- a/pipeline/backend/local/local.go +++ b/pipeline/backend/local/local.go @@ -64,7 +64,6 @@ func (e *local) Load(context.Context) (*types.EngineInfo, error) { e.loadClone() return &types.EngineInfo{ - Backend: e.Name(), Platform: runtime.GOOS + "/" + runtime.GOARCH, }, nil } diff --git a/pipeline/backend/types/engine.go b/pipeline/backend/types/engine.go index c1e39f1a15..d35abeb47b 100644 --- a/pipeline/backend/types/engine.go +++ b/pipeline/backend/types/engine.go @@ -54,5 +54,4 @@ type Engine interface { // EngineInfo represents the reported information of a loaded engine type EngineInfo struct { Platform string - Backend string } From 21541402a158206b22b9a98c1d2890668473ddbd Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 1 Nov 2023 13:38:22 +0100 Subject: [PATCH 5/5] Update pipeline/backend/kubernetes/kubernetes.go --- pipeline/backend/kubernetes/kubernetes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline/backend/kubernetes/kubernetes.go b/pipeline/backend/kubernetes/kubernetes.go index 520cbf6b6d..263b55a275 100644 --- a/pipeline/backend/kubernetes/kubernetes.go +++ b/pipeline/backend/kubernetes/kubernetes.go @@ -127,7 +127,7 @@ func (e *kube) Load(context.Context) (*types.EngineInfo, error) { e.client = kubeClient - // TODO: use info resp of kubeClient to define platform var + // TODO(2693): use info resp of kubeClient to define platform var e.goos = runtime.GOOS return &types.EngineInfo{ Platform: runtime.GOOS + "/" + runtime.GOARCH,