fix: support expression for step's continue-on-error field (#900) (#1331)

Co-authored-by: Markus Wolf <KnisterPeter@users.noreply.github.com>
This commit is contained in:
Alex Savchuk 2022-09-08 17:20:39 +03:00 committed by GitHub
parent b514649c3d
commit d1daf2f28d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 105 additions and 12 deletions

View File

@ -350,17 +350,17 @@ type ContainerSpec struct {
// Step is the structure of one step in a job
type Step struct {
ID string `yaml:"id"`
If yaml.Node `yaml:"if"`
Name string `yaml:"name"`
Uses string `yaml:"uses"`
Run string `yaml:"run"`
WorkingDirectory string `yaml:"working-directory"`
Shell string `yaml:"shell"`
Env yaml.Node `yaml:"env"`
With map[string]string `yaml:"with"`
ContinueOnError bool `yaml:"continue-on-error"`
TimeoutMinutes string `yaml:"timeout-minutes"`
ID string `yaml:"id"`
If yaml.Node `yaml:"if"`
Name string `yaml:"name"`
Uses string `yaml:"uses"`
Run string `yaml:"run"`
WorkingDirectory string `yaml:"working-directory"`
Shell string `yaml:"shell"`
Env yaml.Node `yaml:"env"`
With map[string]string `yaml:"with"`
RawContinueOnError string `yaml:"continue-on-error"`
TimeoutMinutes string `yaml:"timeout-minutes"`
}
// String gets the name of step

View File

@ -99,7 +99,14 @@ func runStepExecutor(step step, stage stepStage, executor common.Executor) commo
logger.WithField("stepResult", rc.StepResults[rc.CurrentStep].Outcome).Infof(" \u2705 Success - %s %s", stage, stepString)
} else {
rc.StepResults[rc.CurrentStep].Outcome = model.StepStatusFailure
if stepModel.ContinueOnError {
continueOnError, parseErr := isContinueOnError(ctx, stepModel.RawContinueOnError, step, stage)
if parseErr != nil {
rc.StepResults[rc.CurrentStep].Conclusion = model.StepStatusFailure
return parseErr
}
if continueOnError {
logger.Infof("Failed but continue next step")
err = nil
rc.StepResults[rc.CurrentStep].Conclusion = model.StepStatusSuccess
@ -183,6 +190,22 @@ func isStepEnabled(ctx context.Context, expr string, step step, stage stepStage)
return runStep, nil
}
func isContinueOnError(ctx context.Context, expr string, step step, stage stepStage) (bool, error) {
// https://github.com/github/docs/blob/3ae84420bd10997bb5f35f629ebb7160fe776eae/content/actions/reference/workflow-syntax-for-github-actions.md?plain=true#L962
if len(strings.TrimSpace(expr)) == 0 {
return false, nil
}
rc := step.getRunContext()
continueOnError, err := EvalBool(ctx, rc.NewStepExpressionEvaluator(ctx, step), expr, exprparser.DefaultStatusCheckNone)
if err != nil {
return false, fmt.Errorf(" \u274C Error in continue-on-error-expression: \"continue-on-error: %s\" (%s)", expr, err)
}
return continueOnError, nil
}
func mergeIntoMap(target *map[string]string, maps ...map[string]string) {
for _, m := range maps {
for k, v := range m {

View File

@ -276,3 +276,73 @@ func TestIsStepEnabled(t *testing.T) {
}
assertObject.True(isStepEnabled(context.Background(), step.getStepModel().If.Value, step, stepStageMain))
}
func TestIsContinueOnError(t *testing.T) {
createTestStep := func(t *testing.T, input string) step {
var step *model.Step
err := yaml.Unmarshal([]byte(input), &step)
assert.NoError(t, err)
return &stepRun{
RunContext: &RunContext{
Config: &Config{
Workdir: ".",
Platforms: map[string]string{
"ubuntu-latest": "ubuntu-latest",
},
},
StepResults: map[string]*model.StepResult{},
Env: map[string]string{},
Run: &model.Run{
JobID: "job1",
Workflow: &model.Workflow{
Name: "workflow1",
Jobs: map[string]*model.Job{
"job1": createJob(t, `runs-on: ubuntu-latest`, ""),
},
},
},
},
Step: step,
}
}
log.SetLevel(log.DebugLevel)
assertObject := assert.New(t)
// absent
step := createTestStep(t, "name: test")
continueOnError, err := isContinueOnError(context.Background(), step.getStepModel().RawContinueOnError, step, stepStageMain)
assertObject.False(continueOnError)
assertObject.Nil(err)
// explcit true
step = createTestStep(t, "continue-on-error: true")
continueOnError, err = isContinueOnError(context.Background(), step.getStepModel().RawContinueOnError, step, stepStageMain)
assertObject.True(continueOnError)
assertObject.Nil(err)
// explicit false
step = createTestStep(t, "continue-on-error: false")
continueOnError, err = isContinueOnError(context.Background(), step.getStepModel().RawContinueOnError, step, stepStageMain)
assertObject.False(continueOnError)
assertObject.Nil(err)
// expression true
step = createTestStep(t, "continue-on-error: ${{ 'test' == 'test' }}")
continueOnError, err = isContinueOnError(context.Background(), step.getStepModel().RawContinueOnError, step, stepStageMain)
assertObject.True(continueOnError)
assertObject.Nil(err)
// expression false
step = createTestStep(t, "continue-on-error: ${{ 'test' != 'test' }}")
continueOnError, err = isContinueOnError(context.Background(), step.getStepModel().RawContinueOnError, step, stepStageMain)
assertObject.False(continueOnError)
assertObject.Nil(err)
// expression parse error
step = createTestStep(t, "continue-on-error: ${{ 'test' != test }}")
continueOnError, err = isContinueOnError(context.Background(), step.getStepModel().RawContinueOnError, step, stepStageMain)
assertObject.False(continueOnError)
assertObject.NotNil(err)
}