Skip to content

Commit 9bd73ea

Browse files
committed
feat: add option continueAndFail on step.onError
1 parent 5b082b1 commit 9bd73ea

12 files changed

+97
-21
lines changed

cmd/entrypoint/main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ var (
5454
breakpointOnFailure = flag.Bool("breakpoint_on_failure", false, "If specified, expect steps to not skip on failure")
5555
debugBeforeStep = flag.Bool("debug_before_step", false, "If specified, wait for a debugger to attach before executing the step")
5656
onError = flag.String("on_error", "", "Set to \"continue\" to ignore an error and continue when a container terminates with a non-zero exit code."+
57-
" Set to \"stopAndFail\" to declare a failure with a step error and stop executing the rest of the steps.")
57+
" Set to \"stopAndFail\" to declare a failure with a step error and stop executing the rest of the steps."+
58+
" Set to \"continueAndFail\" to declare a failure with a step error after having executed the rest of the steps.")
5859
stepMetadataDir = flag.String("step_metadata_dir", "", "If specified, create directory to store the step metadata e.g. /tekton/steps/<step-name>/")
5960
resultExtractionMethod = flag.String("result_from", entrypoint.ResultExtractionMethodTerminationMessage, "The method using which to extract results from tasks. Default is using the termination message.")
6061
)

docs/pipeline-api.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -1752,6 +1752,9 @@ IncludeParamsList
17521752
<tbody><tr><td><p>&#34;continue&#34;</p></td>
17531753
<td><p>Continue indicates continue executing the rest of the steps irrespective of the container exit code</p>
17541754
</td>
1755+
</tr><tr><td><p>&#34;continueAndFail&#34;</p></td>
1756+
<td><p>ContinueAndFail indicates continue executing the rest of the steps irrespective of the container exit code and exit after all steps are completed with the first possible non-zero exit code of the step that has this OnErrorType</p>
1757+
</td>
17551758
</tr><tr><td><p>&#34;stopAndFail&#34;</p></td>
17561759
<td><p>StopAndFail indicates exit the taskRun if the container exits with non-zero exit code</p>
17571760
</td>
@@ -4602,7 +4605,7 @@ OnErrorType
46024605
</td>
46034606
<td>
46044607
<p>OnError defines the exiting behavior of a container on error
4605-
can be set to [ continue | stopAndFail ]</p>
4608+
can be set to [ continue | stopAndFail | continueAndFail ]</p>
46064609
</td>
46074610
</tr>
46084611
<tr>

docs/tasks.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -385,11 +385,15 @@ When a `step` in a `task` results in a failure, the rest of the steps in the `ta
385385
declared a failure. If you would like to ignore such step errors and continue executing the rest of the steps in
386386
the task, you can specify `onError` for such a `step`.
387387

388-
`onError` can be set to either `continue` or `stopAndFail` as part of the step definition. If `onError` is
388+
`onError` can be set to `continue`, `stopAndFail` or `continueAndFail` as part of the step definition. If `onError` is
389389
set to `continue`, the entrypoint sets the original failed exit code of the [script](#running-scripts-within-steps)
390390
in the container terminated state. A `step` with `onError` set to `continue` does not fail the `taskRun` and continues
391391
executing the rest of the steps in a task.
392392

393+
If `onError` is set to `continueAndFail`, the `taskRun` continues executing the rest of the steps in a task and if no
394+
following `step` fails, the `taskRun` fails eventually with the exit code of the step that had set `onError` with
395+
`continueAndFail`.
396+
393397
To ignore a step error, set `onError` to `continue`:
394398

395399
```yaml

pkg/apis/pipeline/v1/container_types.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ type Step struct {
127127
Workspaces []WorkspaceUsage `json:"workspaces,omitempty"`
128128

129129
// OnError defines the exiting behavior of a container on error
130-
// can be set to [ continue | stopAndFail ]
130+
// can be set to [ continue | stopAndFail | continueAndFail ]
131131
OnError OnErrorType `json:"onError,omitempty"`
132132
// Stores configuration for the stdout stream of the step.
133133
// +optional
@@ -176,6 +176,8 @@ const (
176176
StopAndFail OnErrorType = "stopAndFail"
177177
// Continue indicates continue executing the rest of the steps irrespective of the container exit code
178178
Continue OnErrorType = "continue"
179+
// Continue indicates continue executing the rest of the steps irrespective of the container exit code and exit after all steps are completed with the first possible non-zero exit code of the step that has this OnErrorType
180+
ContinueAndFail OnErrorType = "continueAndFail"
179181
)
180182

181183
// StepOutputConfig stores configuration for a step output stream.

pkg/apis/pipeline/v1/openapi_generated.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/apis/pipeline/v1/swagger.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1531,7 +1531,7 @@
15311531
"default": ""
15321532
},
15331533
"onError": {
1534-
"description": "OnError defines the exiting behavior of a container on error can be set to [ continue | stopAndFail ]",
1534+
"description": "OnError defines the exiting behavior of a container on error can be set to [ continue | stopAndFail | continueAndFail ]",
15351535
"type": "string"
15361536
},
15371537
"params": {

pkg/apis/pipeline/v1/task_validation.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -505,11 +505,11 @@ func validateStep(ctx context.Context, s Step, names sets.String) (errs *apis.Fi
505505
}
506506

507507
if s.OnError != "" {
508-
if !isParamRefs(string(s.OnError)) && s.OnError != Continue && s.OnError != StopAndFail {
508+
if !isParamRefs(string(s.OnError)) && s.OnError != Continue && s.OnError != StopAndFail && s.OnError != ContinueAndFail {
509509
errs = errs.Also(&apis.FieldError{
510510
Message: fmt.Sprintf("invalid value: \"%v\"", s.OnError),
511511
Paths: []string{"onError"},
512-
Details: "Task step onError must be either \"continue\" or \"stopAndFail\"",
512+
Details: "Task step onError must be \"continue\", \"stopAndFail\" or \"continueAndFail\"",
513513
})
514514
}
515515
}

pkg/apis/pipeline/v1/task_validation_test.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -2279,6 +2279,13 @@ func TestStepOnError(t *testing.T) {
22792279
Image: "image",
22802280
Args: []string{"arg"},
22812281
}},
2282+
}, {
2283+
name: "valid step - valid onError usage - set to continueAndFail",
2284+
steps: []v1.Step{{
2285+
OnError: v1.ContinueAndFail,
2286+
Image: "image",
2287+
Args: []string{"arg"},
2288+
}},
22822289
}, {
22832290
name: "valid step - valid onError usage - set to a task parameter",
22842291
params: []v1.ParamSpec{{
@@ -2300,7 +2307,7 @@ func TestStepOnError(t *testing.T) {
23002307
expectedError: &apis.FieldError{
23012308
Message: `invalid value: "onError"`,
23022309
Paths: []string{"steps[0].onError"},
2303-
Details: `Task step onError must be either "continue" or "stopAndFail"`,
2310+
Details: `Task step onError must be "continue", "stopAndFail" or "continueAndFail"`,
23042311
},
23052312
}}
23062313
for _, tt := range tests {

pkg/entrypoint/entrypointer.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ import (
4646

4747
// RFC3339 with millisecond
4848
const (
49-
timeFormat = "2006-01-02T15:04:05.000Z07:00"
50-
ContinueOnError = "continue"
51-
FailOnError = "stopAndFail"
49+
timeFormat = "2006-01-02T15:04:05.000Z07:00"
50+
ContinueOnError = "continue"
51+
StopAndFailOnError = "stopAndFail"
52+
ContinueAndFailOnError = "continueAndFail"
5253
)
5354

5455
const (
@@ -143,6 +144,7 @@ type Entrypointer struct {
143144
// OnError defines exiting behavior of the entrypoint
144145
// set it to "stopAndFail" to indicate the entrypoint to exit the taskRun if the container exits with non zero exit code
145146
// set it to "continue" to indicate the entrypoint to continue executing the rest of the steps irrespective of the container exit code
147+
// set it to "continueAndFail" to indicate the entrypoint to continue executing the rest of the steps irrespective of the container exit code and exit the taskRun if the container exits with non zero exit code
146148
OnError string
147149
// StepMetadataDir is the directory for a step where the step related metadata can be stored
148150
StepMetadataDir string
@@ -294,6 +296,16 @@ func (e Entrypointer) Go() error {
294296
})
295297
e.WritePostFile(e.PostFile, nil)
296298
e.WriteExitCodeFile(e.StepMetadataDir, exitCode)
299+
case e.OnError == ContinueAndFailOnError && errors.As(err, &ee):
300+
// with continueAndFail on error and an ExitError, write non-zero exit code and a post file with the error
301+
exitCode := strconv.Itoa(ee.ExitCode())
302+
output = append(output, result.RunResult{
303+
Key: "ExitCode",
304+
Value: exitCode,
305+
ResultType: result.InternalTektonResultType,
306+
})
307+
e.WritePostFile(e.PostFile, err)
308+
e.WriteExitCodeFile(e.StepMetadataDir, exitCode)
297309
case err == nil:
298310
// if err is nil, write zero exit code and a post file
299311
e.WritePostFile(e.PostFile, nil)

pkg/entrypoint/entrypointer_test.go

+31-3
Original file line numberDiff line numberDiff line change
@@ -501,12 +501,24 @@ func TestEntrypointer_OnError(t *testing.T) {
501501
runner: &fakeExitErrorRunner{},
502502
expectedError: true,
503503
postFile: "step-one",
504-
onError: FailOnError,
504+
onError: StopAndFailOnError,
505505
}, {
506506
desc: "the step is exiting with 0, treat the step error (but there is none) as failure with onError set to stopAndFail",
507507
runner: &fakeRunner{},
508508
postFile: "step-one",
509-
onError: FailOnError,
509+
onError: StopAndFailOnError,
510+
expectedError: false,
511+
}, {
512+
desc: "the step is exiting with 1, treat the step error as failure with onError set to continueAndFail",
513+
runner: &fakeExitErrorRunner{},
514+
postFile: "step-one",
515+
onError: ContinueAndFailOnError,
516+
expectedError: true,
517+
}, {
518+
desc: "the step is exiting with 0, treat the step error (but there is none) as failure with onError set to continueAndFail",
519+
runner: &fakeRunner{},
520+
postFile: "step-one",
521+
onError: ContinueAndFailOnError,
510522
expectedError: false,
511523
}, {
512524
desc: "the step set debug before step, and before step breakpoint fail-continue",
@@ -566,14 +578,30 @@ func TestEntrypointer_OnError(t *testing.T) {
566578
}
567579
}
568580

569-
if c.onError == FailOnError {
581+
if c.onError == StopAndFailOnError {
570582
switch {
571583
case fpw.wrote == nil:
572584
t.Error("Wanted post file written, got nil")
573585
case c.expectedError && *fpw.wrote != c.postFile+".err":
574586
t.Errorf("Wrote post file %q, want %q", *fpw.wrote, c.postFile+".err")
575587
}
576588
}
589+
590+
if c.onError == ContinueAndFailOnError {
591+
switch {
592+
case fpw.wrote == nil:
593+
t.Error("Wanted post file written, got nil")
594+
case fpw.exitCodeFile == nil:
595+
t.Error("Wanted exitCode file written, got nil")
596+
case *fpw.exitCodeFile != "exitCode":
597+
t.Errorf("Wrote exitCode file %q, want %q", *fpw.exitCodeFile, "exitCode")
598+
case c.expectedError && *fpw.exitCode == "0":
599+
t.Errorf("Wrote zero exit code but want non-zero when expecting an error")
600+
case c.expectedError && *fpw.wrote != c.postFile+".err":
601+
t.Errorf("Wrote post file %q, want %q", *fpw.wrote, c.postFile+".err")
602+
}
603+
}
604+
577605
})
578606
}
579607
}

pkg/pod/entrypoint.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,9 @@ func orderContainers(ctx context.Context, commonExtraEntrypointArgs []string, st
154154
if taskSpec != nil {
155155
if taskSpec.Steps != nil && len(taskSpec.Steps) >= i+1 {
156156
if taskSpec.Steps[i].OnError != "" {
157-
if taskSpec.Steps[i].OnError != v1.Continue && taskSpec.Steps[i].OnError != v1.StopAndFail {
158-
return nil, fmt.Errorf("task step onError must be either \"%s\" or \"%s\" but it is set to an invalid value \"%s\"",
159-
v1.Continue, v1.StopAndFail, taskSpec.Steps[i].OnError)
157+
if taskSpec.Steps[i].OnError != v1.Continue && taskSpec.Steps[i].OnError != v1.StopAndFail && taskSpec.Steps[i].OnError != v1.ContinueAndFail {
158+
return nil, fmt.Errorf("task step onError must have one of the following values: \"%s\", \"%s\" or \"%s\". But it is set to an invalid value \"%s\"",
159+
v1.Continue, v1.StopAndFail, v1.ContinueAndFail, taskSpec.Steps[i].OnError)
160160
}
161161
argsForEntrypoint = append(argsForEntrypoint, "-on_error", string(taskSpec.Steps[i].OnError))
162162
}

pkg/pod/entrypoint_test.go

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*/
22
Copyright 2019 The Tekton Authors
33
44
Licensed under the Apache License, Version 2.0 (the "License");
@@ -645,6 +645,10 @@ func TestEntryPointOnError(t *testing.T) {
645645
Name: "passing-step",
646646
Image: "step-2",
647647
Command: []string{"cmd"},
648+
}, {
649+
Name: "passing-step",
650+
Image: "step-3",
651+
Command: []string{"cmd"},
648652
}}
649653

650654
for _, tc := range []struct {
@@ -658,6 +662,8 @@ func TestEntryPointOnError(t *testing.T) {
658662
OnError: v1.Continue,
659663
}, {
660664
OnError: v1.StopAndFail,
665+
}, {
666+
OnError: v1.ContinueAndFail,
661667
}},
662668
},
663669
wantContainers: []corev1.Container{{
@@ -688,14 +694,27 @@ func TestEntryPointOnError(t *testing.T) {
688694
"-entrypoint", "cmd", "--",
689695
},
690696
TerminationMessagePath: "/tekton/termination",
697+
}, {
698+
Name: "passing-step",
699+
Image: "step-3",
700+
Command: []string{entrypointBinary},
701+
Args: []string{
702+
"-wait_file", "/tekton/run/1/out",
703+
"-post_file", "/tekton/run/2/out",
704+
"-termination_path", "/tekton/termination",
705+
"-step_metadata_dir", "/tekton/run/2/status",
706+
"-on_error", "continueAndFail",
707+
"-entrypoint", "cmd", "--",
708+
},
709+
TerminationMessagePath: "/tekton/termination",
691710
}},
692711
}, {
693712
taskSpec: v1.TaskSpec{
694713
Steps: []v1.Step{{
695714
OnError: "invalid-on-error",
696715
}},
697716
},
698-
err: errors.New("task step onError must be either \"continue\" or \"stopAndFail\" but it is set to an invalid value \"invalid-on-error\""),
717+
err: errors.New("task step onError must have one of the following values: \"continue\", \"stopAndFail\" or \"continueAndFail\". But it is set to an invalid value \"invalid-on-error\""),
699718
}} {
700719
t.Run(tc.desc, func(t *testing.T) {
701720
got, err := orderContainers(context.Background(), []string{}, steps, &tc.taskSpec, nil, true, false)
@@ -711,7 +730,7 @@ func TestEntryPointOnError(t *testing.T) {
711730
t.Fatalf("orderContainers: %v", err)
712731
}
713732
if d := cmp.Diff(tc.wantContainers, got); d != "" {
714-
t.Errorf("Diff %s", diff.PrintWantGot(d))
733+
t.Errorf("Edoooo eimaste\nDiff %s", diff.PrintWantGot(d))
715734
}
716735
}
717736
})

0 commit comments

Comments
 (0)