Building a ChatOps Bot With Tekton
ChatOps with YAML and Bash
One of the goals of Tekton is to provide a flexible platform for assembling software delivery systems. This post explains how to assemble a basic “ChatOps” style CI system using the standard Tekton components. Readers should come away with an understanding of how Tekton Pipelines
, PipelineResources
and Triggers
all work together, and can be used in a real-world project.
ChatOps is a way of interacting with software delivery systems — usually CI workflows — through chat. This can take the form of comments on Pull Requests, commands in a Slack channel or any other place developers are used to communicating. Software development is a highly collaborative process, the ChatOps model pushes this to the forefront by using the communication tools teams are already using to drive delivery workflows.
Overview
Like Kelsey Hightower said, there is no perfect CI/CD system for everyone. Each company or team encodes their unique culture and DNA into the software delivery process, usually in a set of brittle, untested bash scripts. We’ll be making use of some bash scripts again in this example, but hopefully it will be clear how Tekton helps draw clear boundaries around these bash scripts to make them easier to maintain.
Getting Started
For this ChatOps bot, we’re going to build a system that responds to comments on a Pull Request of the form /chatbot <command>
. To start, we’ll add a command to run tests and leave a comment with the output.
We’ll use Tekton Triggers
to respond to events, the pullRequest
PipelineResource
type to add comments, the Git
PipelineResource
type to access the code to run tests, and wrap all of this up in a reusable, portable Task
. All of the code for this can be found on GitHub at github.com/dlorenc/tekton-chatbot.
Task Definition
Let’s start with the Task
itself. We’re going to take three inputs: the text of the comment as a Param
, a resource of type git so we can access the code in the PR, and a resource of type PullRequest
to access the PR itself. The task will be manipulating the PR by adding a comment, so it will also produce an output of type PullRequest
as well.
We want to support multiple commands in the future, so we’ll use a bash case
statement to handle that. Since the first command involves running tests from the PR, we need to be careful to only run them if the PR comes from a trusted source. Otherwise, a malicious person could send a PR to our repo with tests that do bad things inside our CI cluster. We’ll use the GitHub author_association
field to make sure the author is an OWNER on the repo before continuing.
Our task will look something like this:
Trigger Definition
Next, we need to wire up the GitHub events to our cluster. For this, we need to create a few objects from the Tekton Triggers project. For a full guide on how these concepts work, see my other post on how to get started with Triggers, or the documentation.
In short, we’ll create:
- A
TriggerTemplate
that uses data from the incoming event to create ourTaskRun
andPipelineResources
- A
TriggerBinding
that extracts the required info from the incoming eventjson
payload - An
EventListener
that ties these first two concepts together and exposes an endpoint for us to point GitHub at.
Let’s start with the TriggerTemplate
. We want to create a run of our previously defined Task
, so we’ll need to fill in the two input PipelineResources
, the input Param
, and the output PipelineResource
. That will look like:
We defined four params here, corresponding to the data we need to fill in to run our Task
. Next, we’ll need to define a TriggerBinding
that extracts these params from the incoming event. We can find information what fields are available in the event in Github documentation. Here, we’re using the IssueCommentEvent
type, and we need to grab the issue.html_url
, repository.html_url
, issue.author_association
and comment.body
fields
Finally, we have to create our EventListener to give us an endpoint GitHub can send webhooks to. That simply binds the previous two objects and adds some special GitHub filtering. We can verify the request actually came from GitHub using the secret field, and we can limit the event types that this will run on. This will look like:
We still need to create the secret that will hold our validation token, we can do that with kubectl like so:
kubectl create secret generic github-shared-secret — from-literal=token=$(openssl rand 10 -hex)
Feel free to change the command used to generate the secret token to something else. It can be any value, we just need to remember it later when we configure the webhook.
All that’s left to do is setup the GitHub webhook and configure authentication so we have comment permissions on the repository. To setup the webhook, we first have to get the IP address of our EventListener:
kubectl get service el-listener-interceptor
and looking for the External IP
field.
Now we can create the webhook using the GitHub UI, under Settings -> Webhooks
Make sure to select application/json as the Content type. Fill in your External IP from the Kubernetes service, and the Secret you created earlier. If you need that value, you can retrieve it with:
echo $(kubectl get secret github-shared-secret -o=jsonpath=’{.data.token}’ | base64 — decode)
Ok, our last step is to setup another Secret
so that our TaskRun
can actually leave a comment back on the PR. To use your account, create a personal access token using the GitHub UI, under Profile -> Settings -> Developer Settings -> Personal Access Tokens -> Generate New Token.
Copy this value to your terminal, then create a secret with kubectl
, replacing the $TOKEN value with your access token.
kubectl create secret generic github-token --from-literal token=$TOKEN
Great, now you should be all set. You can test it out with a PR on your repo:
Wrapping Up
In this post, we showed how to create a simple, extensible ChatOps bot for working with GitHub PRs using Tekton Pipelines and Triggers. The ChatOps logic itself is contained in a small bash script that works with files, rather than needing to access the GitHub API directly. This means that as Tekton gains support for more SCM types, these will automatically work with this Task.
Today you can use either GitHub or GitLab — the only change you would need to make to this post is around the way data gets parsed out of the event payload.
The bash script in our Task definition is extensible so you can easily add more commands. If you found this useful, let me know! PRs with more fun commands welcome!