diff --git a/pkg/resources/create.go b/pkg/resources/create.go new file mode 100644 index 000000000..d7d2cafb6 --- /dev/null +++ b/pkg/resources/create.go @@ -0,0 +1,118 @@ +/* +Copyright 2019 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. +*/ + +package resources + +import ( + "encoding/json" + "fmt" + "strings" + + "k8s.io/client-go/dynamic" + + "go.uber.org/zap" + + triggersv1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + "github.com/tidwall/sjson" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + discoveryclient "k8s.io/client-go/discovery" +) + +// FindAPIResource returns the APIResource definition using the discovery client c. +func FindAPIResource(apiVersion, kind string, c discoveryclient.ServerResourcesInterface) (*metav1.APIResource, error) { + resourceList, err := c.ServerResourcesForGroupVersion(apiVersion) + if err != nil { + return nil, fmt.Errorf("Error getting kubernetes server resources for apiVersion %s: %s", apiVersion, err) + } + for _, apiResource := range resourceList.APIResources { + if apiResource.Kind != kind { + continue + } + r := &apiResource + // Resolve GroupVersion from parent list to have consistent resource identifiers. + if r.Version == "" || r.Group == "" { + gv, err := schema.ParseGroupVersion(resourceList.GroupVersion) + if err != nil { + return nil, fmt.Errorf("error parsing parsing GroupVersion: %v", err) + } + r.Group = gv.Group + r.Version = gv.Version + } + return r, nil + } + return nil, fmt.Errorf("Error could not find resource with apiVersion %s and kind %s", apiVersion, kind) +} + +// Create uses the kubeClient to create the resource defined in the +// TriggerResourceTemplate and returns any errors with this process +func Create(logger *zap.SugaredLogger, rt json.RawMessage, triggerName, eventID, elName, elNamespace string, c discoveryclient.ServerResourcesInterface, dc dynamic.Interface) error { + rt, err := AddLabels(rt, map[string]string{ + triggersv1.EventListenerLabelKey: elName, + triggersv1.EventIDLabelKey: eventID, + triggersv1.TriggerLabelKey: triggerName, + }) + if err != nil { + return err + } + + // Assume the TriggerResourceTemplate is valid (it has an apiVersion and Kind) + data := new(unstructured.Unstructured) + if err := data.UnmarshalJSON(rt); err != nil { + return err + } + + namespace := data.GetNamespace() + // Default the resource creation to the EventListenerNamespace if not found in the resource template + if namespace == "" { + namespace = elNamespace + } + + // Resolve resource kind to the underlying API Resource type. + apiResource, err := FindAPIResource(data.GetAPIVersion(), data.GetKind(), c) + if err != nil { + return err + } + + name := data.GetName() + if name == "" { + name = data.GetGenerateName() + } + logger.Infof("Generating resource: kind: %+v, name: %s", apiResource, name) + + gvr := schema.GroupVersionResource{ + Group: apiResource.Group, + Version: apiResource.Version, + Resource: apiResource.Name, + } + + _, err = dc.Resource(gvr).Namespace(namespace).Create(data, metav1.CreateOptions{}) + return err +} + +// AddLabels adds autogenerated Tekton labels to created resources. +func AddLabels(rt json.RawMessage, labels map[string]string) (json.RawMessage, error) { + var err error + for k, v := range labels { + l := fmt.Sprintf("metadata.labels.%s/%s", triggersv1.LabelEscape, strings.TrimLeft(k, "/")) + rt, err = sjson.SetBytes(rt, l, v) + if err != nil { + return rt, err + } + } + return rt, err +} diff --git a/pkg/resources/create_test.go b/pkg/resources/create_test.go new file mode 100644 index 000000000..6648e4525 --- /dev/null +++ b/pkg/resources/create_test.go @@ -0,0 +1,280 @@ +/* +Copyright 2019 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. +*/ + +package resources + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + pipelinev1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + triggersv1 "github.com/tektoncd/triggers/pkg/apis/triggers/v1alpha1" + dynamicclientset "github.com/tektoncd/triggers/pkg/client/dynamic/clientset" + "github.com/tektoncd/triggers/pkg/client/dynamic/clientset/tekton" + "github.com/tektoncd/triggers/test" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + fakedynamic "k8s.io/client-go/dynamic/fake" + fakekubeclientset "k8s.io/client-go/kubernetes/fake" + ktesting "k8s.io/client-go/testing" + "knative.dev/pkg/logging" +) + +const ( + resourceLabel = triggersv1.GroupName + triggersv1.EventListenerLabelKey + triggerLabel = triggersv1.GroupName + triggersv1.TriggerLabelKey + eventIDLabel = triggersv1.GroupName + triggersv1.EventIDLabelKey + + triggerName = "trigger" + eventID = "12345" +) + +func Test_FindAPIResource_error(t *testing.T) { + dc := fakekubeclientset.NewSimpleClientset().Discovery() + if _, err := FindAPIResource("v1", "Pod", dc); err == nil { + t.Error("findAPIResource() did not return error when expected") + } +} + +func TestFindAPIResource(t *testing.T) { + // Create fake kubeclient with list of resources + kubeClient := fakekubeclientset.NewSimpleClientset() + kubeClient.Resources = []*metav1.APIResourceList{{ + GroupVersion: "v1", + APIResources: []metav1.APIResource{{ + Name: "pods", + Namespaced: true, + Kind: "Pod", + }, { + Name: "namespaces", + Namespaced: false, + Kind: "Namespace", + }}, + }} + test.AddTektonResources(kubeClient) + dc := kubeClient.Discovery() + + tests := []struct { + apiVersion string + kind string + want *metav1.APIResource + }{{ + apiVersion: "v1", + kind: "Pod", + want: &metav1.APIResource{ + Name: "pods", + Namespaced: true, + Version: "v1", + Kind: "Pod", + }, + }, { + apiVersion: "v1", + kind: "Namespace", + want: &metav1.APIResource{ + Name: "namespaces", + Namespaced: false, + Version: "v1", + Kind: "Namespace", + }, + }, { + apiVersion: "tekton.dev/v1alpha1", + kind: "TriggerTemplate", + want: &metav1.APIResource{ + Group: "tekton.dev", + Version: "v1alpha1", + Name: "triggertemplates", + Namespaced: true, + Kind: "TriggerTemplate", + }, + }, { + apiVersion: "tekton.dev/v1alpha1", + kind: "PipelineRun", + want: &metav1.APIResource{ + Group: "tekton.dev", + Version: "v1alpha1", + Name: "pipelineruns", + Namespaced: true, + Kind: "PipelineRun", + }, + }, + } + for _, tt := range tests { + t.Run(fmt.Sprintf("%s_%s", tt.apiVersion, tt.kind), func(t *testing.T) { + got, err := FindAPIResource(tt.apiVersion, tt.kind, dc) + if err != nil { + t.Errorf("findAPIResource() returned error: %s", err) + } else if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("findAPIResource() Diff: -want +got: %s", diff) + } + }) + } +} + +func TestCreateResource(t *testing.T) { + elName := "foo-el" + elNamespace := "bar" + + kubeClient := fakekubeclientset.NewSimpleClientset() + test.AddTektonResources(kubeClient) + + dynamicClient := fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()) + dynamicSet := dynamicclientset.New(tekton.WithClient(dynamicClient)) + + logger, _ := logging.NewLogger("", "") + + tests := []struct { + name string + resource pipelinev1.PipelineResource + want pipelinev1.PipelineResource + }{{ + name: "PipelineResource without namespace", + resource: pipelinev1.PipelineResource{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "tekton.dev/v1alpha1", + Kind: "PipelineResource", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pipelineresource", + Labels: map[string]string{"woriginal-label-1": "label-1"}, + }, + Spec: pipelinev1.PipelineResourceSpec{}, + }, + want: pipelinev1.PipelineResource{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "tekton.dev/v1alpha1", + Kind: "PipelineResource", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pipelineresource", + Labels: map[string]string{ + "woriginal-label-1": "label-1", + resourceLabel: elName, + triggerLabel: triggerName, + eventIDLabel: eventID, + }, + }, + Spec: pipelinev1.PipelineResourceSpec{}, + }, + }, { + name: "PipelineResource with namespace", + resource: pipelinev1.PipelineResource{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "tekton.dev/v1alpha1", + Kind: "PipelineResource", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "my-pipelineresource", + Labels: map[string]string{"woriginal-label-1": "label-1"}, + }, + Spec: pipelinev1.PipelineResourceSpec{}, + }, + want: pipelinev1.PipelineResource{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "tekton.dev/v1alpha1", + Kind: "PipelineResource", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "foo", + Name: "my-pipelineresource", + Labels: map[string]string{ + "woriginal-label-1": "label-1", + resourceLabel: elName, + triggerLabel: triggerName, + eventIDLabel: eventID, + }, + }, + Spec: pipelinev1.PipelineResourceSpec{}, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dynamicClient.ClearActions() + + b, err := json.Marshal(tt.resource) + if err != nil { + t.Fatalf("error marshalling resource: %v", tt.resource) + } + if err := Create(logger, b, triggerName, eventID, elName, elNamespace, kubeClient.Discovery(), dynamicSet); err != nil { + t.Errorf("createResource() returned error: %s", err) + } + + gvr := schema.GroupVersionResource{ + Group: "tekton.dev", + Version: "v1alpha1", + Resource: "pipelineresources", + } + namespace := tt.want.Namespace + if namespace == "" { + namespace = elNamespace + } + want := []ktesting.Action{ktesting.NewCreateAction(gvr, namespace, test.ToUnstructured(t, tt.want))} + if diff := cmp.Diff(want, dynamicClient.Actions()); diff != "" { + t.Error(diff) + } + }) + } +} + +func Test_AddLabels(t *testing.T) { + b, err := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + // should be overwritten + "tekton.dev/a": "0", + // should be preserved. + "tekton.dev/z": "0", + "best-palindrome": "tacocat", + }, + }, + }) + if err != nil { + t.Fatalf("json.Marshal: %v", err) + } + + raw, err := AddLabels(json.RawMessage(b), map[string]string{ + "a": "1", + "/b": "2", + "//c": "3", + }) + if err != nil { + t.Fatalf("addLabels: %v", err) + } + + got := make(map[string]interface{}) + if err := json.Unmarshal(raw, &got); err != nil { + t.Fatalf("json.Unmarshal: %v", err) + } + + want := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + "tekton.dev/a": "1", + "tekton.dev/b": "2", + "tekton.dev/c": "3", + "tekton.dev/z": "0", + "best-palindrome": "tacocat", + }, + }, + } + + if diff := cmp.Diff(want, got); diff != "" { + t.Error(diff) + } +} diff --git a/pkg/sink/sink.go b/pkg/sink/sink.go index 118b8ed1d..2a0d8bfda 100644 --- a/pkg/sink/sink.go +++ b/pkg/sink/sink.go @@ -21,16 +21,13 @@ import ( "fmt" "io/ioutil" "net/http" - "strings" "github.com/tektoncd/triggers/pkg/interceptors/github" "github.com/tektoncd/triggers/pkg/interceptors/gitlab" + "github.com/tektoncd/triggers/pkg/resources" "github.com/tektoncd/triggers/pkg/interceptors" - "github.com/tidwall/sjson" - "golang.org/x/xerrors" - "github.com/tektoncd/triggers/pkg/interceptors/webhook" pipelineclientset "github.com/tektoncd/pipeline/pkg/client/clientset/versioned" @@ -40,8 +37,6 @@ import ( "github.com/tektoncd/triggers/pkg/template" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" discoveryclient "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -126,8 +121,8 @@ func (r Sink) HandleEvent(response http.ResponseWriter, request *http.Request) { return } log.Info("params: %+v", params) - resources := template.ResolveResources(rt.TriggerTemplate, params) - if err := r.createResources(resources, t.Name, eventID); err != nil { + res := template.ResolveResources(rt.TriggerTemplate, params) + if err := r.createResources(res, t.Name, eventID); err != nil { log.Error(err) } result <- http.StatusCreated @@ -150,96 +145,11 @@ func (r Sink) HandleEvent(response http.ResponseWriter, request *http.Request) { r.EventListenerName, r.EventListenerNamespace, string(eventID), string(event), request.Header) } -func (r Sink) createResources(resources []json.RawMessage, triggerName, eventID string) error { - for _, resource := range resources { - if err := r.createResource(resource, triggerName, eventID); err != nil { +func (r Sink) createResources(res []json.RawMessage, triggerName, eventID string) error { + for _, rr := range res { + if err := resources.Create(r.Logger, rr, triggerName, eventID, r.EventListenerName, r.EventListenerNamespace, r.DiscoveryClient, r.DynamicClient); err != nil { return err } } return nil } - -// createResource uses the kubeClient to create the resource defined in the -// TriggerResourceTemplate and returns any errors with this process -func (r Sink) createResource(rt json.RawMessage, triggerName string, eventID string) error { - // Add common labels - rt, err := addLabels(rt, map[string]string{ - triggersv1.EventListenerLabelKey: r.EventListenerName, - triggersv1.EventIDLabelKey: eventID, - triggersv1.TriggerLabelKey: triggerName, - }) - if err != nil { - return err - } - - // Assume the TriggerResourceTemplate is valid (it has an apiVersion and Kind) - data := new(unstructured.Unstructured) - if err := data.UnmarshalJSON(rt); err != nil { - return err - } - - namespace := data.GetNamespace() - // Default the resource creation to the EventListenerNamespace if not found in the resource template - if namespace == "" { - namespace = r.EventListenerNamespace - } - - // Resolve resource kind to the underlying API Resource type. - apiResource, err := r.findAPIResource(data.GetAPIVersion(), data.GetKind()) - if err != nil { - return err - } - - name := data.GetName() - if name == "" { - name = data.GetGenerateName() - } - r.Logger.Infof("Generating resource: kind: %+v, name: %s", apiResource, name) - - gvr := schema.GroupVersionResource{ - Group: apiResource.Group, - Version: apiResource.Version, - Resource: apiResource.Name, - } - - _, err = r.DynamicClient.Resource(gvr).Namespace(namespace).Create(data, metav1.CreateOptions{}) - return err -} - -// findAPIResource returns the APIResource definition using the discovery client. -func (r Sink) findAPIResource(apiVersion, kind string) (*metav1.APIResource, error) { - resourceList, err := r.DiscoveryClient.ServerResourcesForGroupVersion(apiVersion) - if err != nil { - return nil, xerrors.Errorf("Error getting kubernetes server resources for apiVersion %s: %s", apiVersion, err) - } - for _, apiResource := range resourceList.APIResources { - if apiResource.Kind != kind { - continue - } - r := &apiResource - // Resolve GroupVersion from parent list to have consistent resource identifiers. - if r.Version == "" || r.Group == "" { - gv, err := schema.ParseGroupVersion(resourceList.GroupVersion) - if err != nil { - return nil, xerrors.Errorf("error parsing parsing GroupVersion: %v", err) - } - r.Group = gv.Group - r.Version = gv.Version - } - return r, nil - } - return nil, xerrors.Errorf("Error could not find resource with apiVersion %s and kind %s", apiVersion, kind) -} - -// addLabels adds autogenerated Tekton labels to created resources. -func addLabels(rt json.RawMessage, labels map[string]string) (json.RawMessage, error) { - var err error - for k, v := range labels { - l := fmt.Sprintf("metadata.labels.%s/%s", triggersv1.LabelEscape, strings.TrimLeft(k, "/")) - rt, err = sjson.SetBytes(rt, l, v) - if err != nil { - return rt, err - } - } - return rt, err -} diff --git a/pkg/sink/sink_test.go b/pkg/sink/sink_test.go index 20d09ab01..a8694f71b 100644 --- a/pkg/sink/sink_test.go +++ b/pkg/sink/sink_test.go @@ -19,7 +19,6 @@ package sink import ( "bytes" "encoding/json" - "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -35,9 +34,9 @@ import ( dynamicclientset "github.com/tektoncd/triggers/pkg/client/dynamic/clientset" "github.com/tektoncd/triggers/pkg/client/dynamic/clientset/tekton" "github.com/tektoncd/triggers/pkg/template" + "github.com/tektoncd/triggers/test" bldr "github.com/tektoncd/triggers/test/builder" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" fakedynamic "k8s.io/client-go/dynamic/fake" @@ -50,8 +49,7 @@ const ( triggerLabel = triggersv1.GroupName + triggersv1.TriggerLabelKey eventIDLabel = triggersv1.GroupName + triggersv1.EventIDLabelKey - triggerName = "trigger" - eventID = "12345" + eventID = "12345" ) func init() { @@ -59,225 +57,6 @@ func init() { template.UID = func() string { return eventID } } -func Test_findAPIResource_error(t *testing.T) { - s := Sink{DiscoveryClient: fakekubeclientset.NewSimpleClientset().Discovery()} - if _, err := s.findAPIResource("v1", "Pod"); err == nil { - t.Error("findAPIResource() did not return error when expected") - } -} - -func addTektonResources(clientset *fakekubeclientset.Clientset) { - nameKind := map[string]string{ - "triggertemplates": "TriggerTemplate", - "pipelineruns": "PipelineRun", - "taskruns": "TaskRun", - "pipelineresources": "PipelineResource", - } - resources := make([]metav1.APIResource, 0, len(nameKind)) - for name, kind := range nameKind { - resources = append(resources, metav1.APIResource{ - Group: "tekton.dev", - Version: "v1alpha1", - Namespaced: true, - Name: name, - Kind: kind, - }) - } - - clientset.Resources = append(clientset.Resources, &metav1.APIResourceList{ - GroupVersion: "tekton.dev/v1alpha1", - APIResources: resources, - }) -} - -func TestFindAPIResource(t *testing.T) { - // Create fake kubeclient with list of resources - kubeClient := fakekubeclientset.NewSimpleClientset() - kubeClient.Resources = []*metav1.APIResourceList{{ - GroupVersion: "v1", - APIResources: []metav1.APIResource{{ - Name: "pods", - Namespaced: true, - Kind: "Pod", - }, { - Name: "namespaces", - Namespaced: false, - Kind: "Namespace", - }}, - }} - addTektonResources(kubeClient) - s := Sink{DiscoveryClient: kubeClient.Discovery()} - - tests := []struct { - apiVersion string - kind string - want *metav1.APIResource - }{{ - apiVersion: "v1", - kind: "Pod", - want: &metav1.APIResource{ - Name: "pods", - Namespaced: true, - Version: "v1", - Kind: "Pod", - }, - }, { - apiVersion: "v1", - kind: "Namespace", - want: &metav1.APIResource{ - Name: "namespaces", - Namespaced: false, - Version: "v1", - Kind: "Namespace", - }, - }, { - apiVersion: "tekton.dev/v1alpha1", - kind: "TriggerTemplate", - want: &metav1.APIResource{ - Group: "tekton.dev", - Version: "v1alpha1", - Name: "triggertemplates", - Namespaced: true, - Kind: "TriggerTemplate", - }, - }, { - apiVersion: "tekton.dev/v1alpha1", - kind: "PipelineRun", - want: &metav1.APIResource{ - Group: "tekton.dev", - Version: "v1alpha1", - Name: "pipelineruns", - Namespaced: true, - Kind: "PipelineRun", - }, - }, - } - for _, tt := range tests { - t.Run(fmt.Sprintf("%s_%s", tt.apiVersion, tt.kind), func(t *testing.T) { - got, err := s.findAPIResource(tt.apiVersion, tt.kind) - if err != nil { - t.Errorf("findAPIResource() returned error: %s", err) - } else if diff := cmp.Diff(tt.want, got); diff != "" { - t.Errorf("findAPIResource() Diff: -want +got: %s", diff) - } - }) - } -} - -func TestCreateResource(t *testing.T) { - elName := "foo-el" - elNamespace := "bar" - - kubeClient := fakekubeclientset.NewSimpleClientset() - addTektonResources(kubeClient) - - dynamicClient := fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()) - dynamicSet := dynamicclientset.New(tekton.WithClient(dynamicClient)) - - logger, _ := logging.NewLogger("", "") - - tests := []struct { - name string - resource pipelinev1.PipelineResource - want pipelinev1.PipelineResource - }{{ - name: "PipelineResource without namespace", - resource: pipelinev1.PipelineResource{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "tekton.dev/v1alpha1", - Kind: "PipelineResource", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "my-pipelineresource", - Labels: map[string]string{"woriginal-label-1": "label-1"}, - }, - Spec: pipelinev1.PipelineResourceSpec{}, - }, - want: pipelinev1.PipelineResource{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "tekton.dev/v1alpha1", - Kind: "PipelineResource", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "my-pipelineresource", - Labels: map[string]string{ - "woriginal-label-1": "label-1", - resourceLabel: elName, - triggerLabel: triggerName, - eventIDLabel: eventID, - }, - }, - Spec: pipelinev1.PipelineResourceSpec{}, - }, - }, { - name: "PipelineResource with namespace", - resource: pipelinev1.PipelineResource{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "tekton.dev/v1alpha1", - Kind: "PipelineResource", - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "foo", - Name: "my-pipelineresource", - Labels: map[string]string{"woriginal-label-1": "label-1"}, - }, - Spec: pipelinev1.PipelineResourceSpec{}, - }, - want: pipelinev1.PipelineResource{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "tekton.dev/v1alpha1", - Kind: "PipelineResource", - }, - ObjectMeta: metav1.ObjectMeta{ - Namespace: "foo", - Name: "my-pipelineresource", - Labels: map[string]string{ - "woriginal-label-1": "label-1", - resourceLabel: elName, - triggerLabel: triggerName, - eventIDLabel: eventID, - }, - }, - Spec: pipelinev1.PipelineResourceSpec{}, - }, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - dynamicClient.ClearActions() - - r := Sink{ - DynamicClient: dynamicSet, - DiscoveryClient: kubeClient.Discovery(), - EventListenerNamespace: elNamespace, - EventListenerName: elName, - Logger: logger, - } - - b, err := json.Marshal(tt.resource) - if err != nil { - t.Fatalf("error marshalling resource: %v", tt.resource) - } - if err := r.createResource(b, triggerName, eventID); err != nil { - t.Errorf("createResource() returned error: %s", err) - } - - gvr := schema.GroupVersionResource{ - Group: "tekton.dev", - Version: "v1alpha1", - Resource: "pipelineresources", - } - namespace := tt.want.Namespace - if namespace == "" { - namespace = elNamespace - } - want := []ktesting.Action{ktesting.NewCreateAction(gvr, namespace, toUnstructured(t, tt.want))} - if diff := cmp.Diff(want, dynamicClient.Actions()); diff != "" { - t.Error(diff) - } - }) - } -} - func TestHandleEvent(t *testing.T) { namespace := "foo" eventBody := json.RawMessage(`{"head_commit": {"id": "testrevision"}, "repository": {"url": "testurl"}}`) @@ -323,7 +102,7 @@ func TestHandleEvent(t *testing.T) { bldr.EventListenerSpec(bldr.EventListenerTrigger("my-triggerbinding", "my-triggertemplate", "v1alpha1"))) kubeClient := fakekubeclientset.NewSimpleClientset() - addTektonResources(kubeClient) + test.AddTektonResources(kubeClient) triggersClient := faketriggersclientset.NewSimpleClientset() if _, err := triggersClient.TektonV1alpha1().TriggerTemplates(namespace).Create(tt); err != nil { @@ -411,70 +190,8 @@ func TestHandleEvent(t *testing.T) { Version: "v1alpha1", Resource: "pipelineresources", } - want := []ktesting.Action{ktesting.NewCreateAction(gvr, "foo", toUnstructured(t, wantResource))} + want := []ktesting.Action{ktesting.NewCreateAction(gvr, "foo", test.ToUnstructured(t, wantResource))} if diff := cmp.Diff(want, dynamicClient.Actions()); diff != "" { t.Error(diff) } } - -func toUnstructured(t *testing.T, in interface{}) *unstructured.Unstructured { - t.Helper() - - b, err := json.Marshal(in) - if err != nil { - t.Fatalf("error encoding to JSON: %v", err) - } - - out := new(unstructured.Unstructured) - if err := out.UnmarshalJSON(b); err != nil { - t.Fatalf("error encoding to unstructured: %v", err) - } - return out -} - -func Test_addLabels(t *testing.T) { - b, err := json.Marshal(map[string]interface{}{ - "metadata": map[string]interface{}{ - "labels": map[string]interface{}{ - // should be overwritten - "tekton.dev/a": "0", - // should be preserved. - "tekton.dev/z": "0", - "best-palindrome": "tacocat", - }, - }, - }) - if err != nil { - t.Fatalf("json.Marshal: %v", err) - } - - raw, err := addLabels(json.RawMessage(b), map[string]string{ - "a": "1", - "/b": "2", - "//c": "3", - }) - if err != nil { - t.Fatalf("addLabels: %v", err) - } - - got := make(map[string]interface{}) - if err := json.Unmarshal(raw, &got); err != nil { - t.Fatalf("json.Unmarshal: %v", err) - } - - want := map[string]interface{}{ - "metadata": map[string]interface{}{ - "labels": map[string]interface{}{ - "tekton.dev/a": "1", - "tekton.dev/b": "2", - "tekton.dev/c": "3", - "tekton.dev/z": "0", - "best-palindrome": "tacocat", - }, - }, - } - - if diff := cmp.Diff(want, got); diff != "" { - t.Error(diff) - } -} diff --git a/test/resources.go b/test/resources.go new file mode 100644 index 000000000..8d6574a6f --- /dev/null +++ b/test/resources.go @@ -0,0 +1,68 @@ +/* +Copyright 2019 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. +*/ + +package test + +import ( + "encoding/json" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + fakekubeclientset "k8s.io/client-go/kubernetes/fake" +) + +// ToUnstructured returns an Unstructured object from interface in. +func ToUnstructured(t *testing.T, in interface{}) *unstructured.Unstructured { + t.Helper() + + b, err := json.Marshal(in) + if err != nil { + t.Fatalf("error encoding to JSON: %v", err) + } + + out := new(unstructured.Unstructured) + if err := out.UnmarshalJSON(b); err != nil { + t.Fatalf("error encoding to unstructured: %v", err) + } + return out +} + +// AddTektonResources will update clientset to konw it knows about the types it is +// expected to be able to interact with. +func AddTektonResources(clientset *fakekubeclientset.Clientset) { + nameKind := map[string]string{ + "triggertemplates": "TriggerTemplate", + "pipelineruns": "PipelineRun", + "taskruns": "TaskRun", + "pipelineresources": "PipelineResource", + } + resources := make([]metav1.APIResource, 0, len(nameKind)) + for name, kind := range nameKind { + resources = append(resources, metav1.APIResource{ + Group: "tekton.dev", + Version: "v1alpha1", + Namespaced: true, + Name: name, + Kind: kind, + }) + } + + clientset.Resources = append(clientset.Resources, &metav1.APIResourceList{ + GroupVersion: "tekton.dev/v1alpha1", + APIResources: resources, + }) +}