EventListener

EventListener is a Kubernetes custom resource that allows users a declarativeway to process incoming HTTP based events with JSON payloads. EventListenersexpose an addressable “Sink” to which incoming events are directed. Users candeclare TriggerBindings to extract fields from events,and apply them to TriggerTemplates in order to createTekton resources. In addition, EventListeners allow lightweight event processingusing Event Interceptors.

Multi-Tenant Concerns

The EventListener is effectively an additional form of client into Tekton, versus whatexample usage via kubectl or tkn which you have seen elsewhere. In particular, the HTTP basedevents bypass the normal Kubernetes authentication path you get via kubeconfig filesand the kubectl config family of commands.

As such, there are set of items to consider when deciding how to

  • best expose (each) EventListener in your cluster to the outside world.
  • best control how (each) EventListener and the underlying API Objects described below access, create,and update Tekton related API Objects in your cluster.

Minimally, each EventListener has its ServiceAccountName as noted below and allevents coming over the “Sink” result in any Tekton resource interactions being done with the permissionsassigned to that ServiceAccount.

However, if you need differing levels of permissions over a set of Tekton resources across the variousTriggers and Interceptors, where not all Triggers or Interceptors canmanipulate certain Tekton Resources in the same way, a simple, single EventListener will not suffice.

Your options at that point are as follows:

Multiple EventListers (One EventListener Per Namespace)

You can create multiple EventListener objects, where your set of Triggers and Interceptors are spread out across theEventListeners.

If you create each of those EventListeners in their own namespace, it becomes easy to assignvarying permissions to the ServiceAccount of each one to serve your needs. And often times namespacecreation is coupled with a default set of ServiceAccounts and Secrets that are also defined.So conceivably some administration steps are taken care of. You just update the permissionsof the automatically created ServiceAccounts.

Possible drawbacks:- Namespaces with associated Secrets and ServiceAccounts in an aggregate sense prove to be the most expensiveitems in Kubernetes underlying etcd store. In larger clusters etcd storage capacity can become a concern.- Multiple EventListeners means multiple HTTP ports that must be exposed to the external entities accessingthe “Sink”. If you happen to have a HTTP Firewall between your Cluster and external entities, that means moreadministrative cost, opening ports in the firewall for each Service, unless you can employ Kubernetes Ingress toserve as a routing abstraction layer for your set of EventListeners.

Multiple EventListeners (Multiple EventListeners per Namespace)

Multiple EventListeners per namespace will most likely mean more ServiceAccount/Secret/RBAC manipulation forthe administrator, as some of the built in generation of those artifacts as part of namespace creation are notapplicable.

However you will save some on the etcd storage costs by reducing the number of namespaces.

Multiple EventListeners and potential Firewall concerns still apply (again unless you employ Ingress).

ServiceAccount per EventListenerTrigger

Being able to set a ServiceAccount on an EventListenerTrigger allows for finer grained permissions as well.

You still have to create the additional ServiceAccounts.

But staying within 1 namespace and minimizing the number of EventListeners with their associated “Sinks” minimizesconcerns around etcd storage and port considerations with Firewalls if Ingress is not utilized.

Syntax

To define a configuration file for an EventListener resource, you can specifythe following fields:

  • Required:
    • apiVersion - Specifies the API version, for exampletriggers.tekton.dev/v1alpha1.
    • kind - Specifies the EventListener resourceobject.
    • metadata - Specifies data to uniquely identify theEventListener resource object, for example a name.
    • spec - Specifies the configuration information foryour EventListener resource object. In order for an EventListener to doanything, the spec must include:
    • triggers - Specifies a list of Triggers to run
    • serviceAccountName - Specifies the ServiceAccountthat the EventListener uses to create resources
  • Optional:
    • serviceType - Specifies what type of service the sink podis exposed as

ServiceAccountName

The serviceAccountName field is required. The ServiceAccount that theEventListener sink uses to create the Tekton resources. The ServiceAccount needsa role with the following rules:

  1. kind: Role
  2. apiVersion: rbac.authorization.k8s.io/v1
  3. metadata:
  4. name: tekton-triggers-example-minimal
  5. rules:
  6. # Permissions for every EventListener deployment to function
  7. - apiGroups: ["triggers.tekton.dev"]
  8. resources: ["eventlisteners", "triggerbindings", "triggertemplates"]
  9. verbs: ["get"]
  10. - apiGroups: [""]
  11. # secrets are only needed for Github/Gitlab interceptors, serviceaccounts only for per trigger authorization
  12. resources: ["configmaps", "secrets", "serviceaccounts"]
  13. verbs: ["get", "list", "watch"]
  14. # Permissions to create resources in associated TriggerTemplates
  15. - apiGroups: ["tekton.dev"]
  16. resources: ["pipelineruns", "pipelineresources", "taskruns"]
  17. verbs: ["create"]

If your EventListener is usingClusterTriggerBindings, you’ll need aServiceAccount with aClusterRole instead.

Triggers

The triggers field is required. Each EventListener can consist of one or moretriggers. A Trigger consists of:

  • name - (Optional) a validKubernetes name
  • interceptors - (Optional) list of interceptors to use
  • bindings - A list of names of TriggerBindings to use
  • template - The name of TriggerTemplate to use
  1. triggers:
  2. - name: trigger-1
  3. interceptors:
  4. - github:
  5. eventTypes: ["pull_request"]
  6. bindings:
  7. - name: pipeline-binding
  8. - name: message-binding
  9. template:
  10. name: pipeline-template

Also, to support multi-tenant styled scenarios, where an administrator may not want all triggers to havethe same permissions as the EventListener, a service account can optionally be set at the trigger leveland used if present in place of the EventListener service account when creating resources:

  1. triggers:
  2. - name: trigger-1
  3. serviceAccount:
  4. name: trigger-1-sa
  5. namespace: event-listener-namespace
  6. interceptors:
  7. - github:
  8. eventTypes: ["pull_request"]
  9. bindings:
  10. - name: pipeline-binding
  11. - name: message-binding
  12. template:
  13. name: pipeline-template

The default ClusterRole for the EventLister allows for reading ServiceAccounts from any namespace.

ServiceType

The serviceType field is optional. EventListener sinks are exposed viaKubernetes Services.By default, the serviceType is ClusterIP which means any pods running in thesame Kubernetes cluster can access services’ via their cluster DNS. Other validvalues are NodePort and LoadBalancer. Check theKubernetes Service typesdocumentations for details.

For external services to connect to your cluster (e.g. GitHub sending webhooks),check out the guide on exposing EventListeners.

Logging

EventListener sinks are exposed as Kubernetes services that are backed by a Podrunning the sink logic. The logging configuration can be controlled via theconfig-logging-triggers ConfigMap present in the namespace that theEventListener was created in. This ConfigMap is automatically created andcontains the default values defined inconfig-logging.yaml.

To access logs for the EventListener sink, you can query for pods with theeventlistener label set to the name of your EventListener resource:

  1. kubectl get pods --selector eventlistener=my-eventlistener

Labels

By default, EventListeners will attach the following labels automatically to allresources it creates:

NameDescription
triggers.tekton.dev/eventlistenerName of the EventListener that generated the resource.
triggers.tekton.dev/triggerName of the Trigger that generated the resource.
triggers.tekton.dev/eventidUID of the incoming event.

Since the EventListener name and Trigger name are used as label values, theymust adhere to theKubernetes syntax and character set requirementsfor label values.

Interceptors

Triggers within an EventListener can optionally specify interceptors, tomodify the behavior or payload of Triggers.

Event Interceptors can take several different forms today:

Webhook Interceptors

Webhook Interceptors allow users to configure an external k8s object whichcontains business logic. These are currently specified under the Webhookfield, which contains anObjectReferenceto a Kubernetes Service. If a Webhook Interceptor is specified, theEventListener sink will forward incoming events to the service referenced bythe Interceptor over HTTP. The service is expected to process the event andreturn a response back. The status code of the response determines if theprocessing is successful - a 200 response means the Interceptor was successfuland that processing should continue, any other status code will halt Triggerprocessing. The returned request (body and headers) is used as the new eventpayload by the EventListener and passed on the TriggerBinding. An Interceptorhas an optional header field with key-value pairs that will be merged with eventheaders before being sent;canonical namesmust be specified.

When multiple Interceptors are specified, requests are piped through eachInterceptor sequentially for processing - e.g. the headers/body of the firstInterceptor’s response will be sent as the request to the second Interceptor. Itis the responsibility of Interceptors to preserve header/body data if desired.The response body and headers of the last Interceptor is used for resourcebinding/templating.

Event Interceptor Services

To be an Event Interceptor, a Kubernetes object should:

  • Be fronted by a regular Kubernetes v1 Service over port 80
  • Accept JSON payloads over HTTP
  • Accept HTTP POST requests with JSON payloads.
  • Return a HTTP 200 OK Status if the EventListener should continue processingthe event
  • Return a JSON body back. This will be used by the EventListener as the eventpayload for any further processing. If the Interceptor does not need to modifythe body, it can simply return the body that it received.
  • Return any Headers that might be required by other chained Interceptors or anybindings.

Note: It is the responsibility of Interceptors to preserve header/body dataif desired. The response body and headers of the last Interceptor is used forresource binding/templating.

  1. ---
  2. apiVersion: triggers.tekton.dev/v1alpha1
  3. kind: EventListener
  4. metadata:
  5. name: listener-interceptor
  6. spec:
  7. serviceAccountName: tekton-triggers-example-sa
  8. triggers:
  9. - name: foo-trig
  10. interceptors:
  11. - webhook:
  12. header:
  13. - name: Foo-Trig-Header1
  14. value: string-value
  15. - name: Foo-Trig-Header2
  16. value:
  17. - array-val1
  18. - array-val2
  19. objectRef:
  20. kind: Service
  21. name: gh-validate
  22. apiVersion: v1
  23. namespace: default
  24. bindings:
  25. - name: pipeline-binding
  26. template:
  27. name: pipeline-template

GitHub Interceptors

GitHub Interceptors contain logic to validate and filter webhooks that come fromGitHub. Supported features include validating webhooks actually came from GitHubusing the logic outlined in GitHubdocumentation, as well asfiltering incoming events.

To use this Interceptor as a validator, create a secret string using the methodof your choice, and configure the GitHub webhook to use that secret value.Create a Kubernetes secret containing this value, and pass that as a referenceto the github Interceptor.

To use this Interceptor as a filter, add the event types you would like toaccept to the eventTypes field. Valid values can be found in GitHubdocs.

The body/header of the incoming request will be preserved in this Interceptor’sresponse.

  1. ---
  2. apiVersion: triggers.tekton.dev/v1alpha1
  3. kind: EventListener
  4. metadata:
  5. name: github-listener-interceptor
  6. spec:
  7. serviceAccountName: tekton-triggers-example-sa
  8. triggers:
  9. - name: foo-trig
  10. interceptors:
  11. - github:
  12. secretRef:
  13. secretName: foo
  14. secretKey: bar
  15. eventTypes:
  16. - pull_request
  17. bindings:
  18. - name: pipeline-binding
  19. template:
  20. name: pipeline-template

GitLab Interceptors

GitLab Interceptors contain logic to validate and filter requests that come fromGitLab. Supported features include validating that a webhook actually came fromGitLab, using the logic outlined in GitLabdocumentation,and to filter incoming events based on the event types. Event types can be foundin GitLabdocumentation.

To use this Interceptor as a validator, create a secret string using the methodof your choice, and configure the GitLab webhook to use that secret value.Create a Kubernetes secret containing this value, and pass that as a referenceto the gitlab Interceptor.

To use this Interceptor as a filter, add the event types you would like toaccept to the eventTypes field.

The body/header of the incoming request will be preserved in this Interceptor’sresponse.

  1. apiVersion: triggers.tekton.dev/v1alpha1
  2. kind: EventListener
  3. metadata:
  4. name: gitlab-listener-interceptor
  5. spec:
  6. serviceAccountName: tekton-triggers-example-sa
  7. triggers:
  8. - name: foo-trig
  9. interceptors:
  10. - gitlab:
  11. secretRef:
  12. secretName: foo
  13. secretKey: bar
  14. eventTypes:
  15. - Push Hook
  16. bindings:
  17. - name: pipeline-binding
  18. template:
  19. name: pipeline-template

CEL Interceptors

CEL Interceptors can be used to filter or modify incoming events, using theCEL expression language.

Please read thecel-spec language definitionfor more details on the expression language syntax.

The cel-trig-with-matches trigger below filters events that don’t have an'X-GitHub-Event' header matching 'pull_request'.

It also modifies the incoming request, adding an extra key to the JSON body,with a truncated string coming from the hook body.

  1. apiVersion: triggers.tekton.dev/v1alpha1
  2. kind: EventListener
  3. metadata:
  4. name: cel-listener-interceptor
  5. spec:
  6. serviceAccountName: tekton-triggers-example-sa
  7. triggers:
  8. - name: cel-trig-with-matches
  9. interceptors:
  10. - cel:
  11. filter: "header.match('X-GitHub-Event', 'pull_request')"
  12. overlays:
  13. - key: extensions.truncated_sha
  14. expression: "truncate(body.pull_request.head.sha, 7)"
  15. bindings:
  16. - name: pipeline-binding
  17. template:
  18. name: pipeline-template
  19. - name: cel-trig-with-canonical
  20. interceptors:
  21. - cel:
  22. filter: "header.canonical('X-GitHub-Event') == 'push'"
  23. bindings:
  24. - name: pipeline-binding
  25. template:
  26. name: pipeline-template

In addition to the standard expressions provided by CEL, Triggers supports someuseful functions for dealing with event dataCEL expressions.

The body/header of the incoming request will be preserved in this Interceptor’sresponse.

  1. apiVersion: triggers.tekton.dev/v1alpha1
  2. kind: EventListener
  3. metadata:
  4. name: cel-listener-interceptor
  5. spec:
  6. serviceAccountName: tekton-triggers-example-sa
  7. triggers:
  8. - name: cel-trig-with-matches
  9. interceptors:
  10. - cel:
  11. filter: "header.match('X-GitHub-Event', 'pull_request')"
  12. overlays:
  13. - key: extensions.truncated_sha
  14. expression: "truncate(body.pull_request.head.sha, 7)"
  15. bindings:
  16. - name: pipeline-binding
  17. template:
  18. name: pipeline-template
  19. - name: cel-trig-with-canonical
  20. interceptors:
  21. - cel:
  22. filter: "header.canonical('X-GitHub-Event') == 'push'"
  23. bindings:
  24. - name: pipeline-binding
  25. template:
  26. name: pipeline-template

The filter expression must return a true value if this trigger is to beprocessed, and the overlays applied.

Optionally, no filter expression can be provided, and the overlays will beapplied to the incoming body.

  1. apiVersion: triggers.tekton.dev/v1alpha1
  2. kind: EventListener
  3. metadata:
  4. name: cel-eventlistener-no-filter
  5. spec:
  6. serviceAccountName: tekton-triggers-example-sa
  7. triggers:
  8. - name: cel-trig
  9. interceptors:
  10. - cel:
  11. overlays:
  12. - key: extensions.truncated_sha
  13. expression: "truncate(body.pull_request.head.sha, 7)"
  14. bindings:
  15. - name: pipeline-binding
  16. template:
  17. name: pipeline-template

Overlays

The CEL interceptor supports “overlays”, these are CEL expressions that areapplied to the body before it’s returned to the event-listener.

  1. apiVersion: triggers.tekton.dev/v1alpha1
  2. kind: EventListener
  3. metadata:
  4. name: example-with-multiple-overlays
  5. spec:
  6. serviceAccountName: tekton-triggers-example-sa
  7. triggers:
  8. - name: cel-trig
  9. interceptors:
  10. - cel:
  11. overlays:
  12. - key: extensions.truncated_sha
  13. expression: "truncate(body.pull_request.head.sha, 7)"
  14. - key: extensions.branch_name
  15. expression: "truncate(body.ref.split, '/')[2]"
  16. bindings:
  17. - name: pipeline-binding
  18. template:
  19. name: pipeline-template

In this example, the bindings will see two additional fields:

Assuming that the input body looked something like this:

  1. {
  2. "ref": "refs/heads/master",
  3. "pull_request": {
  4. "head": {
  5. "sha": "6113728f27ae82c7b1a177c8d03f9e96e0adf246"
  6. }
  7. }
  8. }

The output body would look like this:

  1. {
  2. "ref": "refs/heads/master",
  3. "pull_request": {
  4. "head": {
  5. "sha": "6113728f27ae82c7b1a177c8d03f9e96e0adf246"
  6. }
  7. },
  8. "extensions": {
  9. "truncated_sha": "6113728",
  10. "branch_name": "master"
  11. }
  12. }

The key element of the overlay can create new elements in a body, or, overlayexisting elements.

For example, this expression:

  1. - key: body.pull_request.head.short_sha
  2. expression: "truncate(body.pull_request.head.sha, 7)"

Would see the short_sha being inserted into the existing body:

  1. {
  2. "ref": "refs/heads/master",
  3. "pull_request": {
  4. "head": {
  5. "sha": "6113728f27ae82c7b1a177c8d03f9e96e0adf246",
  6. "short_sha": "6113728"
  7. }
  8. }
  9. }

It’s even possible to replace existing fields, by providing a key that matchesthe path to an existing value.

Anything that is applied as an overlay can be extracted using a binding e.g.

  1. apiVersion: triggers.tekton.dev/v1alpha1
  2. kind: TriggerBinding
  3. metadata:
  4. name: pipeline-binding-with-cel-extensions
  5. spec:
  6. params:
  7. - name: gitrevision
  8. value: $(body.extensions.branch_name)
  9. - name: branch
  10. value: $(body.pull_request.head.short_sha)

Examples

For complete examples, seethe examples folder.