Tekton Triggers 101

This tutorial dives into using Tekton Pipelines. If you aren’t familiar with Tekton or need a refresher on the open source Kubernetes approach to running your continuous integration and continuous delivery (CI/CD) pipelines, then I encourage you to check out the What is Tekton? lightboard video.

If you’re already familiar with Tekton Pipelines and have begun using them, then you’ve probably had this question pop up into your head: I can manually run my Tekton Pipeline, but how do I automatically run my pipeline?

Maybe you want to automatically run your pipeline every time you create a pull request, push code to a repo, or merge a pull request into the master branch. Thankfully, the Tekton Triggers project solves this problem by automatically connecting events to your Tekton Pipelines.

My goal in this tutorial is to show you how this is possible. I will begin by describing the components of Tekton Triggers and how they work. Then, I will walk you through a common use case of setting up Tekton Triggers. You will then take a sample application with a build-and-deploy Tekton Pipeline and set up Tekton Triggers to automatically trigger the pipeline when a GitHub pull request is created. My hope is that you can take the workflow described in this tutorial and apply it to whatever scenario you are dealing with.

Prerequisites

To complete this tutorial, you need:

Estimated time

Completing the steps in this tutorial should take about 45 to 60 minutes.

Introduction to Tekton Triggers

Before getting started, let’s discuss some of the features of Tekton Triggers. In a nutshell, Tekton Triggers allows users to create resource templates that get instantiated when an event is received. Additionally, fields from event payloads can be injected into these resource templates as runtime information. This enables users to automatically create templated PipelineRun or TaskRun resources when an event is received.

Tekton Triggers introduce three Kubernetes custom resources to configure triggers:

  1. TriggerTemplates

    A TriggerTemplate declares a blueprint for each Kubernetes resource you want to create when an event is received. Each TriggerTemplate has parameters that can be substituted anywhere within the blueprint you define. In general, you will have one TriggerTemplate for each of your Tekton Pipelines. In this tutorial, you create a TriggerTemplate for your build-and-deploy PipelineRun because you want to create a build-and-deploy PipelineRun every time you receive a pull request event.

  2. TriggerBindings

    A TriggerBinding describes what information you want to extract from an event to pass to your TriggerTemplate. Each TriggerBinding essentially declares the parameters that get passed to the TriggerTemplate at runtime (when an event is received). In general, you will have one TriggerBinding for each type of event that you receive. In this tutorial, you will create a TriggerBinding for the GitHub pull request event in order to build and deploy the code in the pull request.

  3. EventListeners

    An EventListener creates a Deployment and Service that listen for events. When the EventListener receives an event, it executes a specified TriggerBinding and TriggerTemplate. In this tutorial, the EventListener will receive pull request events from GitHub and execute the TriggerBinding and TriggerTemplate to create a build-and-deploy PipelineRun.

The following diagram illustrates the control flow of the three Tekton Triggers resources when an event is received:

Control flow of Tekton Triggers resources diagram

Now that you have a better understanding of Tekton Triggers, let’s dive into the tutorial!

You will be using a useful cloud-native Node.js application called CatApp, which leverages the Cat API to display random pictures of cats. This app is shared on GitHub and can be found here. As you explore the repository, you will find a Dockerfile to build the app and a few Kubernetes configuration files (in the config directory) for a Deployment and Service to run the app. For CI/CD, I’ve created a Tekton Pipeline in the tekton/ directory to build and deploy CatApp.

The following steps illustrate how to integrate CatApp’s build-and-deploy Tekton Pipeline with GitHub so that it runs every time I create a pull request. That way, I can automatically test the code in my pull request.

Fork the CatApp repository

To follow along with the tutorial, you need your own version of CatApp. At the end of the tutorial you will create a pull request webhook, and you can only create a webhook in a repository that you own.

  1. Follow GitHub’s documentation on how to fork a repo to create your own fork of the CatApp repo.

Please note that if your OpenShift environment is not accessible from public GitHub (for example, if it is behind a firewall), then you will need to clone the CatApp repository and create a copy in GitHub Enterprise (or whatever private Git provider you use behind your firewall). This is because GitHub will need to be able to send pull request events to the EventListener in your OpenShift environment.

Run the CatApp build-and-deploy pipeline

  1. Create a new project in OpenShift for CatApp:

     oc new-project catapp
    
  2. Apply the CatApp Pipeline and Tasks (in tekton/build-and-deploy-openshift-pipeline.yaml):

     oc apply -f tekton/build-and-deploy-openshift-pipeline.yaml
    
  3. Test the CatApp build-and-deploy pipeline with a PipelineRun. Later, you will use the YAML from this PipelineRun to structure your TriggerTemplate. Use the pipeline ServiceAccount to execute the PipelineRun, because it was created by the Pipelines Operator to have the proper permissions to execute Tekton PipelineRuns:

    If you are using a private Git repository, you will need to authenticate your PipelineRun.

     NAMESPACE='catapp'
     URL='https://github.com/ncskier/catapp.git' # Replace with your catapp repository url
     REVISION='master'
     cat << EOF | oc apply -f -
     apiVersion: tekton.dev/v1beta1
     kind: PipelineRun
     metadata:
       name: catapp-build-and-deploy
     spec:
       serviceAccountName: pipeline
       pipelineRef:
         name: build-and-deploy-openshift
       resources:
       - name: source
         resourceSpec:
           type: git
           params:
           - name: revision
             value: ${REVISION}
           - name: url
             value: ${URL}
       - name: image
         resourceSpec:
           type: image
           params:
           - name: url
             value: image-registry.openshift-image-registry.svc:5000/${NAMESPACE}/catapp:${REVISION}
       params:
       - name: DEPLOYMENT
         value: catapp
     EOF
    
  4. Watch the PipelineRun:

     tkn pipelinerun list
     tkn pipelinerun logs --last -f
    
  5. Verify that CatApp has now built and deployed to your OpenShift environment. CatApp is deployed with an OpenShift Route which exposes the app outside of your cluster. You can get the Route URL using:

     oc get route catapp --template='http://{{.spec.host}}'
    
  6. Now open the Route URL in your web browser.

    CatApp

  7. You can delete the CatApp deployment resources with:

     oc delete all -l app=catapp
    

Set up CatApp Tekton Triggers resources

All of the Tekton Triggers resources are defined in the tekton/build-and-deploy-openshift-triggers.yaml file. I’m going to briefly explain the purpose of each resource before you apply the file.

TriggerTemplate

As you can see, the TriggerTemplate, is very similar to the PipelineRun resource you created in the previous step. The only difference is that instead of hardcoding everything, there are a few parameters defined that specify information by a TriggerBinding at runtime. You can create multiple resources from the TriggerTemplate, but you only need to create one PipelineRun for your build-and-deploy pipeline.

I should point out that using the generateName field appends a unique string to the end of the PipelineRun’s name. This unique string ensures that each triggered PipelineRun will have a unique name.

apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
metadata:
  name: catapp-build-and-deploy
spec:
  params:
  - name: URL
    description: The repository url to build and deploy.
  - name: REVISION
    description: The revision to build and deploy.
  - name: NAMESPACE
    description: The namespace is used by OpenShift's internal image registry to store the built image.
  - name: DEPLOYMENT
    description: Name of the Deployment and the container name in the Deployment.
  - name: SERVICE_ACCOUNT
    description: The ServiceAccount under which to run the Pipeline.
  resourcetemplates:
  - apiVersion: tekton.dev/v1beta1
    kind: PipelineRun
    metadata:
      generateName: catapp-build-and-deploy-
    spec:
      serviceAccountName: $(params.SERVICE_ACCOUNT)
      pipelineRef:
        name: build-and-deploy-openshift
      resources:
      - name: source
        resourceSpec:
          type: git
          params:
          - name: revision
            value: $(params.REVISION)
          - name: url
            value: $(params.URL)
      - name: image
        resourceSpec:
          type: image
          params:
          - name: url
            value: image-registry.openshift-image-registry.svc:5000/$(params.NAMESPACE)/catapp:$(params.REVISION)
      params:
      - name: DEPLOYMENT
        value: $(params.DEPLOYMENT)

TriggerBinding

The TriggerBinding specifies the values to use for your TriggerTemplate’s parameters. The URL and REVISION parameters are especially important because they are extracted from the pull request event body. Looking at the GitHub pull request event documentation, you can find the JSON path for values of the URL and REVISION in the event body. For Tekton Triggers to substitute these JSON paths into values, they must be surrounded by $() and they must start with body (because the values are coming from the event body).

The rest of the parameters in the TriggerBinding have hardcoded values, because they do not come from the pull request event; these values are specific to your OpenShift environment.

apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerBinding
metadata:
  name: catapp-build-and-deploy
spec:
  params:
  - name: URL
    value: $(body.repository.clone_url)
  - name: REVISION
    value: $(body.pull_request.head.sha)
  - name: NAMESPACE
    value: catapp
  - name: DEPLOYMENT
    value: catapp
  - name: SERVICE_ACCOUNT
    value: pipeline

EventListener

The EventListener defines a list of triggers. Each trigger pairs a TriggerTemplate with a number of TriggerBindings. In this case, you only need one trigger that pairs your TriggerBinding with your TriggerTemplate. You also need to specify a ServiceAccount with the proper role-based access control (RBAC) to allow the EventListener sink Pod to function; don’t worry, this is explained next with the Role.

apiVersion: triggers.tekton.dev/v1alpha1
kind: EventListener
metadata:
  name: catapp
spec:
  serviceAccountName: catapp
  triggers:
  - name: pullrequest-build-and-deploy
    template:
      name: catapp-build-and-deploy
    bindings:
    - name: catapp-build-and-deploy

Role, RoleBinding, and ServiceAccount

The EventListener sink Pod needs the proper RBAC configuration to function. Every EventListener sink Pod must have permission to read all Tekton Triggers resources, so the Pod knows what to do when it receives an event. The EventListener Pod is what does the work of creating the templated resources, so it must also have permission to create any resource defined in your TriggerTemplates. This Role is bound to the catapp ServiceAccount using the RoleBinding.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: catapp
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: catapp
rules:
# Permissions for every EventListener deployment to function
- apiGroups: ["triggers.tekton.dev"]
  resources: ["eventlisteners", "triggerbindings", "triggertemplates"]
  verbs: ["get"]
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list", "watch"]
# Permissions to create resources in associated TriggerTemplates
- apiGroups: ["tekton.dev"]
  resources: ["pipelineruns"]
  verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: catapp
subjects:
- kind: ServiceAccount
  name: catapp
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: catapp

Route

The Route exposes the EventListener sink Pod, so that GitHub can send events to it.

apiVersion: route.openshift.io/v1
kind: Route
metadata:
  labels:
    eventlistener: catapp
  name: el-catapp
spec:
  port:
    targetPort: http-listener
  to:
    kind: Service
    name: el-catapp
  1. Apply the CatApp triggers resources:

     oc apply -f tekton/build-and-deploy-openshift-triggers.yaml
    
  2. Now verify the EventListener sink Pod is running:

     oc get pods -l eventlistener=catapp
    
  3. Test your Tekton Triggers resources with curl to verify that they have been configured properly (I like using curl to test my Tekton Triggers configurations, because it is faster than creating a GitHub webhook):

     URL="https://github.com/ncskier/catapp" # The URL of your fork of CatApp
     ROUTE_HOST=$(oc get route el-catapp --template='http://{{.spec.host}}')
     curl -v \
        -H 'X-GitHub-Event: pull_request' \
        -H 'Content-Type: application/json' \
        -d '{
          "repository": {"clone_url": "'"${URL}"'"},
          "pull_request": {"head": {"sha": "master"}}
        }' \
        ${ROUTE_HOST}
    

    The EventListener sink Pod received your curl request and created a new PipelineRun using your TriggerTemplate, TriggerBinding, and the body of your curl request.

  4. Next, verify the PipelineRun executes without any errors.

     tkn pipelinerun list
     tkn pipelinerun logs --last -f
    

    Notice the unique postfix in this PipelineRun’s name since it was created with the generateName field.

Set up the GitHub pull request webhook

  1. Open your fork of the CatApp repository in GitHub and navigate to the Webhooks menu. Then click the Add webhook button.

    CatApp repo

  2. Specify the settings for your new webhook (the images below provide a visual on how to do so):

    • Set the Payload URL to the EventListener Route: oc get route el-catapp --template='http://{{.spec.host}}'.
    • Set the Content type to application/json.
    • You aren’t using the Secret in this tutorial, so you can leave it blank.
    • Select Let me select individual events.
    • Check Pull requests (there are a few pull request events, but only check the one that just says Pull requests).
    • Uncheck Pushes.

      Webhook settings Webhook settings

  3. Now click the Add webhook button.

  4. Verify that there is a green checkmark next to your webhook. This indicates that the EventListener sink Pod is receiving events from the webhook.

    images

Create a pull request to test the trigger

  1. Go back to your code editor and open the CatApp index.html file.

  2. Add an h1 tag with the title CatApp above the button (or change the title to whatever you’d like):

     ...
     <body>
       <h1>CatApp</h1>
       <button id="getCatImageButton" class="center" type="button">Click Me! 🐱</button>
     ...
    
  3. Save the index.html file, checkout a new branch, commit your change, and push it to GitHub:

     # Checkout new branch "add-h1"
     git checkout -b add-h1
     # Commit changes
     git add .
     git commit -m "Add CatApp page header"
     # Push to GitHub
     git push origin add-h1
    
  4. Open GitHub in your web browser and create a pull request for your new branch add-h1.

  5. Your new pull request will kick off the PipelineRun to build and deploy your branch add-h1.

    In a realistic scenario, you would probably want to build and deploy this branch into a temporary namespace to run tests against.

     tkn pipelinerun list
     tkn pipelinerun logs –last
    
  6. When the PipelineRun completes, verify that your changes have been deployed and are visible in your web browser at the CatApp Route:

     oc get route catapp --template='http://{{.spec.host}}'
    

    CatApp route

    If you want to clean up all of the resources from this tutorial, the easiest way is to delete the catapp project:

     oc delete project catapp
    

Next steps

I hope that you found this tutorial helpful and that you now feel confident when applying the workflow to your own projects. If you’re interested in learning more about Tekton, here are a few resources to get you started:

If you’re interested in taking a hands-on approach try any of the tutorials featured here, on IBM Developer.