From 2fcbfae2978b4437a89dbfb97a936f4340810e7a Mon Sep 17 00:00:00 2001 From: NikeNano Date: Tue, 14 Apr 2020 14:03:26 +0200 Subject: [PATCH] Add functionality to set task run spec Currently it is not possible to set task run specs on each individual tasks. This PR aims to fix that and give the user more flexibility to set podTemplate for each task. Co-Authored-By: Daniel Helfand --- docs/pipelineruns.md | 23 ++++ .../pipelinerun-taskrunspecs.yaml | 86 ++++++++++++++ .../pipeline/v1alpha1/pipelinerun_types.go | 24 ++++ .../v1alpha1/pipelinerun_types_test.go | 107 ++++++++++++++++++ .../v1alpha1/zz_generated.deepcopy.go | 28 +++++ .../pipeline/v1beta1/pipelinerun_types.go | 25 ++++ .../v1beta1/pipelinerun_types_test.go | 107 ++++++++++++++++++ .../pipeline/v1beta1/zz_generated.deepcopy.go | 28 +++++ pkg/reconciler/pipelinerun/pipelinerun.go | 6 +- 9 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 examples/v1beta1/pipelineruns/pipelinerun-taskrunspecs.yaml diff --git a/docs/pipelineruns.md b/docs/pipelineruns.md index 5a3cab00a0b..fbd865294d8 100644 --- a/docs/pipelineruns.md +++ b/docs/pipelineruns.md @@ -13,6 +13,7 @@ weight: 4 - [Speciying `Parameters`](#specifying-parameters) - [Specifying custom `ServiceAccount` credentials](#specifying-custom-serviceaccount-credentials) - [Mapping `ServiceAccount` credentials to `Tasks`](#mapping-serviceaccount-credentials-to-tasks) + - [Specifying `TaskRunSpecs`](#specifying-task-run-specs) - [Specifying a `Pod` template](#specifying-a-pod-template) - [Specifying `Workspaces`](#specifying-workspaces) - [Specifying `LimitRange` values](#specifying-limitrange-values) @@ -58,6 +59,7 @@ A `PipelineRun` definition supports the following fields: object that supplies specific execution credentials for the `Pipeline`. - [`serviceAccountNames`](#mapping-serviceaccount-credentials-to-tasks) - Maps specific `serviceAccountName` values to `Tasks` in the `Pipeline`. This overrides the credentials set for the entire `Pipeline`. + - [`taskRunSpec`](#specifying-task-run-specs) - Specifies a list of `PipelineRunTaskSpec` which allows for setting `ServiceAccountName` and [`Pod` template](./podtemplates.md) for each task. This overrides the `Pod` template set for the entire `Pipeline`. - [`timeout`](#configuring-a-failure-timeout) - Specifies the timeout before the `PipelineRun` fails. - [`podTemplate`](#pod-template) - Specifies a [`Pod` template](./podtemplates.md) to use as the basis for the configuration of the `Pod` that executes each `Task`. @@ -357,3 +359,24 @@ Except as otherwise noted, the content of this page is licensed under the [Creative Commons Attribution 4.0 License](https://creativecommons.org/licenses/by/4.0/), and code samples are licensed under the [Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0). + +## Specifying task run specs + +Specifies a list of `PipelineRunTaskSpec` which contains `TaskServiceAccountName`,`TaskPodTemplate` and `TaskName`. Mapping the specs to the corresponding `Task` based upon the `TaskName` a PipelineTask will run with the configured `TaskServiceAccountName` and `TaskPodTemplate` overwriting the pipeline wide [`ServiceAccountName`](#service-account) and [`podTemplate`](#pod-template) configuration, for example: + +```yaml +spec: + podTemplate: + securityContext: + runAsUser: 1000 + runAsGroup: 2000 + fsGroup: 3000 + taskRunSpecs: + - taskName: build-task + taskServiceAccountName: sa-for-build + taskPodTemplate: + nodeSelector: + disktype: ssd +``` + +If used with this `Pipeline`, `build-task` will use the task specific pod template (where `nodeSelector` has `disktype` equal to `ssd`). diff --git a/examples/v1beta1/pipelineruns/pipelinerun-taskrunspecs.yaml b/examples/v1beta1/pipelineruns/pipelinerun-taskrunspecs.yaml new file mode 100644 index 00000000000..3e0eece8d4f --- /dev/null +++ b/examples/v1beta1/pipelineruns/pipelinerun-taskrunspecs.yaml @@ -0,0 +1,86 @@ +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: add-task-taskspec +spec: + params: + - name: first + description: the first operand + - name: second + description: the second operand + results: + - name: sum + description: the sum of the first and second operand + steps: + - name: add + image: alpine + env: + - name: OP1 + value: $(params.first) + - name: OP2 + value: $(params.second) + command: ["/bin/sh", "-c"] + args: + - echo -n $((${OP1}+${OP2})) | tee $(results.sum.path); +--- +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: add-pipeline-taskspec +spec: + params: + - name: first + description: the first operand + - name: second + description: the second operand + - name: third + description: the third operand + tasks: + - name: first-add-taskspec + taskRef: + name: add-task-taskspec + params: + - name: first + value: $(params.first) + - name: second + value: $(params.second) + - name: second-add-taskspec + taskRef: + name: add-task-taskspec + params: + - name: first + value: $(tasks.first-add-taskspec.results.sum) + - name: second + value: $(params.third) + results: + - name: sum + description: the sum of all three operands + value: $(tasks.second-add-taskspec.results.sum) + - name: partial-sum + description: the sum of first two operands + value: $(tasks.first-add-taskspec.results.sum) + - name: all-sum + description: the sum of everything + value: $(tasks.second-add-taskspec.results.sum)-$(tasks.first-add-taskspec.results.sum) +--- +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + name: task-spec-pipeline +spec: + pipelineRef: + name: add-pipeline-taskspec + taskRunSpecs: + - pipelineTaskName: first-add-taskspec + taskServiceAccountName: 'default' + - pipelineTaskName: second-add-taskspec + taskPodTemplate: + nodeSelector: + disktype: ssd + params: + - name: first + value: "2" + - name: second + value: "10" + - name: third + value: "10" diff --git a/pkg/apis/pipeline/v1alpha1/pipelinerun_types.go b/pkg/apis/pipeline/v1alpha1/pipelinerun_types.go index 7dee51a3461..57b62d25c42 100644 --- a/pkg/apis/pipeline/v1alpha1/pipelinerun_types.go +++ b/pkg/apis/pipeline/v1alpha1/pipelinerun_types.go @@ -91,6 +91,9 @@ type PipelineRunSpec struct { // with those declared in the pipeline. // +optional Workspaces []WorkspaceBinding `json:"workspaces,omitempty"` + // TaskRunSpecs holds a set of task specific specs + // +optional + TaskRunSpecs []PipelineTaskRunSpec `json:"taskRunSpecs,omitempty"` } // PipelineRunSpecStatus defines the pipelinerun spec status the user can provide @@ -217,3 +220,24 @@ func (pr *PipelineRun) HasVolumeClaimTemplate() bool { } return false } + +// PipelineTaskRunSpec holds task specific specs +type PipelineTaskRunSpec struct { + PipelineTaskName string `json:"pipelineTaskName,omitempty"` + TaskServiceAccountName string `json:"taskServiceAccountName,omitempty"` + TaskPodTemplate *PodTemplate `json:"taskPodTemplate,omitempty"` +} + +// GetTaskRunSpecs returns the task specific spec for a given +// PipelineTask if configured, otherwise it returns the PipelineRun's default. +func (pr *PipelineRun) GetTaskRunSpecs(pipelineTaskName string) (string, *PodTemplate) { + serviceAccountName := pr.GetServiceAccountName(pipelineTaskName) + taskPodTemplate := pr.Spec.PodTemplate + for _, task := range pr.Spec.TaskRunSpecs { + if task.PipelineTaskName == pipelineTaskName { + taskPodTemplate = task.TaskPodTemplate + serviceAccountName = task.TaskServiceAccountName + } + } + return serviceAccountName, taskPodTemplate +} diff --git a/pkg/apis/pipeline/v1alpha1/pipelinerun_types_test.go b/pkg/apis/pipeline/v1alpha1/pipelinerun_types_test.go index 3f43b877856..a81e5bfd4c4 100644 --- a/pkg/apis/pipeline/v1alpha1/pipelinerun_types_test.go +++ b/pkg/apis/pipeline/v1alpha1/pipelinerun_types_test.go @@ -282,3 +282,110 @@ func TestPipelineRunGetServiceAccountName(t *testing.T) { } } } + +func TestPipelineRunGetPodSpecSABackcompatibility(t *testing.T) { + for _, tt := range []struct { + name string + pr *v1alpha1.PipelineRun + expectedSAs map[string]string + }{ + { + name: "test backward compatibility", + pr: &v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: "pr"}, + Spec: v1alpha1.PipelineRunSpec{ + PipelineRef: &v1alpha1.PipelineRef{Name: "prs"}, + ServiceAccountName: "defaultSA", + ServiceAccountNames: []v1alpha1.PipelineRunSpecServiceAccountName{{ + TaskName: "taskName", ServiceAccountName: "taskSA", + }}, + TaskRunSpecs: []v1alpha1.PipelineTaskRunSpec{{ + PipelineTaskName: "taskName", + TaskServiceAccountName: "newTaskSA", + }}, + }, + }, + expectedSAs: map[string]string{ + "unknown": "defaultSA", + "taskName": "newTaskSA", + }, + }, + { + name: "mixed default SA backward compatibility", + pr: &v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: "pr"}, + Spec: v1alpha1.PipelineRunSpec{ + PipelineRef: &v1alpha1.PipelineRef{Name: "prs"}, + ServiceAccountName: "defaultSA", + TaskRunSpecs: []v1alpha1.PipelineTaskRunSpec{{ + PipelineTaskName: "taskNameOne", + TaskServiceAccountName: "TaskSAOne", + }, { + PipelineTaskName: "taskNameTwo", + TaskServiceAccountName: "newTaskTwo", + }}, + }, + }, + expectedSAs: map[string]string{ + "unknown": "defaultSA", + "taskNameOne": "TaskSAOne", + "taskNameTwo": "newTaskTwo", + }, + }, + } { + for taskName, expected := range tt.expectedSAs { + t.Run(tt.name, func(t *testing.T) { + sa, _ := tt.pr.GetTaskRunSpecs(taskName) + if expected != sa { + t.Errorf("%s: wrong service account: got: %v, want: %v", tt.name, sa, expected) + } + }) + } + } +} + +func TestPipelineRunGetPodSpec(t *testing.T) { + for _, tt := range []struct { + name string + pr *v1alpha1.PipelineRun + expectedPodTemplates map[string][]string + }{ + { + name: "mix default and none default", + pr: &v1alpha1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: "pr"}, + Spec: v1alpha1.PipelineRunSpec{ + PodTemplate: &v1alpha1.PodTemplate{SchedulerName: "scheduleTest"}, + PipelineRef: &v1alpha1.PipelineRef{Name: "prs"}, + ServiceAccountName: "defaultSA", + TaskRunSpecs: []v1alpha1.PipelineTaskRunSpec{{ + PipelineTaskName: "taskNameOne", + TaskServiceAccountName: "TaskSAOne", + TaskPodTemplate: &v1alpha1.PodTemplate{SchedulerName: "scheduleTestOne"}, + }, { + PipelineTaskName: "taskNameTwo", + TaskServiceAccountName: "newTaskTwo", + TaskPodTemplate: &v1alpha1.PodTemplate{SchedulerName: "scheduleTestTwo"}, + }}, + }, + }, + expectedPodTemplates: map[string][]string{ + "unknown": {"scheduleTest", "defaultSA"}, + "taskNameOne": {"scheduleTestOne", "TaskSAOne"}, + "taskNameTwo": {"scheduleTestTwo", "newTaskTwo"}, + }, + }, + } { + for taskName, values := range tt.expectedPodTemplates { + t.Run(tt.name, func(t *testing.T) { + sa, taskPodTemplate := tt.pr.GetTaskRunSpecs(taskName) + if values[0] != taskPodTemplate.SchedulerName { + t.Errorf("%s: wrong task podtemplate scheduler name: got: %v, want: %v", tt.name, taskPodTemplate.SchedulerName, values[0]) + } + if values[1] != sa { + t.Errorf("%s: wrong service account: got: %v, want: %v", tt.name, sa, values[1]) + } + }) + } + } +} diff --git a/pkg/apis/pipeline/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1alpha1/zz_generated.deepcopy.go index 0b3fe44bc8b..881a01da0e7 100644 --- a/pkg/apis/pipeline/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/pipeline/v1alpha1/zz_generated.deepcopy.go @@ -426,6 +426,13 @@ func (in *PipelineRunSpec) DeepCopyInto(out *PipelineRunSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.TaskRunSpecs != nil { + in, out := &in.TaskRunSpecs, &out.TaskRunSpecs + *out = make([]PipelineTaskRunSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -582,6 +589,27 @@ func (in PipelineTaskList) DeepCopy() PipelineTaskList { return *out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PipelineTaskRunSpec) DeepCopyInto(out *PipelineTaskRunSpec) { + *out = *in + if in.TaskPodTemplate != nil { + in, out := &in.TaskPodTemplate, &out.TaskPodTemplate + *out = new(pod.Template) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineTaskRunSpec. +func (in *PipelineTaskRunSpec) DeepCopy() *PipelineTaskRunSpec { + if in == nil { + return nil + } + out := new(PipelineTaskRunSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Task) DeepCopyInto(out *Task) { *out = *in diff --git a/pkg/apis/pipeline/v1beta1/pipelinerun_types.go b/pkg/apis/pipeline/v1beta1/pipelinerun_types.go index a850f4f01db..16654e6c39a 100644 --- a/pkg/apis/pipeline/v1beta1/pipelinerun_types.go +++ b/pkg/apis/pipeline/v1beta1/pipelinerun_types.go @@ -168,6 +168,9 @@ type PipelineRunSpec struct { // with those declared in the pipeline. // +optional Workspaces []WorkspaceBinding `json:"workspaces,omitempty"` + // TaskRunSpecs holds a set of runtime specs + // +optional + TaskRunSpecs []PipelineTaskRunSpec `json:"taskRunSpecs,omitempty"` } // PipelineRunSpecStatus defines the pipelinerun spec status the user can provide @@ -313,3 +316,25 @@ type PipelineRunList struct { type PipelineTaskRun struct { Name string `json:"name,omitempty"` } + +// 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"` +} + +// GetTaskRunSpecs returns the task specific spec for a given +// PipelineTask if configured, otherwise it returns the PipelineRun's default. +func (pr *PipelineRun) GetTaskRunSpecs(pipelineTaskName string) (string, *PodTemplate) { + serviceAccountName := pr.GetServiceAccountName(pipelineTaskName) + taskPodTemplate := pr.Spec.PodTemplate + for _, task := range pr.Spec.TaskRunSpecs { + if task.PipelineTaskName == pipelineTaskName { + taskPodTemplate = task.TaskPodTemplate + serviceAccountName = task.TaskServiceAccountName + } + } + return serviceAccountName, taskPodTemplate +} diff --git a/pkg/apis/pipeline/v1beta1/pipelinerun_types_test.go b/pkg/apis/pipeline/v1beta1/pipelinerun_types_test.go index f2844ca3e1e..6b15b0c226f 100644 --- a/pkg/apis/pipeline/v1beta1/pipelinerun_types_test.go +++ b/pkg/apis/pipeline/v1beta1/pipelinerun_types_test.go @@ -295,3 +295,110 @@ func TestPipelineRunGetServiceAccountName(t *testing.T) { } } } + +func TestPipelineRunGetPodSpecSABackcompatibility(t *testing.T) { + for _, tt := range []struct { + name string + pr *v1beta1.PipelineRun + expectedSAs map[string]string + }{ + { + name: "test backward compatibility", + pr: &v1beta1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: "pr"}, + Spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "prs"}, + ServiceAccountName: "defaultSA", + ServiceAccountNames: []v1beta1.PipelineRunSpecServiceAccountName{{ + TaskName: "taskName", ServiceAccountName: "taskSA", + }}, + TaskRunSpecs: []v1beta1.PipelineTaskRunSpec{{ + PipelineTaskName: "taskName", + TaskServiceAccountName: "newTaskSA", + }}, + }, + }, + expectedSAs: map[string]string{ + "unknown": "defaultSA", + "taskName": "newTaskSA", + }, + }, + { + name: "mixed default SA backward compatibility", + pr: &v1beta1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: "pr"}, + Spec: v1beta1.PipelineRunSpec{ + PipelineRef: &v1beta1.PipelineRef{Name: "prs"}, + ServiceAccountName: "defaultSA", + TaskRunSpecs: []v1beta1.PipelineTaskRunSpec{{ + PipelineTaskName: "taskNameOne", + TaskServiceAccountName: "TaskSAOne", + }, { + PipelineTaskName: "taskNameTwo", + TaskServiceAccountName: "newTaskTwo", + }}, + }, + }, + expectedSAs: map[string]string{ + "unknown": "defaultSA", + "taskNameOne": "TaskSAOne", + "taskNameTwo": "newTaskTwo", + }, + }, + } { + for taskName, expected := range tt.expectedSAs { + t.Run(tt.name, func(t *testing.T) { + sa, _ := tt.pr.GetTaskRunSpecs(taskName) + if expected != sa { + t.Errorf("%s: wrong service account: got: %v, want: %v", tt.name, sa, expected) + } + }) + } + } +} + +func TestPipelineRunGetPodSpec(t *testing.T) { + for _, tt := range []struct { + name string + pr *v1beta1.PipelineRun + expectedPodTemplates map[string][]string + }{ + { + name: "mix default and none default", + pr: &v1beta1.PipelineRun{ + ObjectMeta: metav1.ObjectMeta{Name: "pr"}, + Spec: v1beta1.PipelineRunSpec{ + PodTemplate: &v1beta1.PodTemplate{SchedulerName: "scheduleTest"}, + PipelineRef: &v1beta1.PipelineRef{Name: "prs"}, + ServiceAccountName: "defaultSA", + TaskRunSpecs: []v1beta1.PipelineTaskRunSpec{{ + PipelineTaskName: "taskNameOne", + TaskServiceAccountName: "TaskSAOne", + TaskPodTemplate: &v1beta1.PodTemplate{SchedulerName: "scheduleTestOne"}, + }, { + PipelineTaskName: "taskNameTwo", + TaskServiceAccountName: "newTaskTwo", + TaskPodTemplate: &v1beta1.PodTemplate{SchedulerName: "scheduleTestTwo"}, + }}, + }, + }, + expectedPodTemplates: map[string][]string{ + "unknown": {"scheduleTest", "defaultSA"}, + "taskNameOne": {"scheduleTestOne", "TaskSAOne"}, + "taskNameTwo": {"scheduleTestTwo", "newTaskTwo"}, + }, + }, + } { + for taskName, values := range tt.expectedPodTemplates { + t.Run(tt.name, func(t *testing.T) { + sa, taskPodTemplate := tt.pr.GetTaskRunSpecs(taskName) + if values[0] != taskPodTemplate.SchedulerName { + t.Errorf("%s: wrong task podtemplate scheduler name: got: %v, want: %v", tt.name, taskPodTemplate.SchedulerName, values[0]) + } + if values[1] != sa { + t.Errorf("%s: wrong service account: got: %v, want: %v", tt.name, sa, values[1]) + } + }) + } + } +} diff --git a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go index 40fc616861b..67bd84b41d6 100644 --- a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go @@ -594,6 +594,13 @@ func (in *PipelineRunSpec) DeepCopyInto(out *PipelineRunSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.TaskRunSpecs != nil { + in, out := &in.TaskRunSpecs, &out.TaskRunSpecs + *out = make([]PipelineTaskRunSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -980,6 +987,27 @@ func (in *PipelineTaskRun) DeepCopy() *PipelineTaskRun { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PipelineTaskRunSpec) DeepCopyInto(out *PipelineTaskRunSpec) { + *out = *in + if in.TaskPodTemplate != nil { + in, out := &in.TaskPodTemplate, &out.TaskPodTemplate + *out = new(pod.Template) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PipelineTaskRunSpec. +func (in *PipelineTaskRunSpec) DeepCopy() *PipelineTaskRunSpec { + if in == nil { + return nil + } + out := new(PipelineTaskRunSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResultRef) DeepCopyInto(out *ResultRef) { *out = *in diff --git a/pkg/reconciler/pipelinerun/pipelinerun.go b/pkg/reconciler/pipelinerun/pipelinerun.go index e297cf8d100..6d261a1a5b3 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun.go +++ b/pkg/reconciler/pipelinerun/pipelinerun.go @@ -910,7 +910,7 @@ func (c *Reconciler) makeConditionCheckContainer(rprt *resources.ResolvedPipelin if err != nil { return nil, fmt.Errorf("failed to get TaskSpec from Condition: %w", err) } - + serviceAccountName, podtemplate := pr.GetTaskRunSpecs(rprt.PipelineTask.Name) tr := &v1alpha1.TaskRun{ ObjectMeta: metav1.ObjectMeta{ Name: rcc.ConditionCheckName, @@ -921,13 +921,13 @@ func (c *Reconciler) makeConditionCheckContainer(rprt *resources.ResolvedPipelin }, Spec: v1alpha1.TaskRunSpec{ TaskSpec: taskSpec, - ServiceAccountName: pr.GetServiceAccountName(rprt.PipelineTask.Name), + ServiceAccountName: serviceAccountName, Params: rcc.PipelineTaskCondition.Params, Resources: &v1beta1.TaskRunResources{ Inputs: rcc.ToTaskResourceBindings(), }, Timeout: getTaskRunTimeout(pr, rprt), - PodTemplate: pr.Spec.PodTemplate, + PodTemplate: podtemplate, }} cctr, err := c.PipelineClientSet.TektonV1alpha1().TaskRuns(pr.Namespace).Create(tr)