pkg/runner/executable_step.go (91 lines of code) (raw):
package runner
import (
ctx "context"
"fmt"
"os/exec"
"strings"
"google.golang.org/protobuf/types/known/structpb"
"gitlab.com/gitlab-org/step-runner/pkg/internal/expression"
"gitlab.com/gitlab-org/step-runner/proto"
)
// ExecutableStep is a step that executes a command.
type ExecutableStep struct {
loadedFrom StepReference
params *Params
specDef *SpecDefinition
}
func NewExecutableStep(loadedFrom StepReference, params *Params, specDef *SpecDefinition) *ExecutableStep {
return &ExecutableStep{
loadedFrom: loadedFrom,
params: params,
specDef: specDef,
}
}
func (s *ExecutableStep) Describe() string {
return fmt.Sprintf("executable step %q", strings.Join(s.specDef.ExecCommand(), " "))
}
func (s *ExecutableStep) Run(ctx ctx.Context, stepsCtx *StepsContext) (*proto.StepResult, error) {
if err := stepsCtx.Logln("Running step %q", s.loadedFrom.Describe()); err != nil {
return nil, err
}
result := NewStepResultBuilder(s.loadedFrom, s.params, s.specDef)
if err := result.ObserveEnv(stepsCtx.ExpandAndApplyEnv(s.specDef.Env())); err != nil {
return result.BuildFailure(), fmt.Errorf("expand step env: %w", err)
}
if err := result.ObserveExecutedCmd(s.execCommand(ctx, stepsCtx)); err != nil {
return result.BuildFailure(), fmt.Errorf("exec: %w", err)
}
if err := result.ObserveOutputs(s.readOutputs(stepsCtx)); err != nil {
return result.BuildFailure(), fmt.Errorf("output file: %w", err)
}
exports, err := result.ObserveExports(stepsCtx.ReadExportedEnv())
if err != nil {
return result.BuildFailure(), fmt.Errorf("export file: %w", err)
}
stepsCtx.AddGlobalEnv(exports)
return result.Build(), nil
}
func (s *ExecutableStep) execCommand(ctx ctx.Context, stepsCtx *StepsContext) (*ExecResult, error) {
cmdArgs := []string{}
for _, arg := range s.specDef.ExecCommand() {
res, err := expression.ExpandString(stepsCtx.View(), arg)
if err != nil {
return nil, fmt.Errorf("interpolate command argument %q: %w", arg, err)
}
cmdArgs = append(cmdArgs, res)
}
workDir, err := s.determineWorkDir(stepsCtx)
if err != nil {
return nil, err
}
cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
cmd.Dir = workDir
cmd.Env = stepsCtx.GetEnvList()
cmd.Stdout, cmd.Stderr = stepsCtx.Pipe()
err = cmd.Run()
execResult := NewExecResult(cmd.Dir, cmd.Args, cmd.ProcessState.ExitCode())
if err != nil {
return execResult, err
}
return execResult, nil
}
func (s *ExecutableStep) determineWorkDir(stepsCtx *StepsContext) (string, error) {
workDir := s.specDef.ExecWorkDir()
if workDir == "" {
return stepsCtx.WorkDir(), nil
}
res, err := expression.ExpandString(stepsCtx.View(), workDir)
if err != nil {
return "", fmt.Errorf("interpolate workdir %q: %w", workDir, err)
}
return res, nil
}
func (s *ExecutableStep) readOutputs(stepsCtx *StepsContext) (map[string]*structpb.Value, error) {
if s.specDef.IsDelegateOutputs() {
stepResult, err := stepsCtx.ReadOutputStepResult()
if err != nil {
return nil, fmt.Errorf("delegate: %w", err)
}
return stepResult.Outputs, nil
}
return stepsCtx.ReadOutputValues(s.specDef.SpecOutputs())
}