Skip to content

Commit d56067e

Browse files
committed
fix: validate taskRef.apiVersion format for custom tasks
Add validation to ensure taskRef.apiVersion follows the correct format (group/version) when specified. Previously, invalid apiVersion values like 'invalid-api-version' were accepted, causing PipelineRuns to create unhandled CustomRuns that would timeout. Now Pipeline creation fails immediately with a clear error message when an invalid apiVersion is provided. Fixes #9044
1 parent d97e81a commit d56067e

File tree

4 files changed

+189
-15
lines changed

4 files changed

+189
-15
lines changed

pkg/apis/pipeline/v1/pipeline_types_test.go

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ func TestPipelineTask_ValidateCustomTask(t *testing.T) {
447447
expectedError apis.FieldError
448448
}{{
449449
name: "custom task - taskRef without kind",
450-
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "example.dev/v0", Kind: "", Name: ""}},
450+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "example/v0", Kind: "", Name: ""}},
451451
expectedError: apis.FieldError{
452452
Message: `invalid value: custom task ref must specify kind`,
453453
Paths: []string{"taskRef.kind"},
@@ -456,7 +456,7 @@ func TestPipelineTask_ValidateCustomTask(t *testing.T) {
456456
name: "custom task - taskSpec without kind",
457457
task: PipelineTask{Name: "foo", TaskSpec: &EmbeddedTask{
458458
TypeMeta: runtime.TypeMeta{
459-
APIVersion: "example.dev/v0",
459+
APIVersion: "example/v0",
460460
Kind: "",
461461
},
462462
}},
@@ -483,6 +483,39 @@ func TestPipelineTask_ValidateCustomTask(t *testing.T) {
483483
Message: `invalid value: custom task ref must specify apiVersion`,
484484
Paths: []string{"taskRef.apiVersion"},
485485
},
486+
}, {
487+
name: "custom task - taskRef with invalid apiVersion format",
488+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "invalid-api-version", Kind: "some-kind", Name: ""}},
489+
expectedError: apis.FieldError{
490+
Message: `invalid value: invalid apiVersion format "invalid-api-version", must be in the format "group/version"`,
491+
Paths: []string{"taskRef.apiVersion"},
492+
},
493+
}, {
494+
name: "custom task - taskSpec with invalid apiVersion format",
495+
task: PipelineTask{Name: "foo", TaskSpec: &EmbeddedTask{
496+
TypeMeta: runtime.TypeMeta{
497+
APIVersion: "no-slash-no-dot",
498+
Kind: "some-kind",
499+
},
500+
}},
501+
expectedError: apis.FieldError{
502+
Message: `invalid value: invalid apiVersion format "no-slash-no-dot", must be in the format "group/version"`,
503+
Paths: []string{"taskSpec.apiVersion"},
504+
},
505+
}, {
506+
name: "custom task - taskRef with empty group in apiVersion",
507+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "/v1", Kind: "some-kind", Name: ""}},
508+
expectedError: apis.FieldError{
509+
Message: `invalid value: invalid apiVersion format "/v1", must be in the format "group/version"`,
510+
Paths: []string{"taskRef.apiVersion"},
511+
},
512+
}, {
513+
name: "custom task - taskRef with empty version in apiVersion",
514+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "example/", Kind: "some-kind", Name: ""}},
515+
expectedError: apis.FieldError{
516+
Message: `invalid value: invalid apiVersion format "example/", must be in the format "group/version"`,
517+
Paths: []string{"taskRef.apiVersion"},
518+
},
486519
}}
487520
for _, tt := range tests {
488521
t.Run(tt.name, func(t *testing.T) {
@@ -497,6 +530,38 @@ func TestPipelineTask_ValidateCustomTask(t *testing.T) {
497530
}
498531
}
499532

533+
func TestPipelineTask_ValidateCustomTask_ValidAPIVersion(t *testing.T) {
534+
tests := []struct {
535+
name string
536+
task PipelineTask
537+
}{{
538+
name: "custom task - valid apiVersion with group/version",
539+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "example.dev/v1", Kind: "Example", Name: "example"}},
540+
}, {
541+
name: "custom task - valid apiVersion with multi-level group",
542+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "custom.tekton.dev/v1beta1", Kind: "Custom", Name: "custom"}},
543+
}, {
544+
name: "custom task - valid apiVersion without dots in group",
545+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "example/v1", Kind: "Example", Name: "example"}},
546+
}, {
547+
name: "custom task - valid apiVersion in taskSpec",
548+
task: PipelineTask{Name: "foo", TaskSpec: &EmbeddedTask{
549+
TypeMeta: runtime.TypeMeta{
550+
APIVersion: "example.io/v2",
551+
Kind: "CustomTask",
552+
},
553+
}},
554+
}}
555+
for _, tt := range tests {
556+
t.Run(tt.name, func(t *testing.T) {
557+
err := tt.task.validateCustomTask()
558+
if err != nil {
559+
t.Errorf("PipelineTask.validateCustomTask() returned unexpected error: %v", err)
560+
}
561+
})
562+
}
563+
}
564+
500565
func TestPipelineTask_ValidateRegularTask_Success(t *testing.T) {
501566
tests := []struct {
502567
name string
@@ -691,7 +756,7 @@ func TestPipelineTask_Validate_Failure(t *testing.T) {
691756
name: "custom task reference in taskref missing apiversion Kind",
692757
p: PipelineTask{
693758
Name: "invalid-custom-task",
694-
TaskRef: &TaskRef{APIVersion: "example.com"},
759+
TaskRef: &TaskRef{APIVersion: "example.com/v1"},
695760
},
696761
expectedError: apis.FieldError{
697762
Message: `invalid value: custom task ref must specify kind`,
@@ -701,7 +766,7 @@ func TestPipelineTask_Validate_Failure(t *testing.T) {
701766
name: "custom task reference in taskspec missing kind",
702767
p: PipelineTask{Name: "foo", TaskSpec: &EmbeddedTask{
703768
TypeMeta: runtime.TypeMeta{
704-
APIVersion: "example.com",
769+
APIVersion: "example.com/v1",
705770
}}},
706771
expectedError: *apis.ErrInvalidValue("custom task spec must specify kind", "taskSpec.kind"),
707772
}, {

pkg/apis/pipeline/v1/pipeline_validation.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,19 @@ func (pt PipelineTask) validateRefOrSpec(ctx context.Context) (errs *apis.FieldE
336336
return errs
337337
}
338338

339+
// isValidAPIVersion validates the format of an apiVersion string.
340+
// Valid formats are "group/version" where both group and version are non-empty.
341+
// For custom tasks, apiVersion must always be in the "group/version" format.
342+
func isValidAPIVersion(apiVersion string) bool {
343+
parts := strings.Split(apiVersion, "/")
344+
if len(parts) != 2 {
345+
return false
346+
}
347+
group := parts[0]
348+
version := parts[1]
349+
return group != "" && version != ""
350+
}
351+
339352
// validateCustomTask validates custom task specifications - checking kind and fail if not yet supported features specified
340353
func (pt PipelineTask) validateCustomTask() (errs *apis.FieldError) {
341354
if pt.TaskRef != nil && pt.TaskRef.Kind == "" {
@@ -344,10 +357,19 @@ func (pt PipelineTask) validateCustomTask() (errs *apis.FieldError) {
344357
if pt.TaskSpec != nil && pt.TaskSpec.Kind == "" {
345358
errs = errs.Also(apis.ErrInvalidValue("custom task spec must specify kind", "taskSpec.kind"))
346359
}
347-
if pt.TaskRef != nil && pt.TaskRef.APIVersion == "" {
360+
// Validate apiVersion format for custom tasks
361+
if pt.TaskRef != nil && pt.TaskRef.APIVersion != "" {
362+
if !isValidAPIVersion(pt.TaskRef.APIVersion) {
363+
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("invalid apiVersion format %q, must be in the format \"group/version\"", pt.TaskRef.APIVersion), "taskRef.apiVersion"))
364+
}
365+
} else if pt.TaskRef != nil {
348366
errs = errs.Also(apis.ErrInvalidValue("custom task ref must specify apiVersion", "taskRef.apiVersion"))
349367
}
350-
if pt.TaskSpec != nil && pt.TaskSpec.APIVersion == "" {
368+
if pt.TaskSpec != nil && pt.TaskSpec.APIVersion != "" {
369+
if !isValidAPIVersion(pt.TaskSpec.APIVersion) {
370+
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("invalid apiVersion format %q, must be in the format \"group/version\"", pt.TaskSpec.APIVersion), "taskSpec.apiVersion"))
371+
}
372+
} else if pt.TaskSpec != nil {
351373
errs = errs.Also(apis.ErrInvalidValue("custom task spec must specify apiVersion", "taskSpec.apiVersion"))
352374
}
353375
return errs

pkg/apis/pipeline/v1beta1/pipeline_types_test.go

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ func TestPipelineTask_ValidateCustomTask(t *testing.T) {
447447
expectedError apis.FieldError
448448
}{{
449449
name: "custom task - taskRef without kind",
450-
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "example.dev/v0", Kind: "", Name: ""}},
450+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "example/v0", Kind: "", Name: ""}},
451451
expectedError: apis.FieldError{
452452
Message: `invalid value: custom task ref must specify kind`,
453453
Paths: []string{"taskRef.kind"},
@@ -456,7 +456,7 @@ func TestPipelineTask_ValidateCustomTask(t *testing.T) {
456456
name: "custom task - taskSpec without kind",
457457
task: PipelineTask{Name: "foo", TaskSpec: &EmbeddedTask{
458458
TypeMeta: runtime.TypeMeta{
459-
APIVersion: "example.dev/v0",
459+
APIVersion: "example/v0",
460460
Kind: "",
461461
},
462462
}},
@@ -483,6 +483,39 @@ func TestPipelineTask_ValidateCustomTask(t *testing.T) {
483483
Message: `invalid value: custom task ref must specify apiVersion`,
484484
Paths: []string{"taskRef.apiVersion"},
485485
},
486+
}, {
487+
name: "custom task - taskRef with invalid apiVersion format",
488+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "invalid-api-version", Kind: "some-kind", Name: ""}},
489+
expectedError: apis.FieldError{
490+
Message: `invalid value: invalid apiVersion format "invalid-api-version", must be in the format "group/version"`,
491+
Paths: []string{"taskRef.apiVersion"},
492+
},
493+
}, {
494+
name: "custom task - taskSpec with invalid apiVersion format",
495+
task: PipelineTask{Name: "foo", TaskSpec: &EmbeddedTask{
496+
TypeMeta: runtime.TypeMeta{
497+
APIVersion: "no-slash-no-dot",
498+
Kind: "some-kind",
499+
},
500+
}},
501+
expectedError: apis.FieldError{
502+
Message: `invalid value: invalid apiVersion format "no-slash-no-dot", must be in the format "group/version"`,
503+
Paths: []string{"taskSpec.apiVersion"},
504+
},
505+
}, {
506+
name: "custom task - taskRef with empty group in apiVersion",
507+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "/v1", Kind: "some-kind", Name: ""}},
508+
expectedError: apis.FieldError{
509+
Message: `invalid value: invalid apiVersion format "/v1", must be in the format "group/version"`,
510+
Paths: []string{"taskRef.apiVersion"},
511+
},
512+
}, {
513+
name: "custom task - taskRef with empty version in apiVersion",
514+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "example/", Kind: "some-kind", Name: ""}},
515+
expectedError: apis.FieldError{
516+
Message: `invalid value: invalid apiVersion format "example/", must be in the format "group/version"`,
517+
Paths: []string{"taskRef.apiVersion"},
518+
},
486519
}}
487520
for _, tt := range tests {
488521
t.Run(tt.name, func(t *testing.T) {
@@ -497,6 +530,38 @@ func TestPipelineTask_ValidateCustomTask(t *testing.T) {
497530
}
498531
}
499532

533+
func TestPipelineTask_ValidateCustomTask_ValidAPIVersion(t *testing.T) {
534+
tests := []struct {
535+
name string
536+
task PipelineTask
537+
}{{
538+
name: "custom task - valid apiVersion with group/version",
539+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "example.dev/v1", Kind: "Example", Name: "example"}},
540+
}, {
541+
name: "custom task - valid apiVersion with multi-level group",
542+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "custom.tekton.dev/v1beta1", Kind: "Custom", Name: "custom"}},
543+
}, {
544+
name: "custom task - valid apiVersion without dots in group",
545+
task: PipelineTask{Name: "foo", TaskRef: &TaskRef{APIVersion: "example/v1", Kind: "Example", Name: "example"}},
546+
}, {
547+
name: "custom task - valid apiVersion in taskSpec",
548+
task: PipelineTask{Name: "foo", TaskSpec: &EmbeddedTask{
549+
TypeMeta: runtime.TypeMeta{
550+
APIVersion: "example.io/v2",
551+
Kind: "CustomTask",
552+
},
553+
}},
554+
}}
555+
for _, tt := range tests {
556+
t.Run(tt.name, func(t *testing.T) {
557+
err := tt.task.validateCustomTask()
558+
if err != nil {
559+
t.Errorf("PipelineTask.validateCustomTask() returned unexpected error: %v", err)
560+
}
561+
})
562+
}
563+
}
564+
500565
func TestPipelineTask_ValidateRegularTask_Success(t *testing.T) {
501566
tests := []struct {
502567
name string
@@ -687,7 +752,7 @@ func TestPipelineTask_Validate_Failure(t *testing.T) {
687752
name: "custom task reference in taskref missing apiversion Kind",
688753
p: PipelineTask{
689754
Name: "invalid-custom-task",
690-
TaskRef: &TaskRef{APIVersion: "example.com"},
755+
TaskRef: &TaskRef{APIVersion: "example.com/v1"},
691756
},
692757
expectedError: apis.FieldError{
693758
Message: `invalid value: custom task ref must specify kind`,
@@ -697,7 +762,7 @@ func TestPipelineTask_Validate_Failure(t *testing.T) {
697762
name: "custom task reference in taskspec missing kind",
698763
p: PipelineTask{Name: "foo", TaskSpec: &EmbeddedTask{
699764
TypeMeta: runtime.TypeMeta{
700-
APIVersion: "example.com",
765+
APIVersion: "example.com/v1",
701766
},
702767
}},
703768
expectedError: *apis.ErrInvalidValue("custom task spec must specify kind", "taskSpec.kind"),
@@ -846,7 +911,7 @@ func TestPipelineTaskList_Validate(t *testing.T) {
846911
name: "validate all valid custom task, and regular task",
847912
tasks: PipelineTaskList{{
848913
Name: "valid-custom-task",
849-
TaskRef: &TaskRef{APIVersion: "example.com", Kind: "custom"},
914+
TaskRef: &TaskRef{APIVersion: "example.com/v1", Kind: "custom"},
850915
}, {
851916
Name: "valid-task",
852917
TaskRef: &TaskRef{Name: "task"},
@@ -856,7 +921,7 @@ func TestPipelineTaskList_Validate(t *testing.T) {
856921
name: "validate list of tasks with valid custom task and invalid regular task",
857922
tasks: PipelineTaskList{{
858923
Name: "valid-custom-task",
859-
TaskRef: &TaskRef{APIVersion: "example.com", Kind: "custom"},
924+
TaskRef: &TaskRef{APIVersion: "example.com/v1", Kind: "custom"},
860925
}, {
861926
Name: "invalid-task-without-name",
862927
TaskRef: &TaskRef{Name: ""},
@@ -867,7 +932,7 @@ func TestPipelineTaskList_Validate(t *testing.T) {
867932
name: "validate all invalid tasks - custom task and regular task",
868933
tasks: PipelineTaskList{{
869934
Name: "invalid-custom-task",
870-
TaskRef: &TaskRef{APIVersion: "example.com"},
935+
TaskRef: &TaskRef{APIVersion: "example.com/v1"},
871936
}, {
872937
Name: "invalid-task",
873938
TaskRef: &TaskRef{Name: ""},

pkg/apis/pipeline/v1beta1/pipeline_validation.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,19 @@ func (pt PipelineTask) validateRefOrSpec(ctx context.Context) (errs *apis.FieldE
341341
return errs
342342
}
343343

344+
// isValidAPIVersion validates the format of an apiVersion string.
345+
// Valid formats are "group/version" where both group and version are non-empty.
346+
// For custom tasks, apiVersion must always be in the "group/version" format.
347+
func isValidAPIVersion(apiVersion string) bool {
348+
parts := strings.Split(apiVersion, "/")
349+
if len(parts) != 2 {
350+
return false
351+
}
352+
group := parts[0]
353+
version := parts[1]
354+
return group != "" && version != ""
355+
}
356+
344357
// validateCustomTask validates custom task specifications - checking kind and fail if not yet supported features specified
345358
func (pt PipelineTask) validateCustomTask() (errs *apis.FieldError) {
346359
if pt.TaskRef != nil && pt.TaskRef.Kind == "" {
@@ -349,10 +362,19 @@ func (pt PipelineTask) validateCustomTask() (errs *apis.FieldError) {
349362
if pt.TaskSpec != nil && pt.TaskSpec.Kind == "" {
350363
errs = errs.Also(apis.ErrInvalidValue("custom task spec must specify kind", "taskSpec.kind"))
351364
}
352-
if pt.TaskRef != nil && pt.TaskRef.APIVersion == "" {
365+
// Validate apiVersion format for custom tasks
366+
if pt.TaskRef != nil && pt.TaskRef.APIVersion != "" {
367+
if !isValidAPIVersion(pt.TaskRef.APIVersion) {
368+
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("invalid apiVersion format %q, must be in the format \"group/version\"", pt.TaskRef.APIVersion), "taskRef.apiVersion"))
369+
}
370+
} else if pt.TaskRef != nil {
353371
errs = errs.Also(apis.ErrInvalidValue("custom task ref must specify apiVersion", "taskRef.apiVersion"))
354372
}
355-
if pt.TaskSpec != nil && pt.TaskSpec.APIVersion == "" {
373+
if pt.TaskSpec != nil && pt.TaskSpec.APIVersion != "" {
374+
if !isValidAPIVersion(pt.TaskSpec.APIVersion) {
375+
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("invalid apiVersion format %q, must be in the format \"group/version\"", pt.TaskSpec.APIVersion), "taskSpec.apiVersion"))
376+
}
377+
} else if pt.TaskSpec != nil {
356378
errs = errs.Also(apis.ErrInvalidValue("custom task spec must specify apiVersion", "taskSpec.apiVersion"))
357379
}
358380
return errs

0 commit comments

Comments
 (0)