diff --git a/pkg/controller/filter.go b/pkg/controller/filter.go index 92ac30fe6fa..9c38d32a9ee 100644 --- a/pkg/controller/filter.go +++ b/pkg/controller/filter.go @@ -23,6 +23,7 @@ import ( "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" listersalpha "github.com/tektoncd/pipeline/pkg/client/listers/pipeline/v1alpha1" + listersbeta "github.com/tektoncd/pipeline/pkg/client/listers/pipeline/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -105,3 +106,83 @@ func FilterOwnerRunRef(runLister listersalpha.RunLister, apiVersion, kind string return result } } + +// FilterCustomRunRef returns a filter that can be passed to a CustomRun Informer, which +// filters out CustomRuns for apiVersion and kinds that a controller doesn't care +// about. +// +// For example, a controller impl that wants to be notified of updates to CustomRuns +// which reference a Task with apiVersion "example.dev/v0" and kind "Example": +// +// customruninformer.Get(ctx).Informer().AddEventHandler(cache.FilteringResourceEventHandler{ +// FilterFunc: FilterCustomRunRef("example.dev/v0", "Example"), +// Handler: controller.HandleAll(impl.Enqueue), +// }) +func FilterCustomRunRef(apiVersion, kind string) func(interface{}) bool { + return func(obj interface{}) bool { + r, ok := obj.(*v1beta1.CustomRun) + if !ok { + // Somehow got informed of a non-CustomRun object. + // Ignore. + return false + } + if r == nil || (r.Spec.CustomRef == nil && r.Spec.CustomSpec == nil) { + // These are invalid, but just in case they get + // created somehow, don't panic. + return false + } + result := false + if r.Spec.CustomRef != nil { + result = r.Spec.CustomRef.APIVersion == apiVersion && r.Spec.CustomRef.Kind == v1beta1.TaskKind(kind) + } else if r.Spec.CustomSpec != nil { + result = r.Spec.CustomSpec.APIVersion == apiVersion && r.Spec.CustomSpec.Kind == kind + } + return result + } +} + +// FilterOwnerCustomRunRef returns a filter that can be passed to an Informer for any runtime object, which +// filters out objects that aren't controlled by a CustomRun that references a particular apiVersion and kind. +// +// For example, a controller impl that wants to be notified of updates to TaskRuns that are controlled by +// a CustomRun which references a custom task with apiVersion "example.dev/v0" and kind "Example": +// +// taskruninformer.Get(ctx).Informer().AddEventHandler(cache.FilteringResourceEventHandler{ +// FilterFunc: FilterOwnerCustomRunRef("example.dev/v0", "Example"), +// Handler: controller.HandleAll(impl.Enqueue), +// }) +func FilterOwnerCustomRunRef(customRunLister listersbeta.CustomRunLister, apiVersion, kind string) func(interface{}) bool { + return func(obj interface{}) bool { + object, ok := obj.(metav1.Object) + if !ok { + return false + } + owner := metav1.GetControllerOf(object) + if owner == nil { + return false + } + if owner.APIVersion != v1beta1.SchemeGroupVersion.String() || owner.Kind != pipeline.CustomRunControllerName { + // Not owned by a CustomRun + return false + } + run, err := customRunLister.CustomRuns(object.GetNamespace()).Get(owner.Name) + if err != nil { + return false + } + if run.Spec.CustomRef == nil && run.Spec.CustomSpec == nil { + // These are invalid, but just in case they get created somehow, don't panic. + return false + } + if run.Spec.CustomRef != nil && run.Spec.CustomSpec != nil { + // These are invalid. + return false + } + result := false + if run.Spec.CustomRef != nil { + result = run.Spec.CustomRef.APIVersion == apiVersion && run.Spec.CustomRef.Kind == v1beta1.TaskKind(kind) + } else if run.Spec.CustomSpec != nil { + result = run.Spec.CustomSpec.APIVersion == apiVersion && run.Spec.CustomSpec.Kind == kind + } + return result + } +} diff --git a/pkg/controller/filter_test.go b/pkg/controller/filter_test.go index 062abecea82..fb9729958ae 100644 --- a/pkg/controller/filter_test.go +++ b/pkg/controller/filter_test.go @@ -23,6 +23,7 @@ import ( "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" fakeruninformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1alpha1/run/fake" + fakecustomruninformer "github.com/tektoncd/pipeline/pkg/client/injection/informers/pipeline/v1beta1/customrun/fake" "github.com/tektoncd/pipeline/pkg/controller" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -476,3 +477,442 @@ func TestFilterOwnerRunRef(t *testing.T) { }) } } + +func TestFilterCustomRunRef(t *testing.T) { + for _, c := range []struct { + desc string + in interface{} + want bool + }{{ + desc: "not a CustomRun", + in: struct{}{}, + want: false, + }, { + desc: "nil CustomRun", + in: (*v1beta1.CustomRun)(nil), + want: false, + }, { + desc: "nil ref and spec", + in: &v1beta1.CustomRun{ + Spec: v1beta1.CustomRunSpec{ + CustomRef: nil, + CustomSpec: nil, + }, + }, + want: false, + }, { + desc: "both ref and spec", + in: &v1beta1.CustomRun{ + Spec: v1beta1.CustomRunSpec{ + CustomRef: &v1beta1.TaskRef{ + APIVersion: "not-matching", + Kind: kind, + }, + CustomSpec: &v1beta1.EmbeddedCustomRunSpec{ + TypeMeta: runtime.TypeMeta{ + APIVersion: apiVersion, + Kind: kind, + }, + }, + }, + }, + want: false, + }, { + desc: "CustomRun without matching apiVersion in taskRef", + in: &v1beta1.CustomRun{ + Spec: v1beta1.CustomRunSpec{ + CustomRef: &v1beta1.TaskRef{ + APIVersion: "not-matching", + Kind: kind, + }, + }, + }, + want: false, + }, { + desc: "CustomRun without matching kind in taskRef", + in: &v1beta1.CustomRun{ + Spec: v1beta1.CustomRunSpec{ + CustomRef: &v1beta1.TaskRef{ + APIVersion: apiVersion, + Kind: "not-matching", + }, + }, + }, + want: false, + }, { + desc: "CustomRun with matching apiVersion and kind in taskRef", + in: &v1beta1.CustomRun{ + Spec: v1beta1.CustomRunSpec{ + CustomRef: &v1beta1.TaskRef{ + APIVersion: apiVersion, + Kind: kind, + }, + }, + }, + want: true, + }, { + desc: "CustomRun with matching apiVersion and kind in taskSpec", + in: &v1beta1.CustomRun{ + Spec: v1beta1.CustomRunSpec{ + CustomSpec: &v1beta1.EmbeddedCustomRunSpec{ + TypeMeta: runtime.TypeMeta{ + APIVersion: apiVersion, + Kind: kind, + }, + }, + }, + }, + want: true, + }, { + desc: "CustomRun without matching kind for taskSpec", + in: &v1beta1.CustomRun{ + Spec: v1beta1.CustomRunSpec{ + CustomSpec: &v1beta1.EmbeddedCustomRunSpec{ + TypeMeta: runtime.TypeMeta{ + APIVersion: apiVersion, + Kind: "not-matching", + }, + }, + }, + }, + want: false, + }, { + desc: "CustomRun without matching apiVersion for taskSpec", + in: &v1beta1.CustomRun{ + Spec: v1beta1.CustomRunSpec{ + CustomSpec: &v1beta1.EmbeddedCustomRunSpec{ + TypeMeta: runtime.TypeMeta{ + APIVersion: "not-matching", + Kind: kind, + }, + }, + }, + }, + want: false, + }, { + desc: "CustomRun with matching apiVersion and kind and name for taskRef", + in: &v1beta1.CustomRun{ + Spec: v1beta1.CustomRunSpec{ + CustomRef: &v1beta1.TaskRef{ + APIVersion: apiVersion, + Kind: kind, + Name: "some-name", + }, + }, + }, + want: true, + }} { + t.Run(c.desc, func(t *testing.T) { + got := controller.FilterCustomRunRef(apiVersion, kind)(c.in) + if got != c.want { + t.Fatalf("FilterCustomRunRef(%q, %q) got %t, want %t", apiVersion, kind, got, c.want) + } + }) + } +} + +func TestFilterOwnerCustomRunRef(t *testing.T) { + for _, c := range []struct { + desc string + in interface{} + owner *v1beta1.CustomRun + want bool + }{{ + desc: "Owner is a CustomRun for taskRef that references a matching apiVersion and kind", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + Name: "some-customrun", + Controller: &trueB, + }}, + }, + }, + owner: &v1beta1.CustomRun{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-customrun", + Namespace: "default", + }, + Spec: v1beta1.CustomRunSpec{ + CustomRef: &v1beta1.TaskRef{ + APIVersion: apiVersion, + Kind: kind, + }, + }, + }, + want: true, + }, { + desc: "Owner is a CustomRun for taskSpec that references a matching apiVersion and kind", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + Name: "some-customrun", + Controller: &trueB, + }}, + }, + }, + owner: &v1beta1.CustomRun{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-customrun", + Namespace: "default", + }, + Spec: v1beta1.CustomRunSpec{ + CustomSpec: &v1beta1.EmbeddedCustomRunSpec{ + TypeMeta: runtime.TypeMeta{ + APIVersion: apiVersion, + Kind: kind, + }, + }, + }, + }, + want: true, + }, { + desc: "Owner is a CustomRun for taskRef that references a non-matching apiversion", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + Name: "some-other-customrun", + Controller: &trueB, + }}, + }, + }, + owner: &v1beta1.CustomRun{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-other-customrun", + Namespace: "default", + }, + Spec: v1beta1.CustomRunSpec{ + CustomRef: &v1beta1.TaskRef{ + APIVersion: apiVersion2, // different apiversion + Kind: kind, + }, + }, + }, + want: false, + }, { + desc: "Owner is a CustomRun for taskSpec that references a non-matching apiversion", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + Name: "some-other-customrun", + Controller: &trueB, + }}, + }, + }, + owner: &v1beta1.CustomRun{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-other-customrun", + Namespace: "default", + }, + Spec: v1beta1.CustomRunSpec{ + CustomSpec: &v1beta1.EmbeddedCustomRunSpec{ + TypeMeta: runtime.TypeMeta{ + APIVersion: apiVersion2, // different apiVersion than expected + Kind: kind, + }, + }, + }, + }, + want: false, + }, { + desc: "Owner is a CustomRun for taskRef that references a non-matching kind", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + Name: "some-other-customrun2", + Controller: &trueB, + }}, + }, + }, + owner: &v1beta1.CustomRun{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-other-customrun2", + Namespace: "default", + }, + Spec: v1beta1.CustomRunSpec{ + CustomRef: &v1beta1.TaskRef{ + APIVersion: apiVersion, + Kind: kind2, // different kind than expected + }, + }, + }, + want: false, + }, { + desc: "Owner is a CustomRun for taskSpec that references a non-matching kind", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + Name: "some-other-customrun2", + Controller: &trueB, + }}, + }, + }, + owner: &v1beta1.CustomRun{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-other-customrun2", + Namespace: "default", + }, + Spec: v1beta1.CustomRunSpec{ + CustomSpec: &v1beta1.EmbeddedCustomRunSpec{ + TypeMeta: runtime.TypeMeta{ + APIVersion: apiVersion, + Kind: kind2, // different kind than expected + }, + }, + }, + }, + want: false, + }, { + desc: "Owner is a CustomRun with a missing ref and spec", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + Name: "some-strange-customrun", + Controller: &trueB, + }}, + }, + }, + owner: &v1beta1.CustomRun{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-strange-customrun", + Namespace: "default", + }, + Spec: v1beta1.CustomRunSpec{}, // missing ref (illegal) + }, + want: false, + }, { + desc: "Owner is a CustomRun with both ref and spec with matching apiversion and kind", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + Name: "some-strange-customrun", + Controller: &trueB, + }}, + }, + }, + owner: &v1beta1.CustomRun{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1beta1.SchemeGroupVersion.String(), + Kind: pipeline.CustomRunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-strange-customrun", + Namespace: "default", + }, + Spec: v1beta1.CustomRunSpec{ + CustomRef: &v1beta1.TaskRef{ + APIVersion: apiVersion, + Kind: kind, + }, + CustomSpec: &v1beta1.EmbeddedCustomRunSpec{ + TypeMeta: runtime.TypeMeta{ + APIVersion: apiVersion, + Kind: kind, + }, + }, + }, + }, + want: false, + }, { + desc: "Owner is not a CustomRun", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: pipeline.PipelineRunControllerName, // owned by PipelineRun, not Run + Name: "some-pipelinerun", + Controller: &trueB, + }}, + }, + }, + want: false, + }, { + desc: "Object has no owner", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun-no-owner", + Namespace: "default", + }, + }, + want: false, + }, { + desc: "input is not a runtime Object", + in: struct{}{}, + want: false, + }} { + t.Run(c.desc, func(t *testing.T) { + ctx, _ := rtesting.SetupFakeContext(t) + customRunInformer := fakecustomruninformer.Get(ctx) + if c.owner != nil { + if err := customRunInformer.Informer().GetIndexer().Add(c.owner); err != nil { + t.Fatal(err) + } + } + got := controller.FilterOwnerCustomRunRef(customRunInformer.Lister(), apiVersion, kind)(c.in) + if got != c.want { + t.Fatalf("FilterOwnerCustomRunRef(%q, %q) got %t, want %t", apiVersion, kind, got, c.want) + } + }) + } +}