From cccabbc5986dff66c6f4d0b8525367ffc3e13e2c Mon Sep 17 00:00:00 2001 From: ecrupper Date: Fri, 12 Jan 2024 09:26:15 -0600 Subject: [PATCH 01/15] init commit --- Dockerfile | 2 +- cmd/vela-worker/exec.go | 15 ++- executor/linux/build.go | 82 ++++++++++++++- executor/linux/linux.go | 18 ++-- executor/linux/opts.go | 11 ++ executor/linux/outputs.go | 179 ++++++++++++++++++++++++++++++++ executor/linux/step.go | 12 +-- executor/local/build.go | 2 +- executor/setup.go | 3 + runtime/docker/container.go | 27 +++++ runtime/docker/image.go | 14 +-- runtime/docker/volume.go | 10 +- runtime/engine.go | 3 +- runtime/kubernetes/container.go | 6 ++ runtime/kubernetes/volume.go | 4 +- 15 files changed, 353 insertions(+), 35 deletions(-) create mode 100644 executor/linux/outputs.go diff --git a/Dockerfile b/Dockerfile index 4a57a287..eada4af7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM alpine:3.18.4@sha256:eece025e432126ce23f223450a0326fbebde39cdf496a85d8c0162 RUN apk add --update --no-cache ca-certificates -FROM scratch +FROM alpine COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt diff --git a/cmd/vela-worker/exec.go b/cmd/vela-worker/exec.go index a26ddd5f..8961bd25 100644 --- a/cmd/vela-worker/exec.go +++ b/cmd/vela-worker/exec.go @@ -5,6 +5,7 @@ package main import ( "context" "encoding/json" + "fmt" "net/http" "strconv" "sync" @@ -83,9 +84,9 @@ func (w *Worker) exec(index int, config *library.Worker) error { } // get the build pipeline from the build executable - pipeline := new(pipeline.Build) + p := new(pipeline.Build) - err = json.Unmarshal(execBuildExecutable.GetData(), pipeline) + err = json.Unmarshal(execBuildExecutable.GetData(), p) if err != nil { return err } @@ -164,6 +165,13 @@ func (w *Worker) exec(index int, config *library.Worker) error { return err } + outputCtn := &pipeline.Container{ + ID: fmt.Sprintf("outputs_%s", p.ID), + Detach: true, + Image: "alpine:latest", + Environment: make(map[string]string), + } + // setup the executor // // https://godoc.org/github.com/go-vela/worker/executor#New @@ -179,10 +187,11 @@ func (w *Worker) exec(index int, config *library.Worker) error { Hostname: w.Config.API.Address.Hostname(), Runtime: w.Runtime, Build: item.Build, - Pipeline: pipeline.Sanitize(w.Config.Runtime.Driver), + Pipeline: p.Sanitize(w.Config.Runtime.Driver), Repo: item.Repo, User: item.User, Version: v.Semantic(), + OutputCtn: outputCtn, }) // add the executor to the worker diff --git a/executor/linux/build.go b/executor/linux/build.go index a9420c7e..aa967646 100644 --- a/executor/linux/build.go +++ b/executor/linux/build.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "fmt" + "os" "strings" "sync" "time" @@ -19,6 +20,7 @@ import ( context2 "github.com/go-vela/worker/internal/context" "github.com/go-vela/worker/internal/image" "github.com/go-vela/worker/internal/step" + "github.com/sirupsen/logrus" ) // CreateBuild configures the build for execution. @@ -138,7 +140,7 @@ func (c *client) PlanBuild(ctx context.Context) error { c.Logger.Info("creating volume") // create the runtime volume for the pipeline - c.err = c.Runtime.CreateVolume(ctx, c.pipeline) + c.workspacePath, c.err = c.Runtime.CreateVolume(ctx, c.pipeline) if c.err != nil { return fmt.Errorf("unable to create volume: %w", c.err) } @@ -506,6 +508,22 @@ func (c *client) ExecBuild(ctx context.Context) error { build.Upload(c.build, c.Vela, c.err, c.Logger, c.repo) }() + var opEnv, maskEnv map[string]string + + // fire up output container to run with the build + + c.Logger.Infof("creating outputs container %s", c.OutputCtn.ID) + + c.err = c.outputs.create(ctx, c.OutputCtn) + if c.err != nil { + return fmt.Errorf("unable to create outputs container: %w", c.err) + } + + c.err = c.outputs.exec(ctx, c.OutputCtn) + if c.err != nil { + return fmt.Errorf("unable to exec outputs container: %w", c.err) + } + c.Logger.Info("executing secret images") // execute the secret c.err = c.secret.exec(ctx, &c.pipeline.Secrets) @@ -513,6 +531,12 @@ func (c *client) ExecBuild(ctx context.Context) error { return fmt.Errorf("unable to execute secret: %w", c.err) } + // poll outputs container for any updates + opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + if c.err != nil { + return fmt.Errorf("unable to exec outputs container: %w", c.err) + } + // execute the services for the pipeline for _, _service := range c.pipeline.Services { c.Logger.Infof("planning %s service", _service.Name) @@ -528,6 +552,12 @@ func (c *client) ExecBuild(ctx context.Context) error { if c.err != nil { return fmt.Errorf("unable to execute service: %w", c.err) } + + // poll outputs container + opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + if c.err != nil { + return fmt.Errorf("unable to exec outputs container: %w", c.err) + } } // execute the steps for the pipeline @@ -714,12 +744,56 @@ func (c *client) ExecBuild(ctx context.Context) error { return fmt.Errorf("unable to plan step: %w", c.err) } + err = _step.MergeEnv(opEnv) + if err != nil { + return fmt.Errorf("failed to merge environment") + } + + err = _step.MergeEnv(maskEnv) + if err != nil { + return fmt.Errorf("failed to merge mask environment") + } + + // add masked outputs to secret map so they can be masked in logs + for key := range maskEnv { + sec := &pipeline.StepSecret{ + Target: key, + } + _step.Secrets = append(_step.Secrets, sec) + } + + err = _step.Substitute() + if err != nil { + return err + } + c.Logger.Infof("executing %s step", _step.Name) // execute the step c.err = c.ExecStep(ctx, _step) if c.err != nil { return fmt.Errorf("unable to execute step: %w", c.err) } + + opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + if c.err != nil { + return fmt.Errorf("unable to exec outputs container: %w", c.err) + } + + files, err := os.ReadDir(c.workspacePath) + if err != nil { + logrus.Infof("failed to read directory: %s", err) + } + + for _, file := range files { + logrus.Info(file.Name()) + } + + dat, err := os.ReadFile(c.workspacePath + constants.WorkspaceMount + "/outputs.env") + if err != nil { + logrus.Infof("unable to read outputs file: %s", err) + } + + logrus.Infof("OUTPUTS ENV: %s", string(dat)) } // create an error group with the context for each stage @@ -914,6 +988,12 @@ func (c *client) DestroyBuild(ctx context.Context) error { } } + // destroy output container + err = c.outputs.destroy(ctx, c.OutputCtn) + if err != nil { + c.Logger.Errorf("unable to destroy output container: %v", err) + } + c.Logger.Info("deleting volume") // remove the runtime volume for the pipeline err = c.Runtime.RemoveVolume(ctx, c.pipeline) diff --git a/executor/linux/linux.go b/executor/linux/linux.go index 726c6984..6939fb15 100644 --- a/executor/linux/linux.go +++ b/executor/linux/linux.go @@ -19,15 +19,17 @@ type ( // client manages communication with the pipeline resources. client struct { // https://pkg.go.dev/github.com/sirupsen/logrus#Entry - Logger *logrus.Entry - Vela *vela.Client - Runtime runtime.Engine - Secrets map[string]*library.Secret - Hostname string - Version string + Logger *logrus.Entry + Vela *vela.Client + Runtime runtime.Engine + Secrets map[string]*library.Secret + Hostname string + Version string + OutputCtn *pipeline.Container // clients for build actions - secret *secretSvc + secret *secretSvc + outputs *outputSvc // private fields init *pipeline.Container @@ -43,6 +45,7 @@ type ( serviceLogs sync.Map steps sync.Map stepLogs sync.Map + workspacePath string streamRequests chan message.StreamRequest @@ -120,6 +123,7 @@ func New(opts ...Opt) (*client, error) { // instantiate all client services c.secret = &secretSvc{client: c} + c.outputs = &outputSvc{client: c} return c, nil } diff --git a/executor/linux/opts.go b/executor/linux/opts.go index 0695a70e..45aa97e5 100644 --- a/executor/linux/opts.go +++ b/executor/linux/opts.go @@ -218,6 +218,17 @@ func WithVersion(version string) Opt { } } +func WithOutputCtn(ctn *pipeline.Container) Opt { + return func(c *client) error { + c.Logger.Trace("configuring output container in linux executor client") + + // set the version in the client + c.OutputCtn = ctn + + return nil + } +} + // withStreamRequests sets the streamRequests channel in the executor client for Linux // (primarily used for tests). func withStreamRequests(s chan message.StreamRequest) Opt { diff --git a/executor/linux/outputs.go b/executor/linux/outputs.go new file mode 100644 index 00000000..44d73e9e --- /dev/null +++ b/executor/linux/outputs.go @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 + +package linux + +import ( + "bytes" + "context" + "encoding/base64" + "fmt" + "strings" + + "github.com/go-vela/types/pipeline" + "github.com/sirupsen/logrus" +) + +// outputSvc handles communication with the outputs container during the build. +type outputSvc svc + +// traceScript is a helper script that is added to the build script +// to trace a command. +const traceScript = ` +echo $ %s +%s +` + +// create configures the outputs plugin for execution. +func (o *outputSvc) create(ctx context.Context, ctn *pipeline.Container) error { + // update engine logger with secret metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField + logger := o.client.Logger.WithField("outputs", "outputs") + + // generate script from commands + script := generateScriptPosix([]string{"sleep 5400"}) + + // set the entrypoint for the ctn + ctn.Entrypoint = []string{"/bin/sh", "-c"} + + // set the commands for the ctn + ctn.Commands = []string{"echo $VELA_BUILD_SCRIPT | base64 -d | /bin/sh -e"} + + // set the environment variables for the ctn + ctn.Environment["HOME"] = "/root" + ctn.Environment["SHELL"] = "/bin/sh" + ctn.Environment["VELA_BUILD_SCRIPT"] = script + ctn.Environment["VELA_DISTRIBUTION"] = o.client.build.GetDistribution() + ctn.Environment["BUILD_HOST"] = o.client.build.GetHost() + ctn.Environment["VELA_HOST"] = o.client.build.GetHost() + ctn.Environment["VELA_RUNTIME"] = o.client.build.GetRuntime() + ctn.Environment["VELA_VERSION"] = o.client.Version + + logger.Debug("setting up container") + // setup the runtime container + err := o.client.Runtime.SetupContainer(ctx, ctn) + if err != nil { + return err + } + + return nil +} + +// destroy cleans up secret plugin after execution. +func (o *outputSvc) destroy(ctx context.Context, ctn *pipeline.Container) error { + // update engine logger with secret metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField + logger := o.client.Logger.WithField("secret", ctn.Name) + + logger.Debug("inspecting container") + // inspect the runtime container + err := o.client.Runtime.InspectContainer(ctx, ctn) + if err != nil { + return err + } + + logger.Debug("removing container") + // remove the runtime container + err = o.client.Runtime.RemoveContainer(ctx, ctn) + if err != nil { + return err + } + + return nil +} + +// generateScriptPosix is a helper function that generates a build script +// for a linux container using the given commands. +func generateScriptPosix(commands []string) string { + var buf bytes.Buffer + + // iterate through each command provided + for _, command := range commands { + // safely escape entire command + escaped := fmt.Sprintf("%q", command) + + // safely escape trace character + escaped = strings.Replace(escaped, "$", `\$`, -1) + + // write escaped lines to buffer + buf.WriteString(fmt.Sprintf( + traceScript, + escaped, + command, + )) + } + + // create build script with netrc and buffer information + script := buf.String() + + return base64.StdEncoding.EncodeToString([]byte(script)) +} + +// exec runs a secret plugins for a pipeline. +func (o *outputSvc) exec(ctx context.Context, _outputs *pipeline.Container) error { + logrus.Debug("running container") + // run the runtime container + err := o.client.Runtime.RunContainer(ctx, _outputs, o.client.pipeline) + if err != nil { + return err + } + + logrus.Debug("inspecting container") + // inspect the runtime container + err = o.client.Runtime.InspectContainer(ctx, _outputs) + if err != nil { + return err + } + + return nil +} + +// poll tails the output for a secret plugin. +func (o *outputSvc) poll(ctx context.Context, ctn *pipeline.Container) (map[string]string, map[string]string, error) { + // update engine logger with secret metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField + logger := o.client.Logger.WithField("secret", ctn.Name) + + logger.Debug("tailing container") + + // grab outputs + outputBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, "/vela/outputs.env") + if err != nil { + return nil, nil, err + } + + // grab masked outputs + maskedBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, "/vela/masked_outputs.env") + if err != nil { + return nil, nil, err + } + + return toMap(outputBytes), toMap(maskedBytes), nil +} + +// toMap is a helper function that turns raw docker exec output bytes into a map +// by splitting on carriage returns + newlines and once more on `=`. +func toMap(input []byte) map[string]string { + str := string(input[8:]) // Ignore first 8 bytes of Docker output. + + logrus.Infof("string to split: %s", str) + lines := strings.Split(str, "\r\n") + + m := make(map[string]string) + + for _, line := range lines { + parts := strings.Split(line, "=") + if len(parts) == 2 { + s := parts[1] + if !strings.Contains(parts[1], "\\\\n") { + s = strings.Replace(parts[1], "\\n", "\\\n", -1) + } + + m[parts[0]] = s + } + } + + return m +} diff --git a/executor/linux/step.go b/executor/linux/step.go index c34dbd83..fc0a4922 100644 --- a/executor/linux/step.go +++ b/executor/linux/step.go @@ -6,7 +6,6 @@ import ( "bufio" "bytes" "context" - "fmt" "io" "strings" "time" @@ -16,6 +15,7 @@ import ( "github.com/go-vela/types/pipeline" "github.com/go-vela/worker/internal/message" "github.com/go-vela/worker/internal/step" + "github.com/sirupsen/logrus" ) // CreateStep configures the step for execution. @@ -57,13 +57,6 @@ func (c *client) CreateStep(ctx context.Context, ctn *pipeline.Container) error } logger.Debug("substituting container configuration") - // substitute container configuration - // - // https://pkg.go.dev/github.com/go-vela/types/pipeline#Container.Substitute - err = ctn.Substitute() - if err != nil { - return fmt.Errorf("unable to substitute container configuration") - } return nil } @@ -144,6 +137,9 @@ func (c *client) ExecStep(ctx context.Context, ctn *pipeline.Container) error { defer func() { step.Snapshot(ctn, c.build, c.Vela, c.Logger, c.repo, _step) }() logger.Debug("running container") + + logrus.Infof("BEFORE RUN CONTAINER: %s", ctn.Environment["NEW"]) + // run the runtime container err = c.Runtime.RunContainer(ctx, ctn, c.pipeline) if err != nil { diff --git a/executor/local/build.go b/executor/local/build.go index 0c682f88..f5000ee7 100644 --- a/executor/local/build.go +++ b/executor/local/build.go @@ -108,7 +108,7 @@ func (c *client) PlanBuild(ctx context.Context) error { fmt.Fprintln(c.stdout, _pattern, string(network)) // create the runtime volume for the pipeline - err = c.Runtime.CreateVolume(ctx, c.pipeline) + _, err = c.Runtime.CreateVolume(ctx, c.pipeline) if err != nil { c.err = err return fmt.Errorf("unable to create volume: %w", err) diff --git a/executor/setup.go b/executor/setup.go index 55031039..1502acf3 100644 --- a/executor/setup.go +++ b/executor/setup.go @@ -53,6 +53,8 @@ type Setup struct { // engine used for creating runtime resources Runtime runtime.Engine + OutputCtn *pipeline.Container + // Vela Resource Configuration // resource for storing build information in Vela @@ -95,6 +97,7 @@ func (s *Setup) Linux() (Engine, error) { linux.WithVelaClient(s.Client), linux.WithVersion(s.Version), linux.WithLogger(s.Logger), + linux.WithOutputCtn(s.OutputCtn), ) } diff --git a/runtime/docker/container.go b/runtime/docker/container.go index 1c896f1d..ee6b0ad8 100644 --- a/runtime/docker/container.go +++ b/runtime/docker/container.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "io" + "log" "strings" "github.com/go-vela/types/constants" @@ -296,6 +297,32 @@ func (c *client) WaitContainer(ctx context.Context, ctn *pipeline.Container) err return nil } +// PollOutputsContainer +func (c *client) PollOutputsContainer(ctx context.Context, ctn *pipeline.Container, path string) ([]byte, error) { + execConfig := types.ExecConfig{ + Tty: true, + Cmd: []string{"sh", "-c", fmt.Sprintf("cat %s", path)}, + AttachStderr: true, + AttachStdout: true, + } + + responseExec, err := c.Docker.ContainerExecCreate(ctx, ctn.ID, execConfig) + if err != nil { + log.Fatal(err) + } + + hijackedResponse, err := c.Docker.ContainerExecAttach(ctx, responseExec.ID, types.ExecStartCheck{}) + if err != nil { + log.Fatal(err) + } + + defer hijackedResponse.Close() + + result, _ := io.ReadAll(hijackedResponse.Reader) + + return result, nil +} + // ctnConfig is a helper function to // generate the container config. func ctnConfig(ctn *pipeline.Container) *container.Config { diff --git a/runtime/docker/image.go b/runtime/docker/image.go index 136cd233..484fa1df 100644 --- a/runtime/docker/image.go +++ b/runtime/docker/image.go @@ -69,6 +69,13 @@ func (c *client) InspectImage(ctx context.Context, ctn *pipeline.Container) ([]b fmt.Sprintf("$ docker image inspect %s\n", ctn.Image), ) + // check if the container pull policy is on start + if strings.EqualFold(ctn.Pull, constants.PullOnStart) || strings.EqualFold(ctn.Pull, constants.PullNever) { + return []byte( + fmt.Sprintf("skipped for container %s due to pull policy %s\n", ctn.ID, ctn.Pull), + ), nil + } + // parse image from container // // https://pkg.go.dev/github.com/go-vela/worker/internal/image#ParseWithError @@ -77,13 +84,6 @@ func (c *client) InspectImage(ctx context.Context, ctn *pipeline.Container) ([]b return output, err } - // check if the container pull policy is on start - if strings.EqualFold(ctn.Pull, constants.PullOnStart) { - return []byte( - fmt.Sprintf("skipped for container %s due to pull policy %s\n", ctn.ID, ctn.Pull), - ), nil - } - // send API call to inspect the image // // https://godoc.org/github.com/docker/docker/client#Client.ImageInspectWithRaw diff --git a/runtime/docker/volume.go b/runtime/docker/volume.go index f48e3306..87db4c3b 100644 --- a/runtime/docker/volume.go +++ b/runtime/docker/volume.go @@ -20,7 +20,7 @@ import ( ) // CreateVolume creates the pipeline volume. -func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) error { +func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) (string, error) { c.Logger.Tracef("creating volume for pipeline %s", b.ID) // create options for creating volume @@ -34,12 +34,14 @@ func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) error { // send API call to create the volume // // https://godoc.org/github.com/docker/docker/client#Client.VolumeCreate - _, err := c.Docker.VolumeCreate(ctx, opts) + v, err := c.Docker.VolumeCreate(ctx, opts) if err != nil { - return err + return "", err } - return nil + logrus.Infof("VOLUME MOUNT LOCATION: %s", v.Mountpoint) + + return v.Mountpoint, nil } // InspectVolume inspects the pipeline volume. diff --git a/runtime/engine.go b/runtime/engine.go index 79f3a37a..43308a30 100644 --- a/runtime/engine.go +++ b/runtime/engine.go @@ -43,6 +43,7 @@ type Engine interface { // InspectContainer defines a function that inspects // the pipeline container. InspectContainer(context.Context, *pipeline.Container) error + PollOutputsContainer(context.Context, *pipeline.Container, string) ([]byte, error) // RemoveContainer defines a function that deletes // (kill, remove) the pipeline container. RemoveContainer(context.Context, *pipeline.Container) error @@ -84,7 +85,7 @@ type Engine interface { // CreateVolume defines a function that // creates the pipeline volume. - CreateVolume(context.Context, *pipeline.Build) error + CreateVolume(context.Context, *pipeline.Build) (string, error) // InspectVolume defines a function that // inspects the pipeline volume. InspectVolume(context.Context, *pipeline.Build) ([]byte, error) diff --git a/runtime/kubernetes/container.go b/runtime/kubernetes/container.go index e66d348f..00c82d3e 100644 --- a/runtime/kubernetes/container.go +++ b/runtime/kubernetes/container.go @@ -69,6 +69,12 @@ func (c *client) RemoveContainer(ctx context.Context, ctn *pipeline.Container) e return nil } +func (c *client) PollOutputsContainer(ctx context.Context, ctn *pipeline.Container, path string) ([]byte, error) { + c.Logger.Tracef("no-op: removing container %s", ctn.ID) + + return nil, nil +} + // RunContainer creates and starts the pipeline container. func (c *client) RunContainer(ctx context.Context, ctn *pipeline.Container, _ *pipeline.Build) error { c.Logger.Tracef("running container %s", ctn.ID) diff --git a/runtime/kubernetes/volume.go b/runtime/kubernetes/volume.go index 1f91731b..6c51b509 100644 --- a/runtime/kubernetes/volume.go +++ b/runtime/kubernetes/volume.go @@ -16,7 +16,7 @@ import ( ) // CreateVolume creates the pipeline volume. -func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) error { +func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) (string, error) { c.Logger.Tracef("creating volume for pipeline %s", b.ID) // create the workspace volume for the pod @@ -85,7 +85,7 @@ func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) error { // TODO: extend c.config.Volumes to include container-specific volumes (container.Volumes) - return nil + return "", nil } // InspectVolume inspects the pipeline volume. From 023f500677bc931f5e99d3926cbd4ea8c85be8b4 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 24 Jan 2024 09:21:15 -0600 Subject: [PATCH 02/15] yep --- cmd/vela-worker/exec.go | 9 ++------- cmd/vela-worker/run.go | 11 +++++++++++ executor/flags.go | 6 ++++++ executor/linux/build.go | 35 +++++++++++------------------------ executor/linux/outputs.go | 24 ++++++++++++++++++++++-- 5 files changed, 52 insertions(+), 33 deletions(-) diff --git a/cmd/vela-worker/exec.go b/cmd/vela-worker/exec.go index 8961bd25..27893c11 100644 --- a/cmd/vela-worker/exec.go +++ b/cmd/vela-worker/exec.go @@ -165,12 +165,7 @@ func (w *Worker) exec(index int, config *library.Worker) error { return err } - outputCtn := &pipeline.Container{ - ID: fmt.Sprintf("outputs_%s", p.ID), - Detach: true, - Image: "alpine:latest", - Environment: make(map[string]string), - } + w.Config.Executor.OutputCtn.ID = fmt.Sprintf("outputs_%s", p.ID) // setup the executor // @@ -191,7 +186,7 @@ func (w *Worker) exec(index int, config *library.Worker) error { Repo: item.Repo, User: item.User, Version: v.Semantic(), - OutputCtn: outputCtn, + OutputCtn: w.Config.Executor.OutputCtn, }) // add the executor to the worker diff --git a/cmd/vela-worker/run.go b/cmd/vela-worker/run.go index a3274de0..7bdfc43c 100644 --- a/cmd/vela-worker/run.go +++ b/cmd/vela-worker/run.go @@ -9,6 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/go-vela/server/queue" + "github.com/go-vela/types/pipeline" "github.com/go-vela/worker/executor" "github.com/go-vela/worker/runtime" @@ -74,6 +75,15 @@ func run(c *cli.Context) error { return fmt.Errorf("unable to parse address: %w", err) } + var outputsCtn *pipeline.Container + if len(c.String("executor.outputs-image")) > 0 { + outputsCtn = &pipeline.Container{ + Detach: true, + Image: c.String("executor.outputs-image"), + Environment: make(map[string]string), + } + } + // create the worker w := &Worker{ // worker configuration @@ -95,6 +105,7 @@ func run(c *cli.Context) error { MaxLogSize: c.Uint("executor.max_log_size"), LogStreamingTimeout: c.Duration("executor.log_streaming_timeout"), EnforceTrustedRepos: c.Bool("executor.enforce-trusted-repos"), + OutputCtn: outputsCtn, }, // logger configuration Logger: &Logger{ diff --git a/executor/flags.go b/executor/flags.go index e04568b9..e46b72db 100644 --- a/executor/flags.go +++ b/executor/flags.go @@ -43,4 +43,10 @@ var Flags = []cli.Flag{ Usage: "enforce trusted repo restrictions for privileged images", Value: true, }, + &cli.StringFlag{ + EnvVars: []string{"VELA_EXECUTOR_OUTPUTS_IMAGE", "EXECUTOR_OUTPUTS_IMAGE"}, + FilePath: "/vela/executor/outputs_image", + Name: "executor.outputs-image", + Usage: "image used for the outputs container sidecar", + }, } diff --git a/executor/linux/build.go b/executor/linux/build.go index aa967646..962cbee6 100644 --- a/executor/linux/build.go +++ b/executor/linux/build.go @@ -6,7 +6,6 @@ import ( "context" "encoding/json" "fmt" - "os" "strings" "sync" "time" @@ -20,7 +19,6 @@ import ( context2 "github.com/go-vela/worker/internal/context" "github.com/go-vela/worker/internal/image" "github.com/go-vela/worker/internal/step" - "github.com/sirupsen/logrus" ) // CreateBuild configures the build for execution. @@ -377,6 +375,13 @@ func (c *client) AssembleBuild(ctx context.Context) error { // https://pkg.go.dev/github.com/go-vela/types/library#Log.AppendData _log.AppendData(image) } + + // create outputs container with a timeout equal to the repo timeout + c.err = c.outputs.create(ctx, c.OutputCtn, (int64(60) * c.repo.GetTimeout())) + if c.err != nil { + return fmt.Errorf("unable to create outputs container: %w", c.err) + } + // enforce repo.trusted is set for pipelines containing privileged images // if not enforced, allow all that exist in the list of runtime privileged images // this configuration is set as an executor flag @@ -511,14 +516,8 @@ func (c *client) ExecBuild(ctx context.Context) error { var opEnv, maskEnv map[string]string // fire up output container to run with the build - c.Logger.Infof("creating outputs container %s", c.OutputCtn.ID) - c.err = c.outputs.create(ctx, c.OutputCtn) - if c.err != nil { - return fmt.Errorf("unable to create outputs container: %w", c.err) - } - c.err = c.outputs.exec(ctx, c.OutputCtn) if c.err != nil { return fmt.Errorf("unable to exec outputs container: %w", c.err) @@ -744,11 +743,13 @@ func (c *client) ExecBuild(ctx context.Context) error { return fmt.Errorf("unable to plan step: %w", c.err) } + // merge env from outputs err = _step.MergeEnv(opEnv) if err != nil { return fmt.Errorf("failed to merge environment") } + // merge env from masked outputs err = _step.MergeEnv(maskEnv) if err != nil { return fmt.Errorf("failed to merge mask environment") @@ -762,6 +763,7 @@ func (c *client) ExecBuild(ctx context.Context) error { _step.Secrets = append(_step.Secrets, sec) } + // perform any substitution on dynamic variables err = _step.Substitute() if err != nil { return err @@ -774,26 +776,11 @@ func (c *client) ExecBuild(ctx context.Context) error { return fmt.Errorf("unable to execute step: %w", c.err) } + // poll outputs opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) if c.err != nil { return fmt.Errorf("unable to exec outputs container: %w", c.err) } - - files, err := os.ReadDir(c.workspacePath) - if err != nil { - logrus.Infof("failed to read directory: %s", err) - } - - for _, file := range files { - logrus.Info(file.Name()) - } - - dat, err := os.ReadFile(c.workspacePath + constants.WorkspaceMount + "/outputs.env") - if err != nil { - logrus.Infof("unable to read outputs file: %s", err) - } - - logrus.Infof("OUTPUTS ENV: %s", string(dat)) } // create an error group with the context for each stage diff --git a/executor/linux/outputs.go b/executor/linux/outputs.go index 44d73e9e..b02fc45b 100644 --- a/executor/linux/outputs.go +++ b/executor/linux/outputs.go @@ -24,14 +24,19 @@ echo $ %s ` // create configures the outputs plugin for execution. -func (o *outputSvc) create(ctx context.Context, ctn *pipeline.Container) error { +func (o *outputSvc) create(ctx context.Context, ctn *pipeline.Container, timeout int64) error { + // exit if outputs container has not been configured + if len(ctn.Image) == 0 { + return nil + } + // update engine logger with secret metadata // // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField logger := o.client.Logger.WithField("outputs", "outputs") // generate script from commands - script := generateScriptPosix([]string{"sleep 5400"}) + script := generateScriptPosix([]string{fmt.Sprintf("sleep %d", timeout)}) // set the entrypoint for the ctn ctn.Entrypoint = []string{"/bin/sh", "-c"} @@ -61,6 +66,11 @@ func (o *outputSvc) create(ctx context.Context, ctn *pipeline.Container) error { // destroy cleans up secret plugin after execution. func (o *outputSvc) destroy(ctx context.Context, ctn *pipeline.Container) error { + // exit if outputs container has not been configured + if len(ctn.Image) == 0 { + return nil + } + // update engine logger with secret metadata // // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField @@ -112,6 +122,11 @@ func generateScriptPosix(commands []string) string { // exec runs a secret plugins for a pipeline. func (o *outputSvc) exec(ctx context.Context, _outputs *pipeline.Container) error { + // exit if outputs container has not been configured + if len(_outputs.Image) == 0 { + return nil + } + logrus.Debug("running container") // run the runtime container err := o.client.Runtime.RunContainer(ctx, _outputs, o.client.pipeline) @@ -131,6 +146,11 @@ func (o *outputSvc) exec(ctx context.Context, _outputs *pipeline.Container) erro // poll tails the output for a secret plugin. func (o *outputSvc) poll(ctx context.Context, ctn *pipeline.Container) (map[string]string, map[string]string, error) { + // exit if outputs container has not been configured + if len(ctn.Image) == 0 { + return nil, nil, nil + } + // update engine logger with secret metadata // // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField From e193efb23a52cf1b15004893a3fae40e9418b529 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Wed, 31 Jan 2024 13:46:43 -0600 Subject: [PATCH 03/15] more work --- cmd/vela-worker/exec.go | 5 +- docker-compose.yml | 1 + executor/engine.go | 2 +- executor/linux/build.go | 116 +---- executor/linux/build_test.go | 842 +------------------------------- executor/linux/service.go | 13 + executor/linux/stage.go | 32 +- executor/linux/stage_test.go | 3 +- executor/linux/step.go | 14 + executor/local/build.go | 4 +- executor/local/stage.go | 2 +- executor/local/stage_test.go | 2 +- internal/image/image.go | 56 ++- internal/image/image_test.go | 58 +-- runtime/docker/container.go | 16 +- runtime/docker/volume.go | 10 +- runtime/engine.go | 2 +- runtime/kubernetes/container.go | 11 +- runtime/kubernetes/volume.go | 4 +- 19 files changed, 179 insertions(+), 1014 deletions(-) diff --git a/cmd/vela-worker/exec.go b/cmd/vela-worker/exec.go index 27893c11..6f7f562f 100644 --- a/cmd/vela-worker/exec.go +++ b/cmd/vela-worker/exec.go @@ -91,6 +91,9 @@ func (w *Worker) exec(index int, config *library.Worker) error { return err } + // set the outputs container ID + w.Config.Executor.OutputCtn.ID = fmt.Sprintf("outputs_%s", p.ID) + // create logger with extra metadata // // https://pkg.go.dev/github.com/sirupsen/logrus#WithFields @@ -165,8 +168,6 @@ func (w *Worker) exec(index int, config *library.Worker) error { return err } - w.Config.Executor.OutputCtn.ID = fmt.Sprintf("outputs_%s", p.ID) - // setup the executor // // https://godoc.org/github.com/go-vela/worker/executor#New diff --git a/docker-compose.yml b/docker-compose.yml index d756b38d..851544d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,7 @@ services: VELA_SERVER_SECRET: 'zB7mrKDTZqNeNTD8z47yG4DHywspAh' WORKER_ADDR: 'http://worker:8080' WORKER_CHECK_IN: 2m + VELA_EXECUTOR_OUTPUTS_IMAGE: 'alpine:latest' restart: always ports: - "8081:8080" diff --git a/executor/engine.go b/executor/engine.go index f81b72d0..356c4ecb 100644 --- a/executor/engine.go +++ b/executor/engine.go @@ -86,7 +86,7 @@ type Engine interface { PlanStage(context.Context, *pipeline.Stage, *sync.Map) error // ExecStage defines a function that // runs a stage. - ExecStage(context.Context, *pipeline.Stage, *sync.Map) error + ExecStage(context.Context, *pipeline.Stage, *sync.Map, map[string]string, map[string]string) error // DestroyStage defines a function that // cleans up the stage after execution. DestroyStage(context.Context, *pipeline.Stage) error diff --git a/executor/linux/build.go b/executor/linux/build.go index 962cbee6..688e9e8a 100644 --- a/executor/linux/build.go +++ b/executor/linux/build.go @@ -138,7 +138,7 @@ func (c *client) PlanBuild(ctx context.Context) error { c.Logger.Info("creating volume") // create the runtime volume for the pipeline - c.workspacePath, c.err = c.Runtime.CreateVolume(ctx, c.pipeline) + c.err = c.Runtime.CreateVolume(ctx, c.pipeline) if c.err != nil { return fmt.Errorf("unable to create volume: %w", c.err) } @@ -355,6 +355,18 @@ func (c *client) AssembleBuild(ctx context.Context) error { continue } + // verify secret image is allowed to run + if c.enforceTrustedRepos { + priv, err := image.IsPrivilegedImage(s.Origin.Image, c.privilegedImages) + if err != nil { + return err + } + + if priv && !c.repo.GetTrusted() { + return fmt.Errorf("attempting to use privileged image (%s) as untrusted repo", s.Origin.Image) + } + } + c.Logger.Infof("creating %s secret", s.Origin.Name) // create the service c.err = c.secret.create(ctx, s.Origin) @@ -382,92 +394,6 @@ func (c *client) AssembleBuild(ctx context.Context) error { return fmt.Errorf("unable to create outputs container: %w", c.err) } - // enforce repo.trusted is set for pipelines containing privileged images - // if not enforced, allow all that exist in the list of runtime privileged images - // this configuration is set as an executor flag - if c.enforceTrustedRepos { - // group steps services stages and secret origins together - containers := c.pipeline.Steps - - containers = append(containers, c.pipeline.Services...) - - for _, stage := range c.pipeline.Stages { - containers = append(containers, stage.Steps...) - } - - for _, secret := range c.pipeline.Secrets { - containers = append(containers, secret.Origin) - } - - // assume no privileged images are in use - containsPrivilegedImages := false - privImages := []string{} - - // verify all pipeline containers - for _, container := range containers { - // TODO: remove hardcoded reference - if container.Image == "#init" { - continue - } - - // skip over non-plugin secrets origins - if container.Empty() { - continue - } - - c.Logger.Infof("verifying privileges for container %s", container.Name) - - // update the init log with image info - // - // https://pkg.go.dev/github.com/go-vela/types/library#Log.AppendData - _log.AppendData([]byte(fmt.Sprintf("Verifying privileges for image %s...\n", container.Image))) - - for _, pattern := range c.privilegedImages { - // check if image matches privileged pattern - privileged, err := image.IsPrivilegedImage(container.Image, pattern) - if err != nil { - // wrap the error - c.err = fmt.Errorf("unable to verify privileges for image %s: %w", container.Image, err) - - // update the init log with image info - // - // https://pkg.go.dev/github.com/go-vela/types/library#Log.AppendData - _log.AppendData([]byte(fmt.Sprintf("ERROR: %s\n", c.err.Error()))) - - // return error and destroy the build - // ignore checking more images - return c.err - } - - if privileged { - // pipeline contains at least one privileged image - containsPrivilegedImages = privileged - - privImages = append(privImages, container.Image) - } - } - - // update the init log with image info - // - // https://pkg.go.dev/github.com/go-vela/types/library#Log.AppendData - _log.AppendData([]byte(fmt.Sprintf("Privileges verified for image %s\n", container.Image))) - } - - // ensure pipelines containing privileged images are only permitted to run by trusted repos - if (containsPrivilegedImages) && !(c.repo != nil && c.repo.GetTrusted()) { - // update error including privileged image - c.err = fmt.Errorf("unable to assemble build. pipeline contains privileged images and repo is not trusted. privileged image: %v", privImages) - - // update the init log with image info - // - // https://pkg.go.dev/github.com/go-vela/types/library#Log.AppendData - _log.AppendData([]byte(fmt.Sprintf("ERROR: %s\n", c.err.Error()))) - - // return error and destroy the build - return c.err - } - } - // inspect the runtime build (eg a kubernetes pod) for the pipeline buildOutput, err := c.Runtime.InspectBuild(ctx, c.pipeline) if err != nil { @@ -744,16 +670,14 @@ func (c *client) ExecBuild(ctx context.Context) error { } // merge env from outputs - err = _step.MergeEnv(opEnv) - if err != nil { - return fmt.Errorf("failed to merge environment") - } + // + //nolint:errcheck // only errors with empty environment input, which does not matter here + _step.MergeEnv(opEnv) // merge env from masked outputs - err = _step.MergeEnv(maskEnv) - if err != nil { - return fmt.Errorf("failed to merge mask environment") - } + // + //nolint:errcheck // only errors with empty environment input, which does not matter here + _step.MergeEnv(maskEnv) // add masked outputs to secret map so they can be masked in logs for key := range maskEnv { @@ -817,7 +741,7 @@ func (c *client) ExecBuild(ctx context.Context) error { c.Logger.Infof("executing %s stage", stage.Name) // execute the stage - c.err = c.ExecStage(stageCtx, stage, stageMap) + c.err = c.ExecStage(stageCtx, stage, stageMap, opEnv, maskEnv) if c.err != nil { return fmt.Errorf("unable to execute stage: %w", c.err) } diff --git a/executor/linux/build_test.go b/executor/linux/build_test.go index 449dd91d..7f977633 100644 --- a/executor/linux/build_test.go +++ b/executor/linux/build_test.go @@ -222,837 +222,6 @@ func TestLinux_CreateBuild(t *testing.T) { } } -func TestLinux_AssembleBuild_EnforceTrustedRepos(t *testing.T) { - // setup types - set := flag.NewFlagSet("test", 0) - set.String("clone-image", "target/vela-git:latest", "doc") - compiler, _ := native.New(cli.NewContext(nil, set, nil)) - - _build := testBuild() - - // setting mock build for testing dynamic environment tags - _buildWithMessageAlpine := testBuild() - _buildWithMessageAlpine.SetMessage("alpine") - - // test repo is not trusted by default - _untrustedRepo := testRepo() - _user := testUser() - _metadata := testMetadata() - // to be matched with the image used by testdata/build/steps/basic.yml - _privilegedImagesStepsPipeline := []string{"alpine"} - // to be matched with the image used by testdata/build/services/basic.yml - _privilegedImagesServicesPipeline := []string{"postgres"} - // to be matched with the image used by testdata/build/stages/basic.yml - _privilegedImagesStagesPipeline := []string{"alpine"} - // create trusted repo - _trustedRepo := testRepo() - _trustedRepo.SetTrusted(true) - - gin.SetMode(gin.TestMode) - - s := httptest.NewServer(server.FakeHandler()) - - _client, err := vela.NewClient(s.URL, "", nil) - if err != nil { - t.Errorf("unable to create Vela API client: %v", err) - } - - tests := []struct { - name string - failure bool - runtime string - build *library.Build - repo *library.Repo - pipeline string - privilegedImages []string - enforceTrustedRepos bool - }{ - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with untrusted repo", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with untrusted repo and dynamic image:tag", - failure: true, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with untrusted repo", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with untrusted repo and dynamic image:tag", - failure: true, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with untrusted repo", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with untrusted repo and dynamic image:tag", - failure: true, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with untrusted repo and init step name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with untrusted repo and init step name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with untrusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with untrusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with untrusted repo and init step name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with untrusted repo and init step name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with untrusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with untrusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with trusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with untrusted repo and init service name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with trusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with untrusted repo and init service name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with trusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with untrusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with trusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with untrusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - } - - // run test - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - _pipeline, _, err := compiler. - Duplicate(). - WithBuild(_build). - WithRepo(test.repo). - WithMetadata(_metadata). - WithUser(_user). - Compile(test.pipeline) - if err != nil { - t.Errorf("unable to compile pipeline %s: %v", test.pipeline, err) - } - - var _runtime runtime.Engine - - switch test.runtime { - case constants.DriverDocker: - _runtime, err = docker.NewMock() - if err != nil { - t.Errorf("unable to create docker runtime engine: %v", err) - } - } - - _engine, err := New( - WithBuild(test.build), - WithPipeline(_pipeline), - WithRepo(test.repo), - WithRuntime(_runtime), - WithUser(_user), - WithVelaClient(_client), - WithPrivilegedImages(test.privilegedImages), - WithEnforceTrustedRepos(test.enforceTrustedRepos), - ) - if err != nil { - t.Errorf("unable to create executor engine: %v", err) - } - - err = _engine.CreateBuild(context.Background()) - if err != nil { - t.Errorf("CreateBuild returned err: %v", err) - } - - // override mock handler PUT build update - // used for dynamic substitute testing - _engine.build.SetMessage(test.build.GetMessage()) - - err = _engine.AssembleBuild(context.Background()) - - if test.failure { - if err == nil { - t.Errorf("AssembleBuild should have returned err") - } - - return // continue to next test - } - - if err != nil { - t.Errorf("AssembleBuild returned err: %v", err) - } - }) - } -} - func TestLinux_PlanBuild(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) @@ -1488,6 +657,7 @@ func TestLinux_AssembleBuild(t *testing.T) { WithRuntime(_runtime), WithUser(_user), WithVelaClient(_client), + WithOutputCtn(testOutputsCtn()), withStreamRequests(streamRequests), ) if err != nil { @@ -1736,6 +906,7 @@ func TestLinux_ExecBuild(t *testing.T) { WithRuntime(_runtime), WithUser(_user), WithVelaClient(_client), + WithOutputCtn(testOutputsCtn()), withStreamRequests(streamRequests), ) if err != nil { @@ -2660,6 +1831,7 @@ func TestLinux_DestroyBuild(t *testing.T) { WithRuntime(_runtime), WithUser(_user), WithVelaClient(_client), + WithOutputCtn(testOutputsCtn()), ) if err != nil { t.Errorf("unable to create %s executor engine: %v", test.name, err) @@ -2724,3 +1896,11 @@ func TestLinux_DestroyBuild(t *testing.T) { }) } } + +func testOutputsCtn() *pipeline.Container { + return &pipeline.Container{ + ID: "outputs_test", + Environment: make(map[string]string), + Detach: true, + } +} diff --git a/executor/linux/service.go b/executor/linux/service.go index 22ea9260..2e1ed1ae 100644 --- a/executor/linux/service.go +++ b/executor/linux/service.go @@ -13,6 +13,7 @@ import ( "github.com/go-vela/types/constants" "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" + "github.com/go-vela/worker/internal/image" "github.com/go-vela/worker/internal/message" "github.com/go-vela/worker/internal/service" ) @@ -121,6 +122,18 @@ func (c *client) ExecService(ctx context.Context, ctn *pipeline.Container) error // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField logger := c.Logger.WithField("service", ctn.Name) + // verify service is allowed to run + if c.enforceTrustedRepos { + priv, err := image.IsPrivilegedImage(ctn.Image, c.privilegedImages) + if err != nil { + return err + } + + if priv && !c.repo.GetTrusted() { + return fmt.Errorf("attempting to use privileged image (%s) as untrusted repo", ctn.Image) + } + } + // load the service from the client // // https://pkg.go.dev/github.com/go-vela/worker/internal/service#Load diff --git a/executor/linux/stage.go b/executor/linux/stage.go index 0db62038..5221b14e 100644 --- a/executor/linux/stage.go +++ b/executor/linux/stage.go @@ -96,7 +96,7 @@ func (c *client) PlanStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) } // ExecStage runs a stage. -func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) error { +func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, opEnv, maskEnv map[string]string) error { // update engine logger with stage metadata // // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField @@ -156,6 +156,30 @@ func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) return fmt.Errorf("unable to plan step %s: %w", _step.Name, err) } + // merge env from outputs + // + //nolint:errcheck // only errors with empty environment input, which does not matter here + _step.MergeEnv(opEnv) + + // merge env from masked outputs + // + //nolint:errcheck // only errors with empty environment input, which does not matter here + _step.MergeEnv(maskEnv) + + // add masked outputs to secret map so they can be masked in logs + for key := range maskEnv { + sec := &pipeline.StepSecret{ + Target: key, + } + _step.Secrets = append(_step.Secrets, sec) + } + + // perform any substitution on dynamic variables + err = _step.Substitute() + if err != nil { + return err + } + logger.Infof("executing %s step", _step.Name) // execute the step err = c.ExecStep(ctx, _step) @@ -163,6 +187,12 @@ func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) return fmt.Errorf("unable to exec step %s: %w", _step.Name, err) } + // poll outputs + opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + if c.err != nil { + return fmt.Errorf("unable to exec outputs container: %w", c.err) + } + // failed steps within the stage should set the stop value to true unless // the continue rule is set to true. if _step.ExitCode != 0 && !_step.Ruleset.Continue { diff --git a/executor/linux/stage_test.go b/executor/linux/stage_test.go index f79e2155..05b921a1 100644 --- a/executor/linux/stage_test.go +++ b/executor/linux/stage_test.go @@ -589,13 +589,14 @@ func TestLinux_ExecStage(t *testing.T) { WithRuntime(test.runtime), WithUser(_user), WithVelaClient(_client), + WithOutputCtn(testOutputsCtn()), withStreamRequests(streamRequests), ) if err != nil { t.Errorf("unable to create %s executor engine: %v", test.name, err) } - err = _engine.ExecStage(context.Background(), test.stage, stageMap) + err = _engine.ExecStage(context.Background(), test.stage, stageMap, nil, nil) if test.failure { if err == nil { diff --git a/executor/linux/step.go b/executor/linux/step.go index fc0a4922..ad5056f3 100644 --- a/executor/linux/step.go +++ b/executor/linux/step.go @@ -6,6 +6,7 @@ import ( "bufio" "bytes" "context" + "fmt" "io" "strings" "time" @@ -13,6 +14,7 @@ import ( "github.com/go-vela/types/constants" "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" + "github.com/go-vela/worker/internal/image" "github.com/go-vela/worker/internal/message" "github.com/go-vela/worker/internal/step" "github.com/sirupsen/logrus" @@ -118,6 +120,18 @@ func (c *client) ExecStep(ctx context.Context, ctn *pipeline.Container) error { return nil } + // verify step is allowed to run + if c.enforceTrustedRepos { + priv, err := image.IsPrivilegedImage(ctn.Image, c.privilegedImages) + if err != nil { + return err + } + + if priv && !c.repo.GetTrusted() { + return fmt.Errorf("attempting to use privileged image (%s) as untrusted repo", ctn.Image) + } + } + // update engine logger with step metadata // // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField diff --git a/executor/local/build.go b/executor/local/build.go index f5000ee7..54d7d876 100644 --- a/executor/local/build.go +++ b/executor/local/build.go @@ -108,7 +108,7 @@ func (c *client) PlanBuild(ctx context.Context) error { fmt.Fprintln(c.stdout, _pattern, string(network)) // create the runtime volume for the pipeline - _, err = c.Runtime.CreateVolume(ctx, c.pipeline) + err = c.Runtime.CreateVolume(ctx, c.pipeline) if err != nil { c.err = err return fmt.Errorf("unable to create volume: %w", err) @@ -328,7 +328,7 @@ func (c *client) ExecBuild(ctx context.Context) error { } // execute the stage - c.err = c.ExecStage(stageCtx, stage, stageMap) + c.err = c.ExecStage(stageCtx, stage, stageMap, nil, nil) if c.err != nil { return fmt.Errorf("unable to execute stage: %w", c.err) } diff --git a/executor/local/stage.go b/executor/local/stage.go index abed1868..9088ef3b 100644 --- a/executor/local/stage.go +++ b/executor/local/stage.go @@ -74,7 +74,7 @@ func (c *client) PlanStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) } // ExecStage runs a stage. -func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) error { +func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, opEnv, maskEnv map[string]string) error { // close the stage channel at the end defer func() { errChan, ok := m.Load(s.Name) diff --git a/executor/local/stage_test.go b/executor/local/stage_test.go index 9df4a425..1c1b5901 100644 --- a/executor/local/stage_test.go +++ b/executor/local/stage_test.go @@ -325,7 +325,7 @@ func TestLocal_ExecStage(t *testing.T) { t.Errorf("unable to create executor engine: %v", err) } - err = _engine.ExecStage(context.Background(), test.stage, stageMap) + err = _engine.ExecStage(context.Background(), test.stage, stageMap, nil, nil) if test.failure { if err == nil { diff --git a/internal/image/image.go b/internal/image/image.go index ee0c3015..e2d5b353 100644 --- a/internal/image/image.go +++ b/internal/image/image.go @@ -52,34 +52,40 @@ func ParseWithError(_image string) (string, error) { // IsPrivilegedImage digests the provided image with a // privileged pattern to see if the image meets the criteria // needed to allow a Docker Socket mount. -func IsPrivilegedImage(image, privileged string) (bool, error) { - // parse the image provided into a - // named, fully qualified reference - // - // https://pkg.go.dev/github.com/distribution/reference#ParseAnyReference - _refImg, err := reference.ParseAnyReference(image) - if err != nil { - return false, err - } +func IsPrivilegedImage(image string, privilegedSet []string) (bool, error) { + for _, pattern := range privilegedSet { + // parse the image provided into a + // named, fully qualified reference + // + // https://pkg.go.dev/github.com/distribution/reference#ParseAnyReference + _refImg, err := reference.ParseAnyReference(image) + if err != nil { + return false, err + } - // ensure we have the canonical form of the named reference - // - // https://pkg.go.dev/github.com/distribution/reference#ParseNamed - _canonical, err := reference.ParseNamed(_refImg.String()) - if err != nil { - return false, err - } + // ensure we have the canonical form of the named reference + // + // https://pkg.go.dev/github.com/distribution/reference#ParseNamed + _canonical, err := reference.ParseNamed(_refImg.String()) + if err != nil { + return false, err + } - // add default tag "latest" when tag does not exist - _refImg = reference.TagNameOnly(_canonical) + // add default tag "latest" when tag does not exist + _refImg = reference.TagNameOnly(_canonical) - // check if the image matches the privileged pattern - // - // https://pkg.go.dev/github.com/distribution/reference#FamiliarMatch - match, err := reference.FamiliarMatch(privileged, _refImg) - if err != nil { - return false, err + // check if the image matches the privileged pattern + // + // https://pkg.go.dev/github.com/distribution/reference#FamiliarMatch + match, err := reference.FamiliarMatch(pattern, _refImg) + if err != nil { + return false, err + } + + if match { + return match, nil + } } - return match, nil + return false, nil } diff --git a/internal/image/image_test.go b/internal/image/image_test.go index d38d4f87..e76ffc81 100644 --- a/internal/image/image_test.go +++ b/internal/image/image_test.go @@ -154,52 +154,52 @@ func TestImage_ParseWithError(t *testing.T) { func TestImage_IsPrivilegedImage(t *testing.T) { // setup tests tests := []struct { - name string - image string - pattern string - want bool + name string + image string + patterns []string + want bool }{ { - name: "test privileged image without tag", - image: "docker.company.com/foo/bar", - pattern: "docker.company.com/foo/bar", - want: true, + name: "test privileged image without tag", + image: "docker.company.com/foo/bar", + patterns: []string{"docker.company.com/foo/bar"}, + want: true, }, { - name: "test privileged image with tag", - image: "docker.company.com/foo/bar:v0.1.0", - pattern: "docker.company.com/foo/bar", - want: true, + name: "test privileged image with tag", + image: "docker.company.com/foo/bar:v0.1.0", + patterns: []string{"docker.company.com/foo/bar"}, + want: true, }, { - name: "test privileged image with tag", - image: "docker.company.com/foo/bar", - pattern: "docker.company.com/foo/bar:v0.1.0", - want: false, + name: "test privileged image with tag", + image: "docker.company.com/foo/bar", + patterns: []string{"docker.company.com/foo/bar:v0.1.0"}, + want: false, }, { - name: "test privileged with bad image", - image: "!@#$%^&*()", - pattern: "docker.company.com/foo/bar", - want: false, + name: "test privileged with bad image", + image: "!@#$%^&*()", + patterns: []string{"docker.company.com/foo/bar"}, + want: false, }, { - name: "test privileged with bad pattern", - image: "docker.company.com/foo/bar", - pattern: "!@#$%^&*()", - want: false, + name: "test privileged with bad pattern", + image: "docker.company.com/foo/bar", + patterns: []string{"!@#$%^&*()", "docker.company.com/foo/baz"}, + want: false, }, { - name: "test privileged with on extended path image", - image: "docker.company.com/foo/bar", - pattern: "docker.company.com/foo", - want: false, + name: "test privileged with on extended path image", + image: "docker.company.com/foo/bar", + patterns: []string{"docker.company.com/foo", "docker.company.com/fab"}, + want: false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - got, _ := IsPrivilegedImage(test.image, test.pattern) + got, _ := IsPrivilegedImage(test.image, test.patterns) if got != test.want { t.Errorf("IsPrivilegedImage is %v want %v", got, test.want) } diff --git a/runtime/docker/container.go b/runtime/docker/container.go index ee6b0ad8..57b276df 100644 --- a/runtime/docker/container.go +++ b/runtime/docker/container.go @@ -139,21 +139,19 @@ func (c *client) RunContainer(ctx context.Context, ctn *pipeline.Container, b *p } // check if the image is allowed to run privileged - for _, pattern := range c.config.Images { - privileged, err := image.IsPrivilegedImage(ctn.Image, pattern) - if err != nil { - return err - } + privileged, err := image.IsPrivilegedImage(ctn.Image, c.config.Images) + if err != nil { + return err + } - if privileged { - hostConf.Privileged = true - } + if privileged { + hostConf.Privileged = true } // send API call to create the container // // https://godoc.org/github.com/docker/docker/client#Client.ContainerCreate - _, err := c.Docker.ContainerCreate( + _, err = c.Docker.ContainerCreate( ctx, containerConf, hostConf, diff --git a/runtime/docker/volume.go b/runtime/docker/volume.go index 87db4c3b..f48e3306 100644 --- a/runtime/docker/volume.go +++ b/runtime/docker/volume.go @@ -20,7 +20,7 @@ import ( ) // CreateVolume creates the pipeline volume. -func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) (string, error) { +func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) error { c.Logger.Tracef("creating volume for pipeline %s", b.ID) // create options for creating volume @@ -34,14 +34,12 @@ func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) (string, e // send API call to create the volume // // https://godoc.org/github.com/docker/docker/client#Client.VolumeCreate - v, err := c.Docker.VolumeCreate(ctx, opts) + _, err := c.Docker.VolumeCreate(ctx, opts) if err != nil { - return "", err + return err } - logrus.Infof("VOLUME MOUNT LOCATION: %s", v.Mountpoint) - - return v.Mountpoint, nil + return nil } // InspectVolume inspects the pipeline volume. diff --git a/runtime/engine.go b/runtime/engine.go index 43308a30..0dabe0cc 100644 --- a/runtime/engine.go +++ b/runtime/engine.go @@ -85,7 +85,7 @@ type Engine interface { // CreateVolume defines a function that // creates the pipeline volume. - CreateVolume(context.Context, *pipeline.Build) (string, error) + CreateVolume(context.Context, *pipeline.Build) error // InspectVolume defines a function that // inspects the pipeline volume. InspectVolume(context.Context, *pipeline.Build) ([]byte, error) diff --git a/runtime/kubernetes/container.go b/runtime/kubernetes/container.go index 00c82d3e..9be88840 100644 --- a/runtime/kubernetes/container.go +++ b/runtime/kubernetes/container.go @@ -164,15 +164,14 @@ func (c *client) SetupContainer(ctx context.Context, ctn *pipeline.Container) er container.VolumeMounts = volumeMounts // check if the image is allowed to run privileged - for _, pattern := range c.config.Images { - privileged, err := image.IsPrivilegedImage(ctn.Image, pattern) - if err != nil { - return err - } - container.SecurityContext.Privileged = &privileged + privileged, err := image.IsPrivilegedImage(ctn.Image, c.config.Images) + if err != nil { + return err } + container.SecurityContext.Privileged = &privileged + if c.PipelinePodTemplate != nil && c.PipelinePodTemplate.Spec.Container != nil { securityContext := c.PipelinePodTemplate.Spec.Container.SecurityContext diff --git a/runtime/kubernetes/volume.go b/runtime/kubernetes/volume.go index 6c51b509..1f91731b 100644 --- a/runtime/kubernetes/volume.go +++ b/runtime/kubernetes/volume.go @@ -16,7 +16,7 @@ import ( ) // CreateVolume creates the pipeline volume. -func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) (string, error) { +func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) error { c.Logger.Tracef("creating volume for pipeline %s", b.ID) // create the workspace volume for the pod @@ -85,7 +85,7 @@ func (c *client) CreateVolume(ctx context.Context, b *pipeline.Build) (string, e // TODO: extend c.config.Volumes to include container-specific volumes (container.Volumes) - return "", nil + return nil } // InspectVolume inspects the pipeline volume. From eaceb1d447efbf223311ca009297748670a62c07 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Fri, 1 Mar 2024 08:52:26 -0600 Subject: [PATCH 04/15] status reporting --- docker-compose.yml | 2 +- executor/linux/build.go | 30 ++++++++++++++++++++++---- executor/linux/outputs.go | 43 ++++++++++++++++++++++++++----------- executor/linux/stage.go | 16 +++++++++++++- executor/linux/step.go | 3 --- go.mod | 5 +++++ runtime/docker/container.go | 15 +++++++++++-- 7 files changed, 91 insertions(+), 23 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 851544d1..df704663 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,7 +51,7 @@ services: # https://go-vela.github.io/docs/administration/server/ server: container_name: server - image: target/vela-server:latest + image: server:local networks: - vela environment: diff --git a/executor/linux/build.go b/executor/linux/build.go index 744e5fa5..0564a0b2 100644 --- a/executor/linux/build.go +++ b/executor/linux/build.go @@ -437,7 +437,10 @@ func (c *client) ExecBuild(ctx context.Context) error { build.Upload(c.build, c.Vela, c.err, c.Logger, c.repo) }() - var opEnv, maskEnv map[string]string + var ( + opEnv, maskEnv map[string]string + report *library.Report + ) // fire up output container to run with the build c.Logger.Infof("creating outputs container %s", c.OutputCtn.ID) @@ -455,7 +458,7 @@ func (c *client) ExecBuild(ctx context.Context) error { } // poll outputs container for any updates - opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + opEnv, maskEnv, report, c.err = c.outputs.poll(ctx, c.OutputCtn, nil) if c.err != nil { return fmt.Errorf("unable to exec outputs container: %w", c.err) } @@ -477,7 +480,7 @@ func (c *client) ExecBuild(ctx context.Context) error { } // poll outputs container - opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + opEnv, maskEnv, report, c.err = c.outputs.poll(ctx, c.OutputCtn, _service) if c.err != nil { return fmt.Errorf("unable to exec outputs container: %w", c.err) } @@ -547,10 +550,29 @@ func (c *client) ExecBuild(ctx context.Context) error { } // poll outputs - opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + opEnv, maskEnv, report, c.err = c.outputs.poll(ctx, c.OutputCtn, _step) if c.err != nil { return fmt.Errorf("unable to exec outputs container: %w", c.err) } + + if _step.ReportStatus { + libStep, err := step.Load(_step, &c.steps) + if err != nil { + return fmt.Errorf("unable to load step %s", _step.Name) + } + + c.Logger.Infof("REPORTY %v", report) + + libStep.SetReport(report) + + c.Logger.Infof("REPORT REPORT REPORT %v", libStep.GetReport()) + + _, _, err = c.Vela.Step.Update(c.repo.GetOrg(), c.repo.GetName(), c.build.GetNumber(), libStep) + if err != nil { + return fmt.Errorf("unable to update step %s", _step.Name) + } + } + } // create an error group with the context for each stage diff --git a/executor/linux/outputs.go b/executor/linux/outputs.go index b02fc45b..deb3b8e1 100644 --- a/executor/linux/outputs.go +++ b/executor/linux/outputs.go @@ -6,9 +6,11 @@ import ( "bytes" "context" "encoding/base64" + "encoding/json" "fmt" "strings" + "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" "github.com/sirupsen/logrus" ) @@ -36,7 +38,7 @@ func (o *outputSvc) create(ctx context.Context, ctn *pipeline.Container, timeout logger := o.client.Logger.WithField("outputs", "outputs") // generate script from commands - script := generateScriptPosix([]string{fmt.Sprintf("sleep %d", timeout)}) + script := generateScriptPosix([]string{"mkdir /vela/outputs", fmt.Sprintf("sleep %d", timeout)}) // set the entrypoint for the ctn ctn.Entrypoint = []string{"/bin/sh", "-c"} @@ -145,10 +147,10 @@ func (o *outputSvc) exec(ctx context.Context, _outputs *pipeline.Container) erro } // poll tails the output for a secret plugin. -func (o *outputSvc) poll(ctx context.Context, ctn *pipeline.Container) (map[string]string, map[string]string, error) { +func (o *outputSvc) poll(ctx context.Context, ctn *pipeline.Container, stepCtn *pipeline.Container) (map[string]string, map[string]string, *library.Report, error) { // exit if outputs container has not been configured if len(ctn.Image) == 0 { - return nil, nil, nil + return nil, nil, nil, nil } // update engine logger with secret metadata @@ -159,27 +161,44 @@ func (o *outputSvc) poll(ctx context.Context, ctn *pipeline.Container) (map[stri logger.Debug("tailing container") // grab outputs - outputBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, "/vela/outputs.env") + outputBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, "/vela/outputs/.env") if err != nil { - return nil, nil, err + return nil, nil, nil, err } // grab masked outputs - maskedBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, "/vela/masked_outputs.env") + maskedBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, "/vela/outputs/masked.env") if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return toMap(outputBytes), toMap(maskedBytes), nil + var report library.Report + + // grab report if report path specified + if stepCtn != nil && len(stepCtn.ReportPath) > 0 { + logger.Infof("polling report.json file from outputs container %s", ctn.ID) + + reportPath := fmt.Sprintf("/vela/outputs/%s", stepCtn.ReportPath) + + reportBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, reportPath) + if err != nil { + return nil, nil, nil, err + } + + err = json.Unmarshal(reportBytes, &report) + if err != nil { + logger.Infof("ERROR ERROR ERROR: %s", err) + } + } + + return toMap(outputBytes), toMap(maskedBytes), &report, nil } // toMap is a helper function that turns raw docker exec output bytes into a map // by splitting on carriage returns + newlines and once more on `=`. func toMap(input []byte) map[string]string { - str := string(input[8:]) // Ignore first 8 bytes of Docker output. - - logrus.Infof("string to split: %s", str) - lines := strings.Split(str, "\r\n") + logrus.Infof("string to split: %s", string(input)) + lines := strings.Split(string(input), "\r\n") m := make(map[string]string) diff --git a/executor/linux/stage.go b/executor/linux/stage.go index 4494e5c8..4267317a 100644 --- a/executor/linux/stage.go +++ b/executor/linux/stage.go @@ -8,6 +8,7 @@ import ( "sync" "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" "github.com/go-vela/worker/internal/step" ) @@ -193,12 +194,25 @@ func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, return fmt.Errorf("unable to exec step %s: %w", _step.Name, err) } + var report *library.Report + // poll outputs - opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + opEnv, maskEnv, report, c.err = c.outputs.poll(ctx, c.OutputCtn, _step) if c.err != nil { return fmt.Errorf("unable to exec outputs container: %w", c.err) } + if _step.ReportStatus { + libStep, err := step.Load(_step, &c.steps) + if err != nil { + return fmt.Errorf("unable to load step %s", _step.Name) + } + + libStep.SetReport(report) + + _, _, err = c.Vela.Step.Update(c.repo.GetOrg(), c.repo.GetName(), c.build.GetNumber(), libStep) + } + // failed steps within the stage should set the stop value to true unless // the continue rule is set to true. if _step.ExitCode != 0 && !_step.Ruleset.Continue { diff --git a/executor/linux/step.go b/executor/linux/step.go index ad5056f3..dae4bfb1 100644 --- a/executor/linux/step.go +++ b/executor/linux/step.go @@ -17,7 +17,6 @@ import ( "github.com/go-vela/worker/internal/image" "github.com/go-vela/worker/internal/message" "github.com/go-vela/worker/internal/step" - "github.com/sirupsen/logrus" ) // CreateStep configures the step for execution. @@ -152,8 +151,6 @@ func (c *client) ExecStep(ctx context.Context, ctn *pipeline.Container) error { logger.Debug("running container") - logrus.Infof("BEFORE RUN CONTAINER: %s", ctn.Environment["NEW"]) - // run the runtime container err = c.Runtime.RunContainer(ctx, ctn, c.pipeline) if err != nil { diff --git a/go.mod b/go.mod index 0c0de72e..dc470f12 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,11 @@ module github.com/go-vela/worker go 1.21 +replace ( + github.com/go-vela/sdk-go => ../sdk-go + github.com/go-vela/types => ../types +) + require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/distribution/reference v0.5.0 diff --git a/runtime/docker/container.go b/runtime/docker/container.go index 57b276df..f738e233 100644 --- a/runtime/docker/container.go +++ b/runtime/docker/container.go @@ -3,6 +3,7 @@ package docker import ( + "bytes" "context" "fmt" "io" @@ -316,9 +317,19 @@ func (c *client) PollOutputsContainer(ctx context.Context, ctn *pipeline.Contain defer hijackedResponse.Close() - result, _ := io.ReadAll(hijackedResponse.Reader) + outputStdout := new(bytes.Buffer) + outputStderr := new(bytes.Buffer) - return result, nil + stdcopy.StdCopy(outputStdout, outputStderr, hijackedResponse.Reader) + + if outputStderr.Len() > 0 { + fmt.Println("Error: ", outputStderr.String()) + return nil, fmt.Errorf("Error: %s", outputStderr.String()) + } + + data := outputStdout.Bytes() + + return data, nil } // ctnConfig is a helper function to From b6c5297901b82648d8bd2fec06af71043187123e Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 25 Jul 2024 12:03:32 -0500 Subject: [PATCH 05/15] add testing --- cmd/vela-worker/run.go | 2 +- executor/linux/build.go | 45 +-- executor/linux/build_test.go | 1 + executor/linux/outputs.go | 103 ++----- executor/linux/outputs_test.go | 508 +++++++++++++++++++++++++++++++ executor/linux/stage.go | 23 +- executor/linux/step.go | 9 - runtime/docker/container.go | 22 +- runtime/docker/container_test.go | 45 +++ 9 files changed, 614 insertions(+), 144 deletions(-) create mode 100644 executor/linux/outputs_test.go diff --git a/cmd/vela-worker/run.go b/cmd/vela-worker/run.go index fe537490..efad7568 100644 --- a/cmd/vela-worker/run.go +++ b/cmd/vela-worker/run.go @@ -74,7 +74,7 @@ func run(c *cli.Context) error { return fmt.Errorf("unable to parse address: %w", err) } - var outputsCtn *pipeline.Container + outputsCtn := new(pipeline.Container) if len(c.String("executor.outputs-image")) > 0 { outputsCtn = &pipeline.Container{ Detach: true, diff --git a/executor/linux/build.go b/executor/linux/build.go index 42034209..382bef4e 100644 --- a/executor/linux/build.go +++ b/executor/linux/build.go @@ -441,14 +441,13 @@ func (c *client) ExecBuild(ctx context.Context) error { build.Upload(c.build, c.Vela, c.err, c.Logger) }() - var ( - opEnv, maskEnv map[string]string - report *library.Report - ) + // output maps for dynamic environment variables captured from volume + var opEnv, maskEnv map[string]string // fire up output container to run with the build c.Logger.Infof("creating outputs container %s", c.OutputCtn.ID) + // execute outputs container c.err = c.outputs.exec(ctx, c.OutputCtn) if c.err != nil { return fmt.Errorf("unable to exec outputs container: %w", c.err) @@ -461,8 +460,8 @@ func (c *client) ExecBuild(ctx context.Context) error { return fmt.Errorf("unable to execute secret: %w", c.err) } - // poll outputs container for any updates - opEnv, maskEnv, report, c.err = c.outputs.poll(ctx, c.OutputCtn, nil) + // poll outputs container for any updates from secret plugins + opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) if c.err != nil { return fmt.Errorf("unable to exec outputs container: %w", c.err) } @@ -482,12 +481,6 @@ func (c *client) ExecBuild(ctx context.Context) error { if c.err != nil { return fmt.Errorf("unable to execute service: %w", c.err) } - - // poll outputs container - opEnv, maskEnv, report, c.err = c.outputs.poll(ctx, c.OutputCtn, _service) - if c.err != nil { - return fmt.Errorf("unable to exec outputs container: %w", c.err) - } } // execute the steps for the pipeline @@ -546,6 +539,13 @@ func (c *client) ExecBuild(ctx context.Context) error { return err } + c.Logger.Debug("injecting non-substituted secrets") + // inject no-substitution secrets for container + err = injectSecrets(_step, c.NoSubSecrets) + if err != nil { + return err + } + c.Logger.Infof("executing %s step", _step.Name) // execute the step c.err = c.ExecStep(ctx, _step) @@ -554,29 +554,10 @@ func (c *client) ExecBuild(ctx context.Context) error { } // poll outputs - opEnv, maskEnv, report, c.err = c.outputs.poll(ctx, c.OutputCtn, _step) + opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) if c.err != nil { return fmt.Errorf("unable to exec outputs container: %w", c.err) } - - if _step.ReportStatus { - libStep, err := step.Load(_step, &c.steps) - if err != nil { - return fmt.Errorf("unable to load step %s", _step.Name) - } - - c.Logger.Infof("REPORTY %v", report) - - libStep.SetReport(report) - - c.Logger.Infof("REPORT REPORT REPORT %v", libStep.GetReport()) - - _, _, err = c.Vela.Step.Update(c.build.GetRepo().GetOrg(), c.build.GetRepo().GetName(), c.build.GetNumber(), libStep) - if err != nil { - return fmt.Errorf("unable to update step %s", _step.Name) - } - } - } // create an error group with the context for each stage diff --git a/executor/linux/build_test.go b/executor/linux/build_test.go index eaaccbd5..404a6ca8 100644 --- a/executor/linux/build_test.go +++ b/executor/linux/build_test.go @@ -1014,6 +1014,7 @@ func TestLinux_AssembleBuild_EnforceTrustedRepos(t *testing.T) { WithVelaClient(_client), WithPrivilegedImages(test.privilegedImages), WithEnforceTrustedRepos(test.enforceTrustedRepos), + WithOutputCtn(new(pipeline.Container)), ) if err != nil { t.Errorf("unable to create executor engine: %v", err) diff --git a/executor/linux/outputs.go b/executor/linux/outputs.go index deb3b8e1..c2d7b77e 100644 --- a/executor/linux/outputs.go +++ b/executor/linux/outputs.go @@ -3,14 +3,11 @@ package linux import ( - "bytes" "context" "encoding/base64" - "encoding/json" "fmt" "strings" - "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" "github.com/sirupsen/logrus" ) @@ -18,27 +15,20 @@ import ( // outputSvc handles communication with the outputs container during the build. type outputSvc svc -// traceScript is a helper script that is added to the build script -// to trace a command. -const traceScript = ` -echo $ %s -%s -` - -// create configures the outputs plugin for execution. +// create configures the outputs container for execution. func (o *outputSvc) create(ctx context.Context, ctn *pipeline.Container, timeout int64) error { // exit if outputs container has not been configured if len(ctn.Image) == 0 { return nil } - // update engine logger with secret metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField + // set up outputs logger logger := o.client.Logger.WithField("outputs", "outputs") - // generate script from commands - script := generateScriptPosix([]string{"mkdir /vela/outputs", fmt.Sprintf("sleep %d", timeout)}) + // Encode script content to Base64 + script := base64.StdEncoding.EncodeToString( + []byte(fmt.Sprintf("mkdir /vela/outputs\nsleep %d\n", timeout)), + ) // set the entrypoint for the ctn ctn.Entrypoint = []string{"/bin/sh", "-c"} @@ -50,13 +40,8 @@ func (o *outputSvc) create(ctx context.Context, ctn *pipeline.Container, timeout ctn.Environment["HOME"] = "/root" ctn.Environment["SHELL"] = "/bin/sh" ctn.Environment["VELA_BUILD_SCRIPT"] = script - ctn.Environment["VELA_DISTRIBUTION"] = o.client.build.GetDistribution() - ctn.Environment["BUILD_HOST"] = o.client.build.GetHost() - ctn.Environment["VELA_HOST"] = o.client.build.GetHost() - ctn.Environment["VELA_RUNTIME"] = o.client.build.GetRuntime() - ctn.Environment["VELA_VERSION"] = o.client.Version - logger.Debug("setting up container") + logger.Debug("setting up outputs container") // setup the runtime container err := o.client.Runtime.SetupContainer(ctx, ctn) if err != nil { @@ -76,16 +61,16 @@ func (o *outputSvc) destroy(ctx context.Context, ctn *pipeline.Container) error // update engine logger with secret metadata // // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField - logger := o.client.Logger.WithField("secret", ctn.Name) + logger := o.client.Logger.WithField("outputs", ctn.Name) - logger.Debug("inspecting container") + logger.Debug("inspecting outputs container") // inspect the runtime container err := o.client.Runtime.InspectContainer(ctx, ctn) if err != nil { return err } - logger.Debug("removing container") + logger.Debug("removing outputs container") // remove the runtime container err = o.client.Runtime.RemoveContainer(ctx, ctn) if err != nil { @@ -95,33 +80,6 @@ func (o *outputSvc) destroy(ctx context.Context, ctn *pipeline.Container) error return nil } -// generateScriptPosix is a helper function that generates a build script -// for a linux container using the given commands. -func generateScriptPosix(commands []string) string { - var buf bytes.Buffer - - // iterate through each command provided - for _, command := range commands { - // safely escape entire command - escaped := fmt.Sprintf("%q", command) - - // safely escape trace character - escaped = strings.Replace(escaped, "$", `\$`, -1) - - // write escaped lines to buffer - buf.WriteString(fmt.Sprintf( - traceScript, - escaped, - command, - )) - } - - // create build script with netrc and buffer information - script := buf.String() - - return base64.StdEncoding.EncodeToString([]byte(script)) -} - // exec runs a secret plugins for a pipeline. func (o *outputSvc) exec(ctx context.Context, _outputs *pipeline.Container) error { // exit if outputs container has not been configured @@ -129,14 +87,14 @@ func (o *outputSvc) exec(ctx context.Context, _outputs *pipeline.Container) erro return nil } - logrus.Debug("running container") + logrus.Debug("running outputs container") // run the runtime container err := o.client.Runtime.RunContainer(ctx, _outputs, o.client.pipeline) if err != nil { return err } - logrus.Debug("inspecting container") + logrus.Debug("inspecting outputs container") // inspect the runtime container err = o.client.Runtime.InspectContainer(ctx, _outputs) if err != nil { @@ -146,58 +104,39 @@ func (o *outputSvc) exec(ctx context.Context, _outputs *pipeline.Container) erro return nil } -// poll tails the output for a secret plugin. -func (o *outputSvc) poll(ctx context.Context, ctn *pipeline.Container, stepCtn *pipeline.Container) (map[string]string, map[string]string, *library.Report, error) { +// poll tails the output for sidecar container. +func (o *outputSvc) poll(ctx context.Context, ctn *pipeline.Container) (map[string]string, map[string]string, error) { // exit if outputs container has not been configured if len(ctn.Image) == 0 { - return nil, nil, nil, nil + return nil, nil, nil } - // update engine logger with secret metadata + // update engine logger with outputs metadata // // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField - logger := o.client.Logger.WithField("secret", ctn.Name) + logger := o.client.Logger.WithField("outputs", ctn.Name) logger.Debug("tailing container") // grab outputs outputBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, "/vela/outputs/.env") if err != nil { - return nil, nil, nil, err + return nil, nil, err } // grab masked outputs maskedBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, "/vela/outputs/masked.env") if err != nil { - return nil, nil, nil, err - } - - var report library.Report - - // grab report if report path specified - if stepCtn != nil && len(stepCtn.ReportPath) > 0 { - logger.Infof("polling report.json file from outputs container %s", ctn.ID) - - reportPath := fmt.Sprintf("/vela/outputs/%s", stepCtn.ReportPath) - - reportBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, reportPath) - if err != nil { - return nil, nil, nil, err - } - - err = json.Unmarshal(reportBytes, &report) - if err != nil { - logger.Infof("ERROR ERROR ERROR: %s", err) - } + return nil, nil, err } - return toMap(outputBytes), toMap(maskedBytes), &report, nil + return toMap(outputBytes), toMap(maskedBytes), nil } // toMap is a helper function that turns raw docker exec output bytes into a map // by splitting on carriage returns + newlines and once more on `=`. func toMap(input []byte) map[string]string { - logrus.Infof("string to split: %s", string(input)) + // carriage returns are included in the split because the exec config `TTY` value is set to true lines := strings.Split(string(input), "\r\n") m := make(map[string]string) diff --git a/executor/linux/outputs_test.go b/executor/linux/outputs_test.go new file mode 100644 index 00000000..3a45f1c7 --- /dev/null +++ b/executor/linux/outputs_test.go @@ -0,0 +1,508 @@ +// SPDX-License-Identifier: Apache-2.0 + +package linux + +import ( + "context" + "flag" + "net/http/httptest" + "os" + "reflect" + "testing" + + "github.com/gin-gonic/gin" + "github.com/urfave/cli/v2" + + "github.com/go-vela/sdk-go/vela" + "github.com/go-vela/server/compiler/native" + "github.com/go-vela/server/mock/server" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" + "github.com/go-vela/worker/internal/message" + "github.com/go-vela/worker/runtime" + "github.com/go-vela/worker/runtime/docker" +) + +func TestLinux_Outputs_create(t *testing.T) { + // setup types + _build := testBuild() + _steps := testSteps(constants.DriverDocker) + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + _docker, err := docker.NewMock() + if err != nil { + t.Errorf("unable to create docker runtime engine: %v", err) + } + + // setup tests + tests := []struct { + name string + failure bool + runtime runtime.Engine + container *pipeline.Container + }{ + { + name: "good image tag", + failure: false, + runtime: _docker, + container: &pipeline.Container{ + ID: "outputs_github_octocat_1", + Directory: "/vela/src/vcs.company.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "alpine:latest", + Name: "outputs", + Number: 1, + Pull: "not_present", + }, + }, + { + name: "notfound image tag", + failure: true, + runtime: _docker, + container: &pipeline.Container{ + ID: "outputs_github_octocat_1", + Directory: "/vela/src/vcs.company.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "alpine:notfound", + Name: "outputs", + Number: 1, + Pull: "not_present", + }, + }, + { + name: "not supplied image tag", + failure: false, + runtime: _docker, + container: &pipeline.Container{ + ID: "outputs_github_octocat_1", + Directory: "/vela/src/vcs.company.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "", + Name: "outputs", + Number: 1, + Pull: "not_present", + }, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _engine, err := New( + WithBuild(_build), + WithPipeline(_steps), + WithRuntime(test.runtime), + WithVelaClient(_client), + WithOutputCtn(test.container), + ) + if err != nil { + t.Errorf("unable to create %s executor engine: %v", test.name, err) + } + + err = _engine.outputs.create(context.Background(), test.container, 30) + + if test.failure { + if err == nil { + t.Errorf("%s create should have returned err", test.name) + } + + return // continue to next test + } + + if err != nil { + t.Errorf("%s create returned err: %v", test.name, err) + } + }) + } +} + +func TestLinux_Outputs_delete(t *testing.T) { + // setup types + _build := testBuild() + _dockerSteps := testSteps(constants.DriverDocker) + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + _docker, err := docker.NewMock() + if err != nil { + t.Errorf("unable to create docker runtime engine: %v", err) + } + + _step := new(library.Step) + _step.SetName("clone") + _step.SetNumber(2) + _step.SetStatus(constants.StatusPending) + + // setup tests + tests := []struct { + name string + failure bool + runtime runtime.Engine + container *pipeline.Container + step *library.Step + steps *pipeline.Build + }{ + { + name: "docker-running container-empty step", + failure: false, + runtime: _docker, + container: &pipeline.Container{ + ID: "outputs_github_octocat_1", + Directory: "/vela/src/vcs.company.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "alpine:latest", + Name: "outputs", + Number: 1, + Pull: "always", + }, + step: new(library.Step), + steps: _dockerSteps, + }, + { + name: "docker-running container-pending step", + failure: false, + runtime: _docker, + container: &pipeline.Container{ + ID: "outputs_github_octocat_1", + Directory: "/vela/src/vcs.company.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "alpine:latest", + Name: "outputs", + Number: 2, + Pull: "always", + }, + step: _step, + steps: _dockerSteps, + }, + { + name: "docker-inspecting container failure due to invalid container id", + failure: true, + runtime: _docker, + container: &pipeline.Container{ + ID: "outputs_github_octocat_1_notfound", + Directory: "/vela/src/vcs.company.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "alpine:latest", + Name: "notfound", + Number: 2, + Pull: "always", + }, + step: new(library.Step), + steps: _dockerSteps, + }, + { + name: "docker-removing container failure", + failure: true, + runtime: _docker, + container: &pipeline.Container{ + ID: "outputs_github_octocat_1_ignorenotfound", + Directory: "/vela/src/vcs.company.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "alpine:latest", + Name: "ignorenotfound", + Number: 2, + Pull: "always", + }, + step: new(library.Step), + steps: _dockerSteps, + }, + { + name: "no outputs image provided", + failure: false, + runtime: _docker, + container: &pipeline.Container{ + ID: "outputs_github_octocat_1", + Directory: "/vela/src/vcs.company.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "", + Name: "outputs", + Number: 2, + Pull: "always", + }, + step: _step, + steps: _dockerSteps, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _engine, err := New( + WithBuild(_build), + WithPipeline(test.steps), + WithRuntime(test.runtime), + WithVelaClient(_client), + WithOutputCtn(test.container), + ) + if err != nil { + t.Errorf("unable to create %s executor engine: %v", test.name, err) + } + + // add init container info to client + _ = _engine.CreateBuild(context.Background()) + + _engine.steps.Store(test.container.ID, test.step) + + err = _engine.outputs.destroy(context.Background(), test.container) + + if test.failure { + if err == nil { + t.Errorf("%s destroy should have returned err", test.name) + } + + return // continue to next test + } + + if err != nil { + t.Errorf("%s destroy returned err: %v", test.name, err) + } + }) + } +} + +func TestLinux_Outputs_exec(t *testing.T) { + // setup types + set := flag.NewFlagSet("test", 0) + set.String("clone-image", "target/vela-git:latest", "doc") + compiler, _ := native.FromCLIContext(cli.NewContext(nil, set, nil)) + + _build := testBuild() + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + streamRequests, done := message.MockStreamRequestsWithCancel(context.Background()) + defer done() + + // setup tests + tests := []struct { + name string + failure bool + runtime string + pipeline string + }{ + { + name: "basic pipeline", + failure: false, + runtime: constants.DriverDocker, + pipeline: "testdata/build/steps/basic.yml", + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + file, _ := os.ReadFile(test.pipeline) + + p, _, err := compiler. + Duplicate(). + WithBuild(_build). + WithRepo(_build.GetRepo()). + WithUser(_build.GetRepo().GetOwner()). + Compile(file) + if err != nil { + t.Errorf("unable to compile pipeline %s: %v", test.pipeline, err) + } + + // Docker uses _ while Kubernetes uses - + p = p.Sanitize(test.runtime) + + var _runtime runtime.Engine + + _runtime, err = docker.NewMock() + if err != nil { + t.Errorf("unable to create docker runtime engine: %v", err) + } + + outputsCtn := &pipeline.Container{ + ID: "outputs_github_octocat_1", + Directory: "/vela/src/vcs.company.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "alpine:latest", + Name: "outputs", + Number: 2, + Pull: "always", + } + + _engine, err := New( + WithBuild(_build), + WithPipeline(p), + WithRuntime(_runtime), + WithVelaClient(_client), + withStreamRequests(streamRequests), + WithOutputCtn(outputsCtn), + ) + if err != nil { + t.Errorf("unable to create %s executor engine: %v", test.name, err) + } + + _engine.build.SetStatus(constants.StatusSuccess) + + // add init container info to client + _ = _engine.CreateBuild(context.Background()) + + err = _engine.outputs.exec(context.Background(), outputsCtn) + + if test.failure { + if err == nil { + t.Errorf("%s exec should have returned err", test.name) + } + + return // continue to next test + } + + if err != nil { + t.Errorf("%s exec returned err: %v", test.name, err) + } + }) + } +} + +func TestLinux_Outputs_poll(t *testing.T) { + // setup types + _build := testBuild() + _steps := testSteps(constants.DriverDocker) + + gin.SetMode(gin.TestMode) + + s := httptest.NewServer(server.FakeHandler()) + + _client, err := vela.NewClient(s.URL, "", nil) + if err != nil { + t.Errorf("unable to create Vela API client: %v", err) + } + + _docker, err := docker.NewMock() + if err != nil { + t.Errorf("unable to create docker runtime engine: %v", err) + } + + // setup tests + tests := []struct { + name string + failure bool + runtime runtime.Engine + container *pipeline.Container + }{ + { + name: "succeeds", + failure: false, + runtime: _docker, + container: &pipeline.Container{ + ID: "outputs_github_octocat_1", + Directory: "/home/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "alpine:latest", + Name: "outputs", + Number: 1, + Pull: "always", + }, + }, + { + name: "no outputs image provided", + failure: false, + runtime: _docker, + container: &pipeline.Container{ + ID: "outputs_github_octocat_1_notfound", + Directory: "/vela/src/vcs.company.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "", + Name: "notfound", + Number: 2, + Pull: "always", + }, + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _engine, err := New( + WithBuild(_build), + WithPipeline(_steps), + WithRuntime(test.runtime), + WithVelaClient(_client), + ) + if err != nil { + t.Errorf("unable to create %s executor engine: %v", test.name, err) + } + + // add init container info to client + _ = _engine.CreateBuild(context.Background()) + + _, _, err = _engine.outputs.poll(context.Background(), test.container) + + if test.failure { + if err == nil { + t.Errorf("%s poll should have returned err", test.name) + } + + return // continue to next test + } + + if err != nil { + t.Errorf("%s poll returned err: %v", test.name, err) + } + }) + } +} + +func TestLinux_Outputs_toMap(t *testing.T) { + // setup tests + tests := []struct { + name string + runtime string + input []byte + wantMap map[string]string + }{ + { + name: "basic", + runtime: constants.DriverDocker, + input: []byte("FOO=bar\r\nONE=1\r\nTEST=hello world\r\n"), + wantMap: map[string]string{ + "FOO": "bar", + "ONE": "1", + "TEST": "hello world", + }, + }, + { + name: "bad format", + runtime: constants.DriverDocker, + input: []byte("FOO;bar//ONE^TEST,,,hello world"), + wantMap: make(map[string]string), + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := toMap(test.input) + + if !reflect.DeepEqual(got, test.wantMap) { + t.Errorf("toMap is %v, want %v", got, test.wantMap) + } + }) + } +} diff --git a/executor/linux/stage.go b/executor/linux/stage.go index 00039e45..d00589c8 100644 --- a/executor/linux/stage.go +++ b/executor/linux/stage.go @@ -8,7 +8,6 @@ import ( "sync" "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" "github.com/go-vela/types/pipeline" "github.com/go-vela/worker/internal/step" ) @@ -189,6 +188,13 @@ func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, return err } + c.Logger.Debug("injecting non-substituted secrets") + // inject no-substitution secrets for container + err = injectSecrets(_step, c.NoSubSecrets) + if err != nil { + return err + } + logger.Infof("executing %s step", _step.Name) // execute the step err = c.ExecStep(ctx, _step) @@ -196,25 +202,12 @@ func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, return fmt.Errorf("unable to exec step %s: %w", _step.Name, err) } - var report *library.Report - // poll outputs - opEnv, maskEnv, report, c.err = c.outputs.poll(ctx, c.OutputCtn, _step) + opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) if c.err != nil { return fmt.Errorf("unable to exec outputs container: %w", c.err) } - if _step.ReportStatus { - libStep, err := step.Load(_step, &c.steps) - if err != nil { - return fmt.Errorf("unable to load step %s", _step.Name) - } - - libStep.SetReport(report) - - _, _, err = c.Vela.Step.Update(c.build.GetRepo().GetOrg(), c.build.GetRepo().GetName(), c.build.GetNumber(), libStep) - } - // failed steps within the stage should set the stop value to true unless // the continue rule is set to true. if _step.ExitCode != 0 && !_step.Ruleset.Continue { diff --git a/executor/linux/step.go b/executor/linux/step.go index 36f9cc5a..88ed5da7 100644 --- a/executor/linux/step.go +++ b/executor/linux/step.go @@ -58,15 +58,6 @@ func (c *client) CreateStep(ctx context.Context, ctn *pipeline.Container) error return err } - logger.Debug("substituting container configuration") - - logger.Debug("injecting non-substituted secrets") - // inject no-substitution secrets for container - err = injectSecrets(ctn, c.NoSubSecrets) - if err != nil { - return err - } - return nil } diff --git a/runtime/docker/container.go b/runtime/docker/container.go index 50db5e15..fd67467c 100644 --- a/runtime/docker/container.go +++ b/runtime/docker/container.go @@ -295,8 +295,12 @@ func (c *client) WaitContainer(ctx context.Context, ctn *pipeline.Container) err return nil } -// PollOutputsContainer +// PollOutputsContainer captures the `cat` response for a given path in the Docker volume. func (c *client) PollOutputsContainer(ctx context.Context, ctn *pipeline.Container, path string) ([]byte, error) { + if len(ctn.Image) == 0 { + return nil, nil + } + execConfig := types.ExecConfig{ Tty: true, Cmd: []string{"sh", "-c", fmt.Sprintf("cat %s", path)}, @@ -314,16 +318,24 @@ func (c *client) PollOutputsContainer(ctx context.Context, ctn *pipeline.Contain log.Fatal(err) } - defer hijackedResponse.Close() + defer func() { + if hijackedResponse.Conn != nil { + hijackedResponse.Close() + } + }() outputStdout := new(bytes.Buffer) outputStderr := new(bytes.Buffer) - stdcopy.StdCopy(outputStdout, outputStderr, hijackedResponse.Reader) + if hijackedResponse.Reader != nil { + _, err := stdcopy.StdCopy(outputStdout, outputStderr, hijackedResponse.Reader) + if err != nil { + c.Logger.Errorf("unable to copy logs for container: %v", err) + } + } if outputStderr.Len() > 0 { - fmt.Println("Error: ", outputStderr.String()) - return nil, fmt.Errorf("Error: %s", outputStderr.String()) + return nil, fmt.Errorf("error: %s", outputStderr.String()) } data := outputStdout.Bytes() diff --git a/runtime/docker/container_test.go b/runtime/docker/container_test.go index ada72242..82e35cda 100644 --- a/runtime/docker/container_test.go +++ b/runtime/docker/container_test.go @@ -397,6 +397,51 @@ func TestDocker_TailContainer(t *testing.T) { } } +func TestDocker_PollOutputsContainer(t *testing.T) { + // setup Docker + _engine, err := NewMock() + if err != nil { + t.Errorf("unable to create runtime engine: %v", err) + } + + // setup tests + tests := []struct { + name string + failure bool + container *pipeline.Container + }{ + { + name: "outputs container", + failure: false, + container: _container, + }, + { + name: "no provided outputs container", + failure: false, + container: new(pipeline.Container), + }, + } + + // run tests + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err = _engine.PollOutputsContainer(context.Background(), test.container, "") + + if test.failure { + if err == nil { + t.Errorf("PollOutputsContainer should have returned err") + } + + return // continue to next test + } + + if err != nil { + t.Errorf("PollOutputs returned err: %v", err) + } + }) + } +} + func TestDocker_WaitContainer(t *testing.T) { // setup Docker _engine, err := NewMock() From 696fd3e37b76d99287048615e154721303743978 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 25 Jul 2024 13:24:18 -0500 Subject: [PATCH 06/15] remove assemble build priv image test and fix compose --- Dockerfile | 2 +- docker-compose.yml | 2 +- executor/linux/build.go | 8 +- executor/linux/build_test.go | 831 --------------------------------- executor/linux/service.go | 27 +- executor/linux/service_test.go | 16 + executor/linux/step.go | 27 +- executor/linux/step_test.go | 16 + go.mod | 2 - go.sum | 2 + runtime/engine.go | 2 + 11 files changed, 74 insertions(+), 861 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0fa7d276..30386377 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM alpine:3.20.0@sha256:77726ef6b57ddf65bb551896826ec38bc3e53f75cdde31354fbffb RUN apk add --update --no-cache ca-certificates -FROM alpine +FROM scratch COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt diff --git a/docker-compose.yml b/docker-compose.yml index df704663..851544d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,7 +51,7 @@ services: # https://go-vela.github.io/docs/administration/server/ server: container_name: server - image: server:local + image: target/vela-server:latest networks: - vela environment: diff --git a/executor/linux/build.go b/executor/linux/build.go index 382bef4e..2bdf7297 100644 --- a/executor/linux/build.go +++ b/executor/linux/build.go @@ -214,8 +214,6 @@ func (c *client) PlanBuild(ctx context.Context) error { } // AssembleBuild prepares the containers within a build for execution. -// -//nolint:gocyclo,funlen // ignore cyclomatic complexity and function length due to comments and logging messages func (c *client) AssembleBuild(ctx context.Context) error { // defer taking a snapshot of the build // @@ -271,6 +269,9 @@ func (c *client) AssembleBuild(ctx context.Context) error { s.Detach = true c.Logger.Infof("creating %s service", s.Name) + + _log.AppendData([]byte(fmt.Sprintf("> Preparing service image %s...\n", s.Image))) + // create the service c.err = c.CreateService(ctx, s) if c.err != nil { @@ -327,6 +328,9 @@ func (c *client) AssembleBuild(ctx context.Context) error { } c.Logger.Infof("creating %s step", s.Name) + + _log.AppendData([]byte(fmt.Sprintf("> Preparing step image %s...\n", s.Image))) + // create the step c.err = c.CreateStep(ctx, s) if c.err != nil { diff --git a/executor/linux/build_test.go b/executor/linux/build_test.go index 404a6ca8..9c758239 100644 --- a/executor/linux/build_test.go +++ b/executor/linux/build_test.go @@ -216,837 +216,6 @@ func TestLinux_CreateBuild(t *testing.T) { } } -func TestLinux_AssembleBuild_EnforceTrustedRepos(t *testing.T) { - // setup types - set := flag.NewFlagSet("test", 0) - set.String("clone-image", "target/vela-git:latest", "doc") - compiler, _ := native.FromCLIContext(cli.NewContext(nil, set, nil)) - - _build := testBuild() - - // setting mock build for testing dynamic environment tags - _buildWithMessageAlpine := testBuild() - _buildWithMessageAlpine.SetMessage("alpine") - - // test repo is not trusted by default - _untrustedRepo := testRepo() - - // to be matched with the image used by testdata/build/steps/basic.yml - _privilegedImagesStepsPipeline := []string{"alpine"} - // to be matched with the image used by testdata/build/services/basic.yml - _privilegedImagesServicesPipeline := []string{"postgres"} - // to be matched with the image used by testdata/build/stages/basic.yml - _privilegedImagesStagesPipeline := []string{"alpine"} - // create trusted repo - _trustedRepo := testRepo() - _trustedRepo.SetTrusted(true) - - gin.SetMode(gin.TestMode) - - s := httptest.NewServer(server.FakeHandler()) - - _client, err := vela.NewClient(s.URL, "", nil) - if err != nil { - t.Errorf("unable to create Vela API client: %v", err) - } - - tests := []struct { - name string - failure bool - runtime string - build *api.Build - repo *api.Repo - pipeline string - privilegedImages []string - enforceTrustedRepos bool - }{ - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with untrusted repo", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with untrusted repo and dynamic image:tag", - failure: true, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with untrusted repo", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with untrusted repo and dynamic image:tag", - failure: true, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/services/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with untrusted repo", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with trusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with untrusted repo", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/basic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with untrusted repo and dynamic image:tag", - failure: true, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with trusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _trustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with untrusted repo and dynamic image:tag", - failure: false, - runtime: constants.DriverDocker, - build: _buildWithMessageAlpine, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/img_environmentdynamic.yml", - privilegedImages: []string{}, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged steps pipeline with untrusted repo and init step name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged steps pipeline with untrusted repo and init step name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged steps pipeline with untrusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged steps pipeline with untrusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/steps/name_init.yml", - privilegedImages: _privilegedImagesStepsPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged stages pipeline with untrusted repo and init step name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged stages pipeline with untrusted repo and init step name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged stages pipeline with untrusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with trusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged stages pipeline with untrusted repo and init step name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/stages/name_init.yml", - privilegedImages: _privilegedImagesStagesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with trusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: privileged services pipeline with untrusted repo and init service name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with trusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos enabled: non-privileged services pipeline with untrusted repo and init service name", - failure: true, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: true, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with trusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: privileged services pipeline with untrusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with trusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _trustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - { - name: "docker-enforce trusted repos disabled: non-privileged services pipeline with untrusted repo and init service name", - failure: false, - runtime: constants.DriverDocker, - build: _build, - repo: _untrustedRepo, - pipeline: "testdata/build/services/name_init.yml", - privilegedImages: _privilegedImagesServicesPipeline, // this matches the image from test.pipeline - enforceTrustedRepos: false, - }, - } - - // run test - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // ensure custom test repos are in the executor engine - test.build.SetRepo(test.repo) - - _pipeline, _, err := compiler. - Duplicate(). - WithBuild(test.build). - WithRepo(test.repo). - Compile(test.pipeline) - if err != nil { - t.Errorf("unable to compile pipeline %s: %v", test.pipeline, err) - } - - var _runtime runtime.Engine - - switch test.runtime { - case constants.DriverDocker: - _runtime, err = docker.NewMock() - if err != nil { - t.Errorf("unable to create docker runtime engine: %v", err) - } - } - - _engine, err := New( - WithBuild(test.build), - WithPipeline(_pipeline), - WithRuntime(_runtime), - WithVelaClient(_client), - WithPrivilegedImages(test.privilegedImages), - WithEnforceTrustedRepos(test.enforceTrustedRepos), - WithOutputCtn(new(pipeline.Container)), - ) - if err != nil { - t.Errorf("unable to create executor engine: %v", err) - } - - err = _engine.CreateBuild(context.Background()) - if err != nil { - t.Errorf("CreateBuild returned err: %v", err) - } - - // override mock handler PUT build update - // used for dynamic substitute testing - _engine.build.SetMessage(test.build.GetMessage()) - _engine.build.SetRepo(test.repo) - - err = _engine.AssembleBuild(context.Background()) - - if test.failure { - if err == nil { - t.Errorf("AssembleBuild should have returned err") - } - - return // continue to next test - } - - if err != nil { - t.Errorf("AssembleBuild returned err: %v", err) - } - }) - } -} - func TestLinux_PlanBuild(t *testing.T) { // setup types set := flag.NewFlagSet("test", 0) diff --git a/executor/linux/service.go b/executor/linux/service.go index cb92f33d..96704c62 100644 --- a/executor/linux/service.go +++ b/executor/linux/service.go @@ -129,18 +129,6 @@ func (c *client) ExecService(ctx context.Context, ctn *pipeline.Container) error // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField logger := c.Logger.WithField("service", ctn.Name) - // verify service is allowed to run - if c.enforceTrustedRepos { - priv, err := image.IsPrivilegedImage(ctn.Image, c.privilegedImages) - if err != nil { - return err - } - - if priv && !c.build.GetRepo().GetTrusted() { - return fmt.Errorf("attempting to use privileged image (%s) as untrusted repo", ctn.Image) - } - } - // load the service from the client // // https://pkg.go.dev/github.com/go-vela/worker/internal/service#Load @@ -154,6 +142,21 @@ func (c *client) ExecService(ctx context.Context, ctn *pipeline.Container) error // https://pkg.go.dev/github.com/go-vela/worker/internal/service#Snapshot defer func() { service.Snapshot(ctn, c.build, c.Vela, c.Logger, _service) }() + // verify service is allowed to run + if c.enforceTrustedRepos { + priv, err := image.IsPrivilegedImage(ctn.Image, c.privilegedImages) + if err != nil { + return err + } + + if priv && !c.build.GetRepo().GetTrusted() { + _service.SetStatus(constants.StatusError) + _service.SetError("attempting to use privileged image as untrusted repo") + + return fmt.Errorf("attempting to use privileged image (%s) as untrusted repo", ctn.Image) + } + } + logger.Debug("running container") // run the runtime container err = c.Runtime.RunContainer(ctx, ctn, c.pipeline) diff --git a/executor/linux/service_test.go b/executor/linux/service_test.go index 6787c391..9ed98a0e 100644 --- a/executor/linux/service_test.go +++ b/executor/linux/service_test.go @@ -404,6 +404,20 @@ func TestLinux_ExecService(t *testing.T) { runtime: _kubernetes, container: new(pipeline.Container), }, + { + name: "privileged image", + failure: true, + runtime: _docker, + container: &pipeline.Container{ + ID: "step_github_octocat_1_echo", + Directory: "/vela/src/github.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "target/vela-docker", + Name: "echo", + Number: 1, + Pull: "not_present", + }, + }, } // run tests @@ -415,6 +429,8 @@ func TestLinux_ExecService(t *testing.T) { WithRuntime(test.runtime), WithVelaClient(_client), withStreamRequests(streamRequests), + WithEnforceTrustedRepos(true), + WithPrivilegedImages([]string{"target/vela-docker"}), ) if err != nil { t.Errorf("unable to create %s executor engine: %v", test.name, err) diff --git a/executor/linux/step.go b/executor/linux/step.go index 88ed5da7..cf2d5152 100644 --- a/executor/linux/step.go +++ b/executor/linux/step.go @@ -135,18 +135,6 @@ func (c *client) ExecStep(ctx context.Context, ctn *pipeline.Container) error { return nil } - // verify step is allowed to run - if c.enforceTrustedRepos { - priv, err := image.IsPrivilegedImage(ctn.Image, c.privilegedImages) - if err != nil { - return err - } - - if priv && !c.build.GetRepo().GetTrusted() { - return fmt.Errorf("attempting to use privileged image (%s) as untrusted repo", ctn.Image) - } - } - // update engine logger with step metadata // // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField @@ -165,6 +153,21 @@ func (c *client) ExecStep(ctx context.Context, ctn *pipeline.Container) error { // https://pkg.go.dev/github.com/go-vela/worker/internal/step#Snapshot defer func() { step.Snapshot(ctn, c.build, c.Vela, c.Logger, _step) }() + // verify step is allowed to run + if c.enforceTrustedRepos { + priv, err := image.IsPrivilegedImage(ctn.Image, c.privilegedImages) + if err != nil { + return err + } + + if priv && !c.build.GetRepo().GetTrusted() { + _step.SetStatus(constants.StatusError) + _step.SetError("attempting to use privileged image as untrusted repo") + + return fmt.Errorf("attempting to use privileged image (%s) as untrusted repo", ctn.Image) + } + } + logger.Debug("running container") // run the runtime container diff --git a/executor/linux/step_test.go b/executor/linux/step_test.go index 96c936e6..52f781d8 100644 --- a/executor/linux/step_test.go +++ b/executor/linux/step_test.go @@ -470,6 +470,20 @@ func TestLinux_ExecStep(t *testing.T) { runtime: _kubernetes, container: new(pipeline.Container), }, + { + name: "privileged image", + failure: true, + runtime: _docker, + container: &pipeline.Container{ + ID: "step_github_octocat_1_echo", + Directory: "/vela/src/github.com/github/octocat", + Environment: map[string]string{"FOO": "bar"}, + Image: "target/vela-docker", + Name: "echo", + Number: 1, + Pull: "not_present", + }, + }, } // run tests @@ -481,6 +495,8 @@ func TestLinux_ExecStep(t *testing.T) { WithRuntime(test.runtime), WithVelaClient(_client), withStreamRequests(streamRequests), + WithPrivilegedImages([]string{"target/vela-docker"}), + WithEnforceTrustedRepos(true), ) if err != nil { t.Errorf("unable to create %s executor engine: %v", test.name, err) diff --git a/go.mod b/go.mod index 4b06f25e..7849f52e 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,6 @@ go 1.22.0 toolchain go1.22.4 -replace github.com/go-vela/types => ../types - require ( github.com/Masterminds/semver/v3 v3.2.1 github.com/distribution/reference v0.6.0 diff --git a/go.sum b/go.sum index 6aadc37b..9f3051f6 100644 --- a/go.sum +++ b/go.sum @@ -107,6 +107,8 @@ github.com/go-vela/sdk-go v0.24.0-rc2 h1:PKumiT2abXcJ66NZJfKa0FpxlUlQJTlNxQiRp3Q github.com/go-vela/sdk-go v0.24.0-rc2/go.mod h1:D6FIaZ4b1tt4SGPcJhOxwZHbtitv1mDt9tsci41pAdw= github.com/go-vela/server v0.24.0-rc2 h1:bM319+e+xZDwtEfsyUcVVVev583bLvS0+jxSLpd4R/k= github.com/go-vela/server v0.24.0-rc2/go.mod h1:yIaa48hyyzsb62JWriHT9CWyo7OC02JXXzKV0ilTcD0= +github.com/go-vela/types v0.24.0-rc2 h1:sdOtUL+pBjgD+4hFDrHN/8ZFXSICTnl+eYcRP2IZ0Wk= +github.com/go-vela/types v0.24.0-rc2/go.mod h1:YWj6BIapl9Kbj4yHq/fp8jltXdGiwD/gTy1ez32Rzag= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= diff --git a/runtime/engine.go b/runtime/engine.go index 0dabe0cc..97ed9c49 100644 --- a/runtime/engine.go +++ b/runtime/engine.go @@ -43,6 +43,8 @@ type Engine interface { // InspectContainer defines a function that inspects // the pipeline container. InspectContainer(context.Context, *pipeline.Container) error + // PollOutputsContainer defines a function that captures + // file contents from the outputs container. PollOutputsContainer(context.Context, *pipeline.Container, string) ([]byte, error) // RemoveContainer defines a function that deletes // (kill, remove) the pipeline container. From cc58cf881eec9cbb320e1989933a997156c4157b Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 25 Jul 2024 13:44:35 -0500 Subject: [PATCH 07/15] k8s sub + no sub secret injection during createstep --- executor/linux/step.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/executor/linux/step.go b/executor/linux/step.go index cf2d5152..c9093c78 100644 --- a/executor/linux/step.go +++ b/executor/linux/step.go @@ -58,6 +58,25 @@ func (c *client) CreateStep(ctx context.Context, ctn *pipeline.Container) error return err } + // K8s runtime needs to substitute + inject prior to reaching ExecBuild (no outputs) + if c.Runtime.Driver() == constants.DriverKubernetes { + logger.Debug("substituting container configuration") + // substitute container configuration + // + // https://pkg.go.dev/github.com/go-vela/types/pipeline#Container.Substitute + err = ctn.Substitute() + if err != nil { + return fmt.Errorf("unable to substitute container configuration") + } + + logger.Debug("injecting non-substituted secrets") + // inject no-substitution secrets for container + err = injectSecrets(ctn, c.NoSubSecrets) + if err != nil { + return err + } + } + return nil } From ca355d8c383eddc34c5e252e36ba573e691075fb Mon Sep 17 00:00:00 2001 From: ecrupper Date: Fri, 26 Jul 2024 09:44:02 -0500 Subject: [PATCH 08/15] address some linter comments --- executor/linux/linux.go | 1 - executor/linux/outputs.go | 3 ++- executor/local/stage.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/executor/linux/linux.go b/executor/linux/linux.go index 4a3e24c5..d2c1bfd2 100644 --- a/executor/linux/linux.go +++ b/executor/linux/linux.go @@ -48,7 +48,6 @@ type ( serviceLogs sync.Map steps sync.Map stepLogs sync.Map - workspacePath string streamRequests chan message.StreamRequest diff --git a/executor/linux/outputs.go b/executor/linux/outputs.go index c2d7b77e..efeb12cf 100644 --- a/executor/linux/outputs.go +++ b/executor/linux/outputs.go @@ -8,8 +8,9 @@ import ( "fmt" "strings" - "github.com/go-vela/types/pipeline" "github.com/sirupsen/logrus" + + "github.com/go-vela/types/pipeline" ) // outputSvc handles communication with the outputs container during the build. diff --git a/executor/local/stage.go b/executor/local/stage.go index b426058d..d7f271cd 100644 --- a/executor/local/stage.go +++ b/executor/local/stage.go @@ -77,7 +77,7 @@ func (c *client) PlanStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) } // ExecStage runs a stage. -func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, opEnv, maskEnv map[string]string) error { +func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, _, _ map[string]string) error { // close the stage channel at the end defer func() { errChan, ok := m.Load(s.Name) From 591883ffd4c451016e317c85f98523452221b447 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Mon, 29 Jul 2024 09:11:19 -0500 Subject: [PATCH 09/15] linter overlord --- executor/linux/build.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/executor/linux/build.go b/executor/linux/build.go index 2bdf7297..9dc75ad5 100644 --- a/executor/linux/build.go +++ b/executor/linux/build.go @@ -214,6 +214,8 @@ func (c *client) PlanBuild(ctx context.Context) error { } // AssembleBuild prepares the containers within a build for execution. +// +//nolint:funlen // consider abstracting parts here but for now this is fine func (c *client) AssembleBuild(ctx context.Context) error { // defer taking a snapshot of the build // @@ -432,6 +434,8 @@ func (c *client) AssembleBuild(ctx context.Context) error { } // ExecBuild runs a pipeline for a build. +// +//nolint:funlen // there is a lot going on here and will probably always be long func (c *client) ExecBuild(ctx context.Context) error { defer func() { // Exec* calls are responsible for sending StreamRequest messages. @@ -543,7 +547,6 @@ func (c *client) ExecBuild(ctx context.Context) error { return err } - c.Logger.Debug("injecting non-substituted secrets") // inject no-substitution secrets for container err = injectSecrets(_step, c.NoSubSecrets) if err != nil { @@ -691,6 +694,8 @@ func (c *client) StreamBuild(ctx context.Context) error { // loadLazySecrets is a helper function that injects secrets // into the container right before execution, rather than // during build planning. It is only available for the Docker runtime. +// +//nolint:funlen // explanation takes up a lot of lines func loadLazySecrets(c *client, _step *pipeline.Container) error { _log := new(library.Log) From 39973b0b92f7ed869c226ac2ff3e19065fa1c7d6 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Mon, 12 Aug 2024 11:53:39 -0500 Subject: [PATCH 10/15] fix error handling in runtime for outputs ctn --- runtime/docker/container.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/runtime/docker/container.go b/runtime/docker/container.go index fd67467c..0beec9e0 100644 --- a/runtime/docker/container.go +++ b/runtime/docker/container.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "io" - "log" "strings" "github.com/docker/docker/api/types" @@ -310,12 +309,12 @@ func (c *client) PollOutputsContainer(ctx context.Context, ctn *pipeline.Contain responseExec, err := c.Docker.ContainerExecCreate(ctx, ctn.ID, execConfig) if err != nil { - log.Fatal(err) + return nil, err } hijackedResponse, err := c.Docker.ContainerExecAttach(ctx, responseExec.ID, types.ExecStartCheck{}) if err != nil { - log.Fatal(err) + return nil, err } defer func() { From 1dda99bba0e7a4e9667a899780d6da5fca56e83d Mon Sep 17 00:00:00 2001 From: ecrupper Date: Tue, 13 Aug 2024 16:29:31 -0500 Subject: [PATCH 11/15] correct pull policy and update secret masking --- cmd/vela-worker/run.go | 2 ++ executor/linux/outputs.go | 4 ++-- executor/linux/secret.go | 2 +- executor/linux/step.go | 2 +- executor/linux/step_test.go | 8 ++++---- go.mod | 2 +- go.sum | 4 ++-- 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cmd/vela-worker/run.go b/cmd/vela-worker/run.go index efad7568..a0ff3cef 100644 --- a/cmd/vela-worker/run.go +++ b/cmd/vela-worker/run.go @@ -14,6 +14,7 @@ import ( api "github.com/go-vela/server/api/types" "github.com/go-vela/server/queue" + "github.com/go-vela/types/constants" "github.com/go-vela/types/pipeline" "github.com/go-vela/worker/executor" "github.com/go-vela/worker/runtime" @@ -80,6 +81,7 @@ func run(c *cli.Context) error { Detach: true, Image: c.String("executor.outputs-image"), Environment: make(map[string]string), + Pull: constants.PullNotPresent, } } diff --git a/executor/linux/outputs.go b/executor/linux/outputs.go index efeb12cf..1c2c1e30 100644 --- a/executor/linux/outputs.go +++ b/executor/linux/outputs.go @@ -52,7 +52,7 @@ func (o *outputSvc) create(ctx context.Context, ctn *pipeline.Container, timeout return nil } -// destroy cleans up secret plugin after execution. +// destroy cleans up outputs container after execution. func (o *outputSvc) destroy(ctx context.Context, ctn *pipeline.Container) error { // exit if outputs container has not been configured if len(ctn.Image) == 0 { @@ -81,7 +81,7 @@ func (o *outputSvc) destroy(ctx context.Context, ctn *pipeline.Container) error return nil } -// exec runs a secret plugins for a pipeline. +// exec runs the outputs sidecar container for a pipeline. func (o *outputSvc) exec(ctx context.Context, _outputs *pipeline.Container) error { // exit if outputs container has not been configured if len(_outputs.Image) == 0 { diff --git a/executor/linux/secret.go b/executor/linux/secret.go index e87c1883..ed343298 100644 --- a/executor/linux/secret.go +++ b/executor/linux/secret.go @@ -349,7 +349,7 @@ func injectSecrets(ctn *pipeline.Container, m map[string]*library.Secret) error logrus.Tracef("matching secret %s to container %s", _secret.Source, ctn.Name) // ensure the secret matches with the container if s.Match(ctn) { - ctn.Environment[strings.ToUpper(_secret.Target)] = s.GetValue() + ctn.Environment[_secret.Target] = s.GetValue() } } diff --git a/executor/linux/step.go b/executor/linux/step.go index c9093c78..88186dbc 100644 --- a/executor/linux/step.go +++ b/executor/linux/step.go @@ -443,7 +443,7 @@ func getSecretValues(ctn *pipeline.Container) []string { // gather secrets' values from the environment map for masking for _, secret := range ctn.Secrets { // capture secret from environment - s, ok := ctn.Environment[strings.ToUpper(secret.Target)] + s, ok := ctn.Environment[secret.Target] if !ok { continue } diff --git a/executor/linux/step_test.go b/executor/linux/step_test.go index 52f781d8..2435afb9 100644 --- a/executor/linux/step_test.go +++ b/executor/linux/step_test.go @@ -889,11 +889,11 @@ func TestLinux_getSecretValues(t *testing.T) { Secrets: pipeline.StepSecretSlice{ { Source: "someSource", - Target: "secret_username", + Target: "SECRET_USERNAME", }, { Source: "someOtherSource", - Target: "secret_password", + Target: "SECRET_PASSWORD", }, { Source: "disallowedSecret", @@ -920,11 +920,11 @@ func TestLinux_getSecretValues(t *testing.T) { Secrets: pipeline.StepSecretSlice{ { Source: "someSource", - Target: "secret_username", + Target: "SECRET_USERNAME", }, { Source: "someOtherSource", - Target: "secret_password", + Target: "SECRET_PASSWORD", }, }, }, diff --git a/go.mod b/go.mod index 67eb174e..491b2750 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/gin-gonic/gin v1.10.0 github.com/go-vela/sdk-go v0.24.0 github.com/go-vela/server v0.24.1 - github.com/go-vela/types v0.24.0 + github.com/go-vela/types v0.24.1-0.20240813201820-772b29b91a5e github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/go-cmp v0.6.0 github.com/joho/godotenv v1.5.1 diff --git a/go.sum b/go.sum index 932849e7..0d40d5eb 100644 --- a/go.sum +++ b/go.sum @@ -107,8 +107,8 @@ github.com/go-vela/sdk-go v0.24.0 h1:QmwcF8h/Fq1mwbE8mdvqyTmXM2Z3sE0dLQLEMc7HA4w github.com/go-vela/sdk-go v0.24.0/go.mod h1:TmJI0KOt/KweLy0HE4JGpKYDPsbhW4sdl2AY/tWP0tY= github.com/go-vela/server v0.24.1 h1:iM5REZBh6oHD0nxEH4O6dkUWNhY3MNrWBLNWGUUwcP8= github.com/go-vela/server v0.24.1/go.mod h1:jCnJPiyaRLcdy1u5fKIf7BqsbYAbVMjjI7dlyxZovME= -github.com/go-vela/types v0.24.0 h1:KkkiXxw3uHckh/foyadmLY1YnLw6vhZbz9XwqONCj6o= -github.com/go-vela/types v0.24.0/go.mod h1:YWj6BIapl9Kbj4yHq/fp8jltXdGiwD/gTy1ez32Rzag= +github.com/go-vela/types v0.24.1-0.20240813201820-772b29b91a5e h1:6OHP0aQ2SKwNyL5Q+qYo+Yts6MD3PwmkqWKGoH3AlTI= +github.com/go-vela/types v0.24.1-0.20240813201820-772b29b91a5e/go.mod h1:YWj6BIapl9Kbj4yHq/fp8jltXdGiwD/gTy1ez32Rzag= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= From 1c9223a1a7069ffed9d6d60d351b982282835a9a Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 15 Aug 2024 10:52:29 -0500 Subject: [PATCH 12/15] move polling in stages to account for needs key --- executor/linux/stage.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/executor/linux/stage.go b/executor/linux/stage.go index d00589c8..ed67476c 100644 --- a/executor/linux/stage.go +++ b/executor/linux/stage.go @@ -164,6 +164,12 @@ func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, return fmt.Errorf("unable to plan step %s: %w", _step.Name, err) } + // poll outputs + opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + if c.err != nil { + return fmt.Errorf("unable to exec outputs container: %w", c.err) + } + // merge env from outputs // //nolint:errcheck // only errors with empty environment input, which does not matter here @@ -202,12 +208,6 @@ func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, return fmt.Errorf("unable to exec step %s: %w", _step.Name, err) } - // poll outputs - opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) - if c.err != nil { - return fmt.Errorf("unable to exec outputs container: %w", c.err) - } - // failed steps within the stage should set the stop value to true unless // the continue rule is set to true. if _step.ExitCode != 0 && !_step.Ruleset.Continue { From 5502d87fc056c20251aeb6a46c024f5879cca1c5 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Thu, 15 Aug 2024 11:14:51 -0500 Subject: [PATCH 13/15] update polling calls and add environment vars for outputs paths --- executor/engine.go | 2 +- executor/linux/build.go | 20 +++++++------------- executor/linux/stage.go | 6 +++--- executor/linux/stage_test.go | 2 +- executor/local/build.go | 2 +- executor/local/stage.go | 2 +- executor/local/stage_test.go | 2 +- internal/step/environment.go | 2 ++ 8 files changed, 17 insertions(+), 21 deletions(-) diff --git a/executor/engine.go b/executor/engine.go index 442d4197..d9cdcb50 100644 --- a/executor/engine.go +++ b/executor/engine.go @@ -83,7 +83,7 @@ type Engine interface { PlanStage(context.Context, *pipeline.Stage, *sync.Map) error // ExecStage defines a function that // runs a stage. - ExecStage(context.Context, *pipeline.Stage, *sync.Map, map[string]string, map[string]string) error + ExecStage(context.Context, *pipeline.Stage, *sync.Map) error // DestroyStage defines a function that // cleans up the stage after execution. DestroyStage(context.Context, *pipeline.Stage) error diff --git a/executor/linux/build.go b/executor/linux/build.go index 9dc75ad5..67dd3c95 100644 --- a/executor/linux/build.go +++ b/executor/linux/build.go @@ -468,12 +468,6 @@ func (c *client) ExecBuild(ctx context.Context) error { return fmt.Errorf("unable to execute secret: %w", c.err) } - // poll outputs container for any updates from secret plugins - opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) - if c.err != nil { - return fmt.Errorf("unable to exec outputs container: %w", c.err) - } - // execute the services for the pipeline for _, _service := range c.pipeline.Services { c.Logger.Infof("planning %s service", _service.Name) @@ -523,6 +517,12 @@ func (c *client) ExecBuild(ctx context.Context) error { return fmt.Errorf("unable to plan step: %w", c.err) } + // poll outputs + opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + if c.err != nil { + return fmt.Errorf("unable to exec outputs container: %w", c.err) + } + // merge env from outputs // //nolint:errcheck // only errors with empty environment input, which does not matter here @@ -559,12 +559,6 @@ func (c *client) ExecBuild(ctx context.Context) error { if c.err != nil { return fmt.Errorf("unable to execute step: %w", c.err) } - - // poll outputs - opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) - if c.err != nil { - return fmt.Errorf("unable to exec outputs container: %w", c.err) - } } // create an error group with the context for each stage @@ -601,7 +595,7 @@ func (c *client) ExecBuild(ctx context.Context) error { c.Logger.Infof("executing %s stage", stage.Name) // execute the stage - c.err = c.ExecStage(stageCtx, stage, stageMap, opEnv, maskEnv) + c.err = c.ExecStage(stageCtx, stage, stageMap) if c.err != nil { return fmt.Errorf("unable to execute stage: %w", c.err) } diff --git a/executor/linux/stage.go b/executor/linux/stage.go index ed67476c..e0549f43 100644 --- a/executor/linux/stage.go +++ b/executor/linux/stage.go @@ -98,7 +98,7 @@ func (c *client) PlanStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) } // ExecStage runs a stage. -func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, opEnv, maskEnv map[string]string) error { +func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) error { // update engine logger with stage metadata // // https://pkg.go.dev/github.com/sirupsen/logrus#Entry.WithField @@ -165,9 +165,9 @@ func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, } // poll outputs - opEnv, maskEnv, c.err = c.outputs.poll(ctx, c.OutputCtn) + opEnv, maskEnv, err := c.outputs.poll(ctx, c.OutputCtn) if c.err != nil { - return fmt.Errorf("unable to exec outputs container: %w", c.err) + return fmt.Errorf("unable to exec outputs container: %w", err) } // merge env from outputs diff --git a/executor/linux/stage_test.go b/executor/linux/stage_test.go index 588e220b..4bb7ba5f 100644 --- a/executor/linux/stage_test.go +++ b/executor/linux/stage_test.go @@ -583,7 +583,7 @@ func TestLinux_ExecStage(t *testing.T) { t.Errorf("unable to create %s executor engine: %v", test.name, err) } - err = _engine.ExecStage(context.Background(), test.stage, stageMap, nil, nil) + err = _engine.ExecStage(context.Background(), test.stage, stageMap) if test.failure { if err == nil { diff --git a/executor/local/build.go b/executor/local/build.go index 7087c461..c3d9e196 100644 --- a/executor/local/build.go +++ b/executor/local/build.go @@ -332,7 +332,7 @@ func (c *client) ExecBuild(ctx context.Context) error { } // execute the stage - c.err = c.ExecStage(stageCtx, stage, stageMap, nil, nil) + c.err = c.ExecStage(stageCtx, stage, stageMap) if c.err != nil { return fmt.Errorf("unable to execute stage: %w", c.err) } diff --git a/executor/local/stage.go b/executor/local/stage.go index d7f271cd..7a4edab1 100644 --- a/executor/local/stage.go +++ b/executor/local/stage.go @@ -77,7 +77,7 @@ func (c *client) PlanStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) } // ExecStage runs a stage. -func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map, _, _ map[string]string) error { +func (c *client) ExecStage(ctx context.Context, s *pipeline.Stage, m *sync.Map) error { // close the stage channel at the end defer func() { errChan, ok := m.Load(s.Name) diff --git a/executor/local/stage_test.go b/executor/local/stage_test.go index 8f40dc98..1777afca 100644 --- a/executor/local/stage_test.go +++ b/executor/local/stage_test.go @@ -317,7 +317,7 @@ func TestLocal_ExecStage(t *testing.T) { t.Errorf("unable to create executor engine: %v", err) } - err = _engine.ExecStage(context.Background(), test.stage, stageMap, nil, nil) + err = _engine.ExecStage(context.Background(), test.stage, stageMap) if test.failure { if err == nil { diff --git a/internal/step/environment.go b/internal/step/environment.go index ea1a9669..7c996276 100644 --- a/internal/step/environment.go +++ b/internal/step/environment.go @@ -41,6 +41,8 @@ func Environment(c *pipeline.Container, b *api.Build, s *library.Step, version, c.Environment["VELA_RUNTIME"] = b.GetRuntime() c.Environment["VELA_VERSION"] = version c.Environment["VELA_ID_TOKEN_REQUEST_TOKEN"] = reqToken + c.Environment["VELA_OUTPUTS"] = "/vela/outputs/.env" + c.Environment["VELA_MASKED_OUTPUTS"] = "/vela/outputs/masked.env" // populate environment variables from build library // From 48da52aa8c450e5098a9f7d22e81b5b7996da4dd Mon Sep 17 00:00:00 2001 From: ecrupper Date: Fri, 6 Sep 2024 13:52:47 -0500 Subject: [PATCH 14/15] gci imports and splitN --- executor/linux/outputs.go | 2 +- executor/linux/outputs_test.go | 9 +++++++++ .../generated/clientset/versioned/clientset.go | 3 ++- .../clientset/versioned/fake/clientset_generated.go | 7 ++++--- .../generated/clientset/versioned/fake/register.go | 3 ++- .../generated/clientset/versioned/scheme/register.go | 3 ++- .../vela/v1alpha1/fake/fake_pipelinepodstemplate.go | 3 ++- .../typed/vela/v1alpha1/fake/fake_vela_client.go | 3 ++- .../typed/vela/v1alpha1/pipelinepodstemplate.go | 5 +++-- .../versioned/typed/vela/v1alpha1/vela_client.go | 3 ++- 10 files changed, 29 insertions(+), 12 deletions(-) diff --git a/executor/linux/outputs.go b/executor/linux/outputs.go index 1c2c1e30..eb61b9b2 100644 --- a/executor/linux/outputs.go +++ b/executor/linux/outputs.go @@ -143,7 +143,7 @@ func toMap(input []byte) map[string]string { m := make(map[string]string) for _, line := range lines { - parts := strings.Split(line, "=") + parts := strings.SplitN(line, "=", 2) if len(parts) == 2 { s := parts[1] if !strings.Contains(parts[1], "\\\\n") { diff --git a/executor/linux/outputs_test.go b/executor/linux/outputs_test.go index 3a45f1c7..dc463e24 100644 --- a/executor/linux/outputs_test.go +++ b/executor/linux/outputs_test.go @@ -487,6 +487,15 @@ func TestLinux_Outputs_toMap(t *testing.T) { "TEST": "hello world", }, }, + { + name: "multiple equals", + runtime: constants.DriverDocker, + input: []byte("FOO=bar\r\nEQUATION=e=mc^2\r\n"), + wantMap: map[string]string{ + "FOO": "bar", + "EQUATION": "e=mc^2", + }, + }, { name: "bad format", runtime: constants.DriverDocker, diff --git a/runtime/kubernetes/generated/clientset/versioned/clientset.go b/runtime/kubernetes/generated/clientset/versioned/clientset.go index 539e0a9c..a06a9d90 100644 --- a/runtime/kubernetes/generated/clientset/versioned/clientset.go +++ b/runtime/kubernetes/generated/clientset/versioned/clientset.go @@ -8,10 +8,11 @@ import ( "fmt" "net/http" - velav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1" discovery "k8s.io/client-go/discovery" rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" + + velav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1" ) type Interface interface { diff --git a/runtime/kubernetes/generated/clientset/versioned/fake/clientset_generated.go b/runtime/kubernetes/generated/clientset/versioned/fake/clientset_generated.go index 2a3563ee..225ee62c 100644 --- a/runtime/kubernetes/generated/clientset/versioned/fake/clientset_generated.go +++ b/runtime/kubernetes/generated/clientset/versioned/fake/clientset_generated.go @@ -5,14 +5,15 @@ package fake import ( - clientset "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned" - velav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1" - fakevelav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/fake" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" fakediscovery "k8s.io/client-go/discovery/fake" "k8s.io/client-go/testing" + + clientset "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned" + velav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1" + fakevelav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/fake" ) // NewSimpleClientset returns a clientset that will respond with the provided objects. diff --git a/runtime/kubernetes/generated/clientset/versioned/fake/register.go b/runtime/kubernetes/generated/clientset/versioned/fake/register.go index 124cfe21..f137c592 100644 --- a/runtime/kubernetes/generated/clientset/versioned/fake/register.go +++ b/runtime/kubernetes/generated/clientset/versioned/fake/register.go @@ -5,12 +5,13 @@ package fake import ( - velav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + velav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" ) var scheme = runtime.NewScheme() diff --git a/runtime/kubernetes/generated/clientset/versioned/scheme/register.go b/runtime/kubernetes/generated/clientset/versioned/scheme/register.go index 83678554..37c62a56 100644 --- a/runtime/kubernetes/generated/clientset/versioned/scheme/register.go +++ b/runtime/kubernetes/generated/clientset/versioned/scheme/register.go @@ -5,12 +5,13 @@ package scheme import ( - velav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" serializer "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + velav1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" ) var Scheme = runtime.NewScheme() diff --git a/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/fake/fake_pipelinepodstemplate.go b/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/fake/fake_pipelinepodstemplate.go index 771a36ce..c6365861 100644 --- a/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/fake/fake_pipelinepodstemplate.go +++ b/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/fake/fake_pipelinepodstemplate.go @@ -7,13 +7,14 @@ package fake import ( "context" - v1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" labels "k8s.io/apimachinery/pkg/labels" schema "k8s.io/apimachinery/pkg/runtime/schema" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" testing "k8s.io/client-go/testing" + + v1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" ) // FakePipelinePodsTemplates implements PipelinePodsTemplateInterface diff --git a/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/fake/fake_vela_client.go b/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/fake/fake_vela_client.go index ca035652..d5695a91 100644 --- a/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/fake/fake_vela_client.go +++ b/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/fake/fake_vela_client.go @@ -5,9 +5,10 @@ package fake import ( - v1alpha1 "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1" rest "k8s.io/client-go/rest" testing "k8s.io/client-go/testing" + + v1alpha1 "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1" ) type FakeVelaV1alpha1 struct { diff --git a/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/pipelinepodstemplate.go b/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/pipelinepodstemplate.go index b2edaee6..3b271f8f 100644 --- a/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/pipelinepodstemplate.go +++ b/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/pipelinepodstemplate.go @@ -8,12 +8,13 @@ import ( "context" "time" - v1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" - scheme "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/scheme" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" rest "k8s.io/client-go/rest" + + v1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" + scheme "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/scheme" ) // PipelinePodsTemplatesGetter has a method to return a PipelinePodsTemplateInterface. diff --git a/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/vela_client.go b/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/vela_client.go index fc128214..a7004432 100644 --- a/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/vela_client.go +++ b/runtime/kubernetes/generated/clientset/versioned/typed/vela/v1alpha1/vela_client.go @@ -7,9 +7,10 @@ package v1alpha1 import ( "net/http" + rest "k8s.io/client-go/rest" + v1alpha1 "github.com/go-vela/worker/runtime/kubernetes/apis/vela/v1alpha1" "github.com/go-vela/worker/runtime/kubernetes/generated/clientset/versioned/scheme" - rest "k8s.io/client-go/rest" ) type VelaV1alpha1Interface interface { From 4c1eca23a0c15ad6df44caef5b417ed7797d8013 Mon Sep 17 00:00:00 2001 From: ecrupper Date: Mon, 9 Sep 2024 11:08:33 -0500 Subject: [PATCH 15/15] use hashicorp envparse over toMap --- executor/linux/outputs.go | 36 ++++++++++--------------- executor/linux/outputs_test.go | 48 ---------------------------------- go.mod | 1 + go.sum | 2 ++ 4 files changed, 17 insertions(+), 70 deletions(-) diff --git a/executor/linux/outputs.go b/executor/linux/outputs.go index eb61b9b2..00c02db5 100644 --- a/executor/linux/outputs.go +++ b/executor/linux/outputs.go @@ -3,11 +3,12 @@ package linux import ( + "bytes" "context" "encoding/base64" "fmt" - "strings" + envparse "github.com/hashicorp/go-envparse" "github.com/sirupsen/logrus" "github.com/go-vela/types/pipeline" @@ -125,34 +126,25 @@ func (o *outputSvc) poll(ctx context.Context, ctn *pipeline.Container) (map[stri return nil, nil, err } + reader := bytes.NewReader(outputBytes) + + outputMap, err := envparse.Parse(reader) + if err != nil { + logger.Debugf("unable to parse output map: %v", err) + } + // grab masked outputs maskedBytes, err := o.client.Runtime.PollOutputsContainer(ctx, ctn, "/vela/outputs/masked.env") if err != nil { return nil, nil, err } - return toMap(outputBytes), toMap(maskedBytes), nil -} + reader = bytes.NewReader(maskedBytes) -// toMap is a helper function that turns raw docker exec output bytes into a map -// by splitting on carriage returns + newlines and once more on `=`. -func toMap(input []byte) map[string]string { - // carriage returns are included in the split because the exec config `TTY` value is set to true - lines := strings.Split(string(input), "\r\n") - - m := make(map[string]string) - - for _, line := range lines { - parts := strings.SplitN(line, "=", 2) - if len(parts) == 2 { - s := parts[1] - if !strings.Contains(parts[1], "\\\\n") { - s = strings.Replace(parts[1], "\\n", "\\\n", -1) - } - - m[parts[0]] = s - } + maskMap, err := envparse.Parse(reader) + if err != nil { + logger.Debugf("unable to parse masked output map: %v", err) } - return m + return outputMap, maskMap, nil } diff --git a/executor/linux/outputs_test.go b/executor/linux/outputs_test.go index dc463e24..db0a3174 100644 --- a/executor/linux/outputs_test.go +++ b/executor/linux/outputs_test.go @@ -7,7 +7,6 @@ import ( "flag" "net/http/httptest" "os" - "reflect" "testing" "github.com/gin-gonic/gin" @@ -468,50 +467,3 @@ func TestLinux_Outputs_poll(t *testing.T) { }) } } - -func TestLinux_Outputs_toMap(t *testing.T) { - // setup tests - tests := []struct { - name string - runtime string - input []byte - wantMap map[string]string - }{ - { - name: "basic", - runtime: constants.DriverDocker, - input: []byte("FOO=bar\r\nONE=1\r\nTEST=hello world\r\n"), - wantMap: map[string]string{ - "FOO": "bar", - "ONE": "1", - "TEST": "hello world", - }, - }, - { - name: "multiple equals", - runtime: constants.DriverDocker, - input: []byte("FOO=bar\r\nEQUATION=e=mc^2\r\n"), - wantMap: map[string]string{ - "FOO": "bar", - "EQUATION": "e=mc^2", - }, - }, - { - name: "bad format", - runtime: constants.DriverDocker, - input: []byte("FOO;bar//ONE^TEST,,,hello world"), - wantMap: make(map[string]string), - }, - } - - // run tests - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - got := toMap(test.input) - - if !reflect.DeepEqual(got, test.wantMap) { - t.Errorf("toMap is %v, want %v", got, test.wantMap) - } - }) - } -} diff --git a/go.mod b/go.mod index 491b2750..f27744e5 100644 --- a/go.mod +++ b/go.mod @@ -77,6 +77,7 @@ require ( github.com/goware/urlx v0.3.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-envparse v0.1.0 github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/huandu/xstrings v1.3.3 // indirect diff --git a/go.sum b/go.sum index 0d40d5eb..51e75d6f 100644 --- a/go.sum +++ b/go.sum @@ -147,6 +147,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY= +github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=