fix: update output handling for reusable workflows (#1521)

* fix: map job output for reusable workflows

This fixes the job outputs for reusable workflows. There is
a required indirection. Before this we took the outputs from
all jobs which is not what users express with the workflow
outputs.

* fix: remove double evaluation

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Markus Wolf 2023-02-23 23:34:47 +01:00 committed by GitHub
parent 53095d76f4
commit 89cb558558
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 35 additions and 4 deletions

View file

@ -15,6 +15,7 @@ type EvaluationEnvironment struct {
Github *model.GithubContext Github *model.GithubContext
Env map[string]string Env map[string]string
Job *model.JobContext Job *model.JobContext
Jobs *map[string]*model.WorkflowCallResult
Steps map[string]*model.StepResult Steps map[string]*model.StepResult
Runner map[string]interface{} Runner map[string]interface{}
Secrets map[string]string Secrets map[string]string
@ -155,6 +156,11 @@ func (impl *interperterImpl) evaluateVariable(variableNode *actionlint.VariableN
return impl.env.Env, nil return impl.env.Env, nil
case "job": case "job":
return impl.env.Job, nil 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": case "steps":
return impl.env.Steps, nil return impl.env.Steps, nil
case "runner": case "runner":

View file

@ -117,6 +117,10 @@ type WorkflowCall struct {
Outputs map[string]WorkflowCallOutput `yaml:"outputs"` Outputs map[string]WorkflowCallOutput `yaml:"outputs"`
} }
type WorkflowCallResult struct {
Outputs map[string]string
}
func (w *Workflow) WorkflowCallConfig() *WorkflowCall { func (w *Workflow) WorkflowCallConfig() *WorkflowCall {
if w.RawOn.Kind != yaml.MappingNode { if w.RawOn.Kind != yaml.MappingNode {
return nil return nil

View file

@ -25,6 +25,8 @@ func (rc *RunContext) NewExpressionEvaluator(ctx context.Context) ExpressionEval
} }
func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map[string]string) ExpressionEvaluator { func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map[string]string) ExpressionEvaluator {
var workflowCallResult map[string]*model.WorkflowCallResult
// todo: cleanup EvaluationEnvironment creation // todo: cleanup EvaluationEnvironment creation
using := make(map[string]exprparser.Needs) using := make(map[string]exprparser.Needs)
strategy := make(map[string]interface{}) strategy := make(map[string]interface{})
@ -44,6 +46,23 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map
Result: jobs[needs].Result, 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) ghc := rc.getGithubContext(ctx)
@ -53,6 +72,7 @@ func (rc *RunContext) NewExpressionEvaluatorWithEnv(ctx context.Context, env map
Github: ghc, Github: ghc,
Env: env, Env: env,
Job: rc.getJobContext(), Job: rc.getJobContext(),
Jobs: &workflowCallResult,
// todo: should be unavailable // todo: should be unavailable
// but required to interpolate/evaluate the step outputs on the job // but required to interpolate/evaluate the step outputs on the job
Steps: rc.getStepsContext(), Steps: rc.getStepsContext(),

View file

@ -162,8 +162,9 @@ func setJobOutputs(ctx context.Context, rc *RunContext) {
callerOutputs := make(map[string]string) callerOutputs := make(map[string]string)
ee := rc.NewExpressionEvaluator(ctx) 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 rc.caller.runContext.Run.Job().Outputs = callerOutputs

View file

@ -27,7 +27,7 @@ on:
outputs: outputs:
output: output:
description: "A workflow output" description: "A workflow output"
value: ${{ jobs.reusable_workflow_job.outputs.output }} value: ${{ jobs.reusable_workflow_job.outputs.job-output }}
jobs: jobs:
reusable_workflow_job: reusable_workflow_job:
@ -79,4 +79,4 @@ jobs:
echo "value=${{ inputs.string_required }}" >> $GITHUB_OUTPUT echo "value=${{ inputs.string_required }}" >> $GITHUB_OUTPUT
outputs: outputs:
output: ${{ steps.output_test.outputs.value }} job-output: ${{ steps.output_test.outputs.value }}