pkg/runner/steps_context.go (133 lines of code) (raw):
package runner
import (
"fmt"
"io"
"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/pkg/precond"
"gitlab.com/gitlab-org/step-runner/proto"
)
type StepsContext struct {
globalCtx *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, stepDir string, inputs map[string]*structpb.Value, env *Environment, options ...func(*StepsContext)) (*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)
}
stepsCtx := &StepsContext{
globalCtx: globalCtx,
stepDir: stepDir,
env: env,
inputs: inputs,
steps: map[string]*proto.StepResult{},
outputFile: outputFile,
exportFile: exportFile,
}
for _, option := range options {
option(stepsCtx)
}
precond.MustNotBeNil(stepsCtx.env, "steps context must have an environment")
return stepsCtx, 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.globalCtx.Job(),
OutputFile: s.outputFile.Path(),
StepDir: s.stepDir,
StepResults: stepResultViews,
WorkDir: s.globalCtx.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()
}
func (s *StepsContext) AddGlobalEnv(env *Environment) {
s.globalCtx.AddGlobalEnv(env)
}
func (s *StepsContext) Logln(format string, v ...any) error {
return s.globalCtx.Logln(format, v...)
}
func (s *StepsContext) WorkDir() string {
return s.globalCtx.WorkDir()
}
func (s *StepsContext) Pipe() (io.Writer, io.Writer) {
return s.globalCtx.Pipe()
}
func (s *StepsContext) ReadOutputStepResult() (*proto.StepResult, error) {
return s.outputFile.ReadStepResult()
}
func (s *StepsContext) ReadOutputValues(specOutputs map[string]*proto.Spec_Content_Output) (map[string]*structpb.Value, error) {
return s.outputFile.ReadValues(specOutputs)
}
func (s *StepsContext) ReadExportedEnv() (*Environment, error) {
return s.exportFile.ReadEnvironment()
}
func (s *StepsContext) EnvWithLexicalScope(envVars map[string]string) *Environment {
return s.env.AddLexicalScope(envVars)
}
func WithStepsCtxOutputFile(outputFile *StepFile) func(*StepsContext) {
return func(s *StepsContext) {
s.outputFile = outputFile
}
}
func WithStepsCtxExportFile(exportFile *StepFile) func(*StepsContext) {
return func(s *StepsContext) {
s.exportFile = exportFile
}
}
func WithStepsCtxStepResults(results map[string]*proto.StepResult) func(*StepsContext) {
return func(s *StepsContext) {
s.steps = results
}
}