From 3a8d414c349c2321b5acadba62347e9b5c720fcd Mon Sep 17 00:00:00 2001 From: Greg Dritschler Date: Thu, 1 Oct 2020 11:27:16 -0400 Subject: [PATCH] Add helper function to filter by owning Run Add a filter function that can be used when creating an event handler to notify your custom controller when an object owned by a Run is modified. --- pkg/controller/filter.go | 39 ++++++++ pkg/controller/filter_test.go | 183 +++++++++++++++++++++++++++++++++- 2 files changed, 220 insertions(+), 2 deletions(-) diff --git a/pkg/controller/filter.go b/pkg/controller/filter.go index 4644f5b5423..659cd6211f4 100644 --- a/pkg/controller/filter.go +++ b/pkg/controller/filter.go @@ -19,7 +19,10 @@ limitations under the License. package controller import ( + "github.com/tektoncd/pipeline/pkg/apis/pipeline" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + listersalpha "github.com/tektoncd/pipeline/pkg/client/listers/pipeline/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // FilterRunRef returns a filter that can be passed to a Run Informer, which @@ -50,3 +53,39 @@ func FilterRunRef(apiVersion, kind string) func(interface{}) bool { return r.Spec.Ref.APIVersion == apiVersion && r.Spec.Ref.Kind == v1alpha1.TaskKind(kind) } } + +// FilterOwnerRunRef returns a filter that can be passed to an Informer for any runtime object, which +// filters out objects that aren't controlled by a Run 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 Run which references a custom task with apiVersion "example.dev/v0" and kind "Example": +// +// taskruninformer.Get(ctx).Informer().AddEventHandler(cache.FilteringResourceEventHandler{ +// FilterFunc: FilterOwnerRunRef("example.dev/v0", "Example"), +// Handler: controller.HandleAll(impl.Enqueue), +// }) +func FilterOwnerRunRef(runLister listersalpha.RunLister, 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 != v1alpha1.SchemeGroupVersion.String() || owner.Kind != pipeline.RunControllerName { + // Not owned by a Run + return false + } + run, err := runLister.Runs(object.GetNamespace()).Get(owner.Name) + if err != nil { + return false + } + if run.Spec.Ref == nil { + // These are invalid, but just in case they get created somehow, don't panic. + return false + } + return run.Spec.Ref.APIVersion == apiVersion && run.Spec.Ref.Kind == v1alpha1.TaskKind(kind) + } +} diff --git a/pkg/controller/filter_test.go b/pkg/controller/filter_test.go index ba6768e7032..bef4158665f 100644 --- a/pkg/controller/filter_test.go +++ b/pkg/controller/filter_test.go @@ -19,15 +19,24 @@ package controller_test import ( "testing" + "github.com/tektoncd/pipeline/pkg/apis/pipeline" "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" "github.com/tektoncd/pipeline/pkg/controller" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + rtesting "knative.dev/pkg/reconciler/testing" ) const ( - apiVersion = "example.dev/v0" - kind = "Example" + apiVersion = "example.dev/v0" + apiVersion2 = "example.dev/v1" + kind = "Example" + kind2 = "SomethingCompletelyDifferent" ) +var trueB = true + func TestFilterRunRef(t *testing.T) { for _, c := range []struct { desc string @@ -103,3 +112,173 @@ func TestFilterRunRef(t *testing.T) { }) } } + +func TestFilterOwnerRunRef(t *testing.T) { + for _, c := range []struct { + desc string + in interface{} + owner *v1alpha1.Run + want bool + }{{ + desc: "Owner is a Run that references a matching apiVersion and kind", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: pipeline.RunControllerName, + Name: "some-run", + Controller: &trueB, + }}, + }, + }, + owner: &v1alpha1.Run{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: pipeline.RunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-run", + Namespace: "default", + }, + Spec: v1alpha1.RunSpec{ + Ref: &v1alpha1.TaskRef{ + APIVersion: apiVersion, + Kind: kind, + }, + }, + }, + want: true, + }, { + desc: "Owner is a Run that references a non-matching apiversion", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: pipeline.RunControllerName, + Name: "some-other-run", + Controller: &trueB, + }}, + }, + }, + owner: &v1alpha1.Run{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: pipeline.RunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-other-run", + Namespace: "default", + }, + Spec: v1alpha1.RunSpec{ + Ref: &v1alpha1.TaskRef{ + APIVersion: apiVersion2, // different apiversion + Kind: kind, + }, + }, + }, + want: false, + }, { + desc: "Owner is a Run that references a non-matching kind", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: pipeline.RunControllerName, + Name: "some-other-run2", + Controller: &trueB, + }}, + }, + }, + owner: &v1alpha1.Run{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: pipeline.RunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-other-run2", + Namespace: "default", + }, + Spec: v1alpha1.RunSpec{ + Ref: &v1alpha1.TaskRef{ + APIVersion: apiVersion, + Kind: kind2, // different kind + }, + }, + }, + want: false, + }, { + desc: "Owner is a Run that with a missing ref", + in: &v1beta1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-taskrun", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: pipeline.RunControllerName, + Name: "some-strange-run", + Controller: &trueB, + }}, + }, + }, + owner: &v1alpha1.Run{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: pipeline.RunControllerName, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "some-strange-run", + Namespace: "default", + }, + Spec: v1alpha1.RunSpec{}, // missing ref (illegal) + }, + want: false, + }, { + desc: "Owner is not a Run", + 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) + runInformer := fakeruninformer.Get(ctx) + if c.owner != nil { + if err := runInformer.Informer().GetIndexer().Add(c.owner); err != nil { + t.Fatal(err) + } + } + got := controller.FilterOwnerRunRef(runInformer.Lister(), apiVersion, kind)(c.in) + if got != c.want { + t.Fatalf("FilterOwnerRunRef(%q, %q) got %t, want %t", apiVersion, kind, got, c.want) + } + }) + } +}