Skip to content

Commit 538c99a

Browse files
committed
aggregate status of tasks
Implementing TEP-0049, it is now possible to access aggregate execution status of all tasks using `$(tasks.status)`. This context variable is only available in a finally task.
1 parent 565c55b commit 538c99a

File tree

11 files changed

+218
-15
lines changed

11 files changed

+218
-15
lines changed

docs/pipelines.md

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,8 @@ what kind of events are triggered based on the `Pipelinerun` status.
869869

870870
### Using Execution `Status` of `pipelineTask`
871871

872-
Finally Task can utilize execution status of any of the `pipelineTasks` under `tasks` section using param:
872+
A `pipeline` can check the status of a specific `pipelineTask` from the `tasks` section in `finally` through the task
873+
parameters:
873874

874875
```yaml
875876
finally:
@@ -900,6 +901,40 @@ This kind of variable can have any one of the values from the following table:
900901

901902
For an end-to-end example, see [`status` in a `PipelineRun`](../examples/v1beta1/pipelineruns/pipelinerun-task-execution-status.yaml).
902903

904+
### Using Aggregate Execution `Status` of All `Tasks`
905+
906+
A `pipeline` can check an aggregate status of all the `tasks` section in `finally` through the task parameters:
907+
908+
```yaml
909+
finally:
910+
- name: finaltask
911+
params:
912+
- name: aggregateTasksStatus
913+
value: "$(tasks.status)"
914+
taskSpec:
915+
params:
916+
- name: aggregateTasksStatus
917+
steps:
918+
- image: ubuntu
919+
name: check-task-status
920+
script: |
921+
if [ $(params.aggregateTasksStatus) == "Failed" ]
922+
then
923+
echo "Looks like one or more tasks returned failure, continue processing the failure"
924+
fi
925+
```
926+
927+
This kind of variable can have any one of the values from the following table:
928+
929+
| Status | Description |
930+
| ------- | -----------|
931+
| `Succeeded` | all `tasks` have succeeded |
932+
| `Failed` | one ore more `tasks` failed |
933+
| `Completed` | all `tasks` completed successfully including one or more skipped tasks |
934+
| `None` | no aggregate execution status available (i.e. none of the above), one or more `tasks` could be pending/running/cancelled/timedout |
935+
936+
For an end-to-end example, see [`$(tasks.status)` usage in a `Pipeline`](../examples/v1beta1/pipelineruns/pipelinerun-task-execution-status.yaml).
937+
903938
### Guard `Finally Task` execution using `WhenExpressions`
904939

905940
Similar to `Tasks`, `Finally Tasks` can be guarded using [`WhenExpressions`](#guard-task-execution-using-whenexpressions)
@@ -985,7 +1020,7 @@ If the `WhenExpressions` in a `Finally Task` use `Results` from a skipped or fai
9851020
#### `WhenExpressions` using `Execution Status` of `PipelineTask` in `Finally Tasks`
9861021

9871022
`WhenExpressions` in `Finally Tasks` can utilize [`Execution Status` of `PipelineTasks`](#using-execution-status-of-pipelinetask),
988-
as as demonstrated using [`golang-build`](https://github.com/tektoncd/catalog/tree/main/task/golang-build/0.1) and
1023+
as demonstrated using [`golang-build`](https://github.com/tektoncd/catalog/tree/main/task/golang-build/0.1) and
9891024
[`send-to-channel-slack`](https://github.com/tektoncd/catalog/tree/main/task/send-to-channel-slack/0.1) Catalog `Tasks`:
9901025

9911026
```yaml
@@ -1013,6 +1048,24 @@ spec:
10131048

10141049
For an end-to-end example, see [PipelineRun with WhenExpressions](../examples/v1beta1/pipelineruns/pipelinerun-with-when-expressions.yaml).
10151050

1051+
#### `WhenExpressions` using `Aggregate Execution Status` of `Tasks` in `Finally Tasks`
1052+
1053+
`WhenExpressions` in `Finally Tasks` can utilize
1054+
[`Aggregate Execution Status` of `Tasks`](#using-aggregate-execution-status-of-all-tasks) as demonstrated:
1055+
1056+
```yaml
1057+
finally:
1058+
- name: notify-any-failure # executed only when one or more tasks fail
1059+
when:
1060+
- input: $(tasks.status)
1061+
operator: in
1062+
values: ["Failed"]
1063+
taskRef:
1064+
name: notify-failure
1065+
```
1066+
1067+
For an end-to-end example, see [PipelineRun with WhenExpressions](../examples/v1beta1/pipelineruns/pipelinerun-with-when-expressions.yaml).
1068+
10161069
### Known Limitations
10171070

10181071
#### Specifying `Resources` in Final Tasks

docs/variables.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ For instructions on using variable substitutions see the relevant section of [th
2424
| `context.pipelineRun.uid` | The uid of the `PipelineRun` that this `Pipeline` is running in. |
2525
| `context.pipeline.name` | The name of this `Pipeline` . |
2626
| `tasks.<pipelineTaskName>.status` | The execution status of the specified `pipelineTask`, only available in `finally` tasks. |
27-
27+
| `tasks.status` | An aggregate status of all `tasks`, only available in `finally` tasks. |
2828

2929
## Variables available in a `Task`
3030

examples/v1beta1/pipelineruns/pipelinerun-task-execution-status.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,17 @@ spec:
4242
if [[ $(params.task1Status) != "Succeeded" || $(params.task2Status) != "None" ]]; then
4343
exit 1;
4444
fi
45+
- name: task4 # this task verifies the aggregate status of all tasks, it fails if verification fails
46+
params:
47+
- name: aggregateStatus
48+
value: "$(tasks.status)"
49+
taskSpec:
50+
params:
51+
- name: aggregateStatus
52+
steps:
53+
- image: alpine
54+
name: verify-aggregate-tasks-status
55+
script: |
56+
if [[ $(params.aggregateStatus) != "Completed" ]]; then
57+
exit 1;
58+
fi

examples/v1beta1/pipelineruns/pipelinerun-with-when-expressions.yaml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,24 @@ spec:
130130
- name: echo
131131
image: ubuntu
132132
script: exit 1
133-
- name: finally-task-should-be-executed # when expression using execution status, param and results
133+
- name: finally-task-should-be-skipped-4 # when expression using tasks execution status, evaluates to false
134+
when:
135+
- input: "$(tasks.status)"
136+
operator: in
137+
values: ["Failure"]
138+
taskSpec:
139+
steps:
140+
- name: echo
141+
image: ubuntu
142+
script: exit 1
143+
- name: finally-task-should-be-executed # when expression using execution status, tasks execution status, param, and results
134144
when:
135145
- input: "$(tasks.echo-file-exists.status)"
136146
operator: in
137147
values: ["Succeeded"]
148+
- input: "$(tasks.status)"
149+
operator: in
150+
values: ["Succeeded"]
138151
- input: "$(tasks.check-file.results.exists)"
139152
operator: in
140153
values: ["yes"]

pkg/apis/pipeline/v1beta1/pipeline_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ import (
3131
"knative.dev/pkg/apis"
3232
)
3333

34+
const (
35+
// PipelineTasksAggregateStatus is a param representing aggregate status of all dag pipelineTasks
36+
PipelineTasksAggregateStatus = "tasks.status"
37+
)
38+
3439
// +genclient
3540
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
3641
// +genclient:noStatus

pkg/apis/pipeline/v1beta1/pipeline_validation.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,25 +203,30 @@ func validateExecutionStatusVariablesInTasks(tasks []PipelineTask) (errs *apis.F
203203
for _, param := range t.Params {
204204
// retrieve a list of substitution expression from a param
205205
if ps, ok := GetVarSubstitutionExpressionsForParam(param); ok {
206-
// validate tasks.pipelineTask.status if this expression is not a result reference
206+
// validate tasks.pipelineTask.status/tasks.status if this expression is not a result reference
207207
if !LooksLikeContainsResultRefs(ps) {
208208
for _, p := range ps {
209209
// check if it contains context variable accessing execution status - $(tasks.taskname.status)
210+
// or an aggregate status - $(tasks.status)
210211
if containsExecutionStatusRef(p) {
211-
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("pipeline tasks can not refer to execution status of any other pipeline task"),
212-
"value").ViaFieldKey("params", param.Name).ViaFieldIndex("tasks", idx))
212+
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("pipeline tasks can not refer to execution status of any other pipeline task"+
213+
" or aggregate status of tasks"), "value").ViaFieldKey("params", param.Name).ViaFieldIndex("tasks", idx))
213214
}
214215
}
215216
}
216217
}
217218
}
218219
for i, we := range t.WhenExpressions {
220+
// retrieve a list of substitution expression from a when expression
219221
if expressions, ok := we.GetVarSubstitutionExpressions(); ok {
222+
// validate tasks.pipelineTask.status/tasks.status if this expression is not a result reference
220223
if !LooksLikeContainsResultRefs(expressions) {
221224
for _, e := range expressions {
225+
// check if it contains context variable accessing execution status - $(tasks.taskname.status)
226+
// or an aggregate status - $(tasks.status)
222227
if containsExecutionStatusRef(e) {
223-
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("when expressions in pipeline tasks can not refer to execution status of any other pipeline task"),
224-
"").ViaFieldIndex("when", i).ViaFieldIndex("tasks", idx))
228+
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("when expressions in pipeline tasks can not refer to execution status of any other pipeline task"+
229+
" or aggregate status of tasks"), "").ViaFieldIndex("when", i).ViaFieldIndex("tasks", idx))
225230
}
226231
}
227232
}
@@ -257,6 +262,10 @@ func validateExecutionStatusVariablesExpressions(expressions []string, ptNames s
257262
// validate tasks.pipelineTask.status if this expression is not a result reference
258263
if !LooksLikeContainsResultRefs(expressions) {
259264
for _, expression := range expressions {
265+
// its a reference to aggregate status of dag tasks - $(tasks.status)
266+
if expression == PipelineTasksAggregateStatus {
267+
continue
268+
}
260269
// check if it contains context variable accessing execution status - $(tasks.taskname.status)
261270
if containsExecutionStatusRef(expression) {
262271
// strip tasks. and .status from tasks.taskname.status to further verify task name

pkg/apis/pipeline/v1beta1/pipeline_validation_test.go

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2290,11 +2290,17 @@ func TestPipelineTasksExecutionStatus(t *testing.T) {
22902290
TaskRef: &TaskRef{Name: "bar-task"},
22912291
Params: []Param{{
22922292
Name: "foo-status", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(tasks.foo.status)"},
2293+
}, {
2294+
Name: "tasks-status", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(tasks.status)"},
22932295
}},
2294-
WhenExpressions: WhenExpressions{WhenExpression{
2296+
WhenExpressions: WhenExpressions{{
22952297
Input: "$(tasks.foo.status)",
22962298
Operator: selection.In,
22972299
Values: []string{"Failure"},
2300+
}, {
2301+
Input: "$(tasks.status)",
2302+
Operator: selection.In,
2303+
Values: []string{"Success"},
22982304
}},
22992305
}},
23002306
}, {
@@ -2350,12 +2356,25 @@ func TestPipelineTasksExecutionStatus(t *testing.T) {
23502356
}},
23512357
}},
23522358
expectedError: *apis.ErrGeneric("").Also(&apis.FieldError{
2353-
Message: `invalid value: pipeline tasks can not refer to execution status of any other pipeline task`,
2359+
Message: `invalid value: pipeline tasks can not refer to execution status of any other pipeline task or aggregate status of tasks`,
23542360
Paths: []string{"tasks[0].params[bar-status].value"},
23552361
}).Also(&apis.FieldError{
2356-
Message: `invalid value: when expressions in pipeline tasks can not refer to execution status of any other pipeline task`,
2362+
Message: `invalid value: when expressions in pipeline tasks can not refer to execution status of any other pipeline task or aggregate status of tasks`,
23572363
Paths: []string{"tasks[0].when[0]"},
23582364
}),
2365+
}, {
2366+
name: "invalid string variable in dag task accessing aggregate status of tasks",
2367+
tasks: []PipelineTask{{
2368+
Name: "foo",
2369+
TaskRef: &TaskRef{Name: "foo-task"},
2370+
Params: []Param{{
2371+
Name: "tasks-status", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(tasks.status)"},
2372+
}},
2373+
}},
2374+
expectedError: apis.FieldError{
2375+
Message: `invalid value: pipeline tasks can not refer to execution status of any other pipeline task or aggregate status of tasks`,
2376+
Paths: []string{"tasks[0].params[tasks-status].value"},
2377+
},
23592378
}, {
23602379
name: "invalid variable concatenated with extra string in dag task accessing pipelineTask status",
23612380
tasks: []PipelineTask{{
@@ -2366,7 +2385,7 @@ func TestPipelineTasksExecutionStatus(t *testing.T) {
23662385
}},
23672386
}},
23682387
expectedError: apis.FieldError{
2369-
Message: `invalid value: pipeline tasks can not refer to execution status of any other pipeline task`,
2388+
Message: `invalid value: pipeline tasks can not refer to execution status of any other pipeline task or aggregate status of tasks`,
23702389
Paths: []string{"tasks[0].params[bar-status].value"},
23712390
},
23722391
}, {
@@ -2379,9 +2398,22 @@ func TestPipelineTasksExecutionStatus(t *testing.T) {
23792398
}},
23802399
}},
23812400
expectedError: apis.FieldError{
2382-
Message: `invalid value: pipeline tasks can not refer to execution status of any other pipeline task`,
2401+
Message: `invalid value: pipeline tasks can not refer to execution status of any other pipeline task or aggregate status of tasks`,
23832402
Paths: []string{"tasks[0].params[bar-status].value"},
23842403
},
2404+
}, {
2405+
name: "invalid array variable in dag task accessing aggregate tasks status",
2406+
tasks: []PipelineTask{{
2407+
Name: "foo",
2408+
TaskRef: &TaskRef{Name: "foo-task"},
2409+
Params: []Param{{
2410+
Name: "tasks-status", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"$(tasks.status)"}},
2411+
}},
2412+
}},
2413+
expectedError: apis.FieldError{
2414+
Message: `invalid value: pipeline tasks can not refer to execution status of any other pipeline task or aggregate status of tasks`,
2415+
Paths: []string{"tasks[0].params[tasks-status].value"},
2416+
},
23852417
}, {
23862418
name: "invalid string variable in finally accessing missing pipelineTask status",
23872419
finalTasks: []PipelineTask{{

pkg/reconciler/pipelinerun/resources/apply.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func ApplyTaskResults(targets PipelineRunState, resolvedResultRefs ResolvedResul
8787
}
8888
}
8989

90-
//ApplyPipelineTaskContext replaces context variables referring to execution status with the specified status
90+
// ApplyPipelineTaskContext replaces context variables referring to execution status with the specified status
9191
func ApplyPipelineTaskContext(state PipelineRunState, replacements map[string]string) {
9292
for _, resolvedPipelineRunTask := range state {
9393
if resolvedPipelineRunTask.PipelineTask != nil {

pkg/reconciler/pipelinerun/resources/pipelinerunstate.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,28 @@ func (facts *PipelineRunFacts) GetPipelineTaskStatus() map[string]string {
412412
tStatus[PipelineTaskStatusPrefix+t.PipelineTask.Name+PipelineTaskStatusSuffix] = s
413413
}
414414
}
415+
// initialize aggregate status of all dag tasks to None
416+
aggregateStatus := PipelineTaskStateNone
417+
if facts.checkDAGTasksDone() {
418+
// all dag tasks are done, change the aggregate status to succeeded
419+
// will reset it to failed/skipped if needed
420+
aggregateStatus = v1beta1.PipelineRunReasonSuccessful.String()
421+
for _, t := range facts.State {
422+
if facts.isDAGTask(t.PipelineTask.Name) {
423+
// if any of the dag task failed, change the aggregate status to failed and return
424+
if t.IsConditionStatusFalse() {
425+
aggregateStatus = v1beta1.PipelineRunReasonFailed.String()
426+
break
427+
}
428+
// if any of the dag task skipped, change the aggregate status to completed
429+
// but continue checking for any other failure
430+
if t.Skip(facts) {
431+
aggregateStatus = v1beta1.PipelineRunReasonCompleted.String()
432+
}
433+
}
434+
}
435+
}
436+
tStatus[v1beta1.PipelineTasksAggregateStatus] = aggregateStatus
415437
return tStatus
416438
}
417439

0 commit comments

Comments
 (0)