Skip to content

Commit de961a9

Browse files
committed
Add 2 variables context.pipelineTask.retries and context.task.retry-count
`context.pipelineTask.retries` exposes the total retries of a pipelineTask. `context.task.retry-count` exposes the current number of retry. The reason why the variable isn't `context.pipelineTask.retry-count` is that every task has a retry count, only pipelineTask has retries.
1 parent e43dc57 commit de961a9

File tree

8 files changed

+206
-1
lines changed

8 files changed

+206
-1
lines changed

docs/pipelines.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ weight: 3
2020
- [Guard `Task` execution using `Conditions`](#guard-task-execution-using-conditions)
2121
- [Configuring the failure timeout](#configuring-the-failure-timeout)
2222
- [Using variable substitution](#using-variable-substitution)
23+
- [Using the `retries` and `retry-count` variable substitutions](#using-the-retries-and-retry-count-variable-substitutions)
2324
- [Using `Results`](#using-results)
2425
- [Passing one Task's `Results` into the `Parameters` or `WhenExpressions` of another](#passing-one-tasks-results-into-the-parameters-or-whenexpressions-of-another)
2526
- [Emitting `Results` from a `Pipeline`](#emitting-results-from-a-pipeline)
@@ -548,6 +549,29 @@ performed by the Tekton Controller when a PipelineRun is executed.
548549
See the [complete list of variable substitutions for Pipelines](./variables.md#variables-available-in-a-pipeline)
549550
and the [list of fields that accept substitutions](./variables.md#fields-that-accept-variable-substitutions).
550551
552+
### Using the `retries` and `retry-count` variable substitutions
553+
554+
Tekton supports variable substitution for the [`retries`](#using-the-runafter-parameter)
555+
parameter of `PipelineTask`. Varibles like `context.pipelineTask.retries` and
556+
`context.task.retry-count` can be injected into the content of a `PipelineTask`.
557+
`context.pipelineTask.retries` will be replaced by `retries` of the `PipelineTask`, while
558+
`context.task.retry-count` will be replaced by current retry number of the `PipelineTask`.
559+
560+
```yaml
561+
steps:
562+
- image: ubuntu
563+
name: print-if-retries-exhausted
564+
script: |
565+
if [ "$(context.task.retry-count)" == "$(context.pipelineTask.retries)" ]
566+
then
567+
echo "This is the last retry."
568+
fi
569+
exit 1
570+
```
571+
572+
**Note:** Every `PipelineTask` can only access its own `retries` and`retry-count`. These
573+
values aren't accessible for other `PipelineTask`s.
574+
551575
## Using `Results`
552576

553577
Tasks can emit [`Results`](tasks.md#emitting-results) when they execute. A Pipeline can use these

docs/variables.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ For instructions on using variable substitutions see the relevant section of [th
4343
| `context.taskRun.namespace` | The namespace of the `TaskRun` that this `Task` is running in. |
4444
| `context.taskRun.uid` | The uid of the `TaskRun` that this `Task` is running in. |
4545
| `context.task.name` | The name of this `Task`. |
46+
| `context.task.retry-count` | The current retry number of this `Task`. |
47+
| `context.pipelineTask.retries` | The retries of this `PipelineTask`. |
4648

4749
### `PipelineResource` variables available in a `Task`
4850

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
apiVersion: tekton.dev/v1beta1
2+
kind: Pipeline
3+
metadata:
4+
name: retry-example
5+
spec:
6+
tasks:
7+
- name: retry-me
8+
retries: 5
9+
taskSpec:
10+
steps:
11+
- image: alpine:3.12.0
12+
script: |
13+
#!/usr/bin/env sh
14+
if [ "$(context.task.retry-count)" == "$(context.pipelineTask.retries)" ]; then
15+
echo "This is the last retry."
16+
exit 0;
17+
fi
18+
echo "The PipelineTask has retried $(context.task.retry-count) times."
19+
exit 1
20+
- name: hello-world
21+
runAfter:
22+
- retry-me
23+
taskSpec:
24+
steps:
25+
- image: busybox
26+
script: |
27+
echo "hello world"
28+
---
29+
apiVersion: tekton.dev/v1beta1
30+
kind: PipelineRun
31+
metadata:
32+
name: retry-example-run
33+
spec:
34+
pipelineRef:
35+
name: retry-example
36+
serviceAccountName: 'default'

pkg/apis/pipeline/v1beta1/task_validation.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ func validateTaskContextVariables(steps []Step) *apis.FieldError {
234234
)
235235
taskContextNames := sets.NewString().Insert(
236236
"name",
237+
"retry-count",
237238
)
238239
errs := validateVariables(steps, "context\\.taskRun", taskRunContextNames)
239240
return errs.Also(validateVariables(steps, "context\\.task", taskContextNames))

pkg/apis/pipeline/v1beta1/task_validation_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,19 @@ func TestTaskSpecValidate(t *testing.T) {
279279
hello "$(context.task.name)"`,
280280
}},
281281
},
282+
}, {
283+
name: "valid task retry count context",
284+
fields: fields{
285+
Steps: []v1beta1.Step{{
286+
Container: corev1.Container{
287+
Image: "my-image",
288+
Args: []string{"arg"},
289+
},
290+
Script: `
291+
#!/usr/bin/env bash
292+
retry count "$(context.task.retry-count)"`,
293+
}},
294+
},
282295
}, {
283296
name: "valid taskrun name context",
284297
fields: fields{

pkg/reconciler/pipelinerun/pipelinerun.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ func (c *Reconciler) createTaskRun(ctx context.Context, rprt *resources.Resolved
727727
// We pass the entire, original task ref because it may contain additional references like a Bundle url.
728728
tr.Spec.TaskRef = rprt.PipelineTask.TaskRef
729729
} else if rprt.ResolvedTaskResources.TaskSpec != nil {
730-
tr.Spec.TaskSpec = rprt.ResolvedTaskResources.TaskSpec
730+
tr.Spec.TaskSpec = tresources.ApplyPipelineTaskContexts(rprt.ResolvedTaskResources.TaskSpec, rprt.PipelineTask)
731731
}
732732

733733
var pipelinePVCWorkspaceName string

pkg/reconciler/taskrun/resources/apply.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package resources
1919
import (
2020
"fmt"
2121
"path/filepath"
22+
"strconv"
2223

2324
corev1 "k8s.io/api/core/v1"
2425
"k8s.io/apimachinery/pkg/util/sets"
@@ -97,6 +98,15 @@ func ApplyResources(spec *v1beta1.TaskSpec, resolvedResources map[string]v1beta1
9798
return ApplyReplacements(spec, replacements, map[string][]string{})
9899
}
99100

101+
// ApplyPipelineTaskContexts applies the substitution from $(context.pipelineTask.*) with the specified values.
102+
// Uses "" as a default if a value is not available.
103+
func ApplyPipelineTaskContexts(spec *v1beta1.TaskSpec, pt *v1beta1.PipelineTask) *v1beta1.TaskSpec {
104+
replacements := map[string]string{
105+
"context.pipelineTask.retries": strconv.Itoa(pt.Retries),
106+
}
107+
return ApplyReplacements(spec, replacements, map[string][]string{})
108+
}
109+
100110
// ApplyContexts applies the substitution from $(context.(taskRun|task).*) with the specified values.
101111
// Uses "" as a default if a value is not available.
102112
func ApplyContexts(spec *v1beta1.TaskSpec, rtr *ResolvedTaskResources, tr *v1beta1.TaskRun) *v1beta1.TaskSpec {
@@ -105,6 +115,7 @@ func ApplyContexts(spec *v1beta1.TaskSpec, rtr *ResolvedTaskResources, tr *v1bet
105115
"context.task.name": rtr.TaskName,
106116
"context.taskRun.namespace": tr.Namespace,
107117
"context.taskRun.uid": string(tr.ObjectMeta.UID),
118+
"context.task.retry-count": strconv.Itoa(len(tr.Status.RetriesStatus)),
108119
}
109120
return ApplyReplacements(spec, replacements, map[string][]string{})
110121
}

pkg/reconciler/taskrun/resources/apply_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import (
3030
"github.com/tektoncd/pipeline/test/names"
3131
corev1 "k8s.io/api/core/v1"
3232
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33+
"knative.dev/pkg/apis"
34+
duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
3335
)
3436

3537
var (
@@ -994,6 +996,66 @@ func TestContext(t *testing.T) {
994996
},
995997
}},
996998
},
999+
}, {
1000+
description: "context retry count replacement",
1001+
rtr: resources.ResolvedTaskResources{},
1002+
tr: v1beta1.TaskRun{
1003+
Status: v1beta1.TaskRunStatus{
1004+
TaskRunStatusFields: v1beta1.TaskRunStatusFields{
1005+
RetriesStatus: []v1beta1.TaskRunStatus{{
1006+
Status: duckv1beta1.Status{
1007+
Conditions: []apis.Condition{{
1008+
Type: apis.ConditionSucceeded,
1009+
Status: corev1.ConditionFalse,
1010+
}},
1011+
},
1012+
}, {
1013+
Status: duckv1beta1.Status{
1014+
Conditions: []apis.Condition{{
1015+
Type: apis.ConditionSucceeded,
1016+
Status: corev1.ConditionFalse,
1017+
}},
1018+
},
1019+
}},
1020+
},
1021+
},
1022+
},
1023+
spec: v1beta1.TaskSpec{
1024+
Steps: []v1beta1.Step{{
1025+
Container: corev1.Container{
1026+
Name: "ImageName",
1027+
Image: "$(context.task.retry-count)-1",
1028+
},
1029+
}},
1030+
},
1031+
want: v1beta1.TaskSpec{
1032+
Steps: []v1beta1.Step{{
1033+
Container: corev1.Container{
1034+
Name: "ImageName",
1035+
Image: "2-1",
1036+
},
1037+
}},
1038+
},
1039+
}, {
1040+
description: "context retry count replacement with task that never retries",
1041+
rtr: resources.ResolvedTaskResources{},
1042+
tr: v1beta1.TaskRun{},
1043+
spec: v1beta1.TaskSpec{
1044+
Steps: []v1beta1.Step{{
1045+
Container: corev1.Container{
1046+
Name: "ImageName",
1047+
Image: "$(context.task.retry-count)-1",
1048+
},
1049+
}},
1050+
},
1051+
want: v1beta1.TaskSpec{
1052+
Steps: []v1beta1.Step{{
1053+
Container: corev1.Container{
1054+
Name: "ImageName",
1055+
Image: "0-1",
1056+
},
1057+
}},
1058+
},
9971059
}} {
9981060
t.Run(tc.description, func(t *testing.T) {
9991061
got := resources.ApplyContexts(&tc.spec, &tc.rtr, &tc.tr)
@@ -1004,6 +1066,62 @@ func TestContext(t *testing.T) {
10041066
}
10051067
}
10061068

1069+
func TestApplyPipelineTaskContexts(t *testing.T) {
1070+
for _, tc := range []struct {
1071+
description string
1072+
pt v1beta1.PipelineTask
1073+
spec v1beta1.TaskSpec
1074+
want v1beta1.TaskSpec
1075+
}{{
1076+
description: "context retries replacement",
1077+
pt: v1beta1.PipelineTask{
1078+
Retries: 5,
1079+
},
1080+
spec: v1beta1.TaskSpec{
1081+
Steps: []v1beta1.Step{{
1082+
Container: corev1.Container{
1083+
Name: "ImageName",
1084+
Image: "$(context.pipelineTask.retries)",
1085+
},
1086+
}},
1087+
},
1088+
want: v1beta1.TaskSpec{
1089+
Steps: []v1beta1.Step{{
1090+
Container: corev1.Container{
1091+
Name: "ImageName",
1092+
Image: "5",
1093+
},
1094+
}},
1095+
},
1096+
}, {
1097+
description: "context retries replacement with no defined retries",
1098+
pt: v1beta1.PipelineTask{},
1099+
spec: v1beta1.TaskSpec{
1100+
Steps: []v1beta1.Step{{
1101+
Container: corev1.Container{
1102+
Name: "ImageName",
1103+
Image: "$(context.pipelineTask.retries)",
1104+
},
1105+
}},
1106+
},
1107+
want: v1beta1.TaskSpec{
1108+
Steps: []v1beta1.Step{{
1109+
Container: corev1.Container{
1110+
Name: "ImageName",
1111+
Image: "0",
1112+
},
1113+
}},
1114+
},
1115+
}} {
1116+
t.Run(tc.description, func(t *testing.T) {
1117+
got := resources.ApplyPipelineTaskContexts(&tc.spec, &tc.pt)
1118+
if d := cmp.Diff(&tc.want, got); d != "" {
1119+
t.Errorf(diff.PrintWantGot(d))
1120+
}
1121+
})
1122+
}
1123+
}
1124+
10071125
func TestTaskResults(t *testing.T) {
10081126
names.TestingSeed()
10091127
ts := &v1beta1.TaskSpec{

0 commit comments

Comments
 (0)