Triggers

At the end of this chapter you will be able to :

  • What are Tekton Triggers

  • How to Trigger a Pipeline or Task based on Events

Overview

Until now we have seen how to create Tasks and Pipelines. But whenever it comes to running the Tasks and Pipelines we always relied on doing it manually. In the Chapter we will be expoloring how to do it using Triggers, e.g. say you push a commit in to the source repository and it triggers a PipelineRun to deploy a new application or a TaskRun to push the image to container registry. Let’s rock!

If you are not in tutorial chapter folder, then navigate to the folder:

cd $TUTORIAL_HOME/triggers

Prepare namespace

All exercises of this chapter will be done a namespace triggers-demo, let’s create and switch to the triggers-demo namespace.

kubectl create ns triggers-demo
kubectl config set-context --current --namespace triggers-demo

As part of this exercise we will need to install the following external tasks and cluster tasks to be installed in the namespace:

Check if all the tasks are available:

tkn task ls

The command should show an output like:

NAME               DESCRIPTION              AGE
buildah            Buildah task builds...   11 hours ago
git-clone          These Tasks are Git...   11 hours ago
kn                 This Task performs ...   9 minutes ago
list-directory     Simple directory li...   11 hours ago
maven              This Task can be us...   11 hours ago

Deploy Nexus

To show maven artifact caching and using customized maven settings.xml we will deploy Sonatype Nexus to the cluster:

kubectl apply -n triggers-demo \
  -f $TUTORIAL_HOME/install/utils/nexus.yaml

Wait for the nexus3 pod to come up before proceeding further with exercises:

watch kubectl -n triggers-demo get pods,svc

A sucessfully running nexus3 pod should show the following services and pods:

NAME                         READY   STATUS    RESTARTS   AGE
pod/nexus-75fff8bbbd-cmlxs   1/1     Running   0          3m25s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/nexus        NodePort    10.100.205.22   <none>        8081:31743/TCP   3m25s
  • Minikube

  • OpenShift

The nexus maven repository could be opened using the url:

minikube -p tektontutorial service nexus -n triggers-demo --url
http://192.168.64.27:30102

In OpenShift we can use routes to expose the service if we need to access it from outside the cluster by:

oc expose svc -n triggers-demo nexus

Create maven-settings ConfigMap

For us to mount the maven settings.xml, we will create ConfigMap holding the custom maven settings.xml:

kubectl create -n triggers-demo cm maven-settings \
  --from-file=settings.xml=$TUTORIAL_HOME/workspaces/maven-settings.xml
configmap/maven-settings created

You can check the ConfigMap contents using the command:

kubectl get -n triggers-demo cm maven-settings -o yaml \
    -o jsonpath='{.data}'

The output of the above command should be same as in maven-settings.xml.

Deploy Knative Serving

As we will be deploying a Knative serverless application as part of the pipeline, that we will be running later in this chapter, lets deploy Knative to the cluster:

  • Minikube

  • OpenShift

Do it only once per cluster
$TUTORIAL_HOME/bin/enable_knative.sh

Wait for the serving deployment to complete without errors.

OpenShift users can install Knative by following the instructions here for installing OpenShift Serverless.

Follow the instructions for installing the Serverless Operator on the cluster and then follow the instructions for creating Knative Serving.

Check Default Storage Class

Before we create our pipeline ensure that the kubernetes cluster has a default storage class defined:

kubectl get sc
  • Minikube

  • OpenShift

In minikube cluster it should show an output like:

NAME                 PROVISIONER                RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
standard (default)   k8s.io/minikube-hostpath   Delete          Immediate           false                  44m

An example default storage class in Google Cloud:

NAME                 PROVISIONER            RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
standard (default)   kubernetes.io/gce-pd   Delete          WaitForFirstConsumer   true                   2d3h
  • If you dont have one please check Kubernetes Docs on how to have one configured.

  • In OpenShift cluster the default storage class varies based on the underlying cloud platform. Refer to OpenShift Documentation to know more.

Create PVC

Create the PVC tekton-tutorial-sources, which we will use as part of the exercises in this chapter and the upcoming ones.

kubectl apply -n triggers-demo -f $TUTORIAL_HOME/workspaces/sources-pvc.yaml

Create Pipeline

As part of the Trigger exercise we will be running the greeter-app-deploy pipeline, let’s deploy the pipeline to be used later:

kubectl apply -n triggers-demo \
  -f $TUTORIAL_HOME/workspaces/greeter-app-deploy.yaml

Verify the available pipelines:

tkn pipeline -n triggers-demo ls
NAME                    AGE            LAST RUN   STARTED   DURATION   STATUS
greeter-app-deploy   1 minute ago   ---        ---       ---        ---

Deploy Gitea

Since we need to trigger a Pipeline with a git push, we will deploy a Git server to the local cluster. This will avoid our neeting to expose the service to the public internet and also helps us to test things quickly with our local cluster setup.

Gitea is super quick and easy to install self-hosted git service. Let’s deploy it into our Kubernetes cluster using an Operator.

Create Gitea Server

We’ll run a script that creates the Gitea server for us locally passing in as a parameter to the script a minikube based hostname GITEA_HOSTNAME that will allow us to access the instance of Gitea from our host machine

  • Minikube

  • OpenShift

export GITEA_HOSTNAME="gitea.$(minikube ip).nip.io"
$TUTORIAL_HOME/bin/create-gitea.sh -g $GITEA_HOSTNAME
$TUTORIAL_HOME/bin/create-gitea.sh
export GITEA_HOSTNAME=$(kubectl get route gitea -n triggers-demo -o jsonpath='{.spec.host}')

In a separate shell you can follow the installation progress by tailing the logs of the gitea operator that was installed into your minikube cluster.

stern gitea -n gitea-operator

When the script finishes, your gitea deployment should be completed. A successful deployment will report:

Gitea server installed and running

If you want, you can confirm this by running this command

kubectl get pods,svc -lapp=gitea

Which should yield output that looks something like the following

NAME                        READY   STATUS    RESTARTS   AGE
pod/gitea-dc5699668-xnzm9   1/1     Running   0          69m

NAME            TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/gitea   NodePort   10.107.208.196   <none>        3000:31850/TCP   69m

You can access the gitea service using ingress url:

gitea home
Figure 1. Gitea Home

Create ServiceAccount, Roles and Role Bindings

We need to query Kubernetes objects, create Triggers and finally deploy the Knative Service as part of this chapter’s exercies. Let’s create a Kubernetes Service Account and add the required roles/permissions:

kubectl apply -k $TUTORIAL_HOME/triggers/rbac
serviceaccount/pipeline created
role.rbac.authorization.k8s.io/app-deployer-role created
role.rbac.authorization.k8s.io/tekton-triggers-admin created
role.rbac.authorization.k8s.io/tekton-triggers-createwebhook created
rolebinding.rbac.authorization.k8s.io/app-deployer-binding created
rolebinding.rbac.authorization.k8s.io/tekton-triggers-admin-binding created
rolebinding.rbac.authorization.k8s.io/tekton-triggers-createwebhook-binding created
In OpenShift you might not need the pipeline SA, as the OpenShift Pipelines creates it by default

Initialize Source Repository

To run the exercises of this chapter we need the following setup in Git(gitea):

We will run the following Task to perform the necessary intializations listed above:

kubectl create -n triggers-demo \
  -f $TUTORIAL_HOME/triggers/gitea-init-taskrun.yaml
taskrun.tekton.dev/init-gitea-qkf84 created

View the task run logs using,

tkn tr logs -f -a $(tkn tr ls -n triggers-demo | awk 'NR==2{print $1}')

A successfull initializations should show an output like:

[create-gitea-admin] Created Gitea admin user gitea:gitea

[init-gitea] Created git repo tekton-tutorial-greeter
[init-gitea] Repository Clone url http://gitea.192.168.64.7.nip.io/gitea/tekton-tutorial-greeter.git

You should now be able to open the http://$GITEA_HOME/tekton-tutorial-greeter.git and check the repository sources, that we will use as part of this chapter’s exercises.

Gitea admin user is gitea and password gitea.

Install Triggers

  • Minikube

  • OpenShift

Wait for the Tekton Trigger controller and webhook to be ready:

watch kubectl -n tekton-pipelines get pods

A successful Tekton run should have the highlighted Tekton Trigger pods running in the tekton-pipelines:

NAME                                           READY   STATUS    RESTARTS   AGE
tekton-pipelines-controller-849ccccd7f-gc6dp   1/1     Running   1          3d3h
tekton-pipelines-webhook-75bc7666c-9crwq       1/1     Running   1          3d3h
tekton-triggers-controller-697c9b844d-9lz4x    1/1     Running   0          15h
tekton-triggers-webhook-6bcb96f965-gqrbh       1/1     Running   0          15h
In OpenShift, the OpenShift pipelines operator installs Tekton Pipelines and Tekton Triggers in openshift-pipelines namespace. It’s not needed install Tekton Triggers.

Trigger Template

TriggerTemplate is responsible to create the Tekton Resources when it receives the Event from the EventListener.

Create Trigger Template

kubectl create -n triggers-demo \
  -f $TUTORIAL_HOME/triggers/greeter-trigger-template.yaml
triggertemplate.triggers.tekton.dev/tekton-greeter-trigger-template created

List the available TriggerTemplates:

tkn tt -n triggers-demo ls

It should be only one now:

NAME                                 AGE
tekton-greeter-trigger-template   1 minute ago

Adjust Trigger Template Image

  • Minikube

  • OpenShift

by default the greeter pipeline’s default image tag that it builds to is the example.com/tekton-tutorial/greeter. Hence, no further updates to the TriggerTemplate is necessary

When using OpenShift, we want to adjust the image tag that the pipeline builds. We’ll do this by patching the image tag parameter of the trigger template to point to a local image stream:

kubectl patch tt/tekton-greeter-trigger-template -n triggers-demo --type='json' -p='[{"op": "replace", "path": "/spec/resourcetemplates/0/spec/params/3/value", "value":"image-registry.openshift-image-registry.svc:5000/triggers-demo/greeter"}]'

Trigger Bindings

TriggerBinding is responsible to bind the Event payload with Template Parameters. In this case it will bind the gitea event playload to TriggerTemplate parameters, inputs, outputs and workspaces.

Let us see a sample webhook payload that Git events from Gitea might look like:

Gitea Payload sample
{
  "secret": "",
  "ref": "refs/heads/master",
  "before": "7efe116eb6a9763f648aad9061ce8d2f34b702bc",
  "after": "36865805cbaf23a865daa6b1e87fb0498b5eba61",
  "compare_url": "http://gitea.192.168.64.7.nip.io/gitea/tekton-tutorial-greeter/compare/7efe116eb6a9763f648aad9061ce8d2f34b702bc...36865805cbaf23a865daa6b1e87fb0498b5eba61",
  "commits": [
    {
      "id": "36865805cbaf23a865daa6b1e87fb0498b5eba61",
      "message": "Fix test\n",
      "url": "http://gitea.192.168.64.7.nip.io/gitea/tekton-tutorial-greeter/commit/36865805cbaf23a865daa6b1e87fb0498b5eba61",
      "author": {
        "name": "gitea",
        "email": "gitea@gitea.com",
        "username": "gitea"
      },
      "committer": {
        "name": "gitea",
        "email": "gitea@gitea.com",
        "username": "gitea"
      },
      "verification": null,
      "timestamp": "2020-11-16T07:25:01Z",
      "added": [],
      "removed": [],
      "modified": [
        "src/test/java/com/redhat/developers/GreetingResourceTest.java"
      ]
    }
  ],
  "head_commit": null,
  "repository": {
    "id": 1,
    "owner": {
      "id": 1,
      "login": "gitea",
      "full_name": "",
      "email": "gitea@gitea.com",
      "avatar_url": "http://gitea.192.168.64.7.nip.io/user/avatar/gitea/-1",
      "language": "en-US",
      "is_admin": false,
      "last_login": "2020-11-16T06:38:46Z",
      "created": "2020-11-16T06:32:30Z",
      "username": "gitea"
    },
    "name": "tekton-tutorial-greeter",
    "full_name": "gitea/tekton-tutorial-greeter",
    "description": "",
    "empty": false,
    "private": false,
    "fork": false,
    "template": false,
    "parent": null,
    "mirror": false,
    "size": 112,
    "html_url": "http://gitea.192.168.64.7.nip.io/gitea/tekton-tutorial-greeter",
    "ssh_url": "ssh://gitea@gitea.192.168.64.7.nip.io:2022/gitea/tekton-tutorial-greeter.git",
    "clone_url": "http://gitea.192.168.64.7.nip.io/gitea/tekton-tutorial-greeter.git",
    "original_url": "",
    "website": "",
    "stars_count": 0,
    "forks_count": 0,
    "watchers_count": 1,
    "open_issues_count": 0,
    "open_pr_counter": 0,
    "release_counter": 0,
    "default_branch": "master",
    "archived": false,
    "created_at": "2020-11-16T06:32:30Z",
    "updated_at": "2020-11-16T07:25:04Z",
    "permissions": {
      "admin": true,
      "push": true,
      "pull": true
    },
    "has_issues": true,
    "internal_tracker": {
      "enable_time_tracker": true,
      "allow_only_contributors_to_track_time": true,
      "enable_issue_dependencies": true
    },
    "has_wiki": true,
    "has_pull_requests": true,
    "ignore_whitespace_conflicts": false,
    "allow_merge_commits": true,
    "allow_rebase": true,
    "allow_rebase_explicit": true,
    "allow_squash_merge": true,
    "avatar_url": ""
  },
  "pusher": {
    "id": 1,
    "login": "gitea",
    "full_name": "",
    "email": "gitea@gitea.com",
    "avatar_url": "http://gitea.192.168.64.7.nip.io/user/avatar/gitea/-1",
    "language": "en-US",
    "is_admin": false,
    "last_login": "2020-11-16T06:38:46Z",
    "created": "2020-11-16T06:32:30Z",
    "username": "gitea"
  },
  "sender": {
    "id": 1,
    "login": "gitea",
    "full_name": "",
    "email": "gitea@gitea.com",
    "avatar_url": "http://gitea.192.168.64.7.nip.io/user/avatar/gitea/-1",
    "language": "en-US",
    "is_admin": false,
    "last_login": "2020-11-16T06:38:46Z",
    "created": "2020-11-16T06:32:30Z",
    "username": "gitea"
  }
}

We can use any attribute/data from the JSON payload and bind them as the value in TriggerBindings. For this chapter exercise we are interested in two attributes namely:

  • after: the commit hash after our push is merged into the master

  • repository.clone_url: The Git Repo clone url

The HTTP Body and Headers of the event payload are available as body and header JSONPath variables. We can retrieve the values using JSONPath expressions. Check the Event Variable Interpolation for more details.

To retrieve the after and repository.clone_url we use will the JSONPath expressions $(body.after) and $(body.repoistory.clone_url) respectively.

Create Trigger Bindings

---
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerBinding
metadata:
  name: gitea-triggerbinding
spec:
  params:
    - name: gitrevision
      value: $(body.after)
    - name: gitrepositoryurl
      value: $(body.repository.clone_url)
kubectl create -n triggers-demo -f $TUTORIAL_HOME/triggers/gitea-triggerbindings.yaml
triggerbinding.triggers.tekton.dev/gitea-triggerbinding created

List the available TriggerBindings:

tkn tb -n triggers-demo ls

It should show an output like:

NAME                  AGE
gitea-triggerbinding   7 seconds ago

Event Listener

Event Listener is primary interace for external sources to send events, that will trigger the creation of Tekton resources defined as part of the TriggerTemplate.

Create Event Listener

kubectl apply -n triggers-demo -f $TUTORIAL_HOME/triggers/gitea-eventlistener.yaml
eventlistener.triggers.tekton.dev/gitea-webhook created

List the available EventListeners:

tkn el -n triggers-demo ls

It should show an output like:

NAME           AGE
gitea-webhook   4 seconds ago

Wait for the gitea-webhook event listener pod to be running, each EventListener will have a service named el-<EventListener Name> exposed automatically for sources to send events to Triggers.

kubectl get pods,svc -n triggers-demo -leventlistener=gitea-webhook
NAME                                    READY   STATUS    RESTARTS   AGE
pod/el-gitea-webhook-848875db8f-25r2r   1/1     Running   0          58s

NAME                       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/el-gitea-webhook   ClusterIP   10.96.121.97   <none>        8080/TCP   58s

Create Event Listener Ingress

If you need the EventListner service to be available outside of the cluster thereby you can use them as part of the webhook, the service el-gitea-webhook needs to be exposed via Ingress:

  • Minikube

  • OpenShift

export EL_WEBHOOK_HOST="$(kubectl get svc -n triggers-demo el-gitea-webhook -o yaml \
  | yq r - 'metadata.name').$(minikube ip).nip.io"
export EL_WEBHOOK_LISTENER_PORT="$(kubectl get svc -n triggers-demo el-gitea-webhook -o yaml \
  | yq r - 'spec.ports.(name==listener).port')"
yq w $TUTORIAL_HOME/triggers/eventlistener-ingress.yaml \
  spec.virtualhost.fqdn $EL_WEBHOOK_HOST \
  | yq w - 'spec.routes[0].services[0].name' el-gitea-webhook \
  | yq w - 'spec.routes[0].services[0].port' $EL_WEBHOOK_LISTENER_PORT \
  | kubectl apply -n triggers-demo -f  -
httpproxy.projectcontour.io/el-gitea-webhook-ingress created

Let’s verify if the ingress has been configured correctly:

kubectl get httpproxy -n triggers-demo el-gitea-webhook-ingress -o yaml

The command should the following output (trimmed for brevity):

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: el-gitea-webhook-ingress
  namespace: triggers-demo
spec:
  routes:
  - services:
    - name: el-gitea-webhook
      port: 8080
  virtualhost:
    fqdn: el-gitea-webhook.192.168.64.7.nip.io
oc expose svc -n triggers-demo el-gitea-webhook
export EL_WEBHOOK_HOST=$(oc get route el-gitea-webhook -n triggers-demo -ojsonpath='{.spec.host}')

Configure Webhook

For the GitHub Triggers to work, we need to have the Webhook configured for the Git Repository tekton-greeter to point to the exposed host of our webhook service. For this we will use the tkn cli:

kubectl apply -n triggers-demo -f $TUTORIAL_HOME/triggers/gitea-webhook-task.yaml
tkn task start gitea-create-webhook -n triggers-demo -s pipeline -p WEBHOOK_HOST="$EL_WEBHOOK_HOST"
TaskRun started: gitea-create-webhook-run-l2dxl

View the task run logs using,

tkn tr logs -f -a $(tkn tr ls -n triggers-demo | awk 'NR==2{print $1}')

A successful initializations should show an output like:

[webhook-gitea] Configured webhook: http://el-gitea-webhook.192.168.64.7.nip.io

As you note now the tekton-greeter Git repository in Gitea is configured with the webhook to send Git events to EventListener.

gitea webhook settings

Triggers in Action

Terminal 1

As we expect the triggers to start a Pipeline, let’s open a new terminal and watch the running Pipelines:

watch tkn pr -n triggers-demo ls

You Ctrl+c the watch once you see it running and monitor the logs.

Terminal 2

Open a new terminal and watch the logs the event listener to see the incoming events:

stern -n triggers-demo gitea-webhook

Once the event playload is received from the gitea server, you should see output(trimmed for brevity) as shown below:

...
el-gitea-webhook-88bc89db-ld6jz event-listener {"level":"info","logger":"eventlistener","caller":"resources/create.go:93","msg":"Generating resource: kind: &APIResource{Name:pipelineruns,Namespaced:true,Kind:PipelineRun,Verbs:[delete deletecollection get list patch create update watch],ShortNames:[pr prs],SingularName:pipelinerun,Categories:[tekton tekton-pipelines],Group:tekton.dev,Version:v1beta1,StorageVersionHash:4xDTCrDXyFg=,}, name: greeter-app-","knative.dev/controller":"eventlistener"}
el-gitea-webhook-88bc89db-ld6jz event-listener {"level":"info","logger":"eventlistener","caller":"resources/create.go:101","msg":"For event ID \"rrgcn\" creating resource tekton.dev/v1beta1, Resource=pipelineruns","knative.dev/controller":"eventlistener"}
...

Clone and Edit the source

Using your favorite IDE clone the repo tekton-greeter from Gitea:

git clone http://gitea.$(minikube -p tektontutorial ip).nip.io/gitea/tekton-tutorial-greeter.git

Open the tekton-tutorial-greeter in your IDE and edit the Java file:

  • Quarkus

  • SpringBoot

  • quarkus/src/main/java/com/redhat/developers/GreetingResource.java to update the "Tekton 😺 rocks πŸš€" to "Tekton 😺 Triggered"

  • quarkus/src/test/java/com/redhat/developers/GreetingResourceTest.java to match "Tekton 😺 Triggered".

  • springboot/src/main/java/com/redhat/developer/demos/GreetingController.java to update the "Meeow!! from Tekton πŸ˜ΊπŸš€" to "Tekton 😺 Triggered"

  • springboot/src/test/java/com/redhat/developer/demos/GreetingControllerTest.java to match "Tekton 😺 Triggered".

To ensure the pipeline builds the SpringBoot version of the repo, you must first update the greeter-trigger-template to pass in 'springboot' as the 'context-dir' parameter to the greeter-app-deploy pipeline. You can edit the trigger template or you can patch it using this command:
kubectl patch tt/tekton-greeter-trigger-template -n triggers-demo --type='json' -p='[{"op": "replace", "path": "/spec/resourcetemplates/0/spec/params/2/value", "value":"springboot"}]'

Commit and push to see the 😺 Pipeline greeter-app-deploy running πŸš€.

Use the username gitea and password gitea when prompted during Git push to repository.

View the pipeline run logs using,

tkn pr logs -f -a $(tkn pr ls -n triggers-demo | awk 'NR==2{print $1}')

Once the Pipeline succeeds, verify the Knative Service greeter:

http --body $(kn service describe greeter -o url)/

The command should shown output like:

Tekton 😺 Triggered

Expriment with updating the sources to your taste and see the pipeline triggers.

Points to Ponder

To use Tekton Triggers we need:

  • A Kubernetes Service Account with required permissions assigned

  • A TriggerTemplate which defines which Tekton Resources to create for a given event

  • A TriggerBinding which allows to extract the data from an event payload and bind them to the Parameters of a Tekton Task/Pipeline

  • An EventListener that will listen for the events and trigger the execution of Tekton Resources as part of the Trigger Template

Cleanup

If you no longer going to use the triggers-demo, you can do:

kubectl delete ns triggers-demo

If you no longer wish to have the Gitea operator installed, you can run:

kubectl delete ns gitea-operator