Add needs
job output (#629)
* Add outputs field to job model * Add output interpolation for jobs * Add otto config reference for interpolated job output values into 'needs' context * Add output interpolation call after job has completed. * gofmt * Remove whitespace * goimports Co-authored-by: Casey Lee <cplee@nektos.com>
This commit is contained in:
parent
1cf422e411
commit
dcbd5837af
5 changed files with 76 additions and 1 deletions
|
@ -66,6 +66,7 @@ type Job struct {
|
||||||
Strategy *Strategy `yaml:"strategy"`
|
Strategy *Strategy `yaml:"strategy"`
|
||||||
RawContainer yaml.Node `yaml:"container"`
|
RawContainer yaml.Node `yaml:"container"`
|
||||||
Defaults Defaults `yaml:"defaults"`
|
Defaults Defaults `yaml:"defaults"`
|
||||||
|
Outputs map[string]string `yaml:"outputs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy for the job
|
// Strategy for the job
|
||||||
|
|
|
@ -131,6 +131,48 @@ jobs:
|
||||||
assert.Equal(t, workflow.Jobs["test"].Steps[4].Type(), StepTypeUsesActionLocal)
|
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) {
|
func TestStep_ShellCommand(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
shell string
|
shell string
|
||||||
|
|
|
@ -229,6 +229,7 @@ func (rc *RunContext) newVM() *otto.Otto {
|
||||||
rc.vmStrategy(),
|
rc.vmStrategy(),
|
||||||
rc.vmMatrix(),
|
rc.vmMatrix(),
|
||||||
rc.vmEnv(),
|
rc.vmEnv(),
|
||||||
|
rc.vmNeeds(),
|
||||||
}
|
}
|
||||||
vm := otto.New()
|
vm := otto.New()
|
||||||
for _, configer := range configers {
|
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) {
|
func (rc *RunContext) vmJob() func(*otto.Otto) {
|
||||||
job := rc.getJobContext()
|
job := rc.getJobContext()
|
||||||
|
|
||||||
|
|
|
@ -262,7 +262,7 @@ func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
|
||||||
rc.ExprEval = exprEval
|
rc.ExprEval = exprEval
|
||||||
|
|
||||||
common.Logger(ctx).Infof("\u2B50 Run %s", sc.Step)
|
common.Logger(ctx).Infof("\u2B50 Run %s", sc.Step)
|
||||||
err = sc.Executor()(ctx)
|
err = sc.Executor().Then(sc.interpolateOutputs())(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
common.Logger(ctx).Infof(" \u2705 Success - %s", sc.Step)
|
common.Logger(ctx).Infof(" \u2705 Success - %s", sc.Step)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -29,6 +29,7 @@ type StepContext struct {
|
||||||
Env map[string]string
|
Env map[string]string
|
||||||
Cmd []string
|
Cmd []string
|
||||||
Action *model.Action
|
Action *model.Action
|
||||||
|
Needs *model.Job
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *StepContext) execJobContainer() common.Executor {
|
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
|
type formatError string
|
||||||
|
|
||||||
func (e formatError) Error() string {
|
func (e formatError) Error() string {
|
||||||
|
|
Loading…
Reference in a new issue