Skip to content

Commit aff0b65

Browse files
committed
pipelinetask metadata
Adding metadata to PipelineTask to allow specifying metadata when a task is embedded using taskSpec. This metadata will be propogated to taskRun and then to the pods. ``` apiVersion: tekton.dev/v1beta1 kind: PipelineRun metadata: name: pipelinerun-with-taskspec-to-echo-greetings spec: pipelineSpec: tasks: - name: echo-greetings metadata: labels: [ …] ... ``` Metadata is already supported as part of Tasks and Pipelines while respective CRDs are created. But was not possible to specify with embedded resources.
1 parent 648cff3 commit aff0b65

File tree

7 files changed

+270
-3
lines changed

7 files changed

+270
-3
lines changed

internal/builder/v1beta1/pipeline.go

+7
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ func PipelineTaskSpec(spec *v1beta1.TaskSpec) PipelineTaskOp {
183183
}
184184
}
185185

186+
// PipelineTaskMetadata sets the Metadata on a PipelineTask.
187+
func PipelineTaskMetadata(metadata metav1.ObjectMeta) PipelineTaskOp {
188+
return func(pt *v1beta1.PipelineTask) {
189+
pt.ObjectMeta = metadata
190+
}
191+
}
192+
186193
// Retries sets the number of retries on a PipelineTask.
187194
func Retries(retries int) PipelineTaskOp {
188195
return func(pt *v1beta1.PipelineTask) {

internal/builder/v1beta1/pipeline_test.go

+39
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,42 @@ func TestPipelineRunWithPipelineSpec(t *testing.T) {
393393
t.Fatalf("PipelineRun diff -want, +got: %s", diff)
394394
}
395395
}
396+
397+
func TestPipelineRunWithTaskSpec_TaskMetadata(t *testing.T) {
398+
pipelineRun := tb.PipelineRun("pear", tb.PipelineRunNamespace("foo"),
399+
tb.PipelineRunSpec("", tb.PipelineRunPipelineSpec(
400+
tb.PipelineTask("a-task", "some-task",
401+
tb.PipelineTaskMetadata(metav1.ObjectMeta{
402+
Name: "a-task-name",
403+
Labels: map[string]string{"label": "labelvalue"},
404+
Annotations: map[string]string{"annotation": "annotationvalue"}},
405+
))),
406+
tb.PipelineRunServiceAccountName("sa"),
407+
))
408+
409+
expectedPipelineRun := &v1beta1.PipelineRun{
410+
ObjectMeta: metav1.ObjectMeta{
411+
Name: "pear",
412+
Namespace: "foo",
413+
},
414+
Spec: v1beta1.PipelineRunSpec{
415+
PipelineRef: nil,
416+
PipelineSpec: &v1beta1.PipelineSpec{
417+
Tasks: []v1beta1.PipelineTask{{
418+
Name: "a-task",
419+
TaskRef: &v1beta1.TaskRef{Name: "some-task"},
420+
ObjectMeta: metav1.ObjectMeta{
421+
Name: "a-task-name",
422+
Labels: map[string]string{"label": "labelvalue"},
423+
Annotations: map[string]string{"annotation": "annotationvalue"}},
424+
}},
425+
},
426+
ServiceAccountName: "sa",
427+
Timeout: &metav1.Duration{Duration: 1 * time.Hour},
428+
},
429+
}
430+
431+
if diff := cmp.Diff(expectedPipelineRun, pipelineRun); diff != "" {
432+
t.Fatalf("PipelineRun diff -want, +got: %s", diff)
433+
}
434+
}

pkg/apis/pipeline/v1beta1/pipeline_types.go

+25
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"github.com/tektoncd/pipeline/pkg/apis/validate"
2021
"github.com/tektoncd/pipeline/pkg/reconciler/pipeline/dag"
2122
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
"knative.dev/pkg/apis"
2224
)
2325

2426
// +genclient
@@ -93,6 +95,9 @@ type PipelineResult struct {
9395
// PipelineTask defines a task in a Pipeline, passing inputs from both
9496
// Params and from the output of previous tasks.
9597
type PipelineTask struct {
98+
// +optional
99+
metav1.ObjectMeta `json:"metadata,omitempty"`
100+
96101
// Name is the name of this task within the context of a Pipeline. Name is
97102
// used as a coordinate with the `from` and `runAfter` fields to establish
98103
// the execution order of tasks relative to one another.
@@ -139,6 +144,17 @@ type PipelineTask struct {
139144
Timeout *metav1.Duration `json:"timeout,omitempty"`
140145
}
141146

147+
func (pt *PipelineTask) PipelineTaskMetadata() metav1.ObjectMeta {
148+
return pt.ObjectMeta
149+
}
150+
151+
func (pt *PipelineTask) ValidatePipelineTaskMetadata() *apis.FieldError {
152+
if err := validate.ObjectMetadata(pt.GetObjectMeta()); err != nil {
153+
return err.ViaField("[tasks|finally].metadata")
154+
}
155+
return nil
156+
}
157+
142158
func (pt PipelineTask) HashKey() string {
143159
return pt.Name
144160
}
@@ -189,6 +205,15 @@ func (l PipelineTaskList) Items() []dag.Task {
189205
return tasks
190206
}
191207

208+
func (l PipelineTaskList) ValidatePipelineTasksMetadata() *apis.FieldError {
209+
for _, t := range l {
210+
if err := t.ValidatePipelineTaskMetadata(); err != nil {
211+
return err
212+
}
213+
}
214+
return nil
215+
}
216+
192217
// PipelineTaskParam is used to provide arbitrary string parameters to a Task.
193218
type PipelineTaskParam struct {
194219
Name string `json:"name"`

pkg/apis/pipeline/v1beta1/pipeline_validation.go

+8
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ func (ps *PipelineSpec) Validate(ctx context.Context) *apis.FieldError {
155155
return apis.ErrGeneric("expected at least one, got none", "spec.description", "spec.params", "spec.resources", "spec.tasks", "spec.workspaces")
156156
}
157157

158+
if err := PipelineTaskList(ps.Tasks).ValidatePipelineTasksMetadata(); err != nil {
159+
return err
160+
}
161+
162+
if err := PipelineTaskList(ps.Finally).ValidatePipelineTasksMetadata(); err != nil {
163+
return err
164+
}
165+
158166
// PipelineTask must have a valid unique label and at least one of taskRef or taskSpec should be specified
159167
if err := validatePipelineTasks(ctx, ps.Tasks, ps.Finally); err != nil {
160168
return err

pkg/apis/pipeline/v1beta1/pipeline_validation_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,31 @@ func TestPipelineSpec_Validate_Failure(t *testing.T) {
226226
Name: "bar", TaskRef: &TaskRef{Name: "bar-task"}, RunAfter: []string{"foo"},
227227
}},
228228
},
229+
}, {
230+
name: "invalid pipeline spec - invalid metadata in pipeline tasks",
231+
ps: &PipelineSpec{
232+
Tasks: []PipelineTask{{
233+
ObjectMeta: metav1.ObjectMeta{
234+
Name: "taskWith.",
235+
Labels: map[string]string{"label": "value"},
236+
Annotations: map[string]string{"annotation": "value"}},
237+
Name: "foo", TaskRef: &TaskRef{Name: "foo-task"},
238+
}},
239+
},
240+
}, {
241+
name: "invalid pipeline spec - invalid metadata in final tasks",
242+
ps: &PipelineSpec{
243+
Tasks: []PipelineTask{{
244+
Name: "foo", TaskRef: &TaskRef{Name: "pipeline-task"},
245+
}},
246+
Finally: []PipelineTask{{
247+
ObjectMeta: metav1.ObjectMeta{
248+
Name: "finalTaskWith.",
249+
Labels: map[string]string{"label": "value"},
250+
Annotations: map[string]string{"annotation": "value"}},
251+
Name: "bar", TaskRef: &TaskRef{Name: "final-task"},
252+
}},
253+
},
229254
}}
230255
for _, tt := range tests {
231256
t.Run(tt.name, func(t *testing.T) {
@@ -1391,3 +1416,40 @@ func TestValidateFinalTasks_Failure(t *testing.T) {
13911416
})
13921417
}
13931418
}
1419+
1420+
func TestPipelineTaskList_ValidatePipelineTasksMetadata_Failure(t *testing.T) {
1421+
tests := []struct {
1422+
name string
1423+
pipelineTasks []PipelineTask
1424+
}{{
1425+
name: "invalid metadata - pipeline task has pipeline name with special character in metadata",
1426+
pipelineTasks: []PipelineTask{{
1427+
Name: "pipelinetask1",
1428+
}, {
1429+
Name: "pipelinetask2",
1430+
ObjectMeta: metav1.ObjectMeta{Name: "pipelinetask2"},
1431+
}, {
1432+
Name: "pipelinetask2",
1433+
ObjectMeta: metav1.ObjectMeta{Name: "special.CharacterPipelineTask"},
1434+
}},
1435+
}, {
1436+
name: "invalid metadata - pipeline task has too long pipeline name in metadata",
1437+
pipelineTasks: []PipelineTask{{
1438+
Name: "pipelinetask1",
1439+
}, {
1440+
Name: "pipelinetask2",
1441+
ObjectMeta: metav1.ObjectMeta{Name: "pipelineTaskNameTooLongWhichIsNotValidPipelineTaskNameShouldBeLessThan63Characters"},
1442+
}, {
1443+
Name: "pipelinetask3",
1444+
ObjectMeta: metav1.ObjectMeta{Name: "pipelinetask3"},
1445+
}},
1446+
}}
1447+
for _, tt := range tests {
1448+
t.Run(tt.name, func(t *testing.T) {
1449+
err := PipelineTaskList(tt.pipelineTasks).ValidatePipelineTasksMetadata()
1450+
if err == nil {
1451+
t.Errorf("PipelineTaskList.ValidatePipelineTasksMetadata() did not return error for invalid metadata: %s", tt.name)
1452+
}
1453+
})
1454+
}
1455+
}

pkg/reconciler/pipelinerun/pipelinerun.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -610,14 +610,26 @@ func (c *Reconciler) createTaskRun(ctx context.Context, rprt *resources.Resolved
610610
return c.PipelineClientSet.TektonV1beta1().TaskRuns(pr.Namespace).UpdateStatus(tr)
611611
}
612612

613+
// Propagate labels from PipelineRun and PipelineTask to TaskRun.
614+
labels := getTaskrunLabels(pr, rprt.PipelineTask.Name)
615+
for key, value := range rprt.PipelineTask.PipelineTaskMetadata().Labels {
616+
labels[key] = value
617+
}
618+
619+
// Propagate annotations from PipelineRun and PipelineTask to TaskRun.
620+
annotations := getTaskrunAnnotations(pr)
621+
for key, value := range rprt.PipelineTask.PipelineTaskMetadata().Annotations {
622+
annotations[key] = value
623+
}
624+
613625
serviceAccountName, podTemplate := pr.GetTaskRunSpecs(rprt.PipelineTask.Name)
614626
tr = &v1beta1.TaskRun{
615627
ObjectMeta: metav1.ObjectMeta{
616628
Name: rprt.TaskRunName,
617629
Namespace: pr.Namespace,
618630
OwnerReferences: []metav1.OwnerReference{pr.GetOwnerReference()},
619-
Labels: getTaskrunLabels(pr, rprt.PipelineTask.Name),
620-
Annotations: getTaskrunAnnotations(pr),
631+
Labels: labels,
632+
Annotations: annotations,
621633
},
622634
Spec: v1beta1.TaskRunSpec{
623635
Params: rprt.PipelineTask.Params,

pkg/reconciler/pipelinerun/pipelinerun_test.go

+115-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import (
3636
taskrunresources "github.com/tektoncd/pipeline/pkg/reconciler/taskrun/resources"
3737
ttesting "github.com/tektoncd/pipeline/pkg/reconciler/testing"
3838
"github.com/tektoncd/pipeline/pkg/system"
39-
test "github.com/tektoncd/pipeline/test"
39+
"github.com/tektoncd/pipeline/test"
4040
"github.com/tektoncd/pipeline/test/diff"
4141
"github.com/tektoncd/pipeline/test/names"
4242
"go.uber.org/zap"
@@ -3037,3 +3037,117 @@ func TestUpdatePipelineRunStatusFromTaskRuns(t *testing.T) {
30373037
})
30383038
}
30393039
}
3040+
3041+
// this test validates pipeline task metadata is embedded into task run
3042+
func TestReconcile_PipelineTaskMetadata(t *testing.T) {
3043+
names.TestingSeed()
3044+
3045+
prs := []*v1beta1.PipelineRun{
3046+
tb.PipelineRun("test-pipeline-run-success",
3047+
tb.PipelineRunNamespace("foo"),
3048+
tb.PipelineRunSpec("test-pipeline"),
3049+
),
3050+
}
3051+
ps := []*v1beta1.Pipeline{
3052+
tb.Pipeline("test-pipeline",
3053+
tb.PipelineNamespace("foo"),
3054+
tb.PipelineSpec(
3055+
tb.PipelineTask("task-without-metadata", "",
3056+
tb.PipelineTaskSpec(&v1beta1.TaskSpec{
3057+
Steps: []v1beta1.Step{{Container: corev1.Container{
3058+
Name: "mystep",
3059+
Image: "myimage"}}},
3060+
}),
3061+
),
3062+
tb.PipelineTask("task-with-metadata", "",
3063+
tb.PipelineTaskSpec(&v1beta1.TaskSpec{
3064+
Steps: []v1beta1.Step{{Container: corev1.Container{
3065+
Name: "mystep",
3066+
Image: "myimage"}}},
3067+
}),
3068+
tb.PipelineTaskMetadata(metav1.ObjectMeta{
3069+
Name: "foo",
3070+
Labels: map[string]string{"label1": "labelvalue1", "label2": "labelvalue2"},
3071+
Annotations: map[string]string{"annotation1": "value1", "annotation2": "value2"}}),
3072+
),
3073+
),
3074+
),
3075+
}
3076+
3077+
d := test.Data{
3078+
PipelineRuns: prs,
3079+
Pipelines: ps,
3080+
Tasks: nil,
3081+
ClusterTasks: nil,
3082+
PipelineResources: nil,
3083+
}
3084+
3085+
testAssets, cancel := getPipelineRunController(t, d)
3086+
defer cancel()
3087+
c := testAssets.Controller
3088+
clients := testAssets.Clients
3089+
3090+
if err := c.Reconciler.Reconcile(context.Background(), "foo/test-pipeline-run-success"); err != nil {
3091+
t.Fatalf("Error reconciling: %s", err)
3092+
}
3093+
3094+
if len(clients.Pipeline.Actions()) == 0 {
3095+
t.Fatalf("Expected client to have been used to create a TaskRun but it wasn't")
3096+
}
3097+
3098+
// Check that the PipelineRun was reconciled correctly
3099+
reconciledRun, err := clients.Pipeline.TektonV1beta1().PipelineRuns("foo").Get("test-pipeline-run-success", metav1.GetOptions{})
3100+
if err != nil {
3101+
t.Fatalf("Somehow had error getting reconciled run out of fake client: %s", err)
3102+
}
3103+
3104+
actualTaskRun := make(map[string]*v1beta1.TaskRun)
3105+
for _, a := range clients.Pipeline.Actions() {
3106+
if a.GetResource().Resource == "taskruns" {
3107+
t := a.(ktesting.CreateAction).GetObject().(*v1beta1.TaskRun)
3108+
actualTaskRun[t.Name] = t
3109+
}
3110+
}
3111+
3112+
// Check that the expected TaskRun was created
3113+
if len(actualTaskRun) != 2 {
3114+
t.Errorf("Expected two TaskRuns to be created, but found %d TaskRuns.", len(actualTaskRun))
3115+
}
3116+
3117+
expectedTaskRun := make(map[string]*v1beta1.TaskRun)
3118+
expectedTaskRun["test-pipeline-run-success-task-with-metadata-mz4c7"] = tb.TaskRun(
3119+
"test-pipeline-run-success-task-with-metadata-mz4c7",
3120+
tb.TaskRunNamespace("foo"),
3121+
tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-success",
3122+
tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"),
3123+
tb.Controller, tb.BlockOwnerDeletion),
3124+
tb.TaskRunLabel("tekton.dev/pipeline", "test-pipeline"),
3125+
tb.TaskRunLabel("tekton.dev/pipelineRun", "test-pipeline-run-success"),
3126+
tb.TaskRunLabel(pipeline.GroupName+pipeline.PipelineTaskLabelKey, "task-with-metadata"),
3127+
tb.TaskRunLabel("label1", "labelvalue1"),
3128+
tb.TaskRunLabel("label2", "labelvalue2"),
3129+
tb.TaskRunAnnotation("annotation1", "value1"),
3130+
tb.TaskRunAnnotation("annotation2", "value2"),
3131+
tb.TaskRunSpec(tb.TaskRunTaskSpec(tb.Step("myimage", tb.StepName("mystep")))),
3132+
)
3133+
3134+
expectedTaskRun["test-pipeline-run-success-task-without-metadata-9l9zj"] = tb.TaskRun(
3135+
"test-pipeline-run-success-task-without-metadata-9l9zj",
3136+
tb.TaskRunNamespace("foo"),
3137+
tb.TaskRunOwnerReference("PipelineRun", "test-pipeline-run-success",
3138+
tb.OwnerReferenceAPIVersion("tekton.dev/v1beta1"),
3139+
tb.Controller, tb.BlockOwnerDeletion),
3140+
tb.TaskRunLabel("tekton.dev/pipeline", "test-pipeline"),
3141+
tb.TaskRunLabel("tekton.dev/pipelineRun", "test-pipeline-run-success"),
3142+
tb.TaskRunLabel(pipeline.GroupName+pipeline.PipelineTaskLabelKey, "task-without-metadata"),
3143+
tb.TaskRunSpec(tb.TaskRunTaskSpec(tb.Step("myimage", tb.StepName("mystep")))),
3144+
)
3145+
3146+
if d := cmp.Diff(actualTaskRun, expectedTaskRun); d != "" {
3147+
t.Fatalf("Expected TaskRuns to match, but got a mismatch: %s", d)
3148+
}
3149+
3150+
if len(reconciledRun.Status.TaskRuns) != 2 {
3151+
t.Errorf("Expected PipelineRun status to include both TaskRun status items that can run immediately: %v", reconciledRun.Status.TaskRuns)
3152+
}
3153+
}

0 commit comments

Comments
 (0)