Add more steps context support (#887)
* feat: add more steps context support - Add step outcome - Remove step success in favour of conclusion * feat: add conclusion and outcome steps context tests Add corresponding tests and extend pkg/runner/expression.go vmSteps() to handle steps context conclusion and outcome iota as strings. Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
6517d04b9a
commit
e793d03f4b
|
@ -466,9 +466,19 @@ func (rc *RunContext) vmJob() func(*otto.Otto) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RunContext) vmSteps() func(*otto.Otto) {
|
func (rc *RunContext) vmSteps() func(*otto.Otto) {
|
||||||
steps := rc.getStepsContext()
|
ctxSteps := rc.getStepsContext()
|
||||||
|
|
||||||
|
steps := make(map[string]interface{})
|
||||||
|
for id, ctxStep := range ctxSteps {
|
||||||
|
steps[id] = map[string]interface{}{
|
||||||
|
"conclusion": ctxStep.Conclusion.String(),
|
||||||
|
"outcome": ctxStep.Outcome.String(),
|
||||||
|
"outputs": ctxStep.Outputs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return func(vm *otto.Otto) {
|
return func(vm *otto.Otto) {
|
||||||
|
log.Debugf("context steps => %v", steps)
|
||||||
_ = vm.Set("steps", steps)
|
_ = vm.Set("steps", steps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,22 +49,25 @@ func TestEvaluate(t *testing.T) {
|
||||||
},
|
},
|
||||||
StepResults: map[string]*stepResult{
|
StepResults: map[string]*stepResult{
|
||||||
"idwithnothing": {
|
"idwithnothing": {
|
||||||
|
Conclusion: stepStatusSuccess,
|
||||||
|
Outcome: stepStatusFailure,
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]string{
|
||||||
"foowithnothing": "barwithnothing",
|
"foowithnothing": "barwithnothing",
|
||||||
},
|
},
|
||||||
Success: true,
|
|
||||||
},
|
},
|
||||||
"id-with-hyphens": {
|
"id-with-hyphens": {
|
||||||
|
Conclusion: stepStatusSuccess,
|
||||||
|
Outcome: stepStatusFailure,
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]string{
|
||||||
"foo-with-hyphens": "bar-with-hyphens",
|
"foo-with-hyphens": "bar-with-hyphens",
|
||||||
},
|
},
|
||||||
Success: true,
|
|
||||||
},
|
},
|
||||||
"id_with_underscores": {
|
"id_with_underscores": {
|
||||||
|
Conclusion: stepStatusSuccess,
|
||||||
|
Outcome: stepStatusFailure,
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]string{
|
||||||
"foo_with_underscores": "bar_with_underscores",
|
"foo_with_underscores": "bar_with_underscores",
|
||||||
},
|
},
|
||||||
Success: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -107,8 +110,14 @@ func TestEvaluate(t *testing.T) {
|
||||||
{"github.run_id", "1", ""},
|
{"github.run_id", "1", ""},
|
||||||
{"github.run_number", "1", ""},
|
{"github.run_number", "1", ""},
|
||||||
{"job.status", "success", ""},
|
{"job.status", "success", ""},
|
||||||
|
{"steps.idwithnothing.conclusion", "success", ""},
|
||||||
|
{"steps.idwithnothing.outcome", "failure", ""},
|
||||||
{"steps.idwithnothing.outputs.foowithnothing", "barwithnothing", ""},
|
{"steps.idwithnothing.outputs.foowithnothing", "barwithnothing", ""},
|
||||||
|
{"steps.id-with-hyphens.conclusion", "success", ""},
|
||||||
|
{"steps.id-with-hyphens.outcome", "failure", ""},
|
||||||
{"steps.id-with-hyphens.outputs.foo-with-hyphens", "bar-with-hyphens", ""},
|
{"steps.id-with-hyphens.outputs.foo-with-hyphens", "bar-with-hyphens", ""},
|
||||||
|
{"steps.id_with_underscores.conclusion", "success", ""},
|
||||||
|
{"steps.id_with_underscores.outcome", "failure", ""},
|
||||||
{"steps.id_with_underscores.outputs.foo_with_underscores", "bar_with_underscores", ""},
|
{"steps.id_with_underscores.outputs.foo_with_underscores", "bar_with_underscores", ""},
|
||||||
{"runner.os", "Linux", ""},
|
{"runner.os", "Linux", ""},
|
||||||
{"matrix.os", "Linux", ""},
|
{"matrix.os", "Linux", ""},
|
||||||
|
|
|
@ -52,9 +52,44 @@ func (rc *RunContext) String() string {
|
||||||
return fmt.Sprintf("%s/%s", rc.Run.Workflow.Name, rc.Name)
|
return fmt.Sprintf("%s/%s", rc.Run.Workflow.Name, rc.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stepStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
stepStatusSuccess stepStatus = iota
|
||||||
|
stepStatusFailure
|
||||||
|
)
|
||||||
|
|
||||||
|
var stepStatusStrings = [...]string{
|
||||||
|
"success",
|
||||||
|
"failure",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s stepStatus) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(s.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepStatus) UnmarshalText(b []byte) error {
|
||||||
|
str := string(b)
|
||||||
|
for i, name := range stepStatusStrings {
|
||||||
|
if name == str {
|
||||||
|
*s = stepStatus(i)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("invalid step status %q", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s stepStatus) String() string {
|
||||||
|
if int(s) >= len(stepStatusStrings) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return stepStatusStrings[s]
|
||||||
|
}
|
||||||
|
|
||||||
type stepResult struct {
|
type stepResult struct {
|
||||||
Success bool `json:"success"`
|
|
||||||
Outputs map[string]string `json:"outputs"`
|
Outputs map[string]string `json:"outputs"`
|
||||||
|
Conclusion stepStatus `json:"conclusion"`
|
||||||
|
Outcome stepStatus `json:"outcome"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEnv returns the env for the context
|
// GetEnv returns the env for the context
|
||||||
|
@ -266,7 +301,8 @@ func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
rc.CurrentStep = sc.Step.ID
|
rc.CurrentStep = sc.Step.ID
|
||||||
rc.StepResults[rc.CurrentStep] = &stepResult{
|
rc.StepResults[rc.CurrentStep] = &stepResult{
|
||||||
Success: true,
|
Outcome: stepStatusSuccess,
|
||||||
|
Conclusion: stepStatusSuccess,
|
||||||
Outputs: make(map[string]string),
|
Outputs: make(map[string]string),
|
||||||
}
|
}
|
||||||
runStep, err := rc.EvalBool(sc.Step.If.Value)
|
runStep, err := rc.EvalBool(sc.Step.If.Value)
|
||||||
|
@ -278,7 +314,8 @@ func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rc.ExprEval = exprEval
|
rc.ExprEval = exprEval
|
||||||
rc.StepResults[rc.CurrentStep].Success = false
|
rc.StepResults[rc.CurrentStep].Conclusion = stepStatusFailure
|
||||||
|
rc.StepResults[rc.CurrentStep].Outcome = stepStatusFailure
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,12 +337,13 @@ func (rc *RunContext) newStepExecutor(step *model.Step) common.Executor {
|
||||||
} else {
|
} else {
|
||||||
common.Logger(ctx).Errorf(" \u274C Failure - %s", sc.Step)
|
common.Logger(ctx).Errorf(" \u274C Failure - %s", sc.Step)
|
||||||
|
|
||||||
|
rc.StepResults[rc.CurrentStep].Outcome = stepStatusFailure
|
||||||
if sc.Step.ContinueOnError {
|
if sc.Step.ContinueOnError {
|
||||||
common.Logger(ctx).Infof("Failed but continue next step")
|
common.Logger(ctx).Infof("Failed but continue next step")
|
||||||
err = nil
|
err = nil
|
||||||
rc.StepResults[rc.CurrentStep].Success = true
|
rc.StepResults[rc.CurrentStep].Conclusion = stepStatusSuccess
|
||||||
} else {
|
} else {
|
||||||
rc.StepResults[rc.CurrentStep].Success = false
|
rc.StepResults[rc.CurrentStep].Conclusion = stepStatusFailure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
@ -500,7 +538,7 @@ type jobContext struct {
|
||||||
func (rc *RunContext) getJobContext() *jobContext {
|
func (rc *RunContext) getJobContext() *jobContext {
|
||||||
jobStatus := "success"
|
jobStatus := "success"
|
||||||
for _, stepStatus := range rc.StepResults {
|
for _, stepStatus := range rc.StepResults {
|
||||||
if !stepStatus.Success {
|
if stepStatus.Conclusion == stepStatusFailure {
|
||||||
jobStatus = "failure"
|
jobStatus = "failure"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,10 +54,11 @@ func TestRunContext_EvalBool(t *testing.T) {
|
||||||
},
|
},
|
||||||
StepResults: map[string]*stepResult{
|
StepResults: map[string]*stepResult{
|
||||||
"id1": {
|
"id1": {
|
||||||
|
Conclusion: stepStatusSuccess,
|
||||||
|
Outcome: stepStatusFailure,
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]string{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
Success: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -73,6 +74,10 @@ func TestRunContext_EvalBool(t *testing.T) {
|
||||||
{in: "success()", out: true},
|
{in: "success()", out: true},
|
||||||
{in: "cancelled()", out: false},
|
{in: "cancelled()", out: false},
|
||||||
{in: "always()", out: true},
|
{in: "always()", out: true},
|
||||||
|
{in: "steps.id1.conclusion == 'success'", out: true},
|
||||||
|
{in: "steps.id1.conclusion != 'success'", out: false},
|
||||||
|
{in: "steps.id1.outcome == 'failure'", out: true},
|
||||||
|
{in: "steps.id1.outcome != 'failure'", out: false},
|
||||||
{in: "true", out: true},
|
{in: "true", out: true},
|
||||||
{in: "false", out: false},
|
{in: "false", out: false},
|
||||||
{in: "!true", wantErr: true},
|
{in: "!true", wantErr: true},
|
||||||
|
|
|
@ -121,6 +121,8 @@ func TestRunEvent(t *testing.T) {
|
||||||
{"testdata", "issue-598", "push", "", platforms, ""},
|
{"testdata", "issue-598", "push", "", platforms, ""},
|
||||||
{"testdata", "env-and-path", "push", "", platforms, ""},
|
{"testdata", "env-and-path", "push", "", platforms, ""},
|
||||||
{"testdata", "outputs", "push", "", platforms, ""},
|
{"testdata", "outputs", "push", "", platforms, ""},
|
||||||
|
{"testdata", "steps-context/conclusion", "push", "", platforms, ""},
|
||||||
|
{"testdata", "steps-context/outcome", "push", "", platforms, ""},
|
||||||
{"../model/testdata", "strategy", "push", "", platforms, ""}, // TODO: move all testdata into pkg so we can validate it with planner and runner
|
{"../model/testdata", "strategy", "push", "", platforms, ""}, // TODO: move all testdata into pkg so we can validate it with planner and runner
|
||||||
// {"testdata", "issue-228", "push", "", platforms, ""}, // TODO [igni]: Remove this once everything passes
|
// {"testdata", "issue-228", "push", "", platforms, ""}, // TODO [igni]: Remove this once everything passes
|
||||||
|
|
||||||
|
|
|
@ -640,7 +640,7 @@ func (sc *StepContext) execAsComposite(ctx context.Context, step *model.Step, _
|
||||||
// Setup the outputs for the composite steps
|
// Setup the outputs for the composite steps
|
||||||
if _, ok := rcClone.StepResults[stepClone.ID]; !ok {
|
if _, ok := rcClone.StepResults[stepClone.ID]; !ok {
|
||||||
rcClone.StepResults[stepClone.ID] = &stepResult{
|
rcClone.StepResults[stepClone.ID] = &stepResult{
|
||||||
Success: true,
|
Conclusion: stepStatusSuccess,
|
||||||
Outputs: make(map[string]string),
|
Outputs: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
name: conclusion
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- id: first
|
||||||
|
run: exit 0
|
||||||
|
- id: second
|
||||||
|
continue-on-error: true
|
||||||
|
run: exit 1
|
||||||
|
- run: echo '${{ steps.first.conclusion }}' | grep 'success'
|
||||||
|
- run: echo '${{ steps.second.conclusion }}' | grep 'success'
|
|
@ -0,0 +1,14 @@
|
||||||
|
name: outcome
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- id: first
|
||||||
|
run: exit 0
|
||||||
|
- id: second
|
||||||
|
continue-on-error: true
|
||||||
|
run: exit 1
|
||||||
|
- run: echo '${{ steps.first.outcome }}' | grep 'success'
|
||||||
|
- run: echo '${{ steps.second.outcome }}' | grep 'failure'
|
Loading…
Reference in New Issue