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:
- An OpenShift environment. You can use the OpenShift Playground, but you’ll only have access for 60 minutes (after which time it will be destroyed).
- The Tekton Pipelines Operator installed on your OpenShift environment. For this tutorial, I used version 0.11.0.
- The Tekton command-line interface (CLI)
tkn
. For this tutorial, I used version 0.8.0. - Basic knowledge of Tekton Pipelines.
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:
TriggerTemplates
A
TriggerTemplate
declares a blueprint for each Kubernetes resource you want to create when an event is received. EachTriggerTemplate
has parameters that can be substituted anywhere within the blueprint you define. In general, you will have oneTriggerTemplate
for each of your TektonPipeline
s. In this tutorial, you create aTriggerTemplate
for your build-and-deployPipelineRun
because you want to create a build-and-deployPipelineRun
every time you receive a pull request event.TriggerBindings
A
TriggerBinding
describes what information you want to extract from an event to pass to yourTriggerTemplate
. EachTriggerBinding
essentially declares the parameters that get passed to theTriggerTemplate
at runtime (when an event is received). In general, you will have oneTriggerBinding
for each type of event that you receive. In this tutorial, you will create aTriggerBinding
for the GitHub pull request event in order to build and deploy the code in the pull request.EventListeners
An
EventListener
creates a Deployment and Service that listen for events. When theEventListener
receives an event, it executes a specifiedTriggerBinding
andTriggerTemplate
. In this tutorial, theEventListener
will receive pull request events from GitHub and execute theTriggerBinding
andTriggerTemplate
to create a build-and-deployPipelineRun
.
The following diagram illustrates the control flow of the three Tekton Triggers resources when an event is received:
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.
- 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
Create a new project in OpenShift for CatApp:
oc new-project catapp
Apply the CatApp
Pipeline
andTask
s (intekton/build-and-deploy-openshift-pipeline.yaml
):oc apply -f tekton/build-and-deploy-openshift-pipeline.yaml
Test the CatApp build-and-deploy pipeline with a
PipelineRun
. Later, you will use the YAML from thisPipelineRun
to structure yourTriggerTemplate
. Use thepipeline
ServiceAccount to execute thePipelineRun
, because it was created by the Pipelines Operator to have the proper permissions to execute TektonPipelineRun
s: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
Watch the
PipelineRun
:tkn pipelinerun list tkn pipelinerun logs --last -f
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}}'
Now open the Route URL in your web browser.
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 TriggerBinding
s. 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 TriggerTemplate
s. 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
Apply the CatApp triggers resources:
oc apply -f tekton/build-and-deploy-openshift-triggers.yaml
Now verify the
EventListener
sink Pod is running:oc get pods -l eventlistener=catapp
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 newPipelineRun
using yourTriggerTemplate
,TriggerBinding
, and the body of your curl request.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 thegenerateName
field.
Set up the GitHub pull request webhook
Open your fork of the CatApp repository in GitHub and navigate to the Webhooks menu. Then click the Add webhook button.
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.
- Set the Payload URL to the
Now click the Add webhook button.
Verify that there is a green checkmark next to your webhook. This indicates that the
EventListener
sink Pod is receiving events from the webhook.
Create a pull request to test the trigger
Go back to your code editor and open the CatApp
index.html
file.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> ...
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
Open GitHub in your web browser and create a pull request for your new branch
add-h1
.Your new pull request will kick off the
PipelineRun
to build and deploy your branchadd-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
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}}'
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:
- The Tekton website
- The Tekton Triggers GitHub repository
- The Tekton Community GitHub repository
- The Tekton Slack workspace
If you’re interested in taking a hands-on approach try any of the tutorials featured here, on IBM Developer.