Skip to content

Commit 2788d0d

Browse files
pritidesaitekton-robot
authored andcommitted
enabling consuming task results in finally
Final tasks can be configured to consume Results of PipelineTasks from tasks section
1 parent 5003ca2 commit 2788d0d

File tree

13 files changed

+629
-85
lines changed

13 files changed

+629
-85
lines changed

docs/pipelines.md

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,32 @@ spec:
788788
value: "someURL"
789789
```
790790

791+
#### Consuming `Task` execution results in `finally`
792+
793+
Final tasks can be configured to consume `Results` of `PipelineTask` from the `tasks` section:
794+
795+
```yaml
796+
spec:
797+
tasks:
798+
- name: clone-app-repo
799+
taskRef:
800+
name: git-clone
801+
finally:
802+
- name: discover-git-commit
803+
params:
804+
- name: commit
805+
value: $(tasks.clone-app-repo.results.commit)
806+
```
807+
**Note:** The scheduling of such final task does not change, it will still be executed in parallel with other
808+
final tasks after all non-final tasks are done.
809+
810+
The controller resolves task results before executing the finally task `discover-git-commit`. If the task
811+
`clone-app-repo` failed or skipped with [when expression](#guard-task-execution-using-whenexpressions) resulting in
812+
uninitialized task result `commit`, the finally Task `discover-git-commit` will be included in the list of
813+
`skippedTasks` and continues executing rest of the final tasks. The pipeline exits with `completion` instead of
814+
`success` if a finally task is added to the list of `skippedTasks`.
815+
816+
791817
### `PipelineRun` Status with `finally`
792818

793819
With `finally`, `PipelineRun` status is calculated based on `PipelineTasks` under `tasks` section and final tasks.
@@ -900,35 +926,6 @@ no `runAfter` can be specified in final tasks.
900926
final tasks are guaranteed to be executed after all `PipelineTasks` therefore no `conditions` can be specified in
901927
final tasks.
902928

903-
#### Cannot configure `Task` execution results with `finally`
904-
905-
Final tasks can not be configured to consume `Results` of `PipelineTask` from `tasks` section i.e. the following
906-
example is not supported right now but we are working on adding support for the same (tracked in issue
907-
[#2557](https://github.com/tektoncd/pipeline/issues/2557)).
908-
909-
```yaml
910-
spec:
911-
tasks:
912-
- name: count-comments-before
913-
taskRef:
914-
Name: count-comments
915-
- name: add-comment
916-
taskRef:
917-
Name: add-comment
918-
- name: count-comments-after
919-
taskRef:
920-
Name: count-comments
921-
finally:
922-
- name: check-count
923-
taskRef:
924-
Name: check-count
925-
params:
926-
- name: before-count
927-
value: $(tasks.count-comments-before.results.count) #invalid
928-
- name: after-count
929-
value: $(tasks.count-comments-after.results.count) #invalid
930-
```
931-
932929
#### Cannot configure `Pipeline` result with `finally`
933930

934931
Final tasks can emit `Results` but results emitted from the final tasks can not be configured in the

examples/v1beta1/pipelineruns/pipelinerun-with-final-tasks.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,20 @@ spec:
164164
workspaces:
165165
- name: source
166166
workspace: git-source
167+
- name: check-git-commit
168+
params:
169+
- name: commit
170+
value: $(tasks.clone-app-repo.results.commit)
171+
taskSpec:
172+
params:
173+
- name: commit
174+
steps:
175+
- name: check-commit-initialized
176+
image: alpine
177+
script: |
178+
if [[ ! $(params.commit) ]]; then
179+
exit 1
180+
fi
167181
---
168182

169183
# PipelineRun to execute pipeline - clone-into-workspace-and-cleanup-workspace

pkg/apis/pipeline/v1beta1/pipeline_validation.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func (ps *PipelineSpec) Validate(ctx context.Context) (errs *apis.FieldError) {
6868
// Validate the pipeline's results
6969
errs = errs.Also(validatePipelineResults(ps.Results))
7070
errs = errs.Also(validateTasksAndFinallySection(ps))
71-
errs = errs.Also(validateFinalTasks(ps.Finally))
71+
errs = errs.Also(validateFinalTasks(ps.Tasks, ps.Finally))
7272
errs = errs.Also(validateWhenExpressions(ps.Tasks))
7373
return errs
7474
}
@@ -420,7 +420,7 @@ func validateTasksAndFinallySection(ps *PipelineSpec) *apis.FieldError {
420420
return nil
421421
}
422422

423-
func validateFinalTasks(finalTasks []PipelineTask) *apis.FieldError {
423+
func validateFinalTasks(tasks []PipelineTask, finalTasks []PipelineTask) *apis.FieldError {
424424
for idx, f := range finalTasks {
425425
if len(f.RunAfter) != 0 {
426426
return apis.ErrInvalidValue(fmt.Sprintf("no runAfter allowed under spec.finally, final task %s has runAfter specified", f.Name), "").ViaFieldIndex("finally", idx)
@@ -433,7 +433,10 @@ func validateFinalTasks(finalTasks []PipelineTask) *apis.FieldError {
433433
}
434434
}
435435

436-
if err := validateTaskResultReferenceNotUsed(finalTasks).ViaField("finally"); err != nil {
436+
ts := PipelineTaskList(tasks).Names()
437+
fts := PipelineTaskList(finalTasks).Names()
438+
439+
if err := validateTaskResultReference(finalTasks, ts, fts).ViaField("finally"); err != nil {
437440
return err
438441
}
439442

@@ -444,14 +447,22 @@ func validateFinalTasks(finalTasks []PipelineTask) *apis.FieldError {
444447
return nil
445448
}
446449

447-
func validateTaskResultReferenceNotUsed(tasks []PipelineTask) *apis.FieldError {
448-
for idx, t := range tasks {
450+
func validateTaskResultReference(finalTasks []PipelineTask, ts, fts sets.String) *apis.FieldError {
451+
for idx, t := range finalTasks {
449452
for _, p := range t.Params {
450453
expressions, ok := GetVarSubstitutionExpressionsForParam(p)
451454
if ok {
452455
if LooksLikeContainsResultRefs(expressions) {
453-
return apis.ErrInvalidValue(fmt.Sprintf("no task result allowed under params,"+
454-
"final task param %s has set task result as its value", p.Name), "params").ViaIndex(idx)
456+
resultRefs := NewResultRefs(expressions)
457+
for _, resultRef := range resultRefs {
458+
if fts.Has(resultRef.PipelineTask) {
459+
return apis.ErrInvalidValue(fmt.Sprintf("invalid task result reference, "+
460+
"final task param %s has task result reference from a final task", p.Name), "params").ViaIndex(idx)
461+
} else if !ts.Has(resultRef.PipelineTask) {
462+
return apis.ErrInvalidValue(fmt.Sprintf("invalid task result reference, "+
463+
"final task param %s has task result reference from a task which is not defined in the pipeline", p.Name), "params").ViaIndex(idx)
464+
}
465+
}
455466
}
456467
}
457468
}

pkg/apis/pipeline/v1beta1/pipeline_validation_test.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,24 @@ func TestValidatePipelineWithFinalTasks_Success(t *testing.T) {
16141614
}},
16151615
},
16161616
},
1617+
}, {
1618+
name: "valid pipeline with final tasks referring to task results from a dag task",
1619+
p: &Pipeline{
1620+
ObjectMeta: metav1.ObjectMeta{Name: "pipeline"},
1621+
Spec: PipelineSpec{
1622+
Tasks: []PipelineTask{{
1623+
Name: "non-final-task",
1624+
TaskRef: &TaskRef{Name: "non-final-task"},
1625+
}},
1626+
Finally: []PipelineTask{{
1627+
Name: "final-task-1",
1628+
TaskRef: &TaskRef{Name: "final-task"},
1629+
Params: []Param{{
1630+
Name: "param1", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(tasks.non-final-task.results.output)"},
1631+
}},
1632+
}},
1633+
},
1634+
},
16171635
}}
16181636
for _, tt := range tests {
16191637
t.Run(tt.name, func(t *testing.T) {
@@ -1918,6 +1936,7 @@ func TestValidateTasksAndFinallySection_Failure(t *testing.T) {
19181936
func TestValidateFinalTasks_Failure(t *testing.T) {
19191937
tests := []struct {
19201938
name string
1939+
tasks []PipelineTask
19211940
finalTasks []PipelineTask
19221941
expectedError apis.FieldError
19231942
}{{
@@ -1960,16 +1979,32 @@ func TestValidateFinalTasks_Failure(t *testing.T) {
19601979
Paths: []string{"finally[0].resources.inputs[0]"},
19611980
},
19621981
}, {
1963-
name: "invalid pipeline with final tasks having reference to task results",
1982+
name: "invalid pipeline with final tasks having task results reference from a final task",
1983+
finalTasks: []PipelineTask{{
1984+
Name: "final-task-1",
1985+
TaskRef: &TaskRef{Name: "final-task"},
1986+
}, {
1987+
Name: "final-task-2",
1988+
TaskRef: &TaskRef{Name: "final-task"},
1989+
Params: []Param{{
1990+
Name: "param1", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(tasks.final-task-1.results.output)"},
1991+
}},
1992+
}},
1993+
expectedError: apis.FieldError{
1994+
Message: `invalid value: invalid task result reference, final task param param1 has task result reference from a final task`,
1995+
Paths: []string{"finally[1].params"},
1996+
},
1997+
}, {
1998+
name: "invalid pipeline with final tasks having task results reference from non existent dag task",
19641999
finalTasks: []PipelineTask{{
19652000
Name: "final-task",
19662001
TaskRef: &TaskRef{Name: "final-task"},
19672002
Params: []Param{{
1968-
Name: "param1", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(tasks.a-task.results.output)"},
2003+
Name: "param1", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(tasks.no-dag-task-1.results.output)"},
19692004
}},
19702005
}},
19712006
expectedError: apis.FieldError{
1972-
Message: `invalid value: no task result allowed under params,final task param param1 has set task result as its value`,
2007+
Message: `invalid value: invalid task result reference, final task param param1 has task result reference from a task which is not defined in the pipeline`,
19732008
Paths: []string{"finally[0].params"},
19742009
},
19752010
}, {
@@ -1990,7 +2025,7 @@ func TestValidateFinalTasks_Failure(t *testing.T) {
19902025
}}
19912026
for _, tt := range tests {
19922027
t.Run(tt.name, func(t *testing.T) {
1993-
err := validateFinalTasks(tt.finalTasks)
2028+
err := validateFinalTasks(tt.tasks, tt.finalTasks)
19942029
if err == nil {
19952030
t.Errorf("Pipeline.ValidateFinalTasks() did not return error for invalid pipeline")
19962031
}

pkg/reconciler/pipelinerun/pipelinerun.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,18 @@ func (c *Reconciler) runNextSchedulableTask(ctx context.Context, pr *v1beta1.Pip
581581
if len(fnextRprts) != 0 {
582582
// apply the runtime context just before creating taskRuns for final tasks in queue
583583
resources.ApplyPipelineTaskContext(fnextRprts, pipelineRunFacts.GetPipelineTaskStatus(ctx))
584-
nextRprts = append(nextRprts, fnextRprts...)
584+
585+
// Before creating TaskRun for scheduled final task, check if it's consuming a task result
586+
// Resolve and apply task result wherever applicable, report warning in case resolution fails
587+
for _, rprt := range fnextRprts {
588+
resolvedResultRefs, err := resources.ResolveResultRef(pipelineRunFacts.State, rprt)
589+
if err != nil {
590+
logger.Infof("Final task %q is not executed as it could not resolve task params for %q: %v", rprt.PipelineTask.Name, pr.Name, err)
591+
continue
592+
}
593+
resources.ApplyTaskResults(resources.PipelineRunState{rprt}, resolvedResultRefs)
594+
nextRprts = append(nextRprts, rprt)
595+
}
585596
}
586597

587598
for _, rprt := range nextRprts {

0 commit comments

Comments
 (0)