package runner import ( "fmt" "golang.org/x/exp/maps" "google.golang.org/protobuf/types/known/structpb" "gitlab.com/gitlab-org/step-runner/pkg/internal/expression" "gitlab.com/gitlab-org/step-runner/proto" ) type StepsContext struct { *GlobalContext StepDir string // The path to the YAML definition directory so steps can find their files and sub-steps with relative references know where to start. OutputFile *StepFile // The path to the output file. ExportFile *StepFile // The path to the export file. Env *Environment // Expanded environment values of the executing step. Inputs map[string]*structpb.Value // Expanded input values of the executing step. Steps map[string]*proto.StepResult // Results of previously executed steps. } func NewStepsContext(globalCtx *GlobalContext, dir string, inputs map[string]*structpb.Value, env *Environment) (*StepsContext, error) { outputFile, err := NewStepFileInTmp() if err != nil { return nil, fmt.Errorf("creating steps context: output file: %w", err) } exportFile, err := NewStepFileInTmp() if err != nil { return nil, fmt.Errorf("creating steps context: export file: %w", err) } return &StepsContext{ GlobalContext: globalCtx, StepDir: dir, Env: env, Inputs: inputs, Steps: map[string]*proto.StepResult{}, OutputFile: outputFile, ExportFile: exportFile, }, nil } func (s *StepsContext) GetEnvs() map[string]string { return s.Env.Values() } func (s *StepsContext) GetEnvList() []string { r := []string{} for k, v := range s.GetEnvs() { r = append(r, k+"="+v) } return r } func (s *StepsContext) ExpandAndApplyEnv(env map[string]string) (*Environment, error) { expandedEnv := map[string]string{} for key, value := range env { expanded, err := expression.ExpandString(s.View(), value) if err != nil { return nil, fmt.Errorf("env variable %q: %w", key, err) } expandedEnv[key] = expanded } s.Env = s.Env.AddLexicalScope(expandedEnv) return s.Env, nil } func (s *StepsContext) View() *expression.InterpolationContext { stepResultViews := make(map[string]*expression.StepResultView) for name, step := range s.Steps { stepResultViews[name] = &expression.StepResultView{Outputs: step.Outputs} } return &expression.InterpolationContext{ Env: s.Env.Values(), ExportFile: s.ExportFile.Path(), Inputs: s.Inputs, Job: s.Job, OutputFile: s.OutputFile.Path(), StepDir: s.StepDir, StepResults: stepResultViews, WorkDir: s.WorkDir, } } // RecordResult captures the result of a step even if it failed func (s *StepsContext) RecordResult(stepResult *proto.StepResult) { if stepResult == nil || stepResult.Step == nil { return } s.Steps[stepResult.Step.Name] = stepResult } func (s *StepsContext) StepResults() []*proto.StepResult { return maps.Values(s.Steps) } func (s *StepsContext) Cleanup() { _ = s.OutputFile.Remove() _ = s.ExportFile.Remove() }