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

Add a Timeouts optional field to pipelinerun #3843

Merged
merged 3 commits into from
Jun 7, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Puts the new timeouts field under feature gates
  • Loading branch information
souleb committed Jun 4, 2021
commit a965afd067808e8e6c0a27fedffd8dd4a571eef0
10 changes: 6 additions & 4 deletions examples/v1beta1/pipelineruns/no-ci/pipeline-timeout.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ spec:
- sleep 90s
args:
- "$(inputs.params.MESSAGE)"
---

---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
Expand Down Expand Up @@ -54,8 +54,7 @@ spec:
- name: NIGHT_GREETINGS
value: "Good Night, Bob!"

---

---
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
Expand All @@ -64,7 +63,10 @@ spec:
# 1 hour and half timeout for the pipeline
# 1 hour and fifteen minutes for the pipeline tasks
# 15 minutes for the finally tasks
timeouts:
#
# This field requires enable-api-fields: "alpha" flag
# Check https://github.com/tektoncd/pipeline/blob/main/config/config-feature-flags.yaml
timeouts:
pipeline: 1h30m
tasks: 1h15m
pipelineSpec:
Expand Down
18 changes: 9 additions & 9 deletions internal/builder/v1beta1/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,32 +529,32 @@ func PipelineRunNilTimeout(prs *v1beta1.PipelineRunSpec) {
prs.Timeout = nil
}

func initTimeouts(prs *v1beta1.PipelineRunSpec) {
if prs.Timeouts == nil {
prs.Timeouts = &v1beta1.TimeoutFields{}
}
}

// PipelineRunTasksTimeout sets the timeout to the PipelineRunSpec.
func PipelineRunTasksTimeout(duration time.Duration) PipelineRunSpecOp {
return func(prs *v1beta1.PipelineRunSpec) {
if prs.Timeouts == nil {
prs.Timeouts = &v1beta1.TimeoutFields{}
}
initTimeouts(prs)
prs.Timeouts.Tasks = &metav1.Duration{Duration: duration}
}
}

// PipelineRunFinallyTimeout sets the timeout to the PipelineRunSpec.
func PipelineRunFinallyTimeout(duration time.Duration) PipelineRunSpecOp {
return func(prs *v1beta1.PipelineRunSpec) {
if prs.Timeouts == nil {
prs.Timeouts = &v1beta1.TimeoutFields{}
}
initTimeouts(prs)
prs.Timeouts.Finally = &metav1.Duration{Duration: duration}
}
}

// PipelineRunPipelineTimeout sets the timeout to the PipelineRunSpec.
func PipelineRunPipelineTimeout(duration time.Duration) PipelineRunSpecOp {
return func(prs *v1beta1.PipelineRunSpec) {
if prs.Timeouts == nil {
prs.Timeouts = &v1beta1.TimeoutFields{}
}
initTimeouts(prs)
prs.Timeouts.Pipeline = &metav1.Duration{Duration: duration}
}
}
Expand Down
41 changes: 40 additions & 1 deletion pkg/apis/pipeline/v1beta1/openapi_generated.go

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

10 changes: 8 additions & 2 deletions pkg/apis/pipeline/v1beta1/pipelinerun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ type PipelineRunSpec struct {
// Used for cancelling a pipelinerun (and maybe more later on)
// +optional
Status PipelineRunSpecStatus `json:"status,omitempty"`
// This is an alpha field. You must set the "enable-api-fields" feature flag to "alpha"
Copy link

Choose a reason for hiding this comment

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

👍

// for this field to be supported.
//
// Time after which the Pipeline times out.
// Currently three keys are accepted in the map
// pipeline, tasks and finally
Expand All @@ -201,9 +204,12 @@ type PipelineRunSpec struct {
}

type TimeoutFields struct {
// Pipeline sets the maximum allowed duration for execution of the entire pipeline. The sum of individual timeouts for tasks and finally must not exceed this value.
Pipeline *metav1.Duration `json:"pipeline,omitempty"`
Tasks *metav1.Duration `json:"tasks,omitempty"`
Finally *metav1.Duration `json:"finally,omitempty"`
// Tasks sets the maximum allowed duration of this pipeline's tasks
Tasks *metav1.Duration `json:"tasks,omitempty"`
// Finally sets the maximum allowed duration of this pipeline's finally
Finally *metav1.Duration `json:"finally,omitempty"`
}

// PipelineRunSpecStatus defines the pipelinerun spec status the user can provide
Expand Down
39 changes: 22 additions & 17 deletions pkg/apis/pipeline/v1beta1/pipelinerun_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/google/go-containerregistry/pkg/name"
"github.com/tektoncd/pipeline/pkg/apis/config"
"github.com/tektoncd/pipeline/pkg/apis/validate"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"knative.dev/pkg/apis"
)

Expand Down Expand Up @@ -87,34 +88,30 @@ func (ps *PipelineRunSpec) Validate(ctx context.Context) (errs *apis.FieldError)
}
}

// This is an alpha feature and will fail validation if it's used in a pipelinerun spec
// when the enable-api-fields feature gate is anything but "alpha".
if ps.Timeouts != nil {
if ps.Timeout != nil {
// can't have both at the same time
errs = errs.Also(apis.ErrDisallowedFields("timeout", "timeouts"))
}

errs = errs.Also(ValidateEnabledAPIFields(ctx, "timeouts", config.AlphaAPIFields))

// tasks timeout should be a valid duration of at least 0.
if ps.Timeouts.Tasks != nil && ps.Timeouts.Tasks.Duration < 0 {
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s should be >= 0", ps.Timeouts.Tasks.Duration.String()), "timeouts.tasks"))
}
errs = errs.Also(validateTimeoutDuration("tasks", ps.Timeouts.Tasks))

// finally timeout should be a valid duration of at least 0.
if ps.Timeouts.Finally != nil && ps.Timeouts.Finally.Duration < 0 {
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s should be >= 0", ps.Timeouts.Finally.Duration.String()), "timeouts.finally"))
}
errs = errs.Also(validateTimeoutDuration("finally", ps.Timeouts.Finally))

if ps.Timeouts.Pipeline != nil {
// pipeline timeout should be a valid duration of at least 0.
if ps.Timeouts.Pipeline.Duration < 0 {
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s should be >= 0", ps.Timeouts.Pipeline.Duration.String()), "timeouts.pipeline"))
}
// pipeline timeout should be a valid duration of at least 0.
errs = errs.Also(validateTimeoutDuration("pipeline", ps.Timeouts.Pipeline))

if ps.Timeouts.Pipeline != nil {
errs = errs.Also(ps.validatePipelineTimeout(ps.Timeouts.Pipeline.Duration, "should be <= pipeline duration"))

} else {
defaultTimeout := time.Duration(config.FromContextOrDefaults(ctx).Defaults.DefaultTimeoutMinutes)
errs = errs.Also(ps.validatePipelineTimeout(defaultTimeout, "should be <= default timeout duration"))

}
}

Expand All @@ -138,19 +135,27 @@ func (ps *PipelineRunSpec) Validate(ctx context.Context) (errs *apis.FieldError)
return errs
}

func validateTimeoutDuration(field string, d *metav1.Duration) (errs *apis.FieldError) {
if d != nil && d.Duration < 0 {
fieldPath := fmt.Sprintf("timeouts.%s", field)
return errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s should be >= 0", d.Duration.String()), fieldPath))
}
return nil
}

func (ps *PipelineRunSpec) validatePipelineTimeout(timeout time.Duration, errorMsg string) (errs *apis.FieldError) {
if ps.Timeouts.Tasks != nil && ps.Timeouts.Tasks.Duration > timeout {
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s "+errorMsg, ps.Timeouts.Tasks.Duration.String()), "timeouts.tasks"))
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s %s", ps.Timeouts.Tasks.Duration.String(), errorMsg), "timeouts.tasks"))
}

if ps.Timeouts.Finally != nil && ps.Timeouts.Finally.Duration > timeout {
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s "+errorMsg, ps.Timeouts.Finally.Duration.String()), "timeouts.finally"))
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s %s", ps.Timeouts.Finally.Duration.String(), errorMsg), "timeouts.finally"))
}

if ps.Timeouts.Tasks != nil && ps.Timeouts.Finally != nil {
if ps.Timeouts.Tasks.Duration+ps.Timeouts.Finally.Duration > timeout {
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s + %s "+errorMsg, ps.Timeouts.Tasks.Duration.String(), ps.Timeouts.Finally.Duration.String()), "timeouts.tasks"))
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s + %s "+errorMsg, ps.Timeouts.Tasks.Duration.String(), ps.Timeouts.Finally.Duration.String()), "timeouts.finally"))
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s + %s %s", ps.Timeouts.Tasks.Duration.String(), ps.Timeouts.Finally.Duration.String(), errorMsg), "timeouts.tasks"))
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s + %s %s", ps.Timeouts.Tasks.Duration.String(), ps.Timeouts.Finally.Duration.String(), errorMsg), "timeouts.finally"))
}
}
return errs
Expand Down
Loading