|
| 1 | +--- |
| 2 | +title: "I wrote a Twitter Bot using OpenFaaS to avoid missing out on CfP deadlines" |
| 3 | +description: "I wrote a Twitter bot using Golang and OpenFaaS so that I wouldn't miss the deadline for CNCF talk submissions or Kubernetes releases" |
| 4 | +date: 2021-06-07 |
| 5 | +image: /images/2021-06-cloudnativetv/background-twitter.png |
| 6 | +categories: |
| 7 | + - twitter |
| 8 | + - bot |
| 9 | + - golang |
| 10 | + - usecase |
| 11 | + - functions |
| 12 | +author_staff_member: cpanato |
| 13 | +dark_background: true |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +I wrote a Twitter bot using Golang and OpenFaaS so that I wouldn't miss the deadline for CNCF talk submissions or Kubernetes releases. |
| 18 | + |
| 19 | +In this blog post, I'll show you how I put the bot together and what tools I used along the way so that you can build your own and get ideas for your own projects. |
| 20 | + |
| 21 | +## Introduction |
| 22 | + |
| 23 | +Bots are everywhere. Some are useful that can help you in your daily work, and others are collecting information about you to use for ads. |
| 24 | + |
| 25 | +> I think that the best bots are both fun and useful. |
| 26 | +
|
| 27 | +I created [@CloudNativeBot](https://twitter.com/CloudNativeBot) because: I would like to learn a bit more about OpenFaaS and the ecosystem around that. |
| 28 | + |
| 29 | +I wanted to: |
| 30 | +* let people know when the next KubeCon was coming up and when the Call For Papers (CFP) deadline was |
| 31 | +* to show when the [Kubernetes](https://kubernetes.io/) Patch Releases land |
| 32 | +* and to bring some joy |
| 33 | + |
| 34 | +Most importantly, I want you to have fun, discover new technologies that can be applied to your daily work. |
| 35 | + |
| 36 | +## What you'll need to try it out |
| 37 | + |
| 38 | +Before you get started you'll need Docker on your machine so that you can deploy a Kubernetes cluster and build OpenFaaS functions. |
| 39 | +* [Docker or Docker Desktop](https://www.docker.com/products/docker-desktop) |
| 40 | + |
| 41 | +Then you'll need the following, which you can get by following the docs: |
| 42 | + |
| 43 | +* [OpenFaaS](https://www.openfaas.com) - to run the functions |
| 44 | +* [faas-cli](https://github.com/openfaas/faas-cli) - the CLI for OpenFaaS |
| 45 | +* [connector-sdk](https://github.com/openfaas/connector-sdk) - the SDK for writing event connectors to trigger functions, in this instance from Twitter |
| 46 | +* [ko](https://github.com/google/ko) - a project to hot-reload our Twitter connector in the cluster as we iterate on it |
| 47 | + |
| 48 | +### Using Connector-sdk to listen and dispatch the Twitter Stream |
| 49 | + |
| 50 | +The connector-sdk helps you to connect events to functions. In the beginning of the code you bind to your streaming data source like Twitter, NATS or a cron timer, then whenever a message is received, the topic or channel name is used to trigger matching functions. |
| 51 | + |
| 52 | +You can learn more here: [OpenFaaS Triggers](https://docs.openfaas.com/reference/triggers/) |
| 53 | + |
| 54 | + |
| 55 | +> Functions subscribe to events by adding a "topic" annotation at deploy time |
| 56 | +
|
| 57 | +I was able to setup my connector with just a few lines of code. In the case of Twitter, we need to configure the SDK to listen a stream for various messages like `/cloudnativetv` or `/k8s-patch-schedule`. |
| 58 | + |
| 59 | +Here's an example of how to connect the stream using the Twitter SDK and [anaconda library](https://github.com/ChimeraCoder/anaconda). |
| 60 | + |
| 61 | +```golang |
| 62 | +api = anaconda.NewTwitterApiWithCredentials(Config.TwitterAccessToken, Config.TwitterAccessSecret, Config.TwitterConsumerKey, Config.TwitterConsumerSecretKey) |
| 63 | + if _, err := api.VerifyCredentials(); err != nil { |
| 64 | + log.Fatalf("Bad Authorization Tokens. Please refer to https://apps.twitter.com/ for your Access Tokens: %s", err) |
| 65 | + } |
| 66 | + |
| 67 | + streamValues := url.Values{} |
| 68 | + streamValues.Set("track", "/cloudnativetv,/k8s-patch-schedule,/kubecon-random-video") |
| 69 | + streamValues.Set("stall_warnings", "true") |
| 70 | + log.Println("Starting CloudNative Stream...") |
| 71 | + s := api.PublicStreamFilter(streamValues) |
| 72 | +``` |
| 73 | + |
| 74 | +After that, we need to configure the connector-sdk with the password for the gateway and any other configuration we want. |
| 75 | + |
| 76 | +If you expect to receive many messages over a short period of time, or concurrent slow-running functions then you can set `AsyncFunctionInvocation` to true for execution in the background. |
| 77 | + |
| 78 | +```golang |
| 79 | +creds := &auth.BasicAuthCredentials{ |
| 80 | + User: Config.OpenFaaSUsername, |
| 81 | + Password: Config.OpenFaaSPassword, |
| 82 | + } |
| 83 | + |
| 84 | + config := &types.ControllerConfig{ |
| 85 | + RebuildInterval: time.Millisecond * 1000, |
| 86 | + GatewayURL: Config.OpenFaaSGateway, |
| 87 | + PrintResponse: true, |
| 88 | + PrintResponseBody: true, |
| 89 | + AsyncFunctionInvocation: false, |
| 90 | + } |
| 91 | + |
| 92 | + controller := types.NewController(creds, config) |
| 93 | + |
| 94 | + receiver := ResponseReceiver{} |
| 95 | + controller.Subscribe(&receiver) |
| 96 | + |
| 97 | + controller.BeginMapBuilder() |
| 98 | +``` |
| 99 | + |
| 100 | +Then we can create an event-loop in a separate Go routine to listen to the events and dispatch them when required. |
| 101 | + |
| 102 | +The `controller.Invoke()` function takes a topic and a number of bytes. So the topic could be `"cloudnative.twitter.stream"` and the message in this instance will be a JSON payload that can be parsed by the target function. |
| 103 | + |
| 104 | +```golang |
| 105 | +go func() { |
| 106 | + for t := range s.C { |
| 107 | + switch v := t.(type) { |
| 108 | + case anaconda.Tweet: |
| 109 | + data := []byte(fmt.Sprintf(`{"text": %q,"id": %d, "id_str": %q, "user_screen_name": %q, "api_key": %q}`, v.Text, v.Id, v.IdStr, v.User.ScreenName, Config.TokenAPIKey)) |
| 110 | + |
| 111 | + topic := "cloudnative.twitter.stream" |
| 112 | + log.Printf("Got one message - https://twitter.com/%s/status/%d - Invoking on topic %s\n", v.User.ScreenName, v.Id, topic) |
| 113 | + controller.Invoke(topic, &data) |
| 114 | + default: |
| 115 | + log.Printf("Got something else %v", v) |
| 116 | + } |
| 117 | + } |
| 118 | + }() |
| 119 | +``` |
| 120 | + |
| 121 | +The complete source code can see here: https://github.com/cpanato/cloudnative-bot/blob/main/cmd/twitter-stream/main.go. |
| 122 | + |
| 123 | +Now we are in part to deploy the service into a Kubernetes Cluster. |
| 124 | + |
| 125 | +All other connectors have a Dockerfile and help chart, but I decided to use the [`ko`](https://github.com/google/ko) tool for building my connector. `ko` simplifies and makes it easier to create a container image for your Go application without having to write a Dockerfile. |
| 126 | + |
| 127 | +> Note: The community connectors do however use a Dockerfile and it is easy to use as a template for your own. For example, the [nats-connector](https://github.com/openfaas/nats-connector). |
| 128 | +
|
| 129 | +One way to use `ko` is to define your deployment manifest, and in the image field, have the `ko` syntax, for example: |
| 130 | + |
| 131 | +```yaml |
| 132 | +apiVersion: apps/v1 |
| 133 | +kind: Deployment |
| 134 | +metadata: |
| 135 | + name: cloudnative-bot-stream |
| 136 | + labels: |
| 137 | + app.kubernetes.io/name: cloudnative-bot-stream |
| 138 | +spec: |
| 139 | + replicas: 1 |
| 140 | + spec: |
| 141 | + containers: |
| 142 | + - name: stream |
| 143 | + image: ko://github.com/cpanato/cloudnative-bot/cmd/twitter-stream |
| 144 | + imagePullPolicy: IfNotPresent |
| 145 | +``` |
| 146 | +Note: some lines were removed from the manifest to make it more clear. |
| 147 | +
|
| 148 | +After defining the deployment, you can run `ko` and the output will be the updated manifests ready to be deployed and the image pushed to the registry. |
| 149 | + |
| 150 | +```console |
| 151 | +$ export KO_DOCKER_REPO=YOUR_REGISTRY_NAME |
| 152 | +$ ko resolve -f deployment.yaml > release.yaml |
| 153 | +``` |
| 154 | + |
| 155 | +### Using OpenFaaS functions for the slash commands |
| 156 | + |
| 157 | +We are using OpenFaaS to deploy the slash command functions for the bot, and those will be triggered by the connector-sdk code we show above. |
| 158 | + |
| 159 | +The `CloudNativeBot` have currently three functions: |
| 160 | + |
| 161 | + * `/cloudnativetv` - returns a random logo for the CNCF CloudNative TV shows |
| 162 | + * `/k8s-patch-schedule` - returns the patch schedule for the active release branches fro Kubernetes |
| 163 | + * `/kubecon-random-video`- returns a random KubeCon video |
| 164 | + |
| 165 | +The code for each function you can found in the [cloudnative-bot](https://github.com/cpanato/cloudnative-bot) repo. |
| 166 | + |
| 167 | +An example of how that works: |
| 168 | + |
| 169 | +* A Twitter user post a message `/kubecon-random-video` |
| 170 | +* The stream listener will catch that and will trigger the function |
| 171 | +* The specific function, in this case, the `kubecon-random-video` will grab a random video from Youtube and post a reply message on Twitter. |
| 172 | + |
| 173 | + |
| 174 | +> Example response with a random video from the last KubeCon event. |
| 175 | + |
| 176 | +### Running functions running on a schedule |
| 177 | + |
| 178 | +Now we've covered the functions which are triggered by user-input. We still need to talk about the function that reminds us about the upcoming deadline for talk submissions. You can't give a talk at a conference, if you've not submitted a talk abstract and sometimes these dates come up so quickly, so I wanted to make sure I never missed one. |
| 179 | + |
| 180 | +These functions are similar to the ones we created above, but instead of being triggered by the Twitter connector I built and deployed with ko, they are triggered by the [`cron-connector`](https://github.com/openfaas/cron-connector). |
| 181 | + |
| 182 | +The `cron-connector` is an add-on that you can install along side your OpenFaaS deployment and then using some annotations in your function you can make it run using a defined schedule. |
| 183 | + |
| 184 | +To define the schedule for your function you can add the following in your `stack.yaml` |
| 185 | + |
| 186 | +```yaml |
| 187 | +functions: |
| 188 | + remind-cfp: |
| 189 | + image: ghcr.io/cpanato/remind-cfp:1.0.0 |
| 190 | + annotations: |
| 191 | + topic: cron-function |
| 192 | + schedule: "*/5 * * * *" |
| 193 | +``` |
| 194 | + |
| 195 | +The `topic` is always set to `cron-function` and the `schedule` is a standard Cron expression. |
| 196 | + |
| 197 | +For [faasd](https://github.com/openfaas/faasd) users, you can use cron on your system or deploy the cron-connector. faasd can run on a single virtual machine or Raspberry Pi without the overhead of Kubernetes. |
| 198 | + |
| 199 | +### Summing up |
| 200 | + |
| 201 | +With OpenFaaS and the connector-sdk I was able to bootstrap my project quickly and iterate on it until I had everything in place. |
| 202 | + |
| 203 | +Feel free to test it out, the commands are: |
| 204 | + |
| 205 | +* `/cloudnativetv` |
| 206 | +* `/k8s-patch-schedule` |
| 207 | +* `/kubecon-random-video` |
| 208 | + |
| 209 | +If you like, feel free to fork the repo and build your own bot. The functions can be written in any language that you can find in the template store via `faas-cli template store list` or you can just customise mine to suit your needs. |
| 210 | + |
| 211 | +You can even build your own Slack and GitHub bots with OpenFaaS. Find out what the community and customers are doing in: [Exploring Serverless Use-cases from Companies and the Community](https://www.openfaas.com/blog/exploring-serverless-live/) |
| 212 | + |
| 213 | +What to do next: |
| 214 | + |
| 215 | + - Follow the [@CloudNativeBot](https://twitter.com/CloudNativeBot) |
| 216 | + - Read this blog post on [Golang and OpenFaaS](https://www.openfaas.com/blog/golang-serverless/) |
| 217 | + - Discover more tools and functions in the OpenFaaS ecosystem: https://github.com/openfaas |
| 218 | + - Checkout [KubeCon US](https://events.linuxfoundation.org/kubecon-cloudnativecon-north-america/) - October 2021 |
0 commit comments