pkg/runner/lazily_loaded_step.go (91 lines of code) (raw):

package runner import ( ctx "context" "fmt" "gitlab.com/gitlab-org/step-runner/pkg/context" "gitlab.com/gitlab-org/step-runner/pkg/internal/expression" "gitlab.com/gitlab-org/step-runner/proto" ) // LazilyLoadedStep is a step that dynamically fetches, parses and executes a step definition. type LazilyLoadedStep struct { globalCtx *GlobalContext parser StepParser stepReference *proto.Step stepResource StepResource } func NewLazilyLoadedStep(globalCtx *GlobalContext, parser StepParser, stepReference *proto.Step, stepResource StepResource) *LazilyLoadedStep { return &LazilyLoadedStep{ globalCtx: globalCtx, parser: parser, stepReference: stepReference, stepResource: stepResource, } } func (s *LazilyLoadedStep) Describe() string { return fmt.Sprintf("step %q", s.stepReference.Name) } // Run fetches a step definition, parses the step, and executes it. // The step reference inputs and environment are expanded. // The current environment is cloned into params in preparation for a recursive call to Run. func (s *LazilyLoadedStep) Run(ctx ctx.Context, parentStepsCtx *StepsContext) (*proto.StepResult, error) { step, params, subStepSpecDefinition, err := s.loadStep(ctx, parentStepsCtx) if err != nil { return nil, fmt.Errorf("%s: %w", s.Describe(), err) } env := parentStepsCtx.EnvWithLexicalScope(params.Env) inputs := params.NewInputsWithDefault(subStepSpecDefinition.SpecInputs()) stepsCtx, err := NewStepsContext(s.globalCtx, subStepSpecDefinition.Dir(), inputs, env) if err != nil { return nil, fmt.Errorf("%s: %w", s.Describe(), err) } defer stepsCtx.Cleanup() result, err := step.Run(ctx, stepsCtx) if err != nil { return result, fmt.Errorf("%s: %w", s.Describe(), err) } return result, nil } func (s *LazilyLoadedStep) loadStep(ctx ctx.Context, stepsCtx *StepsContext) (Step, *Params, *SpecDefinition, error) { specDef, err := s.stepResource.Fetch(ctx, stepsCtx.View()) if err != nil { return nil, nil, nil, fmt.Errorf("failed to load: %w", err) } inputs, err := buildInputVars(s.stepReference, specDef) if err != nil { return nil, nil, nil, fmt.Errorf("failed to load: %w", err) } for name, v := range inputs { res, err := expression.Expand(stepsCtx.View(), v.Value) if err != nil { return nil, nil, nil, fmt.Errorf("failed to load: expand input %q: %w", name, err) } err = inputs[name].Assign(res) if err != nil { return nil, nil, nil, fmt.Errorf("failed to load: assign input %q: %w", name, err) } } env := map[string]string{} for k, v := range s.stepReference.Env { res, err := expression.ExpandString(stepsCtx.View(), v) if err != nil { return nil, nil, nil, fmt.Errorf("failed to load: env %q: %w", k, err) } env[k] = res } params := &Params{ Inputs: inputs, Env: stepsCtx.EnvWithLexicalScope(env).Values(), } step, err := s.parser.Parse(stepsCtx.globalCtx, specDef, params, NewNamedStepReference(s.stepReference.Name, s.stepReference.Step)) if err != nil { return nil, nil, nil, fmt.Errorf("failed to load: %w", err) } return step, params, specDef, nil } func buildInputVars(stepReference *proto.Step, stepSpecDef *SpecDefinition) (map[string]*context.Variable, error) { inputs := make(map[string]*context.Variable) for name, val := range stepReference.Inputs { input, ok := stepSpecDef.SpecInputWithName(name) if !ok { return inputs, fmt.Errorf("step does not accept input with name %q", name) } inputs[name] = context.NewVariable(val, input.Sensitive) } return inputs, nil }