diff --git a/cmd/eventlistenersink/main.go b/cmd/eventlistenersink/main.go index 06435cb84d..fbce6ab798 100644 --- a/cmd/eventlistenersink/main.go +++ b/cmd/eventlistenersink/main.go @@ -87,6 +87,11 @@ func main() { factory := externalversions.NewSharedInformerFactoryWithOptions(sinkClients.TriggersClient, 30*time.Second, externalversions.WithNamespace(sinkArgs.ElNamespace)) + if sinkArgs.IsMultiNS { + factory = externalversions.NewSharedInformerFactory(sinkClients.TriggersClient, + 30*time.Second) + } + go func(ctx context.Context) { factory.Start(ctx.Done()) <-ctx.Done() diff --git a/cmd/triggerrun/cmd/root.go b/cmd/triggerrun/cmd/root.go index 9344c4f823..4a3667e422 100644 --- a/cmd/triggerrun/cmd/root.go +++ b/cmd/triggerrun/cmd/root.go @@ -127,7 +127,7 @@ func trigger(triggerFile, httpPath, action, kubeconfig string, writer io.Writer) } case "create": { - err := r.CreateResources("", resources, tri.Name, eventID, eventLog) + err := r.CreateResources(tri.Namespace, "", resources, tri.Name, eventID, eventLog) if err != nil { return fmt.Errorf("fail to create resources: %w", err) } @@ -193,15 +193,9 @@ func processTriggerSpec(kubeClient kubernetes.Interface, client triggersclientse return nil, errors.New("trigger is not defined") } - //convert trigger to eventListener - el, err := triggersv1.ToEventListenerTrigger(tri.Spec) - if err != nil { - return nil, fmt.Errorf("fail to convert Trigger to EvenetListener: %w", err) - } - - log := eventLog.With(zap.String(triggersv1.TriggerLabelKey, el.Name)) + log := eventLog.With(zap.String(triggersv1.TriggerLabelKey, r.EventListenerName)) - finalPayload, header, iresp, err := r.ExecuteInterceptors(&el, request, body, log, eventID) + finalPayload, header, iresp, err := r.ExecuteInterceptors(*tri, request, body, log, eventID) if err != nil { log.Error(err) return nil, err @@ -216,7 +210,7 @@ func processTriggerSpec(kubeClient kubernetes.Interface, client triggersclientse tri.Namespace = "default" } - rt, err := template.ResolveTrigger(el, + rt, err := template.ResolveTrigger(*tri, func(name string) (*triggersv1.TriggerBinding, error) { return client.TriggersV1alpha1().TriggerBindings(tri.Namespace).Get(context.Background(), name, metav1.GetOptions{}) }, diff --git a/docs/eventlisteners.md b/docs/eventlisteners.md index fbd7a9b63c..a875261b86 100644 --- a/docs/eventlisteners.md +++ b/docs/eventlisteners.md @@ -23,6 +23,7 @@ using [Event Interceptors](#Interceptors). - [PodTemplate](#podtemplate) - [Resources](#resources) - [Logging](#logging) + - [NamespaceSelector](#namespaceSelector) - [Labels](#labels) - [Annotations](#annotations) - [Interceptors](#interceptors) @@ -68,6 +69,8 @@ the following fields: for your EventListener pod - [`resources`](#resources) - Specifies the Kubernetes Resource information for your EventListener pod + - [`namespaceSelector`](#namespaceSelector) - Specifies the namespaces where + EventListener can fetch triggers from and create Tekton resources. [kubernetes-overview]: https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/#required-fields @@ -294,6 +297,29 @@ To access logs for the EventListener sink, you can query for pods with the kubectl get pods --selector eventlistener=my-eventlistener ``` +### NamespaceSelector +The `namespaceSelector` field is optional. +This field determines the namespaces where EventListener can search for triggers and +create Tekton resources. If this field isn't provided, EventListener will only serve Triggers from its +own namespace. + +Snippet below will function in foo and bar namespaces. +```yaml + namespaceSelector: + matchNames: + - foo + - bar +``` + +If EventListener is required to listen to serve the whole cluster, then below snippet +can be used where we only provide single argument for `matchNames` as `*`. +```yaml + namespaceSelector: + matchNames: + - * +``` + + ## Labels By default, EventListeners will attach the following labels automatically to all diff --git a/examples/selectors/00_ns.yaml b/examples/selectors/00_ns.yaml new file mode 100644 index 0000000000..f670e01da6 --- /dev/null +++ b/examples/selectors/00_ns.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: foo +--- +apiVersion: v1 +kind: Namespace +metadata: + name: bar diff --git a/examples/selectors/01_rbac.yaml b/examples/selectors/01_rbac.yaml new file mode 100644 index 0000000000..16e6e84142 --- /dev/null +++ b/examples/selectors/01_rbac.yaml @@ -0,0 +1,74 @@ +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: el-sel-clusterrole +rules: +# Permissions for every EventListener deployment to function +- apiGroups: ["triggers.tekton.dev"] + resources: ["eventlisteners", "clustertriggerbindings", "triggerbindings", "triggertemplates", "triggers"] + verbs: ["get", "list", "watch"] +- apiGroups: [""] + resources: ["configmaps", "secrets"] + verbs: ["get", "list", "watch"] +- apiGroups: [""] + resources: ["serviceaccounts"] + verbs: ["impersonate"] +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: foo-el-sa + namespace: foo +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: el-sel-clusterrolebinding +subjects: +- kind: ServiceAccount + name: foo-el-sa + namespace: foo +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: el-sel-clusterrole +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: triggercr-role + namespace: bar +rules: +- apiGroups: ["tekton.dev"] + resources: ["pipelineruns", "pipelineresources", "taskruns"] + verbs: ["create"] +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: bar-trigger-sa + namespace: bar +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: triggercr-rolebinding + namespace: bar +subjects: +- kind: ServiceAccount + name: bar-trigger-sa + namespace: bar +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: triggercr-role +--- +apiVersion: v1 +kind: Secret +metadata: + name: github-secret + namespace: bar +type: Opaque +stringData: + secretToken: "1234567" diff --git a/examples/selectors/02_eventlistener-sel.yaml b/examples/selectors/02_eventlistener-sel.yaml new file mode 100644 index 0000000000..2d0935496f --- /dev/null +++ b/examples/selectors/02_eventlistener-sel.yaml @@ -0,0 +1,11 @@ +apiVersion: triggers.tekton.dev/v1alpha1 +kind: EventListener +metadata: + name: listener-sel + namespace: foo +spec: + serviceAccountName: foo-el-sa + namespaceSelector: + matchNames: + - foo + - bar diff --git a/examples/selectors/03_trigger.yaml b/examples/selectors/03_trigger.yaml new file mode 100644 index 0000000000..fcada5e9ab --- /dev/null +++ b/examples/selectors/03_trigger.yaml @@ -0,0 +1,77 @@ +--- +apiVersion: triggers.tekton.dev/v1alpha1 +kind: Trigger +metadata: + name: trigger + namespace: bar +spec: + serviceAccountName: bar-trigger-sa + interceptors: + - github: + secretRef: + secretName: github-secret + secretKey: secretToken + eventTypes: + - pull_request + bindings: + - ref: pipeline-binding + template: + ref: pipeline-template +--- +apiVersion: triggers.tekton.dev/v1alpha1 +kind: TriggerBinding +metadata: + name: pipeline-binding + namespace: bar +spec: + params: + - name: gitrevision + value: $(body.head_commit.id) + - name: gitrepositoryurl + value: $(body.repository.url) + - name: contenttype + value: $(header.Content-Type) +--- +apiVersion: triggers.tekton.dev/v1alpha1 +kind: TriggerTemplate +metadata: + name: pipeline-template + namespace: bar +spec: + params: + - name: gitrevision + description: The git revision + default: master + - name: gitrepositoryurl + description: The git repository url + - name: message + description: The message to print + default: This is the default message + - name: contenttype + description: The Content-Type of the event + resourcetemplates: + - apiVersion: tekton.dev/v1beta1 + kind: PipelineRun + metadata: + generateName: simple-pipeline-run- + spec: + pipelineRef: + name: simple-pipeline + podTemplate: + securityContext: + runAsNonRoot: true + runAsUser: 1001 + params: + - name: message + value: $(tt.params.message) + - name: contenttype + value: $(tt.params.contenttype) + resources: + - name: git-source + resourceSpec: + type: git + params: + - name: revision + value: $(tt.params.gitrevision) + - name: url + value: $(tt.params.gitrepositoryurl) diff --git a/pkg/apis/triggers/v1alpha1/event_listener_types.go b/pkg/apis/triggers/v1alpha1/event_listener_types.go index daa7f3f7ba..019074bd9e 100644 --- a/pkg/apis/triggers/v1alpha1/event_listener_types.go +++ b/pkg/apis/triggers/v1alpha1/event_listener_types.go @@ -58,6 +58,7 @@ type EventListenerSpec struct { ServiceType corev1.ServiceType `json:"serviceType,omitempty"` Replicas *int32 `json:"replicas,omitempty"` PodTemplate PodTemplate `json:"podTemplate,omitempty"` + NamespaceSelector NamespaceSelector `json:"namespaceSelector,omitempty"` Resources Resources `json:"resources,omitempty"` } @@ -151,6 +152,14 @@ type EventListenerConfig struct { GeneratedResourceName string `json:"generatedName"` } +// NamespaceSelector is a selector for selecting either all namespaces or a +// list of namespaces. +// +k8s:openapi-gen=true +type NamespaceSelector struct { + // List of namespace names. + MatchNames []string `json:"matchNames,omitempty"` +} + // The conditions that are internally resolved by the EventListener reconciler const ( // ServiceExists is the ConditionType set on the EventListener, which diff --git a/pkg/apis/triggers/v1alpha1/event_listener_validation.go b/pkg/apis/triggers/v1alpha1/event_listener_validation.go index 99f9b774a1..5afce7b509 100644 --- a/pkg/apis/triggers/v1alpha1/event_listener_validation.go +++ b/pkg/apis/triggers/v1alpha1/event_listener_validation.go @@ -36,9 +36,6 @@ func (s *EventListenerSpec) validate(ctx context.Context) (errs *apis.FieldError errs = errs.Also(apis.ErrInvalidValue(*s.Replicas, "spec.replicas")) } } - if len(s.Triggers) == 0 { - errs = errs.Also(apis.ErrMissingField("spec.triggers")) - } for i, trigger := range s.Triggers { errs = errs.Also(trigger.validate(ctx).ViaField(fmt.Sprintf("spec.triggers[%d]", i))) } diff --git a/pkg/apis/triggers/v1alpha1/event_listener_validation_test.go b/pkg/apis/triggers/v1alpha1/event_listener_validation_test.go index 0639551221..914066ab40 100644 --- a/pkg/apis/triggers/v1alpha1/event_listener_validation_test.go +++ b/pkg/apis/triggers/v1alpha1/event_listener_validation_test.go @@ -215,28 +215,6 @@ func TestEventListenerValidate_error(t *testing.T) { name string el *v1alpha1.EventListener }{{ - name: "no triggers", - el: &v1alpha1.EventListener{ - ObjectMeta: metav1.ObjectMeta{ - Name: "n", - Namespace: "namespace", - }, - Spec: v1alpha1.EventListenerSpec{ - Triggers: []v1alpha1.EventListenerTrigger{{}}, - }, - }, - }, { - name: "EventListener with no Trigger ref or Template", - el: &v1alpha1.EventListener{ - ObjectMeta: metav1.ObjectMeta{ - Name: "n", - Namespace: "namespace", - }, - Spec: v1alpha1.EventListenerSpec{ - Triggers: nil, - }, - }, - }, { name: "Valid EventListener with empty TriggerTemplate name", el: bldr.EventListener("name", "namespace", bldr.EventListenerSpec( diff --git a/pkg/apis/triggers/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/triggers/v1alpha1/zz_generated.deepcopy.go index d2a1c2ad8e..87d29718b7 100644 --- a/pkg/apis/triggers/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/triggers/v1alpha1/zz_generated.deepcopy.go @@ -243,6 +243,7 @@ func (in *EventListenerSpec) DeepCopyInto(out *EventListenerSpec) { **out = **in } in.PodTemplate.DeepCopyInto(&out.PodTemplate) + in.NamespaceSelector.DeepCopyInto(&out.NamespaceSelector) in.Resources.DeepCopyInto(&out.Resources) return } @@ -388,6 +389,27 @@ func (in *KubernetesResource) DeepCopy() *KubernetesResource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespaceSelector) DeepCopyInto(out *NamespaceSelector) { + *out = *in + if in.MatchNames != nil { + in, out := &in.MatchNames, &out.MatchNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSelector. +func (in *NamespaceSelector) DeepCopy() *NamespaceSelector { + if in == nil { + return nil + } + out := new(NamespaceSelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Param) DeepCopyInto(out *Param) { *out = *in diff --git a/pkg/interceptors/bitbucket/bitbucket.go b/pkg/interceptors/bitbucket/bitbucket.go index b44f399c69..2db7d954bd 100644 --- a/pkg/interceptors/bitbucket/bitbucket.go +++ b/pkg/interceptors/bitbucket/bitbucket.go @@ -31,18 +31,18 @@ import ( ) type Interceptor struct { - KubeClientSet kubernetes.Interface - Logger *zap.SugaredLogger - Bitbucket *triggersv1.BitbucketInterceptor - EventListenerNamespace string + KubeClientSet kubernetes.Interface + Logger *zap.SugaredLogger + Bitbucket *triggersv1.BitbucketInterceptor + TriggerNamespace string } func NewInterceptor(bh *triggersv1.BitbucketInterceptor, k kubernetes.Interface, ns string, l *zap.SugaredLogger) interceptors.Interceptor { return &Interceptor{ - Logger: l, - Bitbucket: bh, - KubeClientSet: k, - EventListenerNamespace: ns, + Logger: l, + Bitbucket: bh, + KubeClientSet: k, + TriggerNamespace: ns, } } @@ -64,7 +64,7 @@ func (w *Interceptor) ExecuteTrigger(request *http.Request) (*http.Response, err if header == "" { return nil, errors.New("no X-Hub-Signature header set") } - secretToken, err := interceptors.GetSecretToken(request, w.KubeClientSet, w.Bitbucket.SecretRef, w.EventListenerNamespace) + secretToken, err := interceptors.GetSecretToken(request, w.KubeClientSet, w.Bitbucket.SecretRef, w.TriggerNamespace) if err != nil { return nil, err } diff --git a/pkg/interceptors/bitbucket/bitbucket_test.go b/pkg/interceptors/bitbucket/bitbucket_test.go index 0dd9be6f8f..8e6983202f 100644 --- a/pkg/interceptors/bitbucket/bitbucket_test.go +++ b/pkg/interceptors/bitbucket/bitbucket_test.go @@ -237,10 +237,10 @@ func TestInterceptor_ExecuteTrigger_Signature(t *testing.T) { } } w := &Interceptor{ - KubeClientSet: kubeClient, - Bitbucket: tt.Bitbucket, - Logger: logger, - EventListenerNamespace: metav1.NamespaceDefault, + KubeClientSet: kubeClient, + Bitbucket: tt.Bitbucket, + Logger: logger, + TriggerNamespace: metav1.NamespaceDefault, } resp, err := w.ExecuteTrigger(request) if err != nil { diff --git a/pkg/interceptors/cel/cel.go b/pkg/interceptors/cel/cel.go index a1de33b799..0ae2306d28 100644 --- a/pkg/interceptors/cel/cel.go +++ b/pkg/interceptors/cel/cel.go @@ -48,10 +48,10 @@ var _ triggersv1.InterceptorInterface = (*Interceptor)(nil) // against the incoming body and headers to match, if the expression returns // a true value, then the interception is "successful". type Interceptor struct { - KubeClientSet kubernetes.Interface - Logger *zap.SugaredLogger - CEL *triggersv1.CELInterceptor - EventListenerNamespace string + KubeClientSet kubernetes.Interface + Logger *zap.SugaredLogger + CEL *triggersv1.CELInterceptor + TriggerNamespace string } var ( diff --git a/pkg/interceptors/github/github.go b/pkg/interceptors/github/github.go index ebbf078635..cee14380a9 100644 --- a/pkg/interceptors/github/github.go +++ b/pkg/interceptors/github/github.go @@ -34,18 +34,18 @@ import ( var ErrInvalidContentType = errors.New("form parameter encoding not supported, please change the hook to send JSON payloads") type Interceptor struct { - KubeClientSet kubernetes.Interface - Logger *zap.SugaredLogger - GitHub *triggersv1.GitHubInterceptor - EventListenerNamespace string + KubeClientSet kubernetes.Interface + Logger *zap.SugaredLogger + GitHub *triggersv1.GitHubInterceptor + TriggerNamespace string } func NewInterceptor(gh *triggersv1.GitHubInterceptor, k kubernetes.Interface, ns string, l *zap.SugaredLogger) interceptors.Interceptor { return &Interceptor{ - Logger: l, - GitHub: gh, - KubeClientSet: k, - EventListenerNamespace: ns, + Logger: l, + GitHub: gh, + KubeClientSet: k, + TriggerNamespace: ns, } } @@ -70,7 +70,7 @@ func (w *Interceptor) ExecuteTrigger(request *http.Request) (*http.Response, err if header == "" { return nil, errors.New("no X-Hub-Signature header set") } - secretToken, err := interceptors.GetSecretToken(request, w.KubeClientSet, w.GitHub.SecretRef, w.EventListenerNamespace) + secretToken, err := interceptors.GetSecretToken(request, w.KubeClientSet, w.GitHub.SecretRef, w.TriggerNamespace) if err != nil { return nil, err } diff --git a/pkg/interceptors/github/github_test.go b/pkg/interceptors/github/github_test.go index 1bfc6ce9e4..6ad7b705d2 100644 --- a/pkg/interceptors/github/github_test.go +++ b/pkg/interceptors/github/github_test.go @@ -237,10 +237,10 @@ func TestInterceptor_ExecuteTrigger_Signature(t *testing.T) { } } w := &Interceptor{ - KubeClientSet: kubeClient, - GitHub: tt.GitHub, - Logger: logger, - EventListenerNamespace: metav1.NamespaceDefault, + KubeClientSet: kubeClient, + GitHub: tt.GitHub, + Logger: logger, + TriggerNamespace: metav1.NamespaceDefault, } resp, err := w.ExecuteTrigger(request) if err != nil { @@ -273,10 +273,10 @@ func TestInterceptor_ExecuteTrigger_with_invalid_content_type(t *testing.T) { }, } w := &Interceptor{ - KubeClientSet: kubeClient, - GitHub: &triggersv1.GitHubInterceptor{}, - Logger: logger, - EventListenerNamespace: metav1.NamespaceDefault, + KubeClientSet: kubeClient, + GitHub: &triggersv1.GitHubInterceptor{}, + Logger: logger, + TriggerNamespace: metav1.NamespaceDefault, } _, err := w.ExecuteTrigger(request) if err != ErrInvalidContentType { diff --git a/pkg/interceptors/gitlab/gitlab.go b/pkg/interceptors/gitlab/gitlab.go index f349e7b489..ae5c832c19 100644 --- a/pkg/interceptors/gitlab/gitlab.go +++ b/pkg/interceptors/gitlab/gitlab.go @@ -31,18 +31,18 @@ import ( ) type Interceptor struct { - KubeClientSet kubernetes.Interface - Logger *zap.SugaredLogger - GitLab *triggersv1.GitLabInterceptor - EventListenerNamespace string + KubeClientSet kubernetes.Interface + Logger *zap.SugaredLogger + GitLab *triggersv1.GitLabInterceptor + TriggerNamespace string } func NewInterceptor(gl *triggersv1.GitLabInterceptor, k kubernetes.Interface, ns string, l *zap.SugaredLogger) interceptors.Interceptor { return &Interceptor{ - Logger: l, - GitLab: gl, - KubeClientSet: k, - EventListenerNamespace: ns, + Logger: l, + GitLab: gl, + KubeClientSet: k, + TriggerNamespace: ns, } } @@ -54,7 +54,7 @@ func (w *Interceptor) ExecuteTrigger(request *http.Request) (*http.Response, err return nil, errors.New("no X-GitLab-Token header set") } - secretToken, err := interceptors.GetSecretToken(request, w.KubeClientSet, w.GitLab.SecretRef, w.EventListenerNamespace) + secretToken, err := interceptors.GetSecretToken(request, w.KubeClientSet, w.GitLab.SecretRef, w.TriggerNamespace) if err != nil { return nil, err } diff --git a/pkg/interceptors/gitlab/gitlab_test.go b/pkg/interceptors/gitlab/gitlab_test.go index fbdfa66c90..e3b8e85e95 100644 --- a/pkg/interceptors/gitlab/gitlab_test.go +++ b/pkg/interceptors/gitlab/gitlab_test.go @@ -221,10 +221,10 @@ func TestInterceptor_ExecuteTrigger(t *testing.T) { } } w := &Interceptor{ - KubeClientSet: kubeClient, - GitLab: tt.GitLab, - Logger: logger, - EventListenerNamespace: metav1.NamespaceDefault, + KubeClientSet: kubeClient, + GitLab: tt.GitLab, + Logger: logger, + TriggerNamespace: metav1.NamespaceDefault, } resp, err := w.ExecuteTrigger(request) if err != nil { diff --git a/pkg/interceptors/interceptors.go b/pkg/interceptors/interceptors.go index 5d94b14bbd..4f5e322a19 100644 --- a/pkg/interceptors/interceptors.go +++ b/pkg/interceptors/interceptors.go @@ -49,10 +49,10 @@ func getCache(req *http.Request) map[string]interface{} { // // As we may have many triggers that all use the same secret, we cache the secret values // in the request cache. -func GetSecretToken(req *http.Request, cs kubernetes.Interface, sr *triggersv1.SecretRef, eventListenerNamespace string) ([]byte, error) { +func GetSecretToken(req *http.Request, cs kubernetes.Interface, sr *triggersv1.SecretRef, triggerNS string) ([]byte, error) { var cache map[string]interface{} - cacheKey := path.Join("secret", eventListenerNamespace, sr.SecretName, sr.SecretKey) + cacheKey := path.Join("secret", triggerNS, sr.SecretName, sr.SecretKey) if req != nil { cache = getCache(req) if secretValue, ok := cache[cacheKey]; ok { @@ -60,7 +60,7 @@ func GetSecretToken(req *http.Request, cs kubernetes.Interface, sr *triggersv1.S } } - secret, err := cs.CoreV1().Secrets(eventListenerNamespace).Get(context.Background(), sr.SecretName, metav1.GetOptions{}) + secret, err := cs.CoreV1().Secrets(triggerNS).Get(context.Background(), sr.SecretName, metav1.GetOptions{}) if err != nil { return nil, err } diff --git a/pkg/interceptors/webhook/webhook.go b/pkg/interceptors/webhook/webhook.go index c29e41aa73..fc85191a5b 100644 --- a/pkg/interceptors/webhook/webhook.go +++ b/pkg/interceptors/webhook/webhook.go @@ -41,10 +41,10 @@ const ( ) type Interceptor struct { - HTTPClient *http.Client - EventListenerNamespace string - Logger *zap.SugaredLogger - Webhook *triggersv1.WebhookInterceptor + HTTPClient *http.Client + TriggerNamespace string + Logger *zap.SugaredLogger + Webhook *triggersv1.WebhookInterceptor } func NewInterceptor(wh *triggersv1.WebhookInterceptor, c *http.Client, ns string, l *zap.SugaredLogger) interceptors.Interceptor { @@ -53,15 +53,15 @@ func NewInterceptor(wh *triggersv1.WebhookInterceptor, c *http.Client, ns string Timeout: interceptorTimeout, } return &Interceptor{ - HTTPClient: timeoutClient, - EventListenerNamespace: ns, - Logger: l, - Webhook: wh, + HTTPClient: timeoutClient, + TriggerNamespace: ns, + Logger: l, + Webhook: wh, } } func (w *Interceptor) ExecuteTrigger(request *http.Request) (*http.Response, error) { - u, err := getURI(w.Webhook.ObjectRef, w.EventListenerNamespace) // TODO: Cache this result or do this on initialization + u, err := getURI(w.Webhook.ObjectRef, w.TriggerNamespace) // TODO: Cache this result or do this on initialization if err != nil { return nil, err } diff --git a/pkg/reconciler/v1alpha1/eventlistener/eventlistener.go b/pkg/reconciler/v1alpha1/eventlistener/eventlistener.go index 66d16f2425..777057e678 100644 --- a/pkg/reconciler/v1alpha1/eventlistener/eventlistener.go +++ b/pkg/reconciler/v1alpha1/eventlistener/eventlistener.go @@ -306,6 +306,10 @@ func (r *Reconciler) reconcileDeployment(ctx context.Context, logger *zap.Sugare resources = el.Spec.Resources.KubernetesResource.Template.Spec.Containers[0].Resources } } + isMultiNS := true + if len(el.Spec.NamespaceSelector.MatchNames) == 0 { + isMultiNS = false + } container := corev1.Container{ Name: "event-listener", Image: *elImage, @@ -344,6 +348,7 @@ func (r *Reconciler) reconcileDeployment(ctx context.Context, logger *zap.Sugare "-writetimeout", strconv.FormatInt(*ELWriteTimeOut, 10), "-idletimeout", strconv.FormatInt(*ELIdleTimeOut, 10), "-timeouthandler", strconv.FormatInt(*ELTimeOutHandler, 10), + "-is-multi-ns", strconv.FormatBool(isMultiNS), }, VolumeMounts: []corev1.VolumeMount{{ Name: "config-logging", diff --git a/pkg/sink/initialization.go b/pkg/sink/initialization.go index cd85366275..794a030759 100644 --- a/pkg/sink/initialization.go +++ b/pkg/sink/initialization.go @@ -33,6 +33,7 @@ const ( name = "el-name" elNamespace = "el-namespace" port = "port" + isMultiNS = "is-multi-ns" ) var ( @@ -50,6 +51,8 @@ var ( "The idle timeout for EventListener Server.") elTimeOutHandler = flag.Int64("timeouthandler", 5, "The timeout for Timeout Handler of EventListener Server.") + isMultiNSFlag = flag.Bool("is-multi-ns", false, + "Whether EventListener serve Multiple NS.") ) // Args define the arguments for Sink. @@ -68,6 +71,8 @@ type Args struct { ELIdleTimeOut time.Duration // ELTimeOutHandler defines the timeout for Timeout Handler of EventListener Server ELTimeOutHandler time.Duration + // IsMultiNS determines whether el functions as namespaced or clustered + IsMultiNS bool } // Clients define the set of client dependencies Sink requires. @@ -89,10 +94,12 @@ func GetArgs() (Args, error) { if *portFlag == "" { return Args{}, xerrors.Errorf("-%s arg not found", port) } + return Args{ ElName: *nameFlag, ElNamespace: *namespaceFlag, Port: *portFlag, + IsMultiNS: *isMultiNSFlag, ELReadTimeOut: time.Duration(*elReadTimeOut), ELWriteTimeOut: time.Duration(*elWriteTimeOut), ELIdleTimeOut: time.Duration(*elIdleTimeOut), diff --git a/pkg/sink/initialization_test.go b/pkg/sink/initialization_test.go index db5aaec9da..723a6a2f97 100644 --- a/pkg/sink/initialization_test.go +++ b/pkg/sink/initialization_test.go @@ -18,6 +18,7 @@ package sink import ( "flag" + "strconv" "testing" ) @@ -31,6 +32,10 @@ func Test_GetArgs(t *testing.T) { if err := flag.Set(port, "port"); err != nil { t.Errorf("Error setting flag port: %s", err) } + if err := flag.Set(isMultiNS, "true"); err != nil { + t.Errorf("Error setting flag isMultiNS: %s", err) + } + sinkArgs, err := GetArgs() if err != nil { t.Fatalf("GetArgs() returned unexpected error: %s", err) @@ -44,6 +49,9 @@ func Test_GetArgs(t *testing.T) { if sinkArgs.Port != "port" { t.Errorf("Error port want port, got %s", sinkArgs.Port) } + if sinkArgs.IsMultiNS != true { + t.Errorf("Error EL Type want type, got %t", sinkArgs.IsMultiNS) + } } func Test_GetArgs_error(t *testing.T) { @@ -52,21 +60,25 @@ func Test_GetArgs_error(t *testing.T) { elName string elNamespace string port string + isMultiNS bool }{{ name: "no eventlistener name", elName: "", elNamespace: "elnamespace", port: "port", + isMultiNS: false, }, { name: "no eventlistener namespace", elName: "elname", elNamespace: "", port: "port", + isMultiNS: false, }, { name: "no eventlistener namespace", elName: "elname", elNamespace: "elnamespace", port: "", + isMultiNS: false, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -79,6 +91,10 @@ func Test_GetArgs_error(t *testing.T) { if err := flag.Set("port", tt.port); err != nil { t.Errorf("Error setting flag %s: %s", port, err) } + isMulNS := strconv.FormatBool(tt.isMultiNS) + if err := flag.Set("is-multi-ns", isMulNS); err != nil { + t.Errorf("Error setting flag %s: %s", isMultiNS, err) + } if sinkArgs, err := GetArgs(); err == nil { t.Errorf("GetArgs() did not return error when expected; sinkArgs: %v", sinkArgs) } diff --git a/pkg/sink/sink.go b/pkg/sink/sink.go index d8fc18200a..ade156ba9d 100644 --- a/pkg/sink/sink.go +++ b/pkg/sink/sink.go @@ -38,6 +38,8 @@ import ( "github.com/tektoncd/triggers/pkg/template" "go.uber.org/zap" kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" discoveryclient "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -93,13 +95,37 @@ func (r Sink) HandleEvent(response http.ResponseWriter, request *http.Request) { eventLog := r.Logger.With(zap.String(triggersv1.EventIDLabelKey, eventID)) eventLog.Debugf("EventListener: %s in Namespace: %s handling event (EventID: %s) with payload: %s and header: %v", r.EventListenerName, r.EventListenerNamespace, eventID, string(event), request.Header) - + var trItems []*triggersv1.Trigger + if len(el.Spec.NamespaceSelector.MatchNames) == 1 && + el.Spec.NamespaceSelector.MatchNames[0] == "*" { + trList, err := r.TriggerLister.List(labels.Everything()) + if err != nil { + r.Logger.Errorf("Error getting Triggers: %s", err) + response.WriteHeader(http.StatusInternalServerError) + return + } + trItems = trList + } else if len(el.Spec.NamespaceSelector.MatchNames) != 0 { + for _, v := range el.Spec.NamespaceSelector.MatchNames { + trList, err := r.TriggerLister.Triggers(v).List(labels.Everything()) + if err != nil { + r.Logger.Errorf("Error getting Triggers: %s", err) + response.WriteHeader(http.StatusInternalServerError) + return + } + trItems = append(trItems, trList...) + } + } + triggers, err := r.merge(el.Spec.Triggers, trItems) + if err != nil { + r.Logger.Errorf("Error merging Triggers: %s", err) + } result := make(chan int, 10) // Execute each Trigger - for _, t := range el.Spec.Triggers { - go func(t triggersv1.EventListenerTrigger) { + for _, t := range triggers { + go func(t triggersv1.Trigger) { localRequest := request.Clone(request.Context()) - if err := r.processTrigger(&t, localRequest, event, eventID, eventLog); err != nil { + if err := r.processTrigger(t, localRequest, event, eventID, eventLog); err != nil { if kerrors.IsUnauthorized(err) { result <- http.StatusUnauthorized return @@ -112,13 +138,13 @@ func (r Sink) HandleEvent(response http.ResponseWriter, request *http.Request) { return } result <- http.StatusCreated - }(t) + }(*t) } //The eventlistener waits until all the trigger executions (up-to the creation of the resources) and //only when at least one of the execution completed successfully, it returns response code 201(Created) otherwise it returns 202 (Accepted). code := http.StatusAccepted - for i := 0; i < len(el.Spec.Triggers); i++ { + for i := 0; i < len(triggers); i++ { thiscode := <-result // current take - if someone is doing unauthorized stuff, we abort immediately; // unauthorized should be the final status code vs. the less than comparison @@ -144,26 +170,37 @@ func (r Sink) HandleEvent(response http.ResponseWriter, request *http.Request) { } } -func (r Sink) processTrigger(t *triggersv1.EventListenerTrigger, request *http.Request, event []byte, eventID string, eventLog *zap.SugaredLogger) error { - - if t == nil { - return errors.New("EventListenerTrigger not defined") - } - - if t.Template == nil && t.TriggerRef != "" { - trigger, err := r.TriggerLister.Triggers(r.EventListenerNamespace).Get(t.TriggerRef) - if err != nil { - r.Logger.Errorf("Error getting Trigger %s in Namespace %s: %s", t.TriggerRef, r.EventListenerNamespace, err) - return err - } - trig, err := triggersv1.ToEventListenerTrigger(trigger.Spec) - if err != nil { - r.Logger.Errorf("Error changing Trigger to EventListenerTrigger: %s", err) - return err +func (r Sink) merge(et []triggersv1.EventListenerTrigger, trItems []*triggersv1.Trigger) ([]*triggersv1.Trigger, error) { + triggers := trItems + for _, t := range et { + switch { + case t.Template == nil && t.TriggerRef != "": + trig, err := r.TriggerLister.Triggers(r.EventListenerNamespace).Get(t.TriggerRef) + if err != nil { + r.Logger.Errorf("Error getting Trigger %s in Namespace %s: %s", t.TriggerRef, r.EventListenerNamespace, err) + continue + } + triggers = append(triggers, trig) + case t.Template != nil: + triggers = append(triggers, &triggersv1.Trigger{ + ObjectMeta: metav1.ObjectMeta{ + Name: t.Name, + Namespace: r.EventListenerNamespace}, + Spec: triggersv1.TriggerSpec{ + ServiceAccountName: t.ServiceAccountName, + Bindings: t.Bindings, + Template: *t.Template, + Interceptors: t.Interceptors, + }, + }) + default: + return nil, errors.New("EventListenerTrigger not defined") } - t = &trig } + return triggers, nil +} +func (r Sink) processTrigger(t triggersv1.Trigger, request *http.Request, event []byte, eventID string, eventLog *zap.SugaredLogger) error { log := eventLog.With(zap.String(triggersv1.TriggerLabelKey, t.Name)) finalPayload, header, iresp, err := r.ExecuteInterceptors(t, request, event, log, eventID) @@ -179,10 +216,10 @@ func (r Sink) processTrigger(t *triggersv1.EventListenerTrigger, request *http.R } } - rt, err := template.ResolveTrigger(*t, - r.TriggerBindingLister.TriggerBindings(r.EventListenerNamespace).Get, + rt, err := template.ResolveTrigger(t, + r.TriggerBindingLister.TriggerBindings(t.Namespace).Get, r.ClusterTriggerBindingLister.Get, - r.TriggerTemplateLister.TriggerTemplates(r.EventListenerNamespace).Get) + r.TriggerTemplateLister.TriggerTemplates(t.Namespace).Get) if err != nil { log.Error(err) return err @@ -199,7 +236,7 @@ func (r Sink) processTrigger(t *triggersv1.EventListenerTrigger, request *http.R log.Infof("ResolvedParams : %+v", params) resources := template.ResolveResources(rt.TriggerTemplate, params) - if err := r.CreateResources(t.ServiceAccountName, resources, t.Name, eventID, log); err != nil { + if err := r.CreateResources(t.Namespace, t.Spec.ServiceAccountName, resources, t.Name, eventID, log); err != nil { log.Error(err) return err } @@ -208,8 +245,8 @@ func (r Sink) processTrigger(t *triggersv1.EventListenerTrigger, request *http.R // ExecuteInterceptor executes all interceptors for the Trigger and returns back the body, header, and InterceptorResponse to use. // When TEP-0022 is fully implemented, this function will only return the InterceptorResponse and error. -func (r Sink) ExecuteInterceptors(t *triggersv1.EventListenerTrigger, in *http.Request, event []byte, log *zap.SugaredLogger, eventID string) ([]byte, http.Header, *triggersv1.InterceptorResponse, error) { - if len(t.Interceptors) == 0 { +func (r Sink) ExecuteInterceptors(t triggersv1.Trigger, in *http.Request, event []byte, log *zap.SugaredLogger, eventID string) ([]byte, http.Header, *triggersv1.InterceptorResponse, error) { + if len(t.Spec.Interceptors) == 0 { return event, in.Header, nil, nil } @@ -223,24 +260,24 @@ func (r Sink) ExecuteInterceptors(t *triggersv1.EventListenerTrigger, in *http.R EventURL: in.URL.String(), EventID: eventID, // t.Name might not be fully accurate until we get rid of triggers inlined within EventListener - TriggerID: fmt.Sprintf("namespaces/%s/triggers/%s", r.EventListenerNamespace, t.Name), // TODO: t.Name might be wrong + TriggerID: fmt.Sprintf("namespaces/%s/triggers/%s", t.Namespace, t.Name), // TODO: t.Name might be wrong }, } var interceptorResponse *triggersv1.InterceptorResponse - for _, i := range t.Interceptors { + for _, i := range t.Spec.Interceptors { var interceptor interceptors.Interceptor switch { case i.Webhook != nil: - interceptor = webhook.NewInterceptor(i.Webhook, r.HTTPClient, r.EventListenerNamespace, log) + interceptor = webhook.NewInterceptor(i.Webhook, r.HTTPClient, t.Namespace, log) case i.GitHub != nil: - interceptor = github.NewInterceptor(i.GitHub, r.KubeClientSet, r.EventListenerNamespace, log) + interceptor = github.NewInterceptor(i.GitHub, r.KubeClientSet, t.Namespace, log) case i.GitLab != nil: - interceptor = gitlab.NewInterceptor(i.GitLab, r.KubeClientSet, r.EventListenerNamespace, log) + interceptor = gitlab.NewInterceptor(i.GitLab, r.KubeClientSet, t.Namespace, log) case i.CEL != nil: interceptor = cel.NewInterceptor(r.KubeClientSet, log) case i.Bitbucket != nil: - interceptor = bitbucket.NewInterceptor(i.Bitbucket, r.KubeClientSet, r.EventListenerNamespace, log) + interceptor = bitbucket.NewInterceptor(i.Bitbucket, r.KubeClientSet, t.Namespace, log) default: return nil, nil, nil, fmt.Errorf("unknown interceptor type: %v", i) } @@ -293,7 +330,7 @@ func (r Sink) ExecuteInterceptors(t *triggersv1.EventListenerTrigger, in *http.R }, nil } -func (r Sink) CreateResources(sa string, res []json.RawMessage, triggerName, eventID string, log *zap.SugaredLogger) error { +func (r Sink) CreateResources(triggerNS, sa string, res []json.RawMessage, triggerName, eventID string, log *zap.SugaredLogger) error { discoveryClient := r.DiscoveryClient dynamicClient := r.DynamicClient var err error @@ -303,7 +340,7 @@ func (r Sink) CreateResources(sa string, res []json.RawMessage, triggerName, eve // However, we also have a ServiceAccountName reference with each EventListenerTrigger to allow // for more fine grained authorization control around the resources we create below. - discoveryClient, dynamicClient, err = r.Auth.OverrideAuthentication(sa, r.EventListenerNamespace, log, r.DiscoveryClient, r.DynamicClient) + discoveryClient, dynamicClient, err = r.Auth.OverrideAuthentication(sa, triggerNS, log, r.DiscoveryClient, r.DynamicClient) if err != nil { log.Errorf("problem cloning rest config: %#v", err) return err @@ -311,7 +348,7 @@ func (r Sink) CreateResources(sa string, res []json.RawMessage, triggerName, eve } for _, rr := range res { - if err := resources.Create(r.Logger, rr, triggerName, eventID, r.EventListenerName, r.EventListenerNamespace, discoveryClient, dynamicClient); err != nil { + if err := resources.Create(r.Logger, rr, triggerName, eventID, r.EventListenerName, triggerNS, discoveryClient, dynamicClient); err != nil { log.Errorf("problem creating obj: %#v", err) return err } diff --git a/pkg/sink/sink_test.go b/pkg/sink/sink_test.go index f9b3948eca..c47d550aef 100644 --- a/pkg/sink/sink_test.go +++ b/pkg/sink/sink_test.go @@ -270,6 +270,115 @@ func TestHandleEvent(t *testing.T) { } } +func TestHandleEventNSSelectorMatchNames(t *testing.T) { + eventBody := json.RawMessage(`{"head_commit": {"id": "testrevision"}, "repository": {"url": "testurl"}, "foo": "bar\t\r\nbaz昨"}`) + + triggerNS := "bar" + + pipelineResource := pipelinev1alpha1.PipelineResource{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "tekton.dev/v1alpha1", + Kind: "PipelineResource", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "$(tt.params.name)", + Namespace: triggerNS, + Labels: map[string]string{ + "app": "$(tt.params.foo)", + "type": "$(tt.params.type)", + }, + }, + Spec: pipelinev1alpha1.PipelineResourceSpec{ + Type: pipelinev1alpha1.PipelineResourceTypeGit, + Params: []pipelinev1alpha1.ResourceParam{ + {Name: "url", Value: "$(tt.params.url)"}, + {Name: "revision", Value: "$(tt.params.revision)"}, + }, + }, + } + pipelineResourceBytes, err := json.Marshal(pipelineResource) + if err != nil { + t.Fatalf("Error unmarshalling pipelineResource: %s", err) + } + + tt := bldr.TriggerTemplate("my-triggertemplate", triggerNS, + bldr.TriggerTemplateSpec( + bldr.TriggerTemplateParam("name", "", ""), + bldr.TriggerTemplateParam("url", "", ""), + bldr.TriggerTemplateParam("revision", "", ""), + bldr.TriggerTemplateParam("foo", "", ""), + bldr.TriggerTemplateParam("type", "", ""), + bldr.TriggerResourceTemplate(runtime.RawExtension{Raw: pipelineResourceBytes}), + )) + // Create TriggerBinding + tbs := []*triggersv1.TriggerBinding{bldr.TriggerBinding("my-triggerbinding", triggerNS, + bldr.TriggerBindingSpec( + bldr.TriggerBindingParam("name", "my-pipelineresource"), + bldr.TriggerBindingParam("url", "$(body.repository.url)"), + bldr.TriggerBindingParam("revision", "$(body.head_commit.id)"), + bldr.TriggerBindingParam("foo", "$(body.foo)"), + bldr.TriggerBindingParam("type", "$(header.Content-Type)"), + ))} + // Add TriggerBinding to Trigger + triggers := []*triggersv1.Trigger{bldr.Trigger("my-trigger", triggerNS, bldr.TriggerSpec( + bldr.TriggerSpecBinding("my-triggerbinding", "", "my-triggerbinding", "v1alpha1"), + bldr.TriggerSpecTemplate("my-triggertemplate", "v1alpha1"), + ))} + // Add TriggerRef to EventListener + el := bldr.EventListener("my-eventlistener", namespace, bldr.EventListenerSpec(bldr.EventListenerNamespaceSelectorMatchNames([]string{triggerNS}))) + + resources := test.Resources{ + TriggerBindings: tbs, + TriggerTemplates: []*triggersv1.TriggerTemplate{tt}, + EventListeners: []*triggersv1.EventListener{el}, + Triggers: triggers, + } + + sink, dynamicClient := getSinkAssets(t, resources, el.Name, DefaultAuthOverride{}) + + ts := httptest.NewServer(http.HandlerFunc(sink.HandleEvent)) + defer ts.Close() + + resp, err := http.Post(ts.URL, "application/json", bytes.NewReader(eventBody)) + if err != nil { + t.Fatalf("Error creating Post request: %s", err) + } + + checkSinkResponse(t, resp, el.Name) + // Check right resources were created. + var wantPrs []pipelinev1alpha1.PipelineResource + wantResource := pipelinev1alpha1.PipelineResource{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "tekton.dev/v1alpha1", + Kind: "PipelineResource", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-pipelineresource", + Namespace: triggerNS, + Labels: map[string]string{ + "app": "bar\t\r\nbaz昨", + "type": "application/json", + resourceLabel: "my-eventlistener", + triggerLabel: "my-trigger", + eventIDLabel: eventID, + }, + }, + Spec: pipelinev1alpha1.PipelineResourceSpec{ + Type: pipelinev1alpha1.PipelineResourceTypeGit, + Params: []pipelinev1alpha1.ResourceParam{ + {Name: "url", Value: "testurl"}, + {Name: "revision", Value: "testrevision"}, + }, + }, + } + wantPrs = append(wantPrs, wantResource) + // Sort actions (we do not know what order they executed in) + gotPrs := getCreatedPipelineResources(t, dynamicClient.Actions()) + if diff := cmp.Diff(wantPrs, gotPrs, cmpopts.SortSlices(comparePR)); diff != "" { + t.Errorf("Created resources mismatch (-want + got): %s", diff) + } +} + func TestHandleEventTriggerRef(t *testing.T) { eventBody := json.RawMessage(`{"head_commit": {"id": "testrevision"}, "repository": {"url": "testurl"}, "foo": "bar\t\r\nbaz昨"}`) @@ -357,7 +466,7 @@ func TestHandleEventTriggerRef(t *testing.T) { "app": "bar\t\r\nbaz昨", "type": "application/json", resourceLabel: "my-eventlistener", - triggerLabel: el.Spec.Triggers[0].Name, + triggerLabel: "my-trigger", eventIDLabel: eventID, }, }, @@ -795,8 +904,9 @@ func TestExecuteInterceptor_Sequential(t *testing.T) { }, }, } - trigger := &triggersv1.EventListenerTrigger{ - Interceptors: []*triggersv1.EventInterceptor{a, a}, + trigger := triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Interceptors: []*triggersv1.EventInterceptor{a, a}}, } for _, method := range []string{ @@ -869,24 +979,26 @@ func TestExecuteInterceptor_error(t *testing.T) { Logger: logger.Sugar(), } - trigger := &triggersv1.EventListenerTrigger{ - Interceptors: []*triggersv1.EventInterceptor{ - // Error interceptor needs to come first. - { - Webhook: &triggersv1.WebhookInterceptor{ - ObjectRef: &corev1.ObjectReference{ - APIVersion: "v1", - Kind: "Service", - Name: errHost, + trigger := triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Interceptors: []*triggersv1.EventInterceptor{ + // Error interceptor needs to come first. + { + Webhook: &triggersv1.WebhookInterceptor{ + ObjectRef: &corev1.ObjectReference{ + APIVersion: "v1", + Kind: "Service", + Name: errHost, + }, }, }, - }, - { - Webhook: &triggersv1.WebhookInterceptor{ - ObjectRef: &corev1.ObjectReference{ - APIVersion: "v1", - Kind: "Service", - Name: "foo", + { + Webhook: &triggersv1.WebhookInterceptor{ + ObjectRef: &corev1.ObjectReference{ + APIVersion: "v1", + Kind: "Service", + Name: "foo", + }, }, }, }, @@ -911,12 +1023,13 @@ func TestExecuteInterceptor_NotContinue(t *testing.T) { Logger: logger.Sugar(), } - trigger := &triggersv1.EventListenerTrigger{ - Interceptors: []*triggersv1.EventInterceptor{{ - CEL: &triggersv1.CELInterceptor{ - Filter: `body.head == "abcde"`, - }, - }}, + trigger := triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Interceptors: []*triggersv1.EventInterceptor{{ + CEL: &triggersv1.CELInterceptor{ + Filter: `body.head == "abcde"`, + }, + }}}, } url, _ := url.Parse("http://example.com") _, _, resp, err := s.ExecuteInterceptors( diff --git a/pkg/template/resource.go b/pkg/template/resource.go index 559ab36464..401be52d76 100644 --- a/pkg/template/resource.go +++ b/pkg/template/resource.go @@ -46,26 +46,26 @@ type getClusterTriggerBinding func(name string) (*triggersv1.ClusterTriggerBindi // ResolveTrigger takes in a trigger containing object refs to bindings and // templates and resolves them to their underlying values. -func ResolveTrigger(trigger triggersv1.EventListenerTrigger, getTB getTriggerBinding, getCTB getClusterTriggerBinding, getTT getTriggerTemplate) (ResolvedTrigger, error) { - bp, err := resolveBindingsToParams(trigger.Bindings, getTB, getCTB) +func ResolveTrigger(trigger triggersv1.Trigger, getTB getTriggerBinding, getCTB getClusterTriggerBinding, getTT getTriggerTemplate) (ResolvedTrigger, error) { + bp, err := resolveBindingsToParams(trigger.Spec.Bindings, getTB, getCTB) if err != nil { return ResolvedTrigger{}, fmt.Errorf("failed to resolve bindings: %w", err) } var resolvedTT *triggersv1.TriggerTemplate - if trigger.Template.Spec != nil { + if trigger.Spec.Template.Spec != nil { resolvedTT = &triggersv1.TriggerTemplate{ ObjectMeta: metav1.ObjectMeta{}, // Unused. TODO: Just return Specs from here. - Spec: *trigger.Template.Spec, + Spec: *trigger.Spec.Template.Spec, } } else { var ttName string - if trigger.Template.Ref != nil { - ttName = *trigger.Template.Ref + if trigger.Spec.Template.Ref != nil { + ttName = *trigger.Spec.Template.Ref } else { // TODO(#791): Remove Name field // Ignore staticcheck linter as it will complain about using deprecated type - ttName = trigger.Template.Name //nolint:staticcheck + ttName = trigger.Spec.Template.Name //nolint:staticcheck } resolvedTT, err = getTT(ttName) if err != nil { diff --git a/pkg/template/resource_test.go b/pkg/template/resource_test.go index a124428a96..3c1a082d0f 100644 --- a/pkg/template/resource_test.go +++ b/pkg/template/resource_test.go @@ -86,7 +86,8 @@ func Test_applyParamToResourceTemplate(t *testing.T) { rt: rtMultipleParamVars, }, want: wantRtMultipleParamVars, - }, { + }, + { name: "espcae quotes in param val", args: args{ param: triggersv1.Param{ @@ -205,6 +206,7 @@ var ( }, }, } + getTB = func(name string) (*triggersv1.TriggerBinding, error) { if v, ok := triggerBindings[name]; ok { return v, nil @@ -228,171 +230,202 @@ var ( func Test_ResolveTrigger(t *testing.T) { tests := []struct { name string - trigger triggersv1.EventListenerTrigger + trigger triggersv1.Trigger want ResolvedTrigger - }{{ - name: "1 binding", - trigger: triggersv1.EventListenerTrigger{ - Bindings: []*triggersv1.EventListenerBinding{{ - Ref: "my-triggerbinding", - Kind: triggersv1.NamespacedTriggerBindingKind, - }}, - Template: &triggersv1.EventListenerTemplate{ - Name: "my-triggertemplate", - APIVersion: "v1alpha1", + }{ + { + name: "1 binding", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Ref: "my-triggerbinding", + Kind: triggersv1.NamespacedTriggerBindingKind, + }}, + Template: triggersv1.EventListenerTemplate{ + Name: "my-triggertemplate", + APIVersion: "v1alpha1", + }, + }, }, - }, - want: ResolvedTrigger{ - TriggerTemplate: &tt, - BindingParams: []triggersv1.Param{}, - }, - }, { - name: "1 clustertype binding", - trigger: triggersv1.EventListenerTrigger{ - Bindings: []*triggersv1.EventListenerBinding{{ - Ref: "my-clustertriggerbinding", - Kind: triggersv1.ClusterTriggerBindingKind, - }}, - Template: &triggersv1.EventListenerTemplate{ - Name: "my-triggertemplate", - APIVersion: "v1alpha1", + want: ResolvedTrigger{ + TriggerTemplate: &tt, + BindingParams: []triggersv1.Param{}, }, }, - want: ResolvedTrigger{ - TriggerTemplate: &tt, - BindingParams: []triggersv1.Param{}, + { + name: "1 clustertype binding", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Ref: "my-clustertriggerbinding", + Kind: triggersv1.ClusterTriggerBindingKind, + }}, + Template: triggersv1.EventListenerTemplate{ + Name: "my-triggertemplate", + APIVersion: "v1alpha1", + }, + }, + }, + want: ResolvedTrigger{ + TriggerTemplate: &tt, + BindingParams: []triggersv1.Param{}, + }, }, - }, { - name: "1 embed binding", - trigger: triggersv1.EventListenerTrigger{ - Bindings: []*triggersv1.EventListenerBinding{{ - Name: "my-embed-binding", - Spec: &triggersv1.TriggerBindingSpec{ - Params: []triggersv1.Param{{ - Name: "key", - Value: "value", + { + name: "1 embed binding", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Name: "my-embed-binding", + Spec: &triggersv1.TriggerBindingSpec{ + Params: []triggersv1.Param{{ + Name: "key", + Value: "value", + }}, + }, }}, + Template: triggersv1.EventListenerTemplate{ + Name: "my-triggertemplate", + APIVersion: "v1alpha1", + }, }, - }}, - Template: &triggersv1.EventListenerTemplate{ - Name: "my-triggertemplate", - APIVersion: "v1alpha1", + }, + want: ResolvedTrigger{ + BindingParams: []triggersv1.Param{{ + Name: "key", + Value: "value", + }}, + TriggerTemplate: &tt, }, }, - want: ResolvedTrigger{ - BindingParams: []triggersv1.Param{{ - Name: "key", - Value: "value", - }}, - TriggerTemplate: &tt, + { + name: "no binding", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Template: triggersv1.EventListenerTemplate{ + Name: "my-triggertemplate", + APIVersion: "v1alpha1", + }, + }, + }, + want: ResolvedTrigger{BindingParams: []triggersv1.Param{}, TriggerTemplate: &tt}, }, - }, { - name: "no binding", - trigger: triggersv1.EventListenerTrigger{ - Template: &triggersv1.EventListenerTemplate{ - Name: "my-triggertemplate", - APIVersion: "v1alpha1", + { + name: "concise bindings", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Template: triggersv1.EventListenerTemplate{ + Name: "my-triggertemplate", + }, + Bindings: []*triggersv1.EventListenerBinding{{ + Name: "p1", + Value: ptr.String("v1"), + }, { + Name: "p2", + Value: ptr.String("v2"), + }}, + }, + }, + want: ResolvedTrigger{ + TriggerTemplate: &tt, + BindingParams: []triggersv1.Param{{ + Name: "p1", + Value: "v1", + }, { + Name: "p2", + Value: "v2", + }}, }, }, - want: ResolvedTrigger{BindingParams: []triggersv1.Param{}, TriggerTemplate: &tt}, - }, { - name: "concise bindings", - trigger: triggersv1.EventListenerTrigger{ - Template: &triggersv1.EventListenerTemplate{ - Name: "my-triggertemplate", - }, - Bindings: []*triggersv1.EventListenerBinding{{ - Name: "p1", - Value: ptr.String("v1"), - }, { - Name: "p2", - Value: ptr.String("v2"), - }}, - }, - want: ResolvedTrigger{ - TriggerTemplate: &tt, - BindingParams: []triggersv1.Param{{ - Name: "p1", - Value: "v1", - }, { - Name: "p2", - Value: "v2", - }}, + { + name: "multiple binding params are merged", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Kind: triggersv1.NamespacedTriggerBindingKind, + Ref: "my-triggerbinding", + }, { + Kind: triggersv1.NamespacedTriggerBindingKind, + Ref: "tb-params", + }, { + Kind: triggersv1.ClusterTriggerBindingKind, + Ref: "my-clustertriggerbinding", + }, { + Kind: triggersv1.ClusterTriggerBindingKind, + Ref: "ctb-params", + }, { + Name: "p1", + Value: ptr.String("v1"), + }, { + Name: "p2", + Value: ptr.String("v2"), + }}, + Template: triggersv1.EventListenerTemplate{ + Name: "my-triggertemplate", + APIVersion: "v1alpha1", + }, + }, + }, + want: ResolvedTrigger{ + TriggerTemplate: &tt, + BindingParams: []triggersv1.Param{{ + Name: "foo", + Value: "bar", + }, { + Name: "foo-ctb", + Value: "bar-ctb", + }, { + Name: "p1", + Value: "v1", + }, { + Name: "p2", + Value: "v2", + }}, + }, }, - }, { - name: "multiple binding params are merged", - trigger: triggersv1.EventListenerTrigger{ - Bindings: []*triggersv1.EventListenerBinding{{ - Kind: triggersv1.NamespacedTriggerBindingKind, - Ref: "my-triggerbinding", - }, { - Kind: triggersv1.NamespacedTriggerBindingKind, - Ref: "tb-params", - }, { - Kind: triggersv1.ClusterTriggerBindingKind, - Ref: "my-clustertriggerbinding", - }, { - Kind: triggersv1.ClusterTriggerBindingKind, - Ref: "ctb-params", - }, { - Name: "p1", - Value: ptr.String("v1"), - }, { - Name: "p2", - Value: ptr.String("v2"), - }}, - Template: &triggersv1.EventListenerTemplate{ - Name: "my-triggertemplate", - APIVersion: "v1alpha1", - }, - }, - want: ResolvedTrigger{ - TriggerTemplate: &tt, - BindingParams: []triggersv1.Param{{ - Name: "foo", - Value: "bar", - }, { - Name: "foo-ctb", - Value: "bar-ctb", - }, { - Name: "p1", - Value: "v1", - }, { - Name: "p2", - Value: "v2", - }}, + { + name: "missing kind implies namespacedTriggerBinding", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Name: "my-triggerbinding", + APIVersion: "v1alpha1", + Ref: "my-triggerbinding", + }}, + Template: triggersv1.EventListenerTemplate{ + Name: "my-triggertemplate", + APIVersion: "v1alpha1", + }, + }, + }, + want: ResolvedTrigger{ + BindingParams: []triggersv1.Param{}, + TriggerTemplate: &tt, + }, }, - }, { - name: "missing kind implies namespacedTriggerBinding", - trigger: triggersv1.EventListenerTrigger{ - Bindings: []*triggersv1.EventListenerBinding{{ - Name: "my-triggerbinding", - APIVersion: "v1alpha1", - Ref: "my-triggerbinding", - }}, - Template: &triggersv1.EventListenerTemplate{ - Name: "my-triggertemplate", - APIVersion: "v1alpha1", - }, - }, - want: ResolvedTrigger{ - BindingParams: []triggersv1.Param{}, - TriggerTemplate: &tt, - }, - }, { name: "embedded trigger template", - trigger: triggersv1.EventListenerTrigger{ - Template: &triggersv1.EventListenerTemplate{ - Spec: &triggersv1.TriggerTemplateSpec{ - ResourceTemplates: []triggersv1.TriggerResourceTemplate{{ - RawExtension: test.RawExtension(t, pipelinev1.PipelineRun{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "tekton.dev/v1alpha1", - Kind: "PipelineRun", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Name: "my-triggerbinding", + APIVersion: "v1alpha1", + Ref: "my-triggerbinding", + }}, + + Template: triggersv1.EventListenerTemplate{ + Spec: &triggersv1.TriggerTemplateSpec{ + ResourceTemplates: []triggersv1.TriggerResourceTemplate{ + { + RawExtension: test.RawExtension(t, pipelinev1.PipelineRun{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "tekton.dev/v1alpha1", + Kind: "PipelineRun", + }, + }), }, - }), - }}, + }, + }, }, }, }, @@ -430,102 +463,124 @@ func Test_ResolveTrigger(t *testing.T) { func Test_ResolveTrigger_error(t *testing.T) { tests := []struct { name string - trigger triggersv1.EventListenerTrigger + trigger triggersv1.Trigger getTB getTriggerBinding getTT getTriggerTemplate getCTB getClusterTriggerBinding - }{{ - name: "triggerbinding not found", - trigger: triggersv1.EventListenerTrigger{ - Bindings: []*triggersv1.EventListenerBinding{{ - Ref: "invalid-tb-name", - Kind: triggersv1.NamespacedTriggerBindingKind, - }}, - Template: &triggersv1.EventListenerTemplate{ - Name: "my-triggertemplate", - APIVersion: "v1alpha1", - }, - }, - getTB: getTB, - getCTB: getCTB, - getTT: getTT, - }, { - name: "clustertriggerbinding not found", - trigger: triggersv1.EventListenerTrigger{ - Bindings: []*triggersv1.EventListenerBinding{{ - Ref: "invalid-ctb-name", - Kind: triggersv1.ClusterTriggerBindingKind, - }}, - Template: &triggersv1.EventListenerTemplate{ - Name: "my-triggertemplate", - APIVersion: "v1alpha1", - }, - }, - getTB: getTB, - getCTB: getCTB, - getTT: getTT, - }, { - name: "triggertemplate not found", - trigger: triggersv1.EventListenerTrigger{ - Bindings: []*triggersv1.EventListenerBinding{{ - Ref: "my-triggerbinding", - Kind: triggersv1.NamespacedTriggerBindingKind, - }}, - Template: &triggersv1.EventListenerTemplate{ - Name: "invalid-tt-name", - APIVersion: "v1alpha1", - }, - }, - getTB: getTB, - getCTB: getCTB, - getTT: getTT, - }, { - name: "triggerbinding and triggertemplate not found", - trigger: triggersv1.EventListenerTrigger{ - Bindings: []*triggersv1.EventListenerBinding{{ - Ref: "invalid-tb-name", - Kind: triggersv1.NamespacedTriggerBindingKind, - }}, - Template: &triggersv1.EventListenerTemplate{ - Name: "invalid-tt-name", - APIVersion: "v1alpha1", - }, - }, - getTB: getTB, - getCTB: getCTB, - getTT: getTT, - }, { - name: "trigger template missing name", - trigger: triggersv1.EventListenerTrigger{ - Template: &triggersv1.EventListenerTemplate{ - Name: "", + }{ + { + name: "triggerbinding not found", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Ref: "invalid-tb-name", + Kind: triggersv1.NamespacedTriggerBindingKind, + }}, + Template: triggersv1.EventListenerTemplate{ + Name: "my-triggertemplate", + APIVersion: "v1alpha1", + }, + }, }, + getTB: getTB, + getCTB: getCTB, + getTT: getTT, }, - getTT: getTT, - }, { - name: "invalid trigger binding", - trigger: triggersv1.EventListenerTrigger{ - Template: &triggersv1.EventListenerTemplate{ - Name: "my-triggertemplate", + { + name: "clustertriggerbinding not found", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Ref: "invalid-ctb-name", + Kind: triggersv1.ClusterTriggerBindingKind, + }}, + Template: triggersv1.EventListenerTemplate{ + Name: "my-triggertemplate", + APIVersion: "v1alpha1", + }, + }, }, - Bindings: []*triggersv1.EventListenerBinding{{ - Value: ptr.String("only-val"), - }}, + getTB: getTB, + getCTB: getCTB, + getTT: getTT, }, - getTT: getTT, - }, { - name: "same param name across multiple bindings", - trigger: triggersv1.EventListenerTrigger{ - Bindings: []*triggersv1.EventListenerBinding{{ - Ref: "tb-params", - Kind: triggersv1.NamespacedTriggerBindingKind, - }, { - Name: "foo", - Value: ptr.String("bar"), - }}, - }, - getTB: getTB, - }} + { + name: "triggertemplate not found", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Ref: "my-triggerbinding", + Kind: triggersv1.NamespacedTriggerBindingKind, + }}, + Template: triggersv1.EventListenerTemplate{ + Name: "invalid-tt-name", + APIVersion: "v1alpha1", + }, + }, + }, + getTB: getTB, + getCTB: getCTB, + getTT: getTT, + }, + { + name: "triggerbinding and triggertemplate not found", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Ref: "invalid-tb-name", + Kind: triggersv1.NamespacedTriggerBindingKind, + }}, + Template: triggersv1.EventListenerTemplate{ + Name: "invalid-tt-name", + APIVersion: "v1alpha1", + }, + }, + }, + getTB: getTB, + getCTB: getCTB, + getTT: getTT, + }, + { + name: "trigger template missing name", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Template: triggersv1.EventListenerTemplate{ + Name: "", + }, + }, + }, + getTT: getTT, + }, + { + name: "invalid trigger binding", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Template: triggersv1.EventListenerTemplate{ + Name: "my-triggertemplate", + }, + Bindings: []*triggersv1.EventListenerBinding{{ + Value: ptr.String("only-val"), + }}, + }, + }, + getTT: getTT, + }, + { + name: "same param name across multiple bindings", + trigger: triggersv1.Trigger{ + Spec: triggersv1.TriggerSpec{ + Bindings: []*triggersv1.EventListenerBinding{{ + Ref: "tb-params", + Kind: triggersv1.NamespacedTriggerBindingKind, + }, { + Name: "foo", + Value: ptr.String("bar"), + }}, + }, + }, + getTB: getTB, + }, + } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if _, err := ResolveTrigger(tt.trigger, tt.getTB, tt.getCTB, tt.getTT); err == nil { @@ -567,6 +622,7 @@ func Test_ApplyUIDToResourceTemplate(t *testing.T) { }) } } + func TestMergeBindingParams(t *testing.T) { tests := []struct { name string diff --git a/test/builder/eventlistener.go b/test/builder/eventlistener.go index 785353c6d8..95e1970b72 100644 --- a/test/builder/eventlistener.go +++ b/test/builder/eventlistener.go @@ -320,6 +320,13 @@ func EventListenerCELOverlay(key, expression string) EventInterceptorOp { } } +// EventListenerNamespaceSelectorMatchNames sets the specified selector for the EventListener. +func EventListenerNamespaceSelectorMatchNames(ns []string) EventListenerSpecOp { + return func(spec *v1alpha1.EventListenerSpec) { + spec.NamespaceSelector.MatchNames = ns + } +} + // EventListenerResources set specified resources to the EventListener. func EventListenerResources(ops ...EventListenerResourceOp) EventListenerSpecOp { return func(spec *v1alpha1.EventListenerSpec) { diff --git a/test/builder/eventlistener_test.go b/test/builder/eventlistener_test.go index b857258101..09cc039553 100644 --- a/test/builder/eventlistener_test.go +++ b/test/builder/eventlistener_test.go @@ -165,6 +165,28 @@ func TestEventListenerBuilder(t *testing.T) { EventListenerSpec( EventListenerServiceAccount("serviceAccount"), EventListenerTriggerRef("my-trigger"))), + }, { + name: "EventListener with Matchnames NamespaceSelector", + normal: &v1alpha1.EventListener{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + }, + Spec: v1alpha1.EventListenerSpec{ + ServiceAccountName: "serviceAccount", + Triggers: []v1alpha1.EventListenerTrigger{{ + TriggerRef: "my-trigger", + }}, + NamespaceSelector: v1alpha1.NamespaceSelector{ + MatchNames: []string{"foo", "bar"}, + }, + }, + }, + builder: EventListener("name", "namespace", + EventListenerSpec( + EventListenerNamespaceSelectorMatchNames([]string{"foo", "bar"}), + EventListenerServiceAccount("serviceAccount"), + EventListenerTriggerRef("my-trigger"))), }, { name: "One Trigger with one Binding", normal: &v1alpha1.EventListener{