Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: override task timeouts in pipelineruns #8636

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions config/300-crds/300-pipelinerun.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4474,6 +4474,11 @@ spec:
description: The name of the Step to override.
type: string
x-kubernetes-list-type: atomic
timeout:
description: |-
Time after which the TaskRun times out.
Refer Go's ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration
type: string
x-kubernetes-list-type: atomic
taskRunTemplate:
description: TaskRunTemplate represent template of taskrun
Expand Down
15 changes: 15 additions & 0 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3322,6 +3322,21 @@ Kubernetes core/v1.ResourceRequirements
<p>Compute resources to use for this TaskRun</p>
</td>
</tr>
<tr>
<td>
<code>timeout</code><br/>
<em>
<a href="https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
Kubernetes meta/v1.Duration
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Time after which the TaskRun times out.
Refer Go&rsquo;s ParseDuration documentation for expected format: <a href="https://golang.org/pkg/time/#ParseDuration">https://golang.org/pkg/time/#ParseDuration</a></p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1.PipelineTaskRunTemplate">PipelineTaskRunTemplate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
name: pipeline-with-timeouts
spec:
tasks:
- name: task-with-timeout
timeout: "1h0m0s"
taskSpec:
steps:
- name: sleep
image: ubuntu
script: |
echo "Starting task..."
sleep 30
echo "Task completed!"

- name: another-task-with-timeout
timeout: "30m0s"
runAfter:
- task-with-timeout
taskSpec:
steps:
- name: sleep
image: ubuntu
script: |
echo "Starting another task..."
sleep 30
echo "Another task completed!"

---
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: pipeline-timeout-override-run
spec:
pipelineRef:
name: pipeline-with-timeouts
timeouts:
pipeline: "3h0m0s" # overall pipeline timeout
taskRunSpecs:
- pipelineTaskName: task-with-timeout
timeout: "2h0m0s" # override timeout to 2 hours
- pipelineTaskName: another-task-with-timeout
timeout: "1h0m0s" # override timeout to 1 hour
8 changes: 7 additions & 1 deletion pkg/apis/pipeline/v1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/apis/pipeline/v1/pipeline_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1370,7 +1370,7 @@ func TestPipelineSpec_Validate_Failure(t *testing.T) {
if err == nil {
t.Errorf("PipelineSpec.Validate() did not return error for invalid pipelineSpec")
}
if d := cmp.Diff(tt.expectedError.Error(), err.Error(), cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" {
if d := cmp.Diff(tt.expectedError.Error(), err.Error()); d != "" {
t.Errorf("PipelineSpec.Validate() errors diff %s", diff.PrintWantGot(d))
}
})
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/pipeline/v1/pipelinerun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,11 @@ type PipelineTaskRunSpec struct {

// Compute resources to use for this TaskRun
ComputeResources *corev1.ResourceRequirements `json:"computeResources,omitempty"`

// Time after which the TaskRun times out.
// Refer Go's ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration
// +optional
Timeout *metav1.Duration `json:"timeout,omitempty"`
}

// GetTaskRunSpec returns the task specific spec for a given
Expand All @@ -678,6 +683,7 @@ func (pr *PipelineRun) GetTaskRunSpec(pipelineTaskName string) PipelineTaskRunSp
s.SidecarSpecs = task.SidecarSpecs
s.Metadata = task.Metadata
s.ComputeResources = task.ComputeResources
s.Timeout = task.Timeout
}
}
return s
Expand Down
22 changes: 22 additions & 0 deletions pkg/apis/pipeline/v1/pipelinerun_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ func (ps *PipelineRunSpec) Validate(ctx context.Context) (errs *apis.FieldError)
// Validate propagated parameters
errs = errs.Also(ps.validateInlineParameters(ctx))

// Validate task run specs first
for idx, trs := range ps.TaskRunSpecs {
if trs.Timeout != nil && trs.Timeout.Duration < 0 {
errs = errs.Also(apis.ErrInvalidValue(trs.Timeout.Duration.String()+" should be >= 0", "taskRunSpecs["+trs.PipelineTaskName+"].timeout"))
}
errs = errs.Also(validateTaskRunSpec(ctx, trs).ViaIndex(idx).ViaField("taskRunSpecs"))
}

if ps.Timeouts != nil {
// tasks timeout should be a valid duration of at least 0.
errs = errs.Also(validateTimeoutDuration("tasks", ps.Timeouts.Tasks))
Expand All @@ -103,6 +111,20 @@ func (ps *PipelineRunSpec) Validate(ctx context.Context) (errs *apis.FieldError)
defaultTimeout := time.Duration(config.FromContextOrDefaults(ctx).Defaults.DefaultTimeoutMinutes)
errs = errs.Also(ps.validatePipelineTimeout(defaultTimeout, "should be <= default timeout duration"))
}

if len(ps.TaskRunSpecs) > 0 {
pipelineTimeout := ps.Timeouts.Pipeline
if pipelineTimeout == nil {
pipelineTimeout = &metav1.Duration{Duration: time.Duration(config.FromContextOrDefaults(ctx).Defaults.DefaultTimeoutMinutes) * time.Minute}
}
for _, taskRunSpec := range ps.TaskRunSpecs {
if taskRunSpec.Timeout != nil {
if taskRunSpec.Timeout.Duration > pipelineTimeout.Duration && pipelineTimeout.Duration != config.NoTimeoutDuration {
errs = errs.Also(apis.ErrInvalidValue(taskRunSpec.Timeout.Duration.String()+" should be <= pipeline duration", "taskRunSpecs["+taskRunSpec.PipelineTaskName+"].timeout"))
}
}
}
}
}

errs = errs.Also(validateSpecStatus(ps.Status))
Expand Down
61 changes: 60 additions & 1 deletion pkg/apis/pipeline/v1/pipelinerun_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,28 @@ func TestPipelineRun_Validate(t *testing.T) {
},
},
wc: cfgtesting.EnableAlphaAPIFields,
}, {
name: "valid task-specific timeouts",
pr: v1.PipelineRun{
ObjectMeta: metav1.ObjectMeta{
Name: "pipelinelinename",
},
Spec: v1.PipelineRunSpec{
PipelineRef: &v1.PipelineRef{
Name: "prname",
},
Timeouts: &v1.TimeoutFields{
Pipeline: &metav1.Duration{Duration: 1 * time.Hour},
},
TaskRunSpecs: []v1.PipelineTaskRunSpec{{
PipelineTaskName: "task1",
Timeout: &metav1.Duration{Duration: 30 * time.Minute},
}, {
PipelineTaskName: "task2",
Timeout: &metav1.Duration{Duration: 45 * time.Minute},
}},
},
},
}}

for _, ts := range tests {
Expand Down Expand Up @@ -1104,7 +1126,7 @@ func TestPipelineRunSpec_Invalidate(t *testing.T) {
ctx = ps.withContext(ctx)
}
err := ps.spec.Validate(ctx)
if d := cmp.Diff(ps.wantErr.Error(), err.Error()); d != "" {
if d := cmp.Diff(ps.wantErr.Error(), err.Error(), cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" {
t.Error(diff.PrintWantGot(d))
}
})
Expand Down Expand Up @@ -1202,6 +1224,23 @@ func TestPipelineRun_InvalidTimeouts(t *testing.T) {
},
},
want: apis.ErrInvalidValue("-48h0m0s should be >= 0", "spec.timeouts.pipeline"),
}, {
name: "negative task-specific timeout",
pr: v1.PipelineRun{
ObjectMeta: metav1.ObjectMeta{
Name: "pipelinelinename",
},
Spec: v1.PipelineRunSpec{
PipelineRef: &v1.PipelineRef{
Name: "prname",
},
TaskRunSpecs: []v1.PipelineTaskRunSpec{{
PipelineTaskName: "task1",
Timeout: &metav1.Duration{Duration: -1 * time.Hour},
}},
},
},
want: apis.ErrInvalidValue("-1h0m0s should be >= 0", "spec.taskRunSpecs[task1].timeout"),
}, {
name: "negative pipeline tasks Timeout",
pr: v1.PipelineRun{
Expand Down Expand Up @@ -1352,6 +1391,26 @@ func TestPipelineRun_InvalidTimeouts(t *testing.T) {
},
},
want: apis.ErrInvalidValue(`0s (no timeout) should be <= pipeline duration`, "spec.timeouts.finally"),
}, {
name: "task-specific timeout exceeds pipeline timeout",
pr: v1.PipelineRun{
ObjectMeta: metav1.ObjectMeta{
Name: "pipelinelinename",
},
Spec: v1.PipelineRunSpec{
PipelineRef: &v1.PipelineRef{
Name: "prname",
},
Timeouts: &v1.TimeoutFields{
Pipeline: &metav1.Duration{Duration: 1 * time.Hour},
},
TaskRunSpecs: []v1.PipelineTaskRunSpec{{
PipelineTaskName: "task1",
Timeout: &metav1.Duration{Duration: 2 * time.Hour},
}},
},
},
want: apis.ErrInvalidValue("2h0m0s should be <= pipeline duration", "spec.taskRunSpecs[task1].timeout"),
}}

for _, tc := range tests {
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,10 @@
"$ref": "#/definitions/v1.TaskRunStepSpec"
},
"x-kubernetes-list-type": "atomic"
},
"timeout": {
"description": "Time after which the TaskRun times out. Refer Go's ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration",
"$ref": "#/definitions/v1.Duration"
}
}
},
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/pipeline/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading