diff --git a/pkg/exprparser/interpreter.go b/pkg/exprparser/interpreter.go index 6b276fd..ea91271 100644 --- a/pkg/exprparser/interpreter.go +++ b/pkg/exprparser/interpreter.go @@ -15,6 +15,7 @@ type EvaluationEnvironment struct { Github *model.GithubContext Env map[string]string Job *model.JobContext + Jobs *map[string]*model.WorkflowCallResult Steps map[string]*model.StepResult Runner map[string]interface{} Secrets map[string]string @@ -155,6 +156,11 @@ func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableN return impl.env.Env, nil case "job": return impl.env.Job, nil + case "jobs": + if impl.env.Jobs == nil { + return nil, fmt.Errorf("Unavailable context: jobs") + } + return impl.env.Jobs, nil case "steps": return impl.env.Steps, nil case "runner": diff --git a/pkg/model/workflow.go b/pkg/model/workflow.go index 3da7a13..d7e2922 100644 --- a/pkg/model/workflow.go +++ b/pkg/model/workflow.go @@ -117,6 +117,10 @@ type WorkflowCall struct { Outputs map[string]WorkflowCallOutput `yaml:"outputs"` } +type WorkflowCallResult struct { + Outputs map[string]string +} + func (w *Workflow) WorkflowCallConfig() *WorkflowCall { if w.RawOn.Kind != yaml.MappingNode { return nil diff --git a/pkg/runner/expression.go b/pkg/runner/expression.go index a8d506e..ca40e95 100644 --- a/pkg/runner/expression.go +++ b/pkg/runner/expression.go @@ -25,6 +25,8 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval } func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map[string]string) ExpressionEvaluator { + var workflowCallResult map[string]*model.WorkflowCallResult + // todo: cleanup EvaluationEnvironment creation using := make(map[string]exprparser.Needs) strategy := make(map[string]interface{}) @@ -44,6 +46,23 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map Result: jobs[needs].Result, } } + + // only setup jobs context in case of workflow_call + // and existing expression evaluator (this means, jobs are at + // least ready to run) + if rc.caller != nil && rc.ExprEval != nil { + workflowCallResult = map[string]*model.WorkflowCallResult{} + + for jobName, job := range jobs { + result := model.WorkflowCallResult{ + Outputs: map[string]string{}, + } + for k, v := range job.Outputs { + result.Outputs[k] = v + } + workflowCallResult[jobName] = &result + } + } } ghc := rc.getGithubContext(ctx) @@ -53,6 +72,7 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map Github: ghc, Env: env, Job: rc.getJobContext(), + Jobs: &workflowCallResult, // todo: should be unavailable // but required to interpolate/evaluate the step outputs on the job Steps: rc.getStepsContext(), diff --git a/pkg/runner/job_executor.go b/pkg/runner/job_executor.go index 88c227f..2f98ada 100644 --- a/pkg/runner/job_executor.go +++ b/pkg/runner/job_executor.go @@ -162,8 +162,9 @@ func setJobOutputs(ctx context.Context, rc *RunContext) { callerOutputs := make(map[string]string) ee := rc.NewExpressionEvaluator(ctx) - for k, v := range rc.Run.Job().Outputs { - callerOutputs[k] = ee.Interpolate(ctx, v) + + for k, v := range rc.Run.Workflow.WorkflowCallConfig().Outputs { + callerOutputs[k] = ee.Interpolate(ctx, ee.Interpolate(ctx, v.Value)) } rc.caller.runContext.Run.Job().Outputs = callerOutputs diff --git a/pkg/runner/testdata/.github/workflows/local-reusable-workflow.yml b/pkg/runner/testdata/.github/workflows/local-reusable-workflow.yml index a52fdcf..d32dc5b 100644 --- a/pkg/runner/testdata/.github/workflows/local-reusable-workflow.yml +++ b/pkg/runner/testdata/.github/workflows/local-reusable-workflow.yml @@ -27,7 +27,7 @@ on: outputs: output: description: "A workflow output" - value: ${{ jobs.reusable_workflow_job.outputs.output }} + value: ${{ jobs.reusable_workflow_job.outputs.job-output }} jobs: reusable_workflow_job: @@ -79,4 +79,4 @@ jobs: echo "value=${{ inputs.string_required }}" >> $GITHUB_OUTPUT outputs: - output: ${{ steps.output_test.outputs.value }} + job-output: ${{ steps.output_test.outputs.value }}