Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ weight: 3
- [Guard `Task` execution using `Conditions`](#guard-task-execution-using-conditions)
- [Configuring the failure timeout](#configuring-the-failure-timeout)
- [Using variable substitution](#using-variable-substitution)
- [Using the `retries` and `retry-count` variable substitutions](#using-the-retries-and-retry-count-variable-substitutions)
- [Using `Results`](#using-results)
- [Passing one Task's `Results` into the `Parameters` or `WhenExpressions` of another](#passing-one-tasks-results-into-the-parameters-or-whenexpressions-of-another)
- [Emitting `Results` from a `Pipeline`](#emitting-results-from-a-pipeline)
Expand Down Expand Up @@ -550,6 +551,35 @@ performed by the Tekton Controller when a PipelineRun is executed.
See the [complete list of variable substitutions for Pipelines](./variables.md#variables-available-in-a-pipeline)
and the [list of fields that accept substitutions](./variables.md#fields-that-accept-variable-substitutions).

### Using the `retries` and `retry-count` variable substitutions

Tekton supports variable substitution for the [`retries`](#using-the-retries-parameter)
parameter of `PipelineTask`. Variables like `context.pipelineTask.retries` and
`context.task.retry-count` can be added to the parameters of a `PipelineTask`.
`context.pipelineTask.retries` will be replaced by `retries` of the `PipelineTask`, while
`context.task.retry-count` will be replaced by current retry number of the `PipelineTask`.

```yaml
params:
- name: pipelineTask-retries
value: "$(context.pipelineTask.retries)"
taskSpec:
params:
- name: pipelineTask-retries
steps:
- image: ubuntu
name: print-if-retries-exhausted
script: |
if [ "$(context.task.retry-count)" == "$(params.pipelineTask-retries)" ]
then
echo "This is the last retry."
fi
exit 1
```

**Note:** Every `PipelineTask` can only access its own `retries` and `retry-count`. These
values aren't accessible for other `PipelineTask`s.

## Using `Results`

Tasks can emit [`Results`](tasks.md#emitting-results) when they execute. A Pipeline can use these
Expand Down
2 changes: 2 additions & 0 deletions docs/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ For instructions on using variable substitutions see the relevant section of [th
| `context.pipeline.name` | The name of this `Pipeline` . |
| `tasks.<pipelineTaskName>.status` | The execution status of the specified `pipelineTask`, only available in `finally` tasks. The execution status can be set to any one of the values (`Succeeded`, `Failed`, or `None`) described [here](pipelines.md#using-execution-status-of-pipelinetask)|
| `tasks.status` | An aggregate status of all the `pipelineTasks` under the `tasks` section (excluding the `finally` section). This variable is only available in the `finally` tasks and can have any one of the values (`Succeeded`, `Failed`, `Completed`, or `None`) described [here](pipelines.md#using-aggregate-execution-status-of-all-tasks). |
| `context.pipelineTask.retries` | The retries of this `PipelineTask`. |

## Variables available in a `Task`

Expand All @@ -43,6 +44,7 @@ For instructions on using variable substitutions see the relevant section of [th
| `context.taskRun.namespace` | The namespace of the `TaskRun` that this `Task` is running in. |
| `context.taskRun.uid` | The uid of the `TaskRun` that this `Task` is running in. |
| `context.task.name` | The name of this `Task`. |
| `context.task.retry-count` | The current retry number of this `Task`. |

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: retry-example-
spec:
serviceAccountName: 'default'
pipelineSpec:
tasks:
- name: retry-me
retries: 5
params:
- name: pipelineTask-retries
value: "$(context.pipelineTask.retries)"
- name: pipelineTask-retry-count
value: "$(context.task.retry-count)"
taskSpec:
params:
- name: pipelineTask-retries
- name: pipelineTask-retry-count
steps:
- image: alpine:3.12.0
script: |
#!/usr/bin/env sh
if [ "$(params.pipelineTask-retry-count)" == "$(params.pipelineTask-retries)" ]; then
echo "This is the last retry."
exit 0;
fi
echo "The PipelineTask has retried $(context.task.retry-count) times."
exit 1
- name: hello-world
runAfter:
- retry-me
taskSpec:
steps:
- image: busybox
script: |
echo "hello world"
11 changes: 10 additions & 1 deletion examples/v1beta1/pipelineruns/using_context_variables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ spec:
pipelineSpec:
tasks:
- name: task1
retries: 7
params:
- name: pipeline-uid
value: "$(context.pipelineRun.uid)"
- name: pipeline-name
value: "$(context.pipeline.name)"
- name: pipelineRun-name
value: "$(context.pipelineRun.name)"
- name: pipelineTask-retries
value: "$(context.pipelineTask.retries)"
taskSpec:
params:
- name: pipeline-uid
- name: pipeline-name
- name: pipelineRun-name
- name: pipelineTask-retries
steps:
- image: ubuntu
name: print-uid
Expand All @@ -31,4 +35,9 @@ spec:
echo "Task name: $(context.task.name)"
echo "TaskRun name: $(context.taskRun.name)"
echo "Pipeline name from params: $(params.pipeline-name)"
echo "PipelineRun name from params: $(params.pipelineRun-name)"
echo "PipelineRun name from params: $(params.pipelineRun-name)"
- image: ubuntu
name: print-retries
script: |
echo "PipelineTask retries from params: $(params.pipelineTask-retries)"
echo "PipelineTask current retry count: $(context.task.retry-count)"
1 change: 1 addition & 0 deletions pkg/apis/pipeline/v1beta1/task_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ func validateTaskContextVariables(steps []Step) *apis.FieldError {
)
taskContextNames := sets.NewString().Insert(
"name",
"retry-count",
)
errs := validateVariables(steps, "context\\.taskRun", taskRunContextNames)
return errs.Also(validateVariables(steps, "context\\.task", taskContextNames))
Expand Down
13 changes: 13 additions & 0 deletions pkg/apis/pipeline/v1beta1/task_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,19 @@ func TestTaskSpecValidate(t *testing.T) {
hello "$(context.task.name)"`,
}},
},
}, {
name: "valid task retry count context",
fields: fields{
Steps: []v1beta1.Step{{
Container: corev1.Container{
Image: "my-image",
Args: []string{"arg"},
},
Script: `
#!/usr/bin/env bash
retry count "$(context.task.retry-count)"`,
}},
},
}, {
name: "valid taskrun name context",
fields: fields{
Expand Down
3 changes: 2 additions & 1 deletion pkg/reconciler/pipelinerun/pipelinerun.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ func (c *Reconciler) runNextSchedulableTask(ctx context.Context, pr *v1beta1.Pip
fnextRprts := pipelineRunFacts.GetFinalTasks()
if len(fnextRprts) != 0 {
// apply the runtime context just before creating taskRuns for final tasks in queue
resources.ApplyPipelineTaskContext(fnextRprts, pipelineRunFacts.GetPipelineTaskStatus())
resources.ApplyPipelineTaskStateContext(fnextRprts, pipelineRunFacts.GetPipelineTaskStatus())

// Before creating TaskRun for scheduled final task, check if it's consuming a task result
// Resolve and apply task result wherever applicable, report warning in case resolution fails
Expand Down Expand Up @@ -707,6 +707,7 @@ func (c *Reconciler) createTaskRun(ctx context.Context, rprt *resources.Resolved
return c.PipelineClientSet.TektonV1beta1().TaskRuns(pr.Namespace).UpdateStatus(ctx, tr, metav1.UpdateOptions{})
}

rprt.PipelineTask = resources.ApplyPipelineTaskContexts(rprt.PipelineTask)
Copy link
Member

@pritidesai pritidesai Jun 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am happy to merge this as is, but setting the context variable here will only make it available to pipelineTask and will not be able to custom task. Since the retries is constant and does not change with the pipeline execution, we can set this while resolving a pipeline task.

taskRunSpec := pr.GetTaskRunSpec(rprt.PipelineTask.Name)
tr = &v1beta1.TaskRun{
ObjectMeta: metav1.ObjectMeta{
Expand Down
9 changes: 6 additions & 3 deletions pkg/reconciler/pipelinerun/pipelinerun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ func TestReconcile(t *testing.T) {
templatedParam := tb.PipelineTaskParam("templatedparam", "$(inputs.workspace.$(params.rev-param))")
contextRunParam := tb.PipelineTaskParam("contextRunParam", "$(context.pipelineRun.name)")
contextPipelineParam := tb.PipelineTaskParam("contextPipelineParam", "$(context.pipeline.name)")
retriesParam := tb.PipelineTaskParam("contextRetriesParam", "$(context.pipelineTask.retries)")
const pipelineName = "test-pipeline"
ps := []*v1beta1.Pipeline{
tb.Pipeline(pipelineName,
Expand All @@ -296,18 +297,19 @@ func TestReconcile(t *testing.T) {
tb.PipelineParamSpec("bar", v1beta1.ParamTypeString),
// unit-test-3 uses runAfter to indicate it should run last
tb.PipelineTask("unit-test-3", "unit-test-task",
funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam,
funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam, retriesParam,
tb.RunAfter("unit-test-2"),
tb.PipelineTaskInputResource("workspace", "git-repo"),
tb.PipelineTaskOutputResource("image-to-use", "best-image"),
tb.PipelineTaskOutputResource("workspace", "git-repo"),
),
// unit-test-1 can run right away because it has no dependencies
tb.PipelineTask("unit-test-1", "unit-test-task",
funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam,
funParam, moreFunParam, templatedParam, contextRunParam, contextPipelineParam, retriesParam,
tb.PipelineTaskInputResource("workspace", "git-repo"),
tb.PipelineTaskOutputResource("image-to-use", "best-image"),
tb.PipelineTaskOutputResource("workspace", "git-repo"),
tb.Retries(5),
),
// unit-test-2 uses `from` to indicate it should run after `unit-test-1`
tb.PipelineTask("unit-test-2", "unit-test-followup-task",
Expand All @@ -327,7 +329,7 @@ func TestReconcile(t *testing.T) {
ts := []*v1beta1.Task{
tb.Task("unit-test-task", tb.TaskSpec(
tb.TaskParam("foo", v1beta1.ParamTypeString), tb.TaskParam("bar", v1beta1.ParamTypeString), tb.TaskParam("templatedparam", v1beta1.ParamTypeString),
tb.TaskParam("contextRunParam", v1beta1.ParamTypeString), tb.TaskParam("contextPipelineParam", v1beta1.ParamTypeString),
tb.TaskParam("contextRunParam", v1beta1.ParamTypeString), tb.TaskParam("contextPipelineParam", v1beta1.ParamTypeString), tb.TaskParam("contextRetriesParam", v1beta1.ParamTypeString),
tb.TaskResources(
tb.TaskResourcesInput("workspace", resourcev1alpha1.PipelineResourceTypeGit),
tb.TaskResourcesOutput("image-to-use", resourcev1alpha1.PipelineResourceTypeImage),
Expand Down Expand Up @@ -404,6 +406,7 @@ func TestReconcile(t *testing.T) {
tb.TaskRunParam("templatedparam", "$(inputs.workspace.revision)"),
tb.TaskRunParam("contextRunParam", pipelineRunName),
tb.TaskRunParam("contextPipelineParam", pipelineName),
tb.TaskRunParam("contextRetriesParam", "5"),
tb.TaskRunResources(
tb.TaskRunResourcesInput("workspace", tb.TaskResourceBindingRef("some-repo")),
tb.TaskRunResourcesOutput("image-to-use",
Expand Down
16 changes: 14 additions & 2 deletions pkg/reconciler/pipelinerun/resources/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package resources

import (
"fmt"
"strconv"
"strings"

"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
Expand Down Expand Up @@ -68,6 +69,17 @@ func ApplyContexts(spec *v1beta1.PipelineSpec, pipelineName string, pr *v1beta1.
return ApplyReplacements(spec, replacements, map[string][]string{})
}

// ApplyPipelineTaskContexts applies the substitution from $(context.pipelineTask.*) with the specified values.
// Uses "0" as a default if a value is not available.
func ApplyPipelineTaskContexts(pt *v1beta1.PipelineTask) *v1beta1.PipelineTask {
pt = pt.DeepCopy()
replacements := map[string]string{
"context.pipelineTask.retries": strconv.Itoa(pt.Retries),
}
pt.Params = replaceParamValues(pt.Params, replacements, map[string][]string{})
return pt
}

// ApplyTaskResults applies the ResolvedResultRef to each PipelineTask.Params and Pipeline.WhenExpressions in targets
func ApplyTaskResults(targets PipelineRunState, resolvedResultRefs ResolvedResultRefs) {
stringReplacements := resolvedResultRefs.getStringReplacements()
Expand All @@ -87,8 +99,8 @@ func ApplyTaskResults(targets PipelineRunState, resolvedResultRefs ResolvedResul
}
}

// ApplyPipelineTaskContext replaces context variables referring to execution status with the specified status
func ApplyPipelineTaskContext(state PipelineRunState, replacements map[string]string) {
// ApplyPipelineTaskStateContext replaces context variables referring to execution status with the specified status
func ApplyPipelineTaskStateContext(state PipelineRunState, replacements map[string]string) {
for _, resolvedPipelineRunTask := range state {
if resolvedPipelineRunTask.PipelineTask != nil {
pipelineTask := resolvedPipelineRunTask.PipelineTask.DeepCopy()
Expand Down
47 changes: 46 additions & 1 deletion pkg/reconciler/pipelinerun/resources/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,51 @@ func TestContext(t *testing.T) {
}
}

func TestApplyPipelineTaskContexts(t *testing.T) {
for _, tc := range []struct {
description string
pt v1beta1.PipelineTask
want v1beta1.PipelineTask
}{{
description: "context retries replacement",
pt: v1beta1.PipelineTask{
Retries: 5,
Params: []v1beta1.Param{{
Name: "retries",
Value: *v1beta1.NewArrayOrString("$(context.pipelineTask.retries)"),
}},
},
want: v1beta1.PipelineTask{
Retries: 5,
Params: []v1beta1.Param{{
Name: "retries",
Value: *v1beta1.NewArrayOrString("5"),
}},
},
}, {
description: "context retries replacement with no defined retries",
pt: v1beta1.PipelineTask{
Params: []v1beta1.Param{{
Name: "retries",
Value: *v1beta1.NewArrayOrString("$(context.pipelineTask.retries)"),
}},
},
want: v1beta1.PipelineTask{
Params: []v1beta1.Param{{
Name: "retries",
Value: *v1beta1.NewArrayOrString("0"),
}},
},
}} {
t.Run(tc.description, func(t *testing.T) {
got := ApplyPipelineTaskContexts(&tc.pt)
if d := cmp.Diff(&tc.want, got); d != "" {
t.Errorf(diff.PrintWantGot(d))
}
})
}
}

func TestApplyWorkspaces(t *testing.T) {
for _, tc := range []struct {
description string
Expand Down Expand Up @@ -1107,7 +1152,7 @@ func TestApplyTaskRunContext(t *testing.T) {
}},
},
}}
ApplyPipelineTaskContext(state, r)
ApplyPipelineTaskStateContext(state, r)
if d := cmp.Diff(expectedState, state); d != "" {
t.Fatalf("ApplyTaskRunContext() %s", diff.PrintWantGot(d))
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/reconciler/taskrun/resources/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"
"path/filepath"
"strconv"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
Expand Down Expand Up @@ -107,6 +108,7 @@ func ApplyContexts(spec *v1beta1.TaskSpec, rtr *ResolvedTaskResources, tr *v1bet
"context.task.name": rtr.TaskName,
"context.taskRun.namespace": tr.Namespace,
"context.taskRun.uid": string(tr.ObjectMeta.UID),
"context.task.retry-count": strconv.Itoa(len(tr.Status.RetriesStatus)),
}
return ApplyReplacements(spec, replacements, map[string][]string{})
}
Expand Down
Loading