From 3aa2c4454446a0076422b58c262e027ce7779ea7 Mon Sep 17 00:00:00 2001 From: pratap0007 Date: Tue, 25 Jul 2023 15:47:24 +0530 Subject: [PATCH] Add resolver support for tkn pr logs command This patch adds all type of resolver support for pipelinerun logs command closes: #2016 Signed-off-by: Shiv Verma --- pkg/cmd/pipelinerun/logs_test.go | 744 ++++++++++++++++++ pkg/log/pipeline_reader.go | 19 +- test/e2e/pipelinerun/pipelinerun_test.go | 23 + .../pipelinerun-with-cluster-resolver.yaml | 47 ++ 4 files changed, 827 insertions(+), 6 deletions(-) create mode 100644 test/resources/pipelinerun-with-cluster-resolver.yaml diff --git a/pkg/cmd/pipelinerun/logs_test.go b/pkg/cmd/pipelinerun/logs_test.go index 75dd66c92..1bd681fd9 100644 --- a/pkg/cmd/pipelinerun/logs_test.go +++ b/pkg/cmd/pipelinerun/logs_test.go @@ -3768,6 +3768,750 @@ func TestPipelinerunLog_finally(t *testing.T) { test.AssertOutput(t, expected, output) } +func TestLogs_Cluster_Resolver(t *testing.T) { + var ( + pipelineName = "pipeline" + prName = "pipeline-run" + ns = "namespace" + taskName = "task" + trName = "taskrun" + taskPodName = "taskPod" + tr1StartTime = test.FakeClock().Now().Add(20 * time.Second) + tr1Step1Name = "step-1" + ) + + nsList := []*corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + }, + }, + } + + prs := []*v1.PipelineRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: prName, + Namespace: ns, + Labels: map[string]string{"tekton.dev/pipeline": prName}, + }, + Spec: v1.PipelineRunSpec{ + PipelineRef: &v1.PipelineRef{ + ResolverRef: v1.ResolverRef{ + Resolver: "cluster", + Params: v1.Params{ + { + Name: "kind", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "pipeline"}, + }, + { + Name: "name", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: pipelineName}, + }, + { + Name: "namespace", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: ns}, + }, + }, + }, + }, + }, + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Status: corev1.ConditionTrue, + Message: "Success", + }, + }, + }, + PipelineRunStatusFields: v1.PipelineRunStatusFields{ + ChildReferences: []v1.ChildStatusReference{ + { + Name: trName, + PipelineTaskName: taskName, + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + }, + }, + PipelineSpec: &v1.PipelineSpec{ + Tasks: []v1.PipelineTask{ + { + Name: taskName, + TaskRef: &v1.TaskRef{ + Kind: "task", + Name: taskName, + }, + }, + }, + }, + }, + }, + }, + } + + ps := []*v1.Pipeline{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: pipelineName, + Namespace: ns, + }, + Spec: v1.PipelineSpec{ + Tasks: []v1.PipelineTask{ + { + Name: taskName, + TaskRef: &v1.TaskRef{ + Name: taskName, + }, + }, + }, + }, + }, + } + + p := []*corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: taskPodName, + Namespace: ns, + Labels: map[string]string{"tekton.dev/task": pipelineName}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: tr1Step1Name, + Image: tr1Step1Name + ":latest", + }, + }, + }, + }, + } + + trs := []*v1.TaskRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: trName, + }, + Spec: v1.TaskRunSpec{ + TaskRef: &v1.TaskRef{ + Name: taskName, + }, + }, + Status: v1.TaskRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Status: corev1.ConditionTrue, + Type: apis.ConditionSucceeded, + }, + }, + }, + TaskRunStatusFields: v1.TaskRunStatusFields{ + StartTime: &metav1.Time{Time: tr1StartTime}, + PodName: taskPodName, + Steps: []v1.StepState{ + { + Name: tr1Step1Name, + ContainerState: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ + ExitCode: 0, + }, + }, + }, + }, + }, + }, + }, + } + + cs, _ := test.SeedTestData(t, test.Data{PipelineRuns: prs, Pipelines: ps, Pods: p, Namespaces: nsList}) + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"task", "taskrun", "pipelinerun"}) + tdc := testDynamic.Options{} + dc, err := tdc.Client( + cb.UnstructuredP(ps[0], version), + cb.UnstructuredPR(prs[0], version), + cb.UnstructuredTR(trs[0], version), + ) + if err != nil { + t.Errorf("unable to create dynamic client: %v", err) + } + + fakeLogStream := fake.Logs( + fake.Task(taskPodName, + fake.Step(tr1Step1Name, "task-1 completed\n"), + ), + ) + prlo := logOpts(prName, ns, cs, dc, fake.Streamer(fakeLogStream), false, false, false, taskName) + + output, err := fetchLogs(prlo) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + test.AssertOutput(t, "task-1 completed\n\n", output) +} + +func TestLogs_Git_Resolver(t *testing.T) { + var ( + pipelineName = "pipeline" + prName = "pipeline-run" + ns = "namespace" + taskName = "task" + trName = "taskrun" + taskPodName = "taskPod" + tr1StartTime = test.FakeClock().Now().Add(20 * time.Second) + tr1Step1Name = "step-1" + ) + + nsList := []*corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + }, + }, + } + + prs := []*v1.PipelineRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: prName, + Namespace: ns, + Labels: map[string]string{"tekton.dev/pipeline": prName}, + }, + Spec: v1.PipelineRunSpec{ + PipelineRef: &v1.PipelineRef{ + ResolverRef: v1.ResolverRef{ + Resolver: "git", + Params: v1.Params{ + { + Name: "kind", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "pipeline"}, + }, + { + Name: "name", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: pipelineName}, + }, + { + Name: "namespace", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: ns}, + }, + }, + }, + }, + }, + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Status: corev1.ConditionTrue, + Message: "Success", + }, + }, + }, + PipelineRunStatusFields: v1.PipelineRunStatusFields{ + ChildReferences: []v1.ChildStatusReference{ + { + Name: trName, + PipelineTaskName: taskName, + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + }, + }, + PipelineSpec: &v1.PipelineSpec{ + Tasks: []v1.PipelineTask{ + { + Name: taskName, + TaskRef: &v1.TaskRef{ + Kind: "task", + Name: taskName, + }, + }, + }, + }, + }, + }, + }, + } + + ps := []*v1.Pipeline{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: pipelineName, + Namespace: ns, + }, + Spec: v1.PipelineSpec{ + Tasks: []v1.PipelineTask{ + { + Name: taskName, + TaskRef: &v1.TaskRef{ + Name: taskName, + }, + }, + }, + }, + }, + } + + p := []*corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: taskPodName, + Namespace: ns, + Labels: map[string]string{"tekton.dev/task": pipelineName}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: tr1Step1Name, + Image: tr1Step1Name + ":latest", + }, + }, + }, + }, + } + + trs := []*v1.TaskRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: trName, + }, + Spec: v1.TaskRunSpec{ + TaskRef: &v1.TaskRef{ + Name: taskName, + }, + }, + Status: v1.TaskRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Status: corev1.ConditionTrue, + Type: apis.ConditionSucceeded, + }, + }, + }, + TaskRunStatusFields: v1.TaskRunStatusFields{ + StartTime: &metav1.Time{Time: tr1StartTime}, + PodName: taskPodName, + Steps: []v1.StepState{ + { + Name: tr1Step1Name, + ContainerState: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ + ExitCode: 0, + }, + }, + }, + }, + }, + }, + }, + } + + cs, _ := test.SeedTestData(t, test.Data{PipelineRuns: prs, Pipelines: ps, Pods: p, Namespaces: nsList}) + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"task", "taskrun", "pipelinerun"}) + tdc := testDynamic.Options{} + dc, err := tdc.Client( + cb.UnstructuredP(ps[0], version), + cb.UnstructuredPR(prs[0], version), + cb.UnstructuredTR(trs[0], version), + ) + if err != nil { + t.Errorf("unable to create dynamic client: %v", err) + } + + fakeLogStream := fake.Logs( + fake.Task(taskPodName, + fake.Step(tr1Step1Name, "task-1 completed\n"), + ), + ) + prlo := logOpts(prName, ns, cs, dc, fake.Streamer(fakeLogStream), false, false, false, taskName) + + output, err := fetchLogs(prlo) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + test.AssertOutput(t, "task-1 completed\n\n", output) +} + +func TestLogs_Bundle_Resolver(t *testing.T) { + var ( + pipelineName = "pipeline" + prName = "pipeline-run" + ns = "namespace" + taskName = "task" + trName = "taskrun" + taskPodName = "taskPod" + tr1StartTime = test.FakeClock().Now().Add(20 * time.Second) + tr1Step1Name = "step-1" + ) + + nsList := []*corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + }, + }, + } + + prs := []*v1.PipelineRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: prName, + Namespace: ns, + Labels: map[string]string{"tekton.dev/pipeline": prName}, + }, + Spec: v1.PipelineRunSpec{ + PipelineRef: &v1.PipelineRef{ + ResolverRef: v1.ResolverRef{ + Resolver: "bundle", + Params: v1.Params{ + { + Name: "kind", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "pipeline"}, + }, + { + Name: "name", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: pipelineName}, + }, + { + Name: "namespace", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: ns}, + }, + }, + }, + }, + }, + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Status: corev1.ConditionTrue, + Message: "Success", + }, + }, + }, + PipelineRunStatusFields: v1.PipelineRunStatusFields{ + ChildReferences: []v1.ChildStatusReference{ + { + Name: trName, + PipelineTaskName: taskName, + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + }, + }, + PipelineSpec: &v1.PipelineSpec{ + Tasks: []v1.PipelineTask{ + { + Name: taskName, + TaskRef: &v1.TaskRef{ + Kind: "task", + Name: taskName, + }, + }, + }, + }, + }, + }, + }, + } + + ps := []*v1.Pipeline{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: pipelineName, + Namespace: ns, + }, + Spec: v1.PipelineSpec{ + Tasks: []v1.PipelineTask{ + { + Name: taskName, + TaskRef: &v1.TaskRef{ + Name: taskName, + }, + }, + }, + }, + }, + } + + p := []*corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: taskPodName, + Namespace: ns, + Labels: map[string]string{"tekton.dev/task": pipelineName}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: tr1Step1Name, + Image: tr1Step1Name + ":latest", + }, + }, + }, + }, + } + + trs := []*v1.TaskRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: trName, + }, + Spec: v1.TaskRunSpec{ + TaskRef: &v1.TaskRef{ + Name: taskName, + }, + }, + Status: v1.TaskRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Status: corev1.ConditionTrue, + Type: apis.ConditionSucceeded, + }, + }, + }, + TaskRunStatusFields: v1.TaskRunStatusFields{ + StartTime: &metav1.Time{Time: tr1StartTime}, + PodName: taskPodName, + Steps: []v1.StepState{ + { + Name: tr1Step1Name, + ContainerState: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ + ExitCode: 0, + }, + }, + }, + }, + }, + }, + }, + } + + cs, _ := test.SeedTestData(t, test.Data{PipelineRuns: prs, Pipelines: ps, Pods: p, Namespaces: nsList}) + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"task", "taskrun", "pipelinerun"}) + tdc := testDynamic.Options{} + dc, err := tdc.Client( + cb.UnstructuredP(ps[0], version), + cb.UnstructuredPR(prs[0], version), + cb.UnstructuredTR(trs[0], version), + ) + if err != nil { + t.Errorf("unable to create dynamic client: %v", err) + } + + fakeLogStream := fake.Logs( + fake.Task(taskPodName, + fake.Step(tr1Step1Name, "task-1 completed\n"), + ), + ) + prlo := logOpts(prName, ns, cs, dc, fake.Streamer(fakeLogStream), false, false, false, taskName) + + output, err := fetchLogs(prlo) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + test.AssertOutput(t, "task-1 completed\n\n", output) +} + +func TestLogs_Hub_Resolver(t *testing.T) { + var ( + pipelineName = "pipeline" + prName = "pipeline-run" + ns = "namespace" + taskName = "task" + trName = "taskrun" + taskPodName = "taskPod" + tr1StartTime = test.FakeClock().Now().Add(20 * time.Second) + tr1Step1Name = "step-1" + ) + + nsList := []*corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + }, + }, + } + + prs := []*v1.PipelineRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: prName, + Namespace: ns, + Labels: map[string]string{"tekton.dev/pipeline": prName}, + }, + Spec: v1.PipelineRunSpec{ + PipelineRef: &v1.PipelineRef{ + ResolverRef: v1.ResolverRef{ + Resolver: "hub", + Params: v1.Params{ + { + Name: "kind", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: "pipeline"}, + }, + { + Name: "name", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: pipelineName}, + }, + { + Name: "namespace", + Value: v1.ParamValue{Type: v1.ParamTypeString, StringVal: ns}, + }, + }, + }, + }, + }, + Status: v1.PipelineRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Status: corev1.ConditionTrue, + Message: "Success", + }, + }, + }, + PipelineRunStatusFields: v1.PipelineRunStatusFields{ + ChildReferences: []v1.ChildStatusReference{ + { + Name: trName, + PipelineTaskName: taskName, + TypeMeta: runtime.TypeMeta{ + APIVersion: "tekton.dev/v1beta1", + Kind: "TaskRun", + }, + }, + }, + PipelineSpec: &v1.PipelineSpec{ + Tasks: []v1.PipelineTask{ + { + Name: taskName, + TaskRef: &v1.TaskRef{ + Kind: "task", + Name: taskName, + }, + }, + }, + }, + }, + }, + }, + } + + ps := []*v1.Pipeline{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: pipelineName, + Namespace: ns, + }, + Spec: v1.PipelineSpec{ + Tasks: []v1.PipelineTask{ + { + Name: taskName, + TaskRef: &v1.TaskRef{ + Name: taskName, + }, + }, + }, + }, + }, + } + + p := []*corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: taskPodName, + Namespace: ns, + Labels: map[string]string{"tekton.dev/task": pipelineName}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: tr1Step1Name, + Image: tr1Step1Name + ":latest", + }, + }, + }, + }, + } + + trs := []*v1.TaskRun{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: trName, + }, + Spec: v1.TaskRunSpec{ + TaskRef: &v1.TaskRef{ + Name: taskName, + }, + }, + Status: v1.TaskRunStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Status: corev1.ConditionTrue, + Type: apis.ConditionSucceeded, + }, + }, + }, + TaskRunStatusFields: v1.TaskRunStatusFields{ + StartTime: &metav1.Time{Time: tr1StartTime}, + PodName: taskPodName, + Steps: []v1.StepState{ + { + Name: tr1Step1Name, + ContainerState: corev1.ContainerState{ + Terminated: &corev1.ContainerStateTerminated{ + ExitCode: 0, + }, + }, + }, + }, + }, + }, + }, + } + + cs, _ := test.SeedTestData(t, test.Data{PipelineRuns: prs, Pipelines: ps, Pods: p, Namespaces: nsList}) + cs.Pipeline.Resources = cb.APIResourceList(version, []string{"task", "taskrun", "pipelinerun"}) + tdc := testDynamic.Options{} + dc, err := tdc.Client( + cb.UnstructuredP(ps[0], version), + cb.UnstructuredPR(prs[0], version), + cb.UnstructuredTR(trs[0], version), + ) + if err != nil { + t.Errorf("unable to create dynamic client: %v", err) + } + + fakeLogStream := fake.Logs( + fake.Task(taskPodName, + fake.Step(tr1Step1Name, "task-1 completed\n"), + ), + ) + prlo := logOpts(prName, ns, cs, dc, fake.Streamer(fakeLogStream), false, false, false, taskName) + + output, err := fetchLogs(prlo) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + test.AssertOutput(t, "task-1 completed\n\n", output) +} + func logOptsv1beta1(name string, ns string, cs pipelinev1beta1test.Clients, dc dynamic.Interface, streamer stream.NewStreamerFunc, allSteps bool, follow bool, prefixing bool, tasks ...string) *options.LogOptions { p := test.Params{ Kube: cs.Kube, diff --git a/pkg/log/pipeline_reader.go b/pkg/log/pipeline_reader.go index a3b91070e..a9af78dd1 100644 --- a/pkg/log/pipeline_reader.go +++ b/pkg/log/pipeline_reader.go @@ -208,15 +208,22 @@ func (r *Reader) setUpTask(taskNumber int, tr taskrunpkg.Run) { // and return trh.Run after converted taskruns into trh.Run. func (r *Reader) getOrderedTasks(pr *v1.PipelineRun) ([]taskrunpkg.Run, error) { var tasks []v1.PipelineTask - switch { case pr.Spec.PipelineRef != nil: - pl, err := pipelinepkg.GetPipeline(pipelineGroupResource, r.clients, pr.Spec.PipelineRef.Name, r.ns) - if err != nil { - return nil, err + if pr.Spec.PipelineRef.Resolver != "" { + if pr.Status.PipelineSpec != nil { + tasks = append(tasks, pr.Status.PipelineSpec.Tasks...) + } else { + return nil, fmt.Errorf("pipelinerun %s does not have the PipelineRunSpec", pr.Name) + } + } else { + pl, err := pipelinepkg.GetPipeline(pipelineGroupResource, r.clients, pr.Spec.PipelineRef.Name, r.ns) + if err != nil { + return nil, err + } + tasks = pl.Spec.Tasks + tasks = append(tasks, pl.Spec.Finally...) } - tasks = pl.Spec.Tasks - tasks = append(tasks, pl.Spec.Finally...) case pr.Spec.PipelineSpec != nil: tasks = pr.Spec.PipelineSpec.Tasks tasks = append(tasks, pr.Spec.PipelineSpec.Finally...) diff --git a/test/e2e/pipelinerun/pipelinerun_test.go b/test/e2e/pipelinerun/pipelinerun_test.go index ed7a780a8..48eeb5887 100644 --- a/test/e2e/pipelinerun/pipelinerun_test.go +++ b/test/e2e/pipelinerun/pipelinerun_test.go @@ -55,3 +55,26 @@ func TestPipelineRunLogE2E(t *testing.T) { helper.AssertOutput(t, expected, res.Stdout()) }) } + +func TestClusterResolverPipelineRunLogE2E(t *testing.T) { + t.Parallel() + c, namespace := framework.Setup(t) + knativetest.CleanupOnInterrupt(func() { framework.TearDown(t, c, namespace) }, t.Logf) + defer framework.TearDown(t, c, namespace) + + kubectl := cli.NewKubectl(namespace) + tkn, err := cli.NewTknRunner(namespace) + assert.NilError(t, err) + + t.Logf("Creating pipelinerun in namespace: %s", namespace) + kubectl.MustSucceed(t, "create", "-f", helper.GetResourcePath("pipelinerun-with-cluster-resolver.yaml")) + + t.Run("Pipelinerun logs with resolver "+namespace, func(t *testing.T) { + res := tkn.Run(t, "pipelinerun", "logs", "git-resolver-run", "-f") + s := []string{ + "[echo : echo] Good Morning!\n", + } + expected := strings.Join(s, "\n") + "\n" + helper.AssertOutput(t, expected, res.Stdout()) + }) +} diff --git a/test/resources/pipelinerun-with-cluster-resolver.yaml b/test/resources/pipelinerun-with-cluster-resolver.yaml new file mode 100644 index 000000000..aaca749b4 --- /dev/null +++ b/test/resources/pipelinerun-with-cluster-resolver.yaml @@ -0,0 +1,47 @@ +# Copyright 2023 The Tekton Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: tekton.dev/v1beta1 +kind: Pipeline +metadata: + name: simple-example-pipeline +spec: + tasks: + - name: echo + taskSpec: + metadata: + labels: + app: "example" + steps: + - name: echo + image: ubuntu + script: | + #!/usr/bin/env bash + echo "Good Morning!" + +--- +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + name: git-resolver-run +spec: + pipelineRef: + resolver: cluster + params: + - name: kind + value: pipeline + - name: name + value: simple-example-pipeline + - name: namespace + value: $(context.pipelineRun.namespace)