Thanks to visit codestin.com
Credit goes to github.com

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
Add Support for managedBy field in TaskRun and PipelineRun
Added a "managedBy" field to delegate responsibility of controlling
the lifecycle of PipelineRuns/TaskRuns.

The semantics of the field:

Whenever the value is set, and it does not point to the built-in
controller, then we skip the reconciliation.
* The field is immutable
* The field is not defaulted

Assisted By: Gemini
  • Loading branch information
khrm committed Sep 25, 2025
commit db10212b5ca11d3cc6104e2037004b221b4a51ff
14 changes: 14 additions & 0 deletions config/300-crds/300-pipelinerun.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ spec:
description: PipelineRunSpec defines the desired state of PipelineRun
type: object
properties:
managedBy:
description: |-
ManagedBy indicates which controller is responsible for reconciling
this resource. If unset or set to "tekton.dev/pipeline", the default
Tekton controller will manage this resource.
This field is immutable.
type: string
params:
description: Params is a list of parameter names and values.
type: array
Expand Down Expand Up @@ -3135,6 +3142,13 @@ spec:
description: PipelineRunSpec defines the desired state of PipelineRun
type: object
properties:
managedBy:
description: |-
ManagedBy indicates which controller is responsible for reconciling
this resource. If unset or set to "tekton.dev/pipeline", the default
Tekton controller will manage this resource.
This field is immutable.
type: string
params:
description: Params is a list of parameter names and values.
type: array
Expand Down
14 changes: 14 additions & 0 deletions config/300-crds/300-taskrun.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ spec:
if enabled, pause TaskRun on failure of a step
failed step will not exit
type: string
managedBy:
description: |-
ManagedBy indicates which controller is responsible for reconciling
this resource. If unset or set to "tekton.dev/pipeline", the default
Tekton controller will manage this resource.
This field is immutable.
type: string
params:
description: Params is a list of Param
type: array
Expand Down Expand Up @@ -2336,6 +2343,13 @@ spec:
if enabled, pause TaskRun on failure of a step
failed step will not exit
type: string
managedBy:
description: |-
ManagedBy indicates which controller is responsible for reconciling
this resource. If unset or set to "tekton.dev/pipeline", the default
Tekton controller will manage this resource.
This field is immutable.
type: string
params:
description: Params is a list of Param
type: array
Expand Down
35 changes: 35 additions & 0 deletions docs/pipelineruns.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ weight: 204
- [The <code>status</code> field](#the-status-field)
- [Monitoring execution status](#monitoring-execution-status)
- [Marking off user errors](#marking-off-user-errors)
- [Delegating reconciliation](#delegating-reconciliation)
- [Cancelling a <code>PipelineRun</code>](#cancelling-a-pipelinerun)
- [Gracefully cancelling a <code>PipelineRun</code>](#gracefully-cancelling-a-pipelinerun)
- [Gracefully stopping a <code>PipelineRun</code>](#gracefully-stopping-a-pipelinerun)
Expand Down Expand Up @@ -80,6 +81,7 @@ A `PipelineRun` definition supports the following fields:
- [`timeouts`](#configuring-a-failure-timeout) - Specifies the timeout before the `PipelineRun` fails. `timeouts` allows more granular timeout configuration, at the pipeline, tasks, and finally levels
- [`podTemplate`](#specifying-a-pod-template) - Specifies a [`Pod` template](./podtemplates.md) to use as the basis for the configuration of the `Pod` that executes each `Task`.
- [`workspaces`](#specifying-workspaces) - Specifies a set of workspace bindings which must match the names of workspaces declared in the pipeline being used.
- [`managedBy`](#delegating-reconciliation) - Specifies the controller responsible for managing this PipelineRun's lifecycle.

[kubernetes-overview]:
https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields
Expand Down Expand Up @@ -1642,6 +1644,39 @@ NAME STARTED DURATION STATUS
pipelinerun-with-params 5 seconds ago 0s Failed(ParameterMissing)
```

## Delegating reconciliation

The `managedBy` field allows you to delegate the responsibility of managing a `PipelineRun`'s lifecycle to an external controller. When this field is set to a value other than `"tekton.dev/pipeline"`, the Tekton Pipeline controller will ignore the `PipelineRun`, allowing your external controller to take full control. This delegation enables several advanced use cases, such as implementing custom pipeline execution logic, integrating with external management tools, using advanced scheduling algorithms, or coordinating PipelineRuns across multiple clusters (like using [MultiKueue](https://kueue.sigs.k8s.io/docs/concepts/multikueue/)).

### Example

```yaml
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: externally-managed-pipeline
spec:
pipelineRef:
name: my-pipeline
managedBy: "my-custom-controller"
```

### Behavior

- **When `managedBy` is empty**: The Tekton Pipeline controller manages the PipelineRun normally
- **When `managedBy` is set to `"tekton.dev/pipeline"`**: The Tekton Pipeline controller manages the PipelineRun normally
- **When `managedBy` is set to any other value**: The Tekton Pipeline controller ignores the PipelineRun completely
- **Immutability**: The `managedBy` field is immutable and cannot be changed after creation

### External controller responsibilities

When you set `managedBy` to a custom value, your external controller is responsible for:

- Creating and managing TaskRuns
- Updating PipelineRun status
- Handling timeouts and cancellations
- Managing retries and error handling

## Cancelling a `PipelineRun`

To cancel a `PipelineRun` that's currently executing, update its definition
Expand Down
36 changes: 36 additions & 0 deletions docs/taskruns.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ weight: 202
- [Debug Environment](#debug-environment)
- [Events](events.md#taskruns)
- [Running a TaskRun Hermetically](hermetic.md)
- [Delegating reconciliation](#delegating-reconciliation)
- [Code examples](#code-examples)
- [Example `TaskRun` with a referenced `Task`](#example-taskrun-with-a-referenced-task)
- [Example `TaskRun` with an embedded `Task`](#example-taskrun-with-an-embedded-task)
Expand Down Expand Up @@ -79,6 +80,7 @@ A `TaskRun` definition supports the following fields:
- [`debug`](#debugging-a-taskrun)- Specifies any breakpoints and debugging configuration for the `Task` execution.
- [`stepSpecs`](#configuring-task-steps-and-sidecars-in-a-taskrun) - Specifies configuration to use to override the `Task`'s `Step`s.
- [`sidecarSpecs`](#configuring-task-steps-and-sidecars-in-a-taskrun) - Specifies configuration to use to override the `Task`'s `Sidecar`s.
- [`managedBy`](#delegating-reconciliation) - Specifies the controller responsible for managing this TaskRun's lifecycle.

[kubernetes-overview]:
https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields
Expand Down Expand Up @@ -1112,6 +1114,40 @@ this is available as a [TaskRun example](../examples/v1/taskruns/run-steps-as-no

More information about Pod and Container Security Contexts can be found via the [Kubernetes website](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod).

## Delegating reconciliation

The `managedBy` field allows you to delegate the responsibility of managing a `TaskRun`'s lifecycle to an external controller. When this field is set to a value other than `"tekton.dev/pipeline"`, the Tekton Pipeline controller will ignore the `TaskRun`, allowing your external controller to take full control. This delegation enables several advanced use cases, such as implementing custom pipeline execution logic, integrating with external management tools, using advanced scheduling algorithms, or coordinating PipelineRuns across multiple clusters (like using [MultiKueue](https://kueue.sigs.k8s.io/docs/concepts/multikueue/)).

### Example

```yaml
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
name: externally-managed-task
spec:
taskRef:
name: my-task
managedBy: "my-custom-controller"
```

### Behavior

- **When `managedBy` is empty**: The Tekton Pipeline controller manages the TaskRun normally
- **When `managedBy` is set to `"tekton.dev/pipeline"`**: The Tekton Pipeline controller manages the TaskRun normally
- **When `managedBy` is set to any other value**: The Tekton Pipeline controller ignores the TaskRun completely
- **Immutability**: The `managedBy` field is immutable and cannot be changed after creation

### External controller responsibilities

When you set `managedBy` to a custom value, your external controller is responsible for:

- Creating and managing Pods
- Updating TaskRun status
- Handling timeouts and cancellations
- Managing retries and error handling
- Processing step results and artifacts

---

Except as otherwise noted, the content of this page is licensed under the
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/pipeline/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ const (
// MemberOfLabelKey is used as the label identifier for a PipelineTask
// Set to Tasks/Finally depending on the position of the PipelineTask
MemberOfLabelKey = GroupName + "/memberOf"

// ManagedBy is the value of the "managedBy" field for resources
// managed by the Tekton Pipeline controller.
ManagedBy = GroupName + "/pipeline"
)

var (
Expand Down
14 changes: 14 additions & 0 deletions pkg/apis/pipeline/v1/openapi_generated.go

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

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 @@ -283,6 +283,12 @@ type PipelineRunSpec struct {
// +optional
// +listType=atomic
TaskRunSpecs []PipelineTaskRunSpec `json:"taskRunSpecs,omitempty"`
// ManagedBy indicates which controller is responsible for reconciling
// this resource. If unset or set to "tekton.dev/pipeline", the default
// Tekton controller will manage this resource.
// This field is immutable.
// +optional
ManagedBy *string `json:"managedBy,omitempty"`
}

// TimeoutFields allows granular specification of pipeline, task, and finally timeouts
Expand Down
7 changes: 6 additions & 1 deletion pkg/apis/pipeline/v1/pipelinerun_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ func (ps *PipelineRunSpec) ValidateUpdate(ctx context.Context) (errs *apis.Field
if !ok || oldObj == nil {
return
}

if (oldObj.Spec.ManagedBy == nil) != (ps.ManagedBy == nil) || (oldObj.Spec.ManagedBy != nil && *oldObj.Spec.ManagedBy != *ps.ManagedBy) {
errs = errs.Also(apis.ErrInvalidValue("managedBy is immutable", "spec.managedBy"))
}

if oldObj.IsDone() {
// try comparing without any copying first
// this handles the common case where only finalizers changed
Expand All @@ -162,10 +167,10 @@ func (ps *PipelineRunSpec) ValidateUpdate(ctx context.Context) (errs *apis.Field
// Handle started but not done case
old := oldObj.Spec.DeepCopy()
old.Status = ps.Status
old.ManagedBy = ps.ManagedBy // Already tested before
if !equality.Semantic.DeepEqual(old, ps) {
errs = errs.Also(apis.ErrInvalidValue("Once the PipelineRun has started, only status updates are allowed", ""))
}

return
}

Expand Down
51 changes: 51 additions & 0 deletions pkg/apis/pipeline/v1/pipelinerun_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
corev1 "k8s.io/api/core/v1"
corev1resources "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ptr "k8s.io/utils/pointer"
"knative.dev/pkg/apis"
duckv1 "knative.dev/pkg/apis/duck/v1"
)
Expand Down Expand Up @@ -1751,6 +1752,56 @@ func TestPipelineRunSpec_ValidateUpdate(t *testing.T) {
Message: `invalid value: Once the PipelineRun is complete, no updates are allowed`,
Paths: []string{""},
},
}, {
name: "is update ctx, baseline is not done, managedBy changes",
baselinePipelineRun: &v1.PipelineRun{
Spec: v1.PipelineRunSpec{
ManagedBy: ptr.String("tekton.dev/pipeline"),
},
Status: v1.PipelineRunStatus{
Status: duckv1.Status{
Conditions: duckv1.Conditions{
{Type: apis.ConditionSucceeded, Status: corev1.ConditionUnknown},
},
},
},
},
pipelineRun: &v1.PipelineRun{
Spec: v1.PipelineRunSpec{
ManagedBy: ptr.String("some-other-controller"),
},
},
isCreate: false,
isUpdate: true,
expectedError: apis.FieldError{
Message: `invalid value: managedBy is immutable`,
Paths: []string{"spec.managedBy"},
},
}, {
name: "is update ctx, baseline is unknown, managedBy changes",
baselinePipelineRun: &v1.PipelineRun{
Spec: v1.PipelineRunSpec{
ManagedBy: ptr.String("tekton.dev/pipeline"),
},
Status: v1.PipelineRunStatus{
Status: duckv1.Status{
Conditions: duckv1.Conditions{
{Type: apis.ConditionSucceeded, Status: corev1.ConditionUnknown},
},
},
},
},
pipelineRun: &v1.PipelineRun{
Spec: v1.PipelineRunSpec{
ManagedBy: ptr.String("some-other-controller"),
},
},
isCreate: false,
isUpdate: true,
expectedError: apis.FieldError{
Message: `invalid value: managedBy is immutable`,
Paths: []string{"spec.managedBy"},
},
},
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/pipeline/v1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,10 @@
"description": "PipelineRunSpec defines the desired state of PipelineRun",
"type": "object",
"properties": {
"managedBy": {
"description": "ManagedBy indicates which controller is responsible for reconciling this resource. If unset or set to \"tekton.dev/pipeline\", the default Tekton controller will manage this resource. This field is immutable.",
"type": "string"
},
"params": {
"description": "Params is a list of parameter names and values.",
"type": "array",
Expand Down Expand Up @@ -2049,6 +2053,10 @@
"debug": {
"$ref": "#/definitions/v1.TaskRunDebug"
},
"managedBy": {
"description": "ManagedBy indicates which controller is responsible for reconciling this resource. If unset or set to \"tekton.dev/pipeline\", the default Tekton controller will manage this resource. This field is immutable.",
"type": "string"
},
"params": {
"type": "array",
"items": {
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/pipeline/v1/taskrun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ type TaskRunSpec struct {
SidecarSpecs []TaskRunSidecarSpec `json:"sidecarSpecs,omitempty"`
// Compute resources to use for this TaskRun
ComputeResources *corev1.ResourceRequirements `json:"computeResources,omitempty"`
// ManagedBy indicates which controller is responsible for reconciling
// this resource. If unset or set to "tekton.dev/pipeline", the default
// Tekton controller will manage this resource.
// This field is immutable.
// +optional
ManagedBy *string `json:"managedBy,omitempty"`
}

// TaskRunSpecStatus defines the TaskRun spec status the user can provide
Expand Down
9 changes: 7 additions & 2 deletions pkg/apis/pipeline/v1/taskrun_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,22 @@ func (ts *TaskRunSpec) ValidateUpdate(ctx context.Context) (errs *apis.FieldErro
if !apis.IsInUpdate(ctx) {
return
}

oldObj, ok := apis.GetBaseline(ctx).(*TaskRun)
if !ok || oldObj == nil {
return
}

if (oldObj.Spec.ManagedBy == nil) != (ts.ManagedBy == nil) || (oldObj.Spec.ManagedBy != nil && *oldObj.Spec.ManagedBy != *ts.ManagedBy) {
errs = errs.Also(apis.ErrInvalidValue("managedBy is immutable", "spec.managedBy"))
}

if oldObj.IsDone() {
// try comparing without any copying first
// this handles the common case where only finalizers changed
if equality.Semantic.DeepEqual(&oldObj.Spec, ts) {
return nil // Specs identical, allow update
}

// Specs differ, this could be due to different defaults after upgrade
// Apply current defaults to old spec to normalize
oldCopy := oldObj.Spec.DeepCopy()
Expand All @@ -155,10 +160,10 @@ func (ts *TaskRunSpec) ValidateUpdate(ctx context.Context) (errs *apis.FieldErro
old := oldObj.Spec.DeepCopy()
old.Status = ts.Status
old.StatusMessage = ts.StatusMessage
old.ManagedBy = ts.ManagedBy // Already tested before
if !equality.Semantic.DeepEqual(old, ts) {
errs = errs.Also(apis.ErrInvalidValue("Once the TaskRun has started, only status and statusMessage updates are allowed", ""))
}

return
}

Expand Down
Loading
Loading