From af3563b8028d7880cb59612988d5649ca7a31ea1 Mon Sep 17 00:00:00 2001 From: Jerop Date: Mon, 27 Jun 2022 15:23:06 -0400 Subject: [PATCH] TEP-0090: Fan Out `Runs` [TEP-0090: Matrix][tep-0090] proposed executing a `PipelineTask` in parallel `TaskRuns` and `Runs` with substitutions from combinations of `Parameters` in a `Matrix`. This change implements the fan out of `Runs` from a `PipelineTask` with a `Matrix`. The fanned-out `Runs` are executed in parallel. [tep-0090]: https://github.com/tektoncd/community/blob/main/teps/0090-matrix.md --- docs/matrix.md | 150 ++++++ pkg/reconciler/pipelinerun/pipelinerun.go | 37 +- .../pipelinerun/pipelinerun_test.go | 501 ++++++++++++++++++ .../resources/pipelinerunresolution.go | 4 +- .../resources/pipelinerunresolution_test.go | 24 +- .../pipelinerun/resources/pipelinerunstate.go | 4 +- .../resources/pipelinerunstate_test.go | 94 ++++ 7 files changed, 802 insertions(+), 12 deletions(-) diff --git a/docs/matrix.md b/docs/matrix.md index 766c2d7b26f..68cd41b4b4d 100644 --- a/docs/matrix.md +++ b/docs/matrix.md @@ -17,6 +17,7 @@ weight: 11 - [Results from fanned out PipelineTasks](#results-from-fanned-out-pipelinetasks) - [Fan Out](#fan-out) - [`PipelineTasks` with `Tasks`](#pipelinetasks-with-tasks) + - [`PipelineTasks` with `Custom Tasks`](#pipelinetasks-with-custom-tasks) ## Overview @@ -307,4 +308,153 @@ status: To execute this example yourself, run [`PipelineRun` with `Matrix`][pr-with-matrix]. +### `PipelineTasks` with `Custom Tasks` + +When a `PipelineTask` has a `Custom Task` and a `Matrix`, the `Custom Task` will be executed in parallel `Runs` with +substitutions from combinations of `Parameters`. + +In the example below, eight `Runs` are created with combinations of CEL expressions, using the [CEL `Custom Task`][cel]. + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: matrixed-pr- +spec: + serviceAccountName: 'default' + pipelineSpec: + tasks: + - name: platforms-and-browsers + matrix: + - name: type + value: + - "type(1)" + - "type(1.0)" + - name: colors + value: + - "{'blue': '0x000080', 'red': '0xFF0000'}['blue']" + - "{'blue': '0x000080', 'red': '0xFF0000'}['red']" + - name: bool + value: + - "type(1) == int" + - "{'blue': '0x000080', 'red': '0xFF0000'}['red'] == '0xFF0000'" + taskRef: + apiVersion: cel.tekton.dev/v1alpha1 + kind: CEL +``` + +When the above `PipelineRun` is executed, these `Runs` are created: + +```shell +$ k get run.tekton.dev + +NAME SUCCEEDED REASON STARTTIME COMPLETIONTIME +matrixed-pr-4djw9-platforms-and-browsers-0 True EvaluationSuccess 10s 10s +matrixed-pr-4djw9-platforms-and-browsers-1 True EvaluationSuccess 10s 10s +matrixed-pr-4djw9-platforms-and-browsers-2 True EvaluationSuccess 10s 10s +matrixed-pr-4djw9-platforms-and-browsers-3 True EvaluationSuccess 9s 9s +matrixed-pr-4djw9-platforms-and-browsers-4 True EvaluationSuccess 9s 9s +matrixed-pr-4djw9-platforms-and-browsers-5 True EvaluationSuccess 9s 9s +matrixed-pr-4djw9-platforms-and-browsers-6 True EvaluationSuccess 9s 9s +matrixed-pr-4djw9-platforms-and-browsers-7 True EvaluationSuccess 9s 9s +``` + +When the above `PipelineRun` is executed, its status is populated with `ChildReferences` of the above `Runs`. The +`PipelineRun` status tracks the status of all the fanned out `Runs`. This is the `PipelineRun` after completing: + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: matrixed-pr- + labels: + tekton.dev/pipeline: matrixed-pr-4djw9 + name: matrixed-pr-4djw9 + namespace: default +spec: + pipelineSpec: + tasks: + - matrix: + - name: type + value: + - type(1) + - type(1.0) + - name: colors + value: + - '{''blue'': ''0x000080'', ''red'': ''0xFF0000''}[''blue'']' + - '{''blue'': ''0x000080'', ''red'': ''0xFF0000''}[''red'']' + - name: bool + value: + - type(1) == int + - '{''blue'': ''0x000080'', ''red'': ''0xFF0000''}[''red''] == ''0xFF0000''' + name: platforms-and-browsers + taskRef: + apiVersion: cel.tekton.dev/v1alpha1 + kind: CEL + serviceAccountName: default + timeout: 1h0m0s +status: + pipelineSpec: + tasks: + - matrix: + - name: type + value: + - type(1) + - type(1.0) + - name: colors + value: + - '{''blue'': ''0x000080'', ''red'': ''0xFF0000''}[''blue'']' + - '{''blue'': ''0x000080'', ''red'': ''0xFF0000''}[''red'']' + - name: bool + value: + - type(1) == int + - '{''blue'': ''0x000080'', ''red'': ''0xFF0000''}[''red''] == ''0xFF0000''' + name: platforms-and-browsers + taskRef: + apiVersion: cel.tekton.dev/v1alpha1 + kind: CEL + startTime: "2022-06-28T20:49:40Z" + completionTime: "2022-06-28T20:49:41Z" + conditions: + - lastTransitionTime: "2022-06-28T20:49:41Z" + message: 'Tasks Completed: 1 (Failed: 0, Cancelled 0), Skipped: 0' + reason: Succeeded + status: "True" + type: Succeeded + childReferences: + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: matrixed-pr-4djw9-platforms-and-browsers-1 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: matrixed-pr-4djw9-platforms-and-browsers-2 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: matrixed-pr-4djw9-platforms-and-browsers-3 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: matrixed-pr-4djw9-platforms-and-browsers-4 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: matrixed-pr-4djw9-platforms-and-browsers-5 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: matrixed-pr-4djw9-platforms-and-browsers-6 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: matrixed-pr-4djw9-platforms-and-browsers-7 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: matrixed-pr-4djw9-platforms-and-browsers-0 + pipelineTaskName: platforms-and-browsers +``` + +[cel]: https://github.com/tektoncd/experimental/tree/1609827ea81d05c8d00f8933c5c9d6150cd36989/cel [pr-with-matrix]: ../examples/v1beta1/pipelineruns/alpha/pipelinerun-with-matrix.yaml \ No newline at end of file diff --git a/pkg/reconciler/pipelinerun/pipelinerun.go b/pkg/reconciler/pipelinerun/pipelinerun.go index 450a6fdffcb..6d50cbcd379 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun.go +++ b/pkg/reconciler/pipelinerun/pipelinerun.go @@ -689,11 +689,21 @@ func (c *Reconciler) runNextSchedulableTask(ctx context.Context, pr *v1beta1.Pip continue } switch { + case rpt.IsCustomTask() && rpt.IsMatrixed(): + if rpt.IsFinalTask(pipelineRunFacts) { + rpt.Runs, err = c.createRuns(ctx, rpt, pr, getFinallyTaskRunTimeout) + } else { + rpt.Runs, err = c.createRuns(ctx, rpt, pr, getTaskRunTimeout) + } + if err != nil { + recorder.Eventf(pr, corev1.EventTypeWarning, "RunsCreationFailed", "Failed to create Runs %q: %v", rpt.RunNames, err) + return fmt.Errorf("error creating Runs called %s for PipelineTask %s from PipelineRun %s: %w", rpt.RunNames, rpt.PipelineTask.Name, pr.Name, err) + } case rpt.IsCustomTask(): if rpt.IsFinalTask(pipelineRunFacts) { - rpt.Run, err = c.createRun(ctx, rpt, pr, getFinallyTaskRunTimeout) + rpt.Run, err = c.createRun(ctx, rpt.RunName, nil, rpt, pr, getFinallyTaskRunTimeout) } else { - rpt.Run, err = c.createRun(ctx, rpt, pr, getTaskRunTimeout) + rpt.Run, err = c.createRun(ctx, rpt.RunName, nil, rpt, pr, getTaskRunTimeout) } if err != nil { recorder.Eventf(pr, corev1.EventTypeWarning, "RunCreationFailed", "Failed to create Run %q: %v", rpt.RunName, err) @@ -836,12 +846,27 @@ func (c *Reconciler) createTaskRun(ctx context.Context, taskRunName string, para return c.PipelineClientSet.TektonV1beta1().TaskRuns(pr.Namespace).Create(ctx, tr, metav1.CreateOptions{}) } -func (c *Reconciler) createRun(ctx context.Context, rpt *resources.ResolvedPipelineTask, pr *v1beta1.PipelineRun, getTimeoutFunc getTimeoutFunc) (*v1alpha1.Run, error) { +func (c *Reconciler) createRuns(ctx context.Context, rpt *resources.ResolvedPipelineTask, pr *v1beta1.PipelineRun, getTimeoutFunc getTimeoutFunc) ([]*v1alpha1.Run, error) { + var runs []*v1alpha1.Run + matrixCombinations := matrix.FanOut(rpt.PipelineTask.Matrix).ToMap() + for i, runName := range rpt.RunNames { + params := matrixCombinations[strconv.Itoa(i)] + run, err := c.createRun(ctx, runName, params, rpt, pr, getTimeoutFunc) + if err != nil { + return nil, err + } + runs = append(runs, run) + } + return runs, nil +} + +func (c *Reconciler) createRun(ctx context.Context, runName string, params []v1beta1.Param, rpt *resources.ResolvedPipelineTask, pr *v1beta1.PipelineRun, getTimeoutFunc getTimeoutFunc) (*v1alpha1.Run, error) { logger := logging.FromContext(ctx) taskRunSpec := pr.GetTaskRunSpec(rpt.PipelineTask.Name) + params = append(params, rpt.PipelineTask.Params...) r := &v1alpha1.Run{ ObjectMeta: metav1.ObjectMeta{ - Name: rpt.RunName, + Name: runName, Namespace: pr.Namespace, OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(pr)}, Labels: getTaskrunLabels(pr, rpt.PipelineTask.Name, true), @@ -850,7 +875,7 @@ func (c *Reconciler) createRun(ctx context.Context, rpt *resources.ResolvedPipel Spec: v1alpha1.RunSpec{ Retries: rpt.PipelineTask.Retries, Ref: rpt.PipelineTask.TaskRef, - Params: rpt.PipelineTask.Params, + Params: params, ServiceAccountName: taskRunSpec.TaskServiceAccountName, Timeout: getTimeoutFunc(ctx, pr, rpt, c.Clock), PodTemplate: taskRunSpec.TaskPodTemplate, @@ -886,7 +911,7 @@ func (c *Reconciler) createRun(ctx context.Context, rpt *resources.ResolvedPipel r.Annotations[workspace.AnnotationAffinityAssistantName] = getAffinityAssistantName(pipelinePVCWorkspaceName, pr.Name) } - logger.Infof("Creating a new Run object %s", rpt.RunName) + logger.Infof("Creating a new Run object %s", runName) return c.PipelineClientSet.TektonV1alpha1().Runs(pr.Namespace).Create(ctx, r, metav1.CreateOptions{}) } diff --git a/pkg/reconciler/pipelinerun/pipelinerun_test.go b/pkg/reconciler/pipelinerun/pipelinerun_test.go index a90a7e45d5a..e6bf44052b4 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun_test.go +++ b/pkg/reconciler/pipelinerun/pipelinerun_test.go @@ -7970,6 +7970,507 @@ spec: } } +func TestReconciler_PipelineTaskMatrixWithCustomTask(t *testing.T) { + names.TestingSeed() + + task := parse.MustParseTask(t, ` +metadata: + name: mytask + namespace: foo +spec: + ref: + apiVersion: example.dev/v0 + kind: Example + params: + - name: platform + - name: browser + steps: + - name: echo + image: alpine + script: | + echo "$(params.platform) and $(params.browser)" +`) + + expectedRuns := []*v1alpha1.Run{ + mustParseRunWithObjectMeta(t, + taskRunObjectMeta("pr-platforms-and-browsers-0", "foo", + "pr", "p", "platforms-and-browsers", false), + ` +spec: + ref: + apiVersion: example.dev/v0 + kind: Example + params: + - name: platform + value: linux + - name: browser + value: chrome + resources: {} + serviceAccountName: test-sa + taskRef: + name: mytask + timeout: 1h0m0s +`), + mustParseRunWithObjectMeta(t, + taskRunObjectMeta("pr-platforms-and-browsers-1", "foo", + "pr", "p", "platforms-and-browsers", false), + ` +spec: + ref: + apiVersion: example.dev/v0 + kind: Example + params: + - name: platform + value: mac + - name: browser + value: chrome + resources: {} + serviceAccountName: test-sa + taskRef: + name: mytask + timeout: 1h0m0s +`), + mustParseRunWithObjectMeta(t, + taskRunObjectMeta("pr-platforms-and-browsers-2", "foo", + "pr", "p", "platforms-and-browsers", false), + ` +spec: + ref: + apiVersion: example.dev/v0 + kind: Example + params: + - name: platform + value: windows + - name: browser + value: chrome + resources: {} + serviceAccountName: test-sa + taskRef: + name: mytask + timeout: 1h0m0s +`), + mustParseRunWithObjectMeta(t, + taskRunObjectMeta("pr-platforms-and-browsers-3", "foo", + "pr", "p", "platforms-and-browsers", false), + ` +spec: + ref: + apiVersion: example.dev/v0 + kind: Example + params: + - name: platform + value: linux + - name: browser + value: safari + resources: {} + serviceAccountName: test-sa + taskRef: + name: mytask + timeout: 1h0m0s +`), + mustParseRunWithObjectMeta(t, + taskRunObjectMeta("pr-platforms-and-browsers-4", "foo", + "pr", "p", "platforms-and-browsers", false), + ` +spec: + ref: + apiVersion: example.dev/v0 + kind: Example + params: + - name: platform + value: mac + - name: browser + value: safari + resources: {} + serviceAccountName: test-sa + taskRef: + name: mytask + timeout: 1h0m0s +`), + mustParseRunWithObjectMeta(t, + taskRunObjectMeta("pr-platforms-and-browsers-5", "foo", + "pr", "p", "platforms-and-browsers", false), + ` +spec: + ref: + apiVersion: example.dev/v0 + kind: Example + params: + - name: platform + value: windows + - name: browser + value: safari + resources: {} + serviceAccountName: test-sa + taskRef: + name: mytask + timeout: 1h0m0s +`), + mustParseRunWithObjectMeta(t, + taskRunObjectMeta("pr-platforms-and-browsers-6", "foo", + "pr", "p", "platforms-and-browsers", false), + ` +spec: + ref: + apiVersion: example.dev/v0 + kind: Example + params: + - name: platform + value: linux + - name: browser + value: firefox + resources: {} + serviceAccountName: test-sa + taskRef: + name: mytask + timeout: 1h0m0s +`), + mustParseRunWithObjectMeta(t, + taskRunObjectMeta("pr-platforms-and-browsers-7", "foo", + "pr", "p", "platforms-and-browsers", false), + ` +spec: + ref: + apiVersion: example.dev/v0 + kind: Example + params: + - name: platform + value: mac + - name: browser + value: firefox + resources: {} + serviceAccountName: test-sa + taskRef: + name: mytask + timeout: 1h0m0s +`), + mustParseRunWithObjectMeta(t, + taskRunObjectMeta("pr-platforms-and-browsers-8", "foo", + "pr", "p", "platforms-and-browsers", false), + ` +spec: + ref: + apiVersion: example.dev/v0 + kind: Example + params: + - name: platform + value: windows + - name: browser + value: firefox + resources: {} + serviceAccountName: test-sa + taskRef: + name: mytask + timeout: 1h0m0s +`), + } + cms := []*corev1.ConfigMap{withEmbeddedStatus(withEnabledAlphaAPIFields(newFeatureFlagsConfigMap()), config.MinimalEmbeddedStatus)} + + tests := []struct { + name string + memberOf string + p *v1beta1.Pipeline + tr *v1beta1.TaskRun + expectedPipelineRun *v1beta1.PipelineRun + }{{ + name: "p-dag", + memberOf: "tasks", + p: parse.MustParsePipeline(t, fmt.Sprintf(` +metadata: + name: %s + namespace: foo +spec: + tasks: + - name: platforms-and-browsers + taskRef: + apiVersion: example.dev/v0 + kind: Example + matrix: + - name: platform + value: + - linux + - mac + - windows + - name: browser + value: + - chrome + - safari + - firefox +`, "p-dag")), + expectedPipelineRun: parse.MustParsePipelineRun(t, ` +metadata: + name: pr + namespace: foo + annotations: {} + labels: + tekton.dev/pipeline: p-dag +spec: + serviceAccountName: test-sa + pipelineRef: + name: p-dag +status: + pipelineSpec: + tasks: + - name: platforms-and-browsers + taskRef: + apiVersion: example.dev/v0 + kind: Example + matrix: + - name: platform + value: + - linux + - mac + - windows + - name: browser + value: + - chrome + - safari + - firefox + conditions: + - type: Succeeded + status: "Unknown" + reason: "Running" + message: "Tasks Completed: 0 (Failed: 0, Cancelled 0), Incomplete: 1, Skipped: 0" + childReferences: + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-0 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-1 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-2 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-3 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-4 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-5 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-6 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-7 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-8 + pipelineTaskName: platforms-and-browsers + taskRuns: {} + runs: {} +`), + }, { + name: "p-finally", + memberOf: "finally", + p: parse.MustParsePipeline(t, fmt.Sprintf(` +metadata: + name: %s + namespace: foo +spec: + tasks: + - name: unmatrixed-pt + params: + - name: platform + value: linux + - name: browser + value: chrome + taskRef: + name: mytask + finally: + - name: platforms-and-browsers + taskRef: + apiVersion: example.dev/v0 + kind: Example + matrix: + - name: platform + value: + - linux + - mac + - windows + - name: browser + value: + - chrome + - safari + - firefox +`, "p-finally")), + tr: mustParseTaskRunWithObjectMeta(t, + taskRunObjectMeta("pr-unmatrixed-pt", "foo", + "pr", "p-finally", "unmatrixed-pt", false), + ` +spec: + params: + - name: platform + value: linux + - name: browser + value: chrome + resources: {} + serviceAccountName: test-sa + taskRef: + name: mytask + timeout: 1h0m0s +status: + conditions: + - type: Succeeded + status: "True" + reason: Succeeded + message: All Tasks have completed executing +`), + expectedPipelineRun: parse.MustParsePipelineRun(t, ` +metadata: + name: pr + namespace: foo + annotations: {} + labels: + tekton.dev/pipeline: p-finally +spec: + serviceAccountName: test-sa + pipelineRef: + name: p-finally +status: + pipelineSpec: + tasks: + - name: unmatrixed-pt + params: + - name: platform + value: linux + - name: browser + value: chrome + taskRef: + name: mytask + finally: + - name: platforms-and-browsers + taskRef: + apiVersion: example.dev/v0 + kind: Example + matrix: + - name: platform + value: + - linux + - mac + - windows + - name: browser + value: + - chrome + - safari + - firefox + conditions: + - type: Succeeded + status: "Unknown" + reason: "Running" + message: "Tasks Completed: 1 (Failed: 0, Cancelled 0), Incomplete: 1, Skipped: 0" + childReferences: + - apiVersion: tekton.dev/v1beta1 + kind: TaskRun + name: pr-unmatrixed-pt + pipelineTaskName: unmatrixed-pt + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-0 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-1 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-2 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-3 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-4 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-5 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-6 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-7 + pipelineTaskName: platforms-and-browsers + - apiVersion: tekton.dev/v1alpha1 + kind: Run + name: pr-platforms-and-browsers-8 + pipelineTaskName: platforms-and-browsers + taskRuns: {} + runs: {} +`), + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + pr := parse.MustParsePipelineRun(t, fmt.Sprintf(` +metadata: + name: pr + namespace: foo +spec: + serviceAccountName: test-sa + pipelineRef: + name: %s +`, tt.name)) + d := test.Data{ + PipelineRuns: []*v1beta1.PipelineRun{pr}, + Pipelines: []*v1beta1.Pipeline{tt.p}, + Tasks: []*v1beta1.Task{task}, + ConfigMaps: cms, + } + if tt.tr != nil { + d.TaskRuns = []*v1beta1.TaskRun{tt.tr} + } + prt := newPipelineRunTest(d, t) + defer prt.Cancel() + + _, clients := prt.reconcileRun("foo", "pr", []string{}, false) + runs, err := clients.Pipeline.TektonV1alpha1().Runs("foo").List(prt.TestAssets.Ctx, metav1.ListOptions{ + LabelSelector: fmt.Sprintf("tekton.dev/pipelineRun=pr,tekton.dev/pipeline=%s,tekton.dev/pipelineTask=platforms-and-browsers", tt.name), + Limit: 1, + }) + if err != nil { + t.Fatalf("Failure to list TaskRun's %s", err) + } + + if len(runs.Items) != 9 { + t.Fatalf("Expected 9 TaskRuns got %d", len(runs.Items)) + } + + for i := range runs.Items { + expectedTaskRun := expectedRuns[i] + expectedTaskRun.Labels["tekton.dev/pipeline"] = tt.name + expectedTaskRun.Labels["tekton.dev/memberOf"] = tt.memberOf + if d := cmp.Diff(expectedTaskRun, &runs.Items[i], ignoreResourceVersion, ignoreTypeMeta); d != "" { + t.Errorf("expected to see TaskRun %v created. Diff %s", expectedRuns[i].Name, diff.PrintWantGot(d)) + } + } + + pipelineRun, err := clients.Pipeline.TektonV1beta1().PipelineRuns("foo").Get(prt.TestAssets.Ctx, "pr", metav1.GetOptions{}) + if err != nil { + t.Fatalf("Got an error getting reconciled run out of fake client: %s", err) + } + if d := cmp.Diff(tt.expectedPipelineRun, pipelineRun, ignoreResourceVersion, ignoreTypeMeta, ignoreLastTransitionTime, ignoreStartTime); d != "" { + t.Errorf("expected PipelineRun was not created. Diff %s", diff.PrintWantGot(d)) + } + }) + } +} + func lessTaskResourceBindings(i, j v1beta1.TaskResourceBinding) bool { return i.Name < j.Name } diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go index 8ab6c366277..1bda4964837 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go @@ -570,7 +570,9 @@ func ResolvePipelineTask( if err != nil && !kerrors.IsNotFound(err) { return nil, fmt.Errorf("error retrieving Run %s: %w", runName, err) } - rpt.Runs = append(rpt.Runs, run) + if run != nil { + rpt.Runs = append(rpt.Runs, run) + } } case rpt.IsCustomTask(): rpt.RunName = getRunName(pipelineRun.Status.Runs, pipelineRun.Status.ChildReferences, pipelineTask.Name, pipelineRun.Name) diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go index 1fb87a8faaa..4a165ef379d 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go @@ -3393,9 +3393,10 @@ func TestResolvePipelineRunTask_WithMatrixedCustomTask(t *testing.T) { getRun := func(name string) (*v1alpha1.Run, error) { return runsMap[name], nil } for _, tc := range []struct { - name string - pt v1beta1.PipelineTask - want *ResolvedPipelineTask + name string + pt v1beta1.PipelineTask + getRun GetRun + want *ResolvedPipelineTask }{{ name: "custom task with matrix - single parameter", pt: pts[0], @@ -3414,6 +3415,18 @@ func TestResolvePipelineRunTask_WithMatrixedCustomTask(t *testing.T) { Runs: runs, PipelineTask: &pts[1], }, + }, { + name: "custom task with matrix - nil run", + pt: pts[1], + getRun: func(name string) (*v1alpha1.Run, error) { + return nil, kerrors.NewNotFound(v1beta1.Resource("run"), name) + }, + want: &ResolvedPipelineTask{ + CustomTask: true, + RunNames: runNames, + Runs: nil, + PipelineTask: &pts[1], + }, }} { t.Run(tc.name, func(t *testing.T) { ctx := context.Background() @@ -3425,7 +3438,10 @@ func TestResolvePipelineRunTask_WithMatrixedCustomTask(t *testing.T) { }, }) ctx = cfg.ToContext(ctx) - rpt, err := ResolvePipelineTask(ctx, pr, getTask, getTaskRun, getRun, tc.pt, nil) + if tc.getRun == nil { + tc.getRun = getRun + } + rpt, err := ResolvePipelineTask(ctx, pr, getTask, getTaskRun, tc.getRun, tc.pt, nil) if err != nil { t.Fatalf("Did not expect error when resolving PipelineRun: %v", err) } diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go b/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go index ef203656297..8b3817d5cee 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunstate.go @@ -231,7 +231,9 @@ func (state PipelineRunState) GetRunsResults() map[string][]v1alpha1.RunResult { if !rpt.isSuccessful() { continue } - results[rpt.PipelineTask.Name] = rpt.Run.Status.Results + if rpt.Run != nil { + results[rpt.PipelineTask.Name] = rpt.Run.Status.Results + } } return results diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunstate_test.go b/pkg/reconciler/pipelinerun/resources/pipelinerunstate_test.go index 03fafe6dae4..171d8ab4635 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunstate_test.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunstate_test.go @@ -2431,6 +2431,100 @@ func TestPipelineRunState_GetResultsFuncs(t *testing.T) { }}}, }, }}, + }, { + RunNames: []string{ + "matrixed-run-0", + "matrixed-run-1", + "matrixed-run-2", + "matrixed-run-3", + }, + PipelineTask: &v1beta1.PipelineTask{ + Name: "matrixed-task", + TaskRef: &v1beta1.TaskRef{ + Kind: "Example", + APIVersion: "example.dev/v0", + }, + Matrix: []v1beta1.Param{{ + Name: "foobar", + Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeArray, ArrayVal: []string{"foo", "bar"}}, + }, { + Name: "quxbaz", + Value: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeArray, ArrayVal: []string{"qux", "baz"}}, + }}, + }, + Runs: []*v1alpha1.Run{{ + TypeMeta: metav1.TypeMeta{APIVersion: "example.dev/v0"}, + ObjectMeta: metav1.ObjectMeta{Name: "matrixed-run-0"}, + Status: v1alpha1.RunStatus{ + Status: duckv1.Status{Conditions: []apis.Condition{{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }}}, + RunStatusFields: v1alpha1.RunStatusFields{ + Results: []v1alpha1.RunResult{{ + Name: "foo", + Value: "oof", + }, { + Name: "bar", + Value: "rab", + }}, + }, + }, + }, { + TypeMeta: metav1.TypeMeta{APIVersion: "example.dev/v0"}, + ObjectMeta: metav1.ObjectMeta{Name: "matrixed-run-1"}, + Status: v1alpha1.RunStatus{ + Status: duckv1.Status{Conditions: []apis.Condition{{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }}}, + RunStatusFields: v1alpha1.RunStatusFields{ + Results: []v1alpha1.RunResult{{ + Name: "foo", + Value: "oof", + }, { + Name: "bar", + Value: "rab", + }}, + }, + }, + }, { + TypeMeta: metav1.TypeMeta{APIVersion: "example.dev/v0"}, + ObjectMeta: metav1.ObjectMeta{Name: "matrixed-run-2"}, + Status: v1alpha1.RunStatus{ + Status: duckv1.Status{Conditions: []apis.Condition{{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }}}, + RunStatusFields: v1alpha1.RunStatusFields{ + Results: []v1alpha1.RunResult{{ + Name: "foo", + Value: "oof", + }, { + Name: "bar", + Value: "rab", + }}, + }, + }, + }, { + TypeMeta: metav1.TypeMeta{APIVersion: "example.dev/v0"}, + ObjectMeta: metav1.ObjectMeta{Name: "matrixed-run-3"}, + Status: v1alpha1.RunStatus{ + Status: duckv1.Status{Conditions: []apis.Condition{{ + Type: apis.ConditionSucceeded, + Status: corev1.ConditionTrue, + }}}, + RunStatusFields: v1alpha1.RunStatusFields{ + Results: []v1alpha1.RunResult{{ + Name: "foo", + Value: "oof", + }, { + Name: "bar", + Value: "rab", + }}, + }, + }, + }}, }} expectedTaskResults := map[string][]v1beta1.TaskRunResult{