diff --git a/pkg/model/workflow.go b/pkg/model/workflow.go index 9920c554..3a03571a 100644 --- a/pkg/model/workflow.go +++ b/pkg/model/workflow.go @@ -66,6 +66,7 @@ type Job struct { Strategy *Strategy `yaml:"strategy"` RawContainer yaml.Node `yaml:"container"` Defaults Defaults `yaml:"defaults"` + Outputs map[string]string `yaml:"outputs"` } // Strategy for the job diff --git a/pkg/model/workflow_test.go b/pkg/model/workflow_test.go index 692ee173..99bed3b3 100644 --- a/pkg/model/workflow_test.go +++ b/pkg/model/workflow_test.go @@ -131,6 +131,48 @@ jobs: assert.Equal(t, workflow.Jobs["test"].Steps[4].Type(), StepTypeUsesActionLocal) } +// See: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idoutputs +func TestReadWorkflow_JobOutputs(t *testing.T) { + yaml := ` +name: job outputs definition + +jobs: + test1: + runs-on: ubuntu-latest + steps: + - id: test1_1 + run: | + echo "::set-output name=a_key::some-a_value" + echo "::set-output name=b-key::some-b-value" + outputs: + some_a_key: ${{ steps.test1_1.outputs.a_key }} + some-b-key: ${{ steps.test1_1.outputs.b-key }} + + test2: + runs-on: ubuntu-latest + needs: + - test1 + steps: + - name: test2_1 + run: | + echo "${{ needs.test1.outputs.some_a_key }}" + echo "${{ needs.test1.outputs.some-b-key }}" +` + + workflow, err := ReadWorkflow(strings.NewReader(yaml)) + assert.NoError(t, err, "read workflow should succeed") + assert.Len(t, workflow.Jobs, 2) + + assert.Len(t, workflow.Jobs["test1"].Steps, 1) + assert.Equal(t, StepTypeRun, workflow.Jobs["test1"].Steps[0].Type()) + assert.Equal(t, "test1_1", workflow.Jobs["test1"].Steps[0].ID) + assert.Len(t, workflow.Jobs["test1"].Outputs, 2) + assert.Contains(t, workflow.Jobs["test1"].Outputs, "some_a_key") + assert.Contains(t, workflow.Jobs["test1"].Outputs, "some-b-key") + assert.Equal(t, "${{ steps.test1_1.outputs.a_key }}", workflow.Jobs["test1"].Outputs["some_a_key"]) + assert.Equal(t, "${{ steps.test1_1.outputs.b-key }}", workflow.Jobs["test1"].Outputs["some-b-key"]) +} + func TestStep_ShellCommand(t *testing.T) { tests := []struct { shell string diff --git a/pkg/runner/expression.go b/pkg/runner/expression.go index 505ae168..a670d7df 100644 --- a/pkg/runner/expression.go +++ b/pkg/runner/expression.go @@ -229,6 +229,7 @@ func (rc *RunContext) newVM() *otto.Otto { rc.vmStrategy(), rc.vmMatrix(), rc.vmEnv(), + rc.vmNeeds(), } vm := otto.New() for _, configer := range configers { @@ -415,6 +416,23 @@ func (sc *StepContext) vmInputs() func(*otto.Otto) { } } +func (rc *RunContext) vmNeeds() func(*otto.Otto) { + jobs := rc.Run.Workflow.Jobs + jobNeeds := rc.Run.Job().Needs() + + using := make(map[string]map[string]map[string]string) + for _, needs := range jobNeeds { + using[needs] = map[string]map[string]string{ + "outputs": jobs[needs].Outputs, + } + } + + return func(vm *otto.Otto) { + log.Debugf("context needs => %v", using) + _ = vm.Set("needs", using) + } +} + func (rc *RunContext) vmJob() func(*otto.Otto) { job := rc.getJobContext() diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go index e0ef92d6..c1431d68 100755 --- a/pkg/runner/run_context.go +++ b/pkg/runner/run_context.go @@ -262,7 +262,7 @@ func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor { rc.ExprEval = exprEval common.Logger(ctx).Infof("\u2B50 Run %s", sc.Step) - err = sc.Executor()(ctx) + err = sc.Executor().Then(sc.interpolateOutputs())(ctx) if err == nil { common.Logger(ctx).Infof(" \u2705 Success - %s", sc.Step) } else { diff --git a/pkg/runner/step_context.go b/pkg/runner/step_context.go index 2858e39a..a6ee62f7 100644 --- a/pkg/runner/step_context.go +++ b/pkg/runner/step_context.go @@ -29,6 +29,7 @@ type StepContext struct { Env map[string]string Cmd []string Action *model.Action + Needs *model.Job } func (sc *StepContext) execJobContainer() common.Executor { @@ -37,6 +38,19 @@ func (sc *StepContext) execJobContainer() common.Executor { } } +func (sc *StepContext) interpolateOutputs() common.Executor { + return func(ctx context.Context) error { + ee := sc.NewExpressionEvaluator() + for k, v := range sc.RunContext.Run.Job().Outputs { + interpolated := ee.Interpolate(v) + if v != interpolated { + sc.RunContext.Run.Job().Outputs[k] = interpolated + } + } + return nil + } +} + type formatError string func (e formatError) Error() string {