diff --git a/docs/install.md b/docs/install.md index 4f2cbb6b1bc..9828bfeedd9 100644 --- a/docs/install.md +++ b/docs/install.md @@ -404,6 +404,7 @@ Features currently in "alpha" are: | [Windows Scripts](./tasks.md#windows-scripts) | [TEP-0057](https://github.com/tektoncd/community/blob/main/teps/0057-windows-support.md) | [v0.28.0](https://github.com/tektoncd/pipeline/releases/tag/v0.28.0) | | | [Remote Tasks](./taskruns.md#remote-tasks) and [Remote Pipelines](./pipelineruns.md#remote-pipelines) | [TEP-0060](https://github.com/tektoncd/community/blob/main/teps/0060-remote-resolutiond.md) | | | | [Debug](./debug.md) | [TEP-0042](https://github.com/tektoncd/community/blob/main/teps/0042-taskrun-breakpoint-on-failure.md) | [v0.26.0](https://github.com/tektoncd/pipeline/releases/tag/v0.26.0) | | +| [Step and Sidecar Overrides](./taskruns.md#overriding-task-steps-and-sidecars)| [TEP-0094](https://github.com/tektoncd/community/blob/main/teps/0094-specifying-resource-requirements-at-runtime.md) | | | ## Configuring High Availability diff --git a/docs/pipelineruns.md b/docs/pipelineruns.md index 7ab3c97081b..1a2ee740e0b 100644 --- a/docs/pipelineruns.md +++ b/docs/pipelineruns.md @@ -472,6 +472,8 @@ spec: ``` If used with this `Pipeline`, `build-task` will use the task specific `PodTemplate` (where `nodeSelector` has `disktype` equal to `ssd`). +`PipelineTaskRunSpec` may also contain `StepOverrides` and `SidecarOverrides`; see +[Overriding `Task` `Steps` and `Sidecars`](./taskruns.md#overriding-task-steps-and-sidecars) for more information. ### Specifying `Workspaces` diff --git a/docs/taskruns.md b/docs/taskruns.md index d75d48e7255..fd5297bf508 100644 --- a/docs/taskruns.md +++ b/docs/taskruns.md @@ -21,6 +21,7 @@ weight: 300 - [Specifying a `Pod` template](#specifying-a-pod-template) - [Specifying `Workspaces`](#specifying-workspaces) - [Specifying `Sidecars`](#specifying-sidecars) + - [Overriding `Task` `Steps` and `Sidecars`](#overriding-task-steps-and-sidecars) - [Specifying `LimitRange` values](#specifying-limitrange-values) - [Configuring the failure timeout](#configuring-the-failure-timeout) - [Specifying `ServiceAccount` credentials](#specifying-serviceaccount-credentials) @@ -306,7 +307,8 @@ spec: ### Specifying `Resource` limits Each Step in a Task can specify its resource requirements. See -[Defining `Steps`](tasks.md#defining-steps) +[Defining `Steps`](tasks.md#defining-steps). Resource requirements defined in Steps and Sidecars +may be overridden by a TaskRun's StepOverrides and SidecarOverrides. ### Specifying a `Pod` template @@ -399,6 +401,56 @@ inside the `Pod`. Only the above command is affected. The `Pod's` description co denotes a "Failed" status and the container statuses correctly denote their exit codes and reasons. +### Overriding Task Steps and Sidecars + +**([alpha only](https://github.com/tektoncd/pipeline/blob/main/docs/install.md#alpha-features))** +**Warning: This feature is still under development and is not yet functional. Do not use it.** + +A TaskRun can specify `StepOverrides` or `SidecarOverrides` to override Step or Sidecar +configuration specified in a Task. + +For example, given the following Task definition: + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: image-build-task +spec: + steps: + - name: build + image: gcr.io/kaniko-project/executor:latest + sidecars: + - name: logging + image: my-logging-image +``` + +An example TaskRun definition could look like: + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: TaskRun +metadata: + name: image-build-taskrun +spec: + taskRef: + name: image-build-task + stepOverrides: + - name: build + resources: + requests: + memory: 1Gi + sidecarOverrides: + - name: logging + resources: + requests: + cpu: 100m + limits: + cpu: 500m +``` +`StepOverrides` and `SidecarOverrides` must include the `name` field and may include `resources`. +No other fields can be overridden. + ### Specifying `LimitRange` values In order to only consume the bare minimum amount of resources needed to execute one `Step` at a diff --git a/pkg/apis/pipeline/v1beta1/openapi_generated.go b/pkg/apis/pipeline/v1beta1/openapi_generated.go index f28b7c6934f..5fd67185b6c 100644 --- a/pkg/apis/pipeline/v1beta1/openapi_generated.go +++ b/pkg/apis/pipeline/v1beta1/openapi_generated.go @@ -93,9 +93,11 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunOutputs": schema_pkg_apis_pipeline_v1beta1_TaskRunOutputs(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunResources": schema_pkg_apis_pipeline_v1beta1_TaskRunResources(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunResult": schema_pkg_apis_pipeline_v1beta1_TaskRunResult(ref), + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunSidecarOverride": schema_pkg_apis_pipeline_v1beta1_TaskRunSidecarOverride(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunSpec": schema_pkg_apis_pipeline_v1beta1_TaskRunSpec(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunStatus": schema_pkg_apis_pipeline_v1beta1_TaskRunStatus(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunStatusFields": schema_pkg_apis_pipeline_v1beta1_TaskRunStatusFields(ref), + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunStepOverride": schema_pkg_apis_pipeline_v1beta1_TaskRunStepOverride(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskSpec": schema_pkg_apis_pipeline_v1beta1_TaskSpec(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TimeoutFields": schema_pkg_apis_pipeline_v1beta1_TimeoutFields(ref), "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.WhenExpression": schema_pkg_apis_pipeline_v1beta1_WhenExpression(ref), @@ -2373,11 +2375,37 @@ func schema_pkg_apis_pipeline_v1beta1_PipelineTaskRunSpec(ref common.ReferenceCa Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/pod.Template"), }, }, + "stepOverrides": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunStepOverride"), + }, + }, + }, + }, + }, + "sidecarOverrides": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunSidecarOverride"), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod.Template"}, + "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod.Template", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunSidecarOverride", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunStepOverride"}, } } @@ -3768,6 +3796,37 @@ func schema_pkg_apis_pipeline_v1beta1_TaskRunResult(ref common.ReferenceCallback } } +func schema_pkg_apis_pipeline_v1beta1_TaskRunSidecarOverride(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TaskRunSidecarOverride is used to override the values of a Sidecar in the corresponding Task.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "Name": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the Sidecar to override.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "Resources": { + SchemaProps: spec.SchemaProps{ + Description: "The resource requirements to apply to the Sidecar.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceRequirements"), + }, + }, + }, + Required: []string{"Name", "Resources"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ResourceRequirements"}, + } +} + func schema_pkg_apis_pipeline_v1beta1_TaskRunSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -3849,11 +3908,39 @@ func schema_pkg_apis_pipeline_v1beta1_TaskRunSpec(ref common.ReferenceCallback) }, }, }, + "stepOverrides": { + SchemaProps: spec.SchemaProps{ + Description: "Overrides to apply to Steps in this TaskRun. If a field is specified in both a Step and a StepOverride, the value from the StepOverride will be used. This field is only supported when the alpha feature gate is enabled.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunStepOverride"), + }, + }, + }, + }, + }, + "sidecarOverrides": { + SchemaProps: spec.SchemaProps{ + Description: "Overrides to apply to Sidecars in this TaskRun. If a field is specified in both a Sidecar and a SidecarOverride, the value from the SidecarOverride will be used. This field is only supported when the alpha feature gate is enabled.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunSidecarOverride"), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod.Template", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.Param", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRef", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunDebug", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunResources", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskSpec", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.WorkspaceBinding", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, + "github.com/tektoncd/pipeline/pkg/apis/pipeline/pod.Template", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.Param", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRef", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunDebug", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunResources", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunSidecarOverride", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunStepOverride", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskSpec", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.WorkspaceBinding", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, } } @@ -4152,6 +4239,37 @@ func schema_pkg_apis_pipeline_v1beta1_TaskRunStatusFields(ref common.ReferenceCa } } +func schema_pkg_apis_pipeline_v1beta1_TaskRunStepOverride(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TaskRunStepOverride is used to override the values of a Step in the corresponding Task.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "Name": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the Step to override.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "Resources": { + SchemaProps: spec.SchemaProps{ + Description: "The resource requirements to apply to the Step.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceRequirements"), + }, + }, + }, + Required: []string{"Name", "Resources"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ResourceRequirements"}, + } +} + func schema_pkg_apis_pipeline_v1beta1_TaskSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/apis/pipeline/v1beta1/pipelinerun_types.go b/pkg/apis/pipeline/v1beta1/pipelinerun_types.go index fd42c322ade..eecc8f59496 100644 --- a/pkg/apis/pipeline/v1beta1/pipelinerun_types.go +++ b/pkg/apis/pipeline/v1beta1/pipelinerun_types.go @@ -511,9 +511,11 @@ type PipelineTaskRun struct { // PipelineTaskRunSpec can be used to configure specific // specs for a concrete Task type PipelineTaskRunSpec struct { - PipelineTaskName string `json:"pipelineTaskName,omitempty"` - TaskServiceAccountName string `json:"taskServiceAccountName,omitempty"` - TaskPodTemplate *PodTemplate `json:"taskPodTemplate,omitempty"` + PipelineTaskName string `json:"pipelineTaskName,omitempty"` + TaskServiceAccountName string `json:"taskServiceAccountName,omitempty"` + TaskPodTemplate *PodTemplate `json:"taskPodTemplate,omitempty"` + StepOverrides []TaskRunStepOverride `json:"stepOverrides,omitempty"` + SidecarOverrides []TaskRunSidecarOverride `json:"sidecarOverrides,omitempty"` } // GetTaskRunSpec returns the task specific spec for a given diff --git a/pkg/apis/pipeline/v1beta1/pipelinerun_validation.go b/pkg/apis/pipeline/v1beta1/pipelinerun_validation.go index 11f2cfbfeb5..715384fee2e 100644 --- a/pkg/apis/pipeline/v1beta1/pipelinerun_validation.go +++ b/pkg/apis/pipeline/v1beta1/pipelinerun_validation.go @@ -112,6 +112,10 @@ func (ps *PipelineRunSpec) Validate(ctx context.Context) (errs *apis.FieldError) } } + for idx, trs := range ps.TaskRunSpecs { + errs = errs.Also(validateTaskRunSpec(ctx, trs).ViaIndex(idx).ViaField("taskRunSpecs")) + } + return errs } @@ -188,3 +192,23 @@ func (ps *PipelineRunSpec) validatePipelineTimeout(timeout time.Duration, errorM } return errs } + +func validateTaskRunSpec(ctx context.Context, trs PipelineTaskRunSpec) (errs *apis.FieldError) { + cfg := config.FromContextOrDefaults(ctx) + if cfg.FeatureFlags.EnableAPIFields == config.AlphaAPIFields { + if trs.StepOverrides != nil { + errs = errs.Also(validateStepOverrides(trs.StepOverrides).ViaField("stepOverrides")) + } + if trs.SidecarOverrides != nil { + errs = errs.Also(validateSidecarOverrides(trs.SidecarOverrides).ViaField("sidecarOverrides")) + } + } else { + if trs.StepOverrides != nil { + errs = errs.Also(apis.ErrDisallowedFields("stepOverrides")) + } + if trs.SidecarOverrides != nil { + errs = errs.Also(apis.ErrDisallowedFields("sidecarOverrides")) + } + } + return errs +} diff --git a/pkg/apis/pipeline/v1beta1/pipelinerun_validation_test.go b/pkg/apis/pipeline/v1beta1/pipelinerun_validation_test.go index 54b74fa17e8..ee4a785f590 100644 --- a/pkg/apis/pipeline/v1beta1/pipelinerun_validation_test.go +++ b/pkg/apis/pipeline/v1beta1/pipelinerun_validation_test.go @@ -26,6 +26,7 @@ import ( "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/test/diff" corev1 "k8s.io/api/core/v1" + corev1resources "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/apis" logtesting "knative.dev/pkg/logging/testing" @@ -380,6 +381,34 @@ func TestPipelineRun_Validate(t *testing.T) { }, }, wc: enableAlphaAPIFields, + }, { + name: "alpha feature: sidecar and step overrides", + pr: v1beta1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pr", + }, + Spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "pr"}, + TaskRunSpecs: []v1beta1.PipelineTaskRunSpec{ + { + PipelineTaskName: "bar", + StepOverrides: []v1beta1.TaskRunStepOverride{{ + Name: "task-1", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }}, + }, + SidecarOverrides: []v1beta1.TaskRunSidecarOverride{{ + Name: "task-1", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }}, + }, + }, + }, + }, + }, + wc: enableAlphaAPIFields, }} for _, ts := range tests { @@ -517,6 +546,104 @@ func TestPipelineRunSpec_Invalidate(t *testing.T) { }, wantErr: apis.ErrMultipleOneOf("bundle", "resolver").ViaField("pipelineRef"), withContext: enableAlphaAPIFields, + }, { + name: "duplicate stepOverride names", + spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "foo"}, + TaskRunSpecs: []v1beta1.PipelineTaskRunSpec{ + { + PipelineTaskName: "bar", + StepOverrides: []v1beta1.TaskRunStepOverride{ + {Name: "baz"}, {Name: "baz"}, + }, + }, + }, + }, + wantErr: apis.ErrMultipleOneOf("taskRunSpecs[0].stepOverrides[1].name"), + withContext: enableAlphaAPIFields, + }, { + name: "stepOverride disallowed without alpha feature gate", + spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "foo"}, + TaskRunSpecs: []v1beta1.PipelineTaskRunSpec{ + { + PipelineTaskName: "bar", + StepOverrides: []v1beta1.TaskRunStepOverride{{ + Name: "task-1", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }}, + }, + }, + }, + }, + wantErr: apis.ErrDisallowedFields("stepOverrides").ViaIndex(0).ViaField("taskRunSpecs"), + }, { + name: "sidecarOverride disallowed without alpha feature gate", + spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "foo"}, + TaskRunSpecs: []v1beta1.PipelineTaskRunSpec{ + { + PipelineTaskName: "bar", + SidecarOverrides: []v1beta1.TaskRunSidecarOverride{{ + Name: "task-1", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }}, + }, + }, + }, + }, + wantErr: apis.ErrDisallowedFields("sidecarOverrides").ViaIndex(0).ViaField("taskRunSpecs"), + }, { + name: "missing stepOverride name", + spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "foo"}, + TaskRunSpecs: []v1beta1.PipelineTaskRunSpec{ + { + PipelineTaskName: "bar", + StepOverrides: []v1beta1.TaskRunStepOverride{{ + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }}, + }, + }, + }, + }, + wantErr: apis.ErrMissingField("taskRunSpecs[0].stepOverrides[0].name"), + withContext: enableAlphaAPIFields, + }, { + name: "duplicate sidecarOverride names", + spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "foo"}, + TaskRunSpecs: []v1beta1.PipelineTaskRunSpec{ + { + PipelineTaskName: "bar", + SidecarOverrides: []v1beta1.TaskRunSidecarOverride{ + {Name: "baz"}, {Name: "baz"}, + }, + }, + }, + }, + wantErr: apis.ErrMultipleOneOf("taskRunSpecs[0].sidecarOverrides[1].name"), + withContext: enableAlphaAPIFields, + }, { + name: "missing sidecarOverride name", + spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "foo"}, + TaskRunSpecs: []v1beta1.PipelineTaskRunSpec{ + { + PipelineTaskName: "bar", + SidecarOverrides: []v1beta1.TaskRunSidecarOverride{{ + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }}, + }, + }, + }, + }, + wantErr: apis.ErrMissingField("taskRunSpecs[0].sidecarOverrides[0].name"), + withContext: enableAlphaAPIFields, }} for _, ps := range tests { t.Run(ps.name, func(t *testing.T) { diff --git a/pkg/apis/pipeline/v1beta1/swagger.json b/pkg/apis/pipeline/v1beta1/swagger.json index dd69ed5807c..209fae22dff 100644 --- a/pkg/apis/pipeline/v1beta1/swagger.json +++ b/pkg/apis/pipeline/v1beta1/swagger.json @@ -1441,6 +1441,20 @@ "pipelineTaskName": { "type": "string" }, + "sidecarOverrides": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/v1beta1.TaskRunSidecarOverride" + } + }, + "stepOverrides": { + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/v1beta1.TaskRunStepOverride" + } + }, "taskPodTemplate": { "$ref": "#/definitions/pod.Template" }, @@ -2229,6 +2243,26 @@ } } }, + "v1beta1.TaskRunSidecarOverride": { + "description": "TaskRunSidecarOverride is used to override the values of a Sidecar in the corresponding Task.", + "type": "object", + "required": [ + "Name", + "Resources" + ], + "properties": { + "Name": { + "description": "The name of the Sidecar to override.", + "type": "string", + "default": "" + }, + "Resources": { + "description": "The resource requirements to apply to the Sidecar.", + "default": {}, + "$ref": "#/definitions/v1.ResourceRequirements" + } + } + }, "v1beta1.TaskRunSpec": { "description": "TaskRunSpec defines the desired state of TaskRun", "type": "object", @@ -2254,10 +2288,26 @@ "type": "string", "default": "" }, + "sidecarOverrides": { + "description": "Overrides to apply to Sidecars in this TaskRun. If a field is specified in both a Sidecar and a SidecarOverride, the value from the SidecarOverride will be used. This field is only supported when the alpha feature gate is enabled.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/v1beta1.TaskRunSidecarOverride" + } + }, "status": { "description": "Used for cancelling a taskrun (and maybe more later on)", "type": "string" }, + "stepOverrides": { + "description": "Overrides to apply to Steps in this TaskRun. If a field is specified in both a Step and a StepOverride, the value from the StepOverride will be used. This field is only supported when the alpha feature gate is enabled.", + "type": "array", + "items": { + "default": {}, + "$ref": "#/definitions/v1beta1.TaskRunStepOverride" + } + }, "taskRef": { "description": "no more than one of the TaskRef and TaskSpec may be specified.", "$ref": "#/definitions/v1beta1.TaskRef" @@ -2450,6 +2500,26 @@ } } }, + "v1beta1.TaskRunStepOverride": { + "description": "TaskRunStepOverride is used to override the values of a Step in the corresponding Task.", + "type": "object", + "required": [ + "Name", + "Resources" + ], + "properties": { + "Name": { + "description": "The name of the Step to override.", + "type": "string", + "default": "" + }, + "Resources": { + "description": "The resource requirements to apply to the Step.", + "default": {}, + "$ref": "#/definitions/v1.ResourceRequirements" + } + } + }, "v1beta1.TaskSpec": { "description": "TaskSpec defines the desired state of Task.", "type": "object", diff --git a/pkg/apis/pipeline/v1beta1/taskrun_types.go b/pkg/apis/pipeline/v1beta1/taskrun_types.go index e9e63bce44b..517887201cc 100644 --- a/pkg/apis/pipeline/v1beta1/taskrun_types.go +++ b/pkg/apis/pipeline/v1beta1/taskrun_types.go @@ -61,6 +61,18 @@ type TaskRunSpec struct { // Workspaces is a list of WorkspaceBindings from volumes to workspaces. // +optional Workspaces []WorkspaceBinding `json:"workspaces,omitempty"` + // Overrides to apply to Steps in this TaskRun. + // If a field is specified in both a Step and a StepOverride, + // the value from the StepOverride will be used. + // This field is only supported when the alpha feature gate is enabled. + // +optional + StepOverrides []TaskRunStepOverride `json:"stepOverrides,omitempty"` + // Overrides to apply to Sidecars in this TaskRun. + // If a field is specified in both a Sidecar and a SidecarOverride, + // the value from the SidecarOverride will be used. + // This field is only supported when the alpha feature gate is enabled. + // +optional + SidecarOverrides []TaskRunSidecarOverride `json:"sidecarOverrides,omitempty"` } // TaskRunSpecStatus defines the taskrun spec status the user can provide @@ -218,6 +230,22 @@ type TaskRunResult struct { Value string `json:"value"` } +// TaskRunStepOverride is used to override the values of a Step in the corresponding Task. +type TaskRunStepOverride struct { + // The name of the Step to override. + Name string + // The resource requirements to apply to the Step. + Resources corev1.ResourceRequirements +} + +// TaskRunSidecarOverride is used to override the values of a Sidecar in the corresponding Task. +type TaskRunSidecarOverride struct { + // The name of the Sidecar to override. + Name string + // The resource requirements to apply to the Sidecar. + Resources corev1.ResourceRequirements +} + // GetGroupVersionKind implements kmeta.OwnerRefable. func (*TaskRun) GetGroupVersionKind() schema.GroupVersionKind { return SchemeGroupVersion.WithKind(pipeline.TaskRunControllerName) diff --git a/pkg/apis/pipeline/v1beta1/taskrun_validation.go b/pkg/apis/pipeline/v1beta1/taskrun_validation.go index f9331529715..761c9446666 100644 --- a/pkg/apis/pipeline/v1beta1/taskrun_validation.go +++ b/pkg/apis/pipeline/v1beta1/taskrun_validation.go @@ -63,6 +63,14 @@ func (ts *TaskRunSpec) Validate(ctx context.Context) (errs *apis.FieldError) { errs = errs.Also(ValidateEnabledAPIFields(ctx, "debug", config.AlphaAPIFields).ViaField("debug")) errs = errs.Also(validateDebug(ts.Debug).ViaField("debug")) } + if ts.StepOverrides != nil { + errs = errs.Also(ValidateEnabledAPIFields(ctx, "stepOverrides", config.AlphaAPIFields).ViaField("stepOverrides")) + errs = errs.Also(validateStepOverrides(ts.StepOverrides).ViaField("stepOverrides")) + } + if ts.SidecarOverrides != nil { + errs = errs.Also(ValidateEnabledAPIFields(ctx, "sidecarOverrides", config.AlphaAPIFields).ViaField("sidecarOverrides")) + errs = errs.Also(validateSidecarOverrides(ts.SidecarOverrides).ViaField("sidecarOverrides")) + } if ts.Status != "" { if ts.Status != TaskRunSpecStatusCancelled { @@ -95,27 +103,63 @@ func validateDebug(db *TaskRunDebug) (errs *apis.FieldError) { // validateWorkspaceBindings makes sure the volumes provided for the Task's declared workspaces make sense. func validateWorkspaceBindings(ctx context.Context, wb []WorkspaceBinding) (errs *apis.FieldError) { - seen := sets.NewString() + var names []string for idx, w := range wb { - if seen.Has(w.Name) { - errs = errs.Also(apis.ErrMultipleOneOf("name").ViaIndex(idx)) - } - seen.Insert(w.Name) - + names = append(names, w.Name) errs = errs.Also(w.Validate(ctx).ViaIndex(idx)) } - + errs = errs.Also(validateNoDuplicateNames(names, true)) return errs } func validateParameters(params []Param) (errs *apis.FieldError) { - // Template must not duplicate parameter names. - seen := sets.NewString() + var names []string for _, p := range params { - if seen.Has(strings.ToLower(p.Name)) { - errs = errs.Also(apis.ErrMultipleOneOf("name").ViaKey(p.Name)) + names = append(names, p.Name) + } + return validateNoDuplicateNames(names, false) +} + +func validateStepOverrides(overrides []TaskRunStepOverride) (errs *apis.FieldError) { + var names []string + for i, o := range overrides { + if o.Name == "" { + errs = errs.Also(apis.ErrMissingField("name").ViaIndex(i)) + } else { + names = append(names, o.Name) + } + } + errs = errs.Also(validateNoDuplicateNames(names, true)) + return errs +} + +func validateSidecarOverrides(overrides []TaskRunSidecarOverride) (errs *apis.FieldError) { + var names []string + for i, o := range overrides { + if o.Name == "" { + errs = errs.Also(apis.ErrMissingField("name").ViaIndex(i)) + } else { + names = append(names, o.Name) + } + } + errs = errs.Also(validateNoDuplicateNames(names, true)) + return errs +} + +// validateNoDuplicateNames returns an error for each name that is repeated in names. +// Case insensitive. +// If byIndex is true, the error will be reported by index instead of by key. +func validateNoDuplicateNames(names []string, byIndex bool) (errs *apis.FieldError) { + seen := sets.NewString() + for i, n := range names { + if seen.Has(strings.ToLower(n)) { + if byIndex { + errs = errs.Also(apis.ErrMultipleOneOf("name").ViaIndex(i)) + } else { + errs = errs.Also(apis.ErrMultipleOneOf("name").ViaKey(n)) + } } - seen.Insert(p.Name) + seen.Insert(n) } return errs } diff --git a/pkg/apis/pipeline/v1beta1/taskrun_validation_test.go b/pkg/apis/pipeline/v1beta1/taskrun_validation_test.go index 333a73dd2a6..c9e08489df9 100644 --- a/pkg/apis/pipeline/v1beta1/taskrun_validation_test.go +++ b/pkg/apis/pipeline/v1beta1/taskrun_validation_test.go @@ -26,6 +26,7 @@ import ( resource "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1" "github.com/tektoncd/pipeline/test/diff" corev1 "k8s.io/api/core/v1" + corev1resources "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/apis" ) @@ -100,6 +101,27 @@ func TestTaskRun_Validate(t *testing.T) { }, }, wc: enableAlphaAPIFields, + }, { + name: "alpha feature: valid step and sidecar overrides", + taskRun: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{Name: "tr"}, + Spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{Name: "task"}, + StepOverrides: []v1beta1.TaskRunStepOverride{{ + Name: "foo", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }, + }}, + SidecarOverrides: []v1beta1.TaskRunSidecarOverride{{ + Name: "bar", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }, + }}, + }, + }, + wc: enableAlphaAPIFields, }} for _, ts := range tests { t.Run(ts.name, func(t *testing.T) { @@ -346,6 +368,94 @@ func TestTaskRunSpec_Invalidate(t *testing.T) { }, wantErr: apis.ErrMultipleOneOf("bundle", "resolver").ViaField("taskRef"), wc: enableAlphaAPIFields, + }, { + name: "stepOverride disallowed without alpha feature gate", + spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{ + Name: "foo", + }, + StepOverrides: []v1beta1.TaskRunStepOverride{{ + Name: "foo", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }, + }}, + }, + wantErr: apis.ErrGeneric("stepOverrides requires \"enable-api-fields\" feature gate to be \"alpha\" but it is \"stable\""), + }, { + name: "sidecarOverride disallowed without alpha feature gate", + spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{ + Name: "foo", + }, + SidecarOverrides: []v1beta1.TaskRunSidecarOverride{{ + Name: "foo", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }, + }}, + }, + wantErr: apis.ErrGeneric("sidecarOverrides requires \"enable-api-fields\" feature gate to be \"alpha\" but it is \"stable\""), + }, { + name: "duplicate stepOverride names", + spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{Name: "task"}, + StepOverrides: []v1beta1.TaskRunStepOverride{{ + Name: "foo", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }, + }, { + Name: "foo", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }, + }}, + }, + wantErr: apis.ErrMultipleOneOf("stepOverrides[1].name"), + wc: enableAlphaAPIFields, + }, { + name: "missing stepOverride names", + spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{Name: "task"}, + StepOverrides: []v1beta1.TaskRunStepOverride{{ + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }, + }}, + }, + wantErr: apis.ErrMissingField("stepOverrides[0].name"), + wc: enableAlphaAPIFields, + }, { + name: "duplicate sidecarOverride names", + spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{Name: "task"}, + SidecarOverrides: []v1beta1.TaskRunSidecarOverride{{ + Name: "bar", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }, + }, { + Name: "bar", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }, + }}, + }, + wantErr: apis.ErrMultipleOneOf("sidecarOverrides[1].name"), + wc: enableAlphaAPIFields, + }, { + name: "missing sidecarOverride names", + spec: v1beta1.TaskRunSpec{ + TaskRef: &v1beta1.TaskRef{Name: "task"}, + SidecarOverrides: []v1beta1.TaskRunSidecarOverride{{ + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceMemory: corev1resources.MustParse("1Gi")}, + }, + }}, + }, + wantErr: apis.ErrMissingField("sidecarOverrides[0].name"), + wc: enableAlphaAPIFields, }} for _, ts := range tests { t.Run(ts.name, func(t *testing.T) { diff --git a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go index e72995598c5..d11aa17f9b4 100644 --- a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go @@ -1132,6 +1132,20 @@ func (in *PipelineTaskRunSpec) DeepCopyInto(out *PipelineTaskRunSpec) { *out = new(pod.Template) (*in).DeepCopyInto(*out) } + if in.StepOverrides != nil { + in, out := &in.StepOverrides, &out.StepOverrides + *out = make([]TaskRunStepOverride, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SidecarOverrides != nil { + in, out := &in.SidecarOverrides, &out.SidecarOverrides + *out = make([]TaskRunSidecarOverride, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -1659,6 +1673,23 @@ func (in *TaskRunResult) DeepCopy() *TaskRunResult { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskRunSidecarOverride) DeepCopyInto(out *TaskRunSidecarOverride) { + *out = *in + in.Resources.DeepCopyInto(&out.Resources) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskRunSidecarOverride. +func (in *TaskRunSidecarOverride) DeepCopy() *TaskRunSidecarOverride { + if in == nil { + return nil + } + out := new(TaskRunSidecarOverride) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TaskRunSpec) DeepCopyInto(out *TaskRunSpec) { *out = *in @@ -1706,6 +1737,20 @@ func (in *TaskRunSpec) DeepCopyInto(out *TaskRunSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.StepOverrides != nil { + in, out := &in.StepOverrides, &out.StepOverrides + *out = make([]TaskRunStepOverride, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.SidecarOverrides != nil { + in, out := &in.SidecarOverrides, &out.SidecarOverrides + *out = make([]TaskRunSidecarOverride, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -1806,6 +1851,23 @@ func (in *TaskRunStatusFields) DeepCopy() *TaskRunStatusFields { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TaskRunStepOverride) DeepCopyInto(out *TaskRunStepOverride) { + *out = *in + in.Resources.DeepCopyInto(&out.Resources) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskRunStepOverride. +func (in *TaskRunStepOverride) DeepCopy() *TaskRunStepOverride { + if in == nil { + return nil + } + out := new(TaskRunStepOverride) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TaskSpec) DeepCopyInto(out *TaskSpec) { *out = *in