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 our TaskRun and PipelineResources
  • A TriggerBinding that extracts the required info from the incoming event json 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!

Founder/CEO at Chainguard