Skip to content

Commit f9b33cf

Browse files
committed
Graceful Pipeline Run Termination
The implementation of TEP-0058: Graceful Pipeline Run Termination. The new `spec.Status` values: - `StoppedRunFinally` - To "stop" (i.e. let the tasks complete, then execute finally tasks) a Pipeline - `CancelledRunFinally` - To "cancel" (i.e. interrupt any executing non finally tasks, then execute finally tasks) - `Cancelled` - Same as today's `PipelineRunCancelled` - i.e. interrupt any executing tasks without running finally tasks `PipelineRunCancelled` is deprecated (replaced by `Cancelled`) New features hidden by alpha API fields flag.
1 parent 565c55b commit f9b33cf

File tree

12 files changed

+1117
-38
lines changed

12 files changed

+1117
-38
lines changed

docs/pipelineruns.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,44 @@ Task Runs:
525525
## Cancelling a `PipelineRun`
526526

527527
To cancel a `PipelineRun` that's currently executing, update its definition
528-
to mark it as cancelled. When you do so, the spawned `TaskRuns` are also marked
529-
as cancelled and all associated `Pods` are deleted. For example:
528+
to mark it as "Cancelled". When you do so, the spawned `TaskRuns` are also marked
529+
as cancelled and all associated `Pods` are deleted. Pending final tasks are not scheduled.
530+
For example:
531+
532+
```yaml
533+
apiVersion: tekton.dev/v1beta1
534+
kind: PipelineRun
535+
metadata:
536+
name: go-example-git
537+
spec:
538+
# […]
539+
status: "Cancelled"
540+
```
541+
542+
## Gracefully cancelling a `PipelineRun`
543+
544+
To gracefully cancel a `PipelineRun` that's currently executing, update its definition
545+
to mark it as "CancelledRunFinally". When you do so, the spawned `TaskRuns` are also marked
546+
as cancelled and all associated `Pods` are deleted. Final tasks are scheduled normally.
547+
For example:
548+
549+
```yaml
550+
apiVersion: tekton.dev/v1beta1
551+
kind: PipelineRun
552+
metadata:
553+
name: go-example-git
554+
spec:
555+
# […]
556+
status: "CancelledRunFinally"
557+
```
558+
559+
560+
## Gracefully stopping a `PipelineRun`
561+
562+
To gracefully stop a `PipelineRun` that's currently executing, update its definition
563+
to mark it as "StoppedRunFinally". When you do so, the spawned `TaskRuns` are completed normally,
564+
but no new non-final task is scheduled. Final tasks are executed afterwards.
565+
For example:
530566

531567
```yaml
532568
apiVersion: tekton.dev/v1beta1
@@ -535,7 +571,7 @@ metadata:
535571
name: go-example-git
536572
spec:
537573
# […]
538-
status: "PipelineRunCancelled"
574+
status: "StoppedRunFinally"
539575
```
540576

541577
## Pending `PipelineRuns`

internal/builder/v1beta1/pipeline.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,27 @@ func PipelineDescription(desc string) PipelineSpecOp {
115115
}
116116
}
117117

118-
// PipelineRunCancelled sets the status to cancel the PipelineRunSpec.
118+
// PipelineRunCancelled sets the status to Cancelled in the PipelineRunSpec.
119119
func PipelineRunCancelled(spec *v1beta1.PipelineRunSpec) {
120120
spec.Status = v1beta1.PipelineRunSpecStatusCancelled
121121
}
122122

123-
// PipelineRunPending sets the status to pending to the PipelineRunSpec.
123+
// PipelineRunCancelledDeprecated sets the status to PipelineRunCancelled in the PipelineRunSpec.
124+
func PipelineRunCancelledDeprecated(spec *v1beta1.PipelineRunSpec) {
125+
spec.Status = v1beta1.PipelineRunSpecStatusCancelledDeprecated
126+
}
127+
128+
// PipelineRunCancelledRunFinally sets the status to cancel and run finally in the PipelineRunSpec.
129+
func PipelineRunCancelledRunFinally(spec *v1beta1.PipelineRunSpec) {
130+
spec.Status = v1beta1.PipelineRunSpecStatusCancelledRunFinally
131+
}
132+
133+
// PipelineRunStoppedRunFinally sets the status to stop and run finally in the PipelineRunSpec.
134+
func PipelineRunStoppedRunFinally(spec *v1beta1.PipelineRunSpec) {
135+
spec.Status = v1beta1.PipelineRunSpecStatusStoppedRunFinally
136+
}
137+
138+
// PipelineRunPending sets the status to pending in the PipelineRunSpec.
124139
func PipelineRunPending(spec *v1beta1.PipelineRunSpec) {
125140
spec.Status = v1beta1.PipelineRunSpecStatusPending
126141
}

pkg/apis/pipeline/v1alpha1/pipelinerun_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ type PipelineRunSpecStatus = v1beta1.PipelineRunSpecStatus
101101
const (
102102
// PipelineRunSpecStatusCancelled indicates that the user wants to cancel the task,
103103
// if not already cancelled or terminated
104-
PipelineRunSpecStatusCancelled = v1beta1.PipelineRunSpecStatusCancelled
104+
PipelineRunSpecStatusCancelled = v1beta1.PipelineRunSpecStatusCancelledDeprecated
105105
)
106106

107107
// PipelineResourceRef can be used to refer to a specific instance of a Resource

pkg/apis/pipeline/v1beta1/pipelinerun_types.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,17 @@ func (pr *PipelineRun) HasStarted() bool {
8585

8686
// IsCancelled returns true if the PipelineRun's spec status is set to Cancelled state
8787
func (pr *PipelineRun) IsCancelled() bool {
88-
return pr.Spec.Status == PipelineRunSpecStatusCancelled
88+
return pr.Spec.Status == PipelineRunSpecStatusCancelled || pr.Spec.Status == PipelineRunSpecStatusCancelledDeprecated
89+
}
90+
91+
// IsGracefullyCancelled returns true if the PipelineRun's spec status is set to CancelledRunFinally state
92+
func (pr *PipelineRun) IsGracefullyCancelled() bool {
93+
return pr.Spec.Status == PipelineRunSpecStatusCancelledRunFinally
94+
}
95+
96+
// IsGracefullyStopped returns true if the PipelineRun's spec status is set to StoppedRunFinally state
97+
func (pr *PipelineRun) IsGracefullyStopped() bool {
98+
return pr.Spec.Status == PipelineRunSpecStatusStoppedRunFinally
8999
}
90100

91101
func (pr *PipelineRun) GetTimeout(ctx context.Context) time.Duration {
@@ -193,9 +203,22 @@ type PipelineRunSpec struct {
193203
type PipelineRunSpecStatus string
194204

195205
const (
206+
// Deprecated: "PipelineRunCancelled" indicates that the user wants to cancel the task,
207+
// if not already cancelled or terminated (replaced by "Cancelled")
208+
PipelineRunSpecStatusCancelledDeprecated = "PipelineRunCancelled"
209+
196210
// PipelineRunSpecStatusCancelled indicates that the user wants to cancel the task,
197211
// if not already cancelled or terminated
198-
PipelineRunSpecStatusCancelled = "PipelineRunCancelled"
212+
PipelineRunSpecStatusCancelled = "Cancelled"
213+
214+
// PipelineRunSpecStatusCancelledRunFinally indicates that the user wants to cancel the pipeline run,
215+
// if not already cancelled or terminated, but ensure finally is run normally
216+
PipelineRunSpecStatusCancelledRunFinally = "CancelledRunFinally"
217+
218+
// PipelineRunSpecStatusStoppedRunFinally indicates that the user wants to stop the pipeline run,
219+
// wait for already running tasks to be completed and run finally
220+
// if not already cancelled or terminated
221+
PipelineRunSpecStatusStoppedRunFinally = "StoppedRunFinally"
199222

200223
// PipelineRunSpecStatusPending indicates that the user wants to postpone starting a PipelineRun
201224
// until some condition is met

pkg/apis/pipeline/v1beta1/pipelinerun_validation.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,7 @@ func (ps *PipelineRunSpec) Validate(ctx context.Context) (errs *apis.FieldError)
8282
}
8383
}
8484

85-
if ps.Status != "" {
86-
if ps.Status != PipelineRunSpecStatusCancelled && ps.Status != PipelineRunSpecStatusPending {
87-
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s should be %s or %s", ps.Status, PipelineRunSpecStatusCancelled, PipelineRunSpecStatusPending), "status"))
88-
}
89-
}
85+
errs = errs.Also(validateSpecStatus(ps.Status, cfg))
9086

9187
if ps.Workspaces != nil {
9288
wsNames := make(map[string]int)
@@ -101,3 +97,30 @@ func (ps *PipelineRunSpec) Validate(ctx context.Context) (errs *apis.FieldError)
10197

10298
return errs
10399
}
100+
101+
func validateSpecStatus(status PipelineRunSpecStatus, cfg *config.Config) *apis.FieldError {
102+
alphaAPIFieldsEnabled := cfg.FeatureFlags.EnableAPIFields == config.AlphaAPIFields
103+
switch status {
104+
case "":
105+
return nil
106+
case PipelineRunSpecStatusPending,
107+
PipelineRunSpecStatusCancelledDeprecated:
108+
return nil
109+
case PipelineRunSpecStatusCancelled,
110+
PipelineRunSpecStatusCancelledRunFinally,
111+
PipelineRunSpecStatusStoppedRunFinally:
112+
if alphaAPIFieldsEnabled {
113+
return nil
114+
}
115+
}
116+
if alphaAPIFieldsEnabled {
117+
return apis.ErrInvalidValue(fmt.Sprintf("%s should be %s, %s, %s or %s", status,
118+
PipelineRunSpecStatusCancelled,
119+
PipelineRunSpecStatusCancelledRunFinally,
120+
PipelineRunSpecStatusStoppedRunFinally,
121+
PipelineRunSpecStatusPending), "status")
122+
}
123+
return apis.ErrInvalidValue(fmt.Sprintf("%s should be %s or %s", status,
124+
PipelineRunSpecStatusCancelledDeprecated,
125+
PipelineRunSpecStatusPending), "status")
126+
}

pkg/apis/pipeline/v1beta1/pipelinerun_validation_test.go

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,22 @@ func TestPipelineRun_Invalid(t *testing.T) {
9191
Status: "PipelineRunCancell",
9292
},
9393
},
94-
want: apis.ErrInvalidValue("PipelineRunCancell should be PipelineRunCancelled or PipelineRunPending", "spec.status"),
94+
want: apis.ErrInvalidValue("PipelineRunCancell should be Cancelled, CancelledRunFinally, StoppedRunFinally or PipelineRunPending", "spec.status"),
95+
wc: enableAlphaAPIFields(t),
96+
}, {
97+
name: "alpha pipelinerun graceful cancel",
98+
pr: v1beta1.PipelineRun{
99+
ObjectMeta: metav1.ObjectMeta{
100+
Name: "pipelinelinename",
101+
},
102+
Spec: v1beta1.PipelineRunSpec{
103+
PipelineRef: &v1beta1.PipelineRef{
104+
Name: "prname",
105+
},
106+
Status: v1beta1.PipelineRunSpecStatusCancelled,
107+
},
108+
},
109+
want: apis.ErrInvalidValue("Cancelled should be PipelineRunCancelled or PipelineRunPending", "spec.status"),
95110
}, {
96111
name: "use of bundle without the feature flag set",
97112
pr: v1beta1.PipelineRun{
@@ -180,6 +195,7 @@ func TestPipelineRun_Validate(t *testing.T) {
180195
tests := []struct {
181196
name string
182197
pr v1beta1.PipelineRun
198+
wc func(context.Context) context.Context
183199
}{{
184200
name: "normal case",
185201
pr: v1beta1.PipelineRun{
@@ -256,11 +272,70 @@ func TestPipelineRun_Validate(t *testing.T) {
256272
},
257273
},
258274
},
275+
}, {
276+
name: "pipelinerun cancelled",
277+
pr: v1beta1.PipelineRun{
278+
ObjectMeta: metav1.ObjectMeta{
279+
Name: "pipelinerunname",
280+
},
281+
Spec: v1beta1.PipelineRunSpec{
282+
Status: v1beta1.PipelineRunSpecStatusCancelled,
283+
PipelineRef: &v1beta1.PipelineRef{
284+
Name: "prname",
285+
},
286+
},
287+
},
288+
wc: enableAlphaAPIFields(t),
289+
}, {
290+
name: "pipelinerun cancelled deprecated",
291+
pr: v1beta1.PipelineRun{
292+
ObjectMeta: metav1.ObjectMeta{
293+
Name: "pipelinerunname",
294+
},
295+
Spec: v1beta1.PipelineRunSpec{
296+
Status: v1beta1.PipelineRunSpecStatusCancelledDeprecated,
297+
PipelineRef: &v1beta1.PipelineRef{
298+
Name: "prname",
299+
},
300+
},
301+
},
302+
}, {
303+
name: "pipelinerun gracefully cancelled",
304+
pr: v1beta1.PipelineRun{
305+
ObjectMeta: metav1.ObjectMeta{
306+
Name: "pipelinerunname",
307+
},
308+
Spec: v1beta1.PipelineRunSpec{
309+
Status: v1beta1.PipelineRunSpecStatusCancelledRunFinally,
310+
PipelineRef: &v1beta1.PipelineRef{
311+
Name: "prname",
312+
},
313+
},
314+
},
315+
wc: enableAlphaAPIFields(t),
316+
}, {
317+
name: "pipelinerun gracefully stopped",
318+
pr: v1beta1.PipelineRun{
319+
ObjectMeta: metav1.ObjectMeta{
320+
Name: "pipelinerunname",
321+
},
322+
Spec: v1beta1.PipelineRunSpec{
323+
Status: v1beta1.PipelineRunSpecStatusStoppedRunFinally,
324+
PipelineRef: &v1beta1.PipelineRef{
325+
Name: "prname",
326+
},
327+
},
328+
},
329+
wc: enableAlphaAPIFields(t),
259330
}}
260331

261332
for _, ts := range tests {
262333
t.Run(ts.name, func(t *testing.T) {
263-
if err := ts.pr.Validate(context.Background()); err != nil {
334+
ctx := context.Background()
335+
if ts.wc != nil {
336+
ctx = ts.wc(ctx)
337+
}
338+
if err := ts.pr.Validate(ctx); err != nil {
264339
t.Error(err)
265340
}
266341
})
@@ -380,3 +455,16 @@ func enableTektonOCIBundles(t *testing.T) func(context.Context) context.Context
380455
return s.ToContext(ctx)
381456
}
382457
}
458+
459+
func enableAlphaAPIFields(t *testing.T) func(context.Context) context.Context {
460+
return func(ctx context.Context) context.Context {
461+
s := config.NewStore(logtesting.TestLogger(t))
462+
s.OnConfigChanged(&corev1.ConfigMap{
463+
ObjectMeta: metav1.ObjectMeta{Name: config.GetFeatureFlagsConfigName()},
464+
Data: map[string]string{
465+
"enable-api-fields": config.AlphaAPIFields,
466+
},
467+
})
468+
return s.ToContext(ctx)
469+
}
470+
}

0 commit comments

Comments
 (0)