What do I want to achieve?
In a GitLab project, when I create an issue or add a note to an issue, I would like that a bot automatically adds a note with a message for me.
What is capsule: Capsule is a WebAssembly function runner: https://github.com/bots-garden/capsule#what-is-capsule
What do I need?
- A GitLab Webhook: every time I invoke the bot by its name in the body of the issue or in a note (a note is a comment), it will trigger a GitLab Webhook.
- A Capsule HTTP service (the bot): the service will be invoked by the webhook.
- The bot will be able to use the GitLab API to add notes to the issue.
First, let's create the webhook
In a GitLab project, go to the Settings>Webhooks menu (left side menu) and set the values of the form. You need:
- The url of your bot (the HTTP service). (*)
- Select the trigger events:
Comments
andIssue events
(*): I use I use Gitpod to develop. The advantage is that I can launch my service from Gitpod and obtain a public URL to make it accessible with the 'toto' command. Otherwise, to test your bot locally, you can use a tunnelling service like Ngrok.
Then click on the Add webhook button.
We need a bot user
You need to create a GitLab user (in addition to your own user) (*). Then generate a Personal Access Tokens for this user, and give the API rights to the token:
Now, the bot user is able to use the GitLab API, and then is able to add notes to an issue.
- 🖐 keep the token somewhere
- 🤖 add the user to the members list of your project
No we can create the code of the bot service
Pre-requisites
- Install Capsule: https://github.com/bots-garden/capsule#installing-capsule
- Install Cabu (aka Capsule Builder): https://github.com/bots-garden/capsule#install-capsule-builder
Generate the skeleton code of the bot:
When the webhook is triggered, it does a POST HTTP request, then, we will create a function callable with a POST HTTP request (service-post
):
cabu generate service-post bob
cd bob
go mod tidy
when cabu is run for the first time, it will pull the Capsule Builder Docker image
Change the code of the function
The Capsule Runner brings some useful host functions to the wasm module, and especially:
-
hf.Http
to make HTTP requests -
hf.GetEnv
to read the value of the environment variables.
hf.GetEnv
is extremely important, because it will help to make your bot service "cloud compliant": have a look at: The twelve-factor app stores config in environment variables
This is the final source code:
package main
import (
"strconv"
"strings"
hf "github.com/bots-garden/capsule/capsulemodule/hostfunctions"
"github.com/tidwall/gjson"
)
const (
Open string = "open" // new issue
Close = "close" // issue or comment
Reopen = "reopen" // issue
Update = "update" // issue or comment
)
const (
Issue = "issue"
Note = "note"
)
func main() {
hf.SetHandleHttp(Handle)
}
func addNoteToTheIssue(issueIid float64, projectId float64, message string) (string, error) {
botToken, _ := hf.GetEnv("BOT_TOKEN")
apiUrl, _ := hf.GetEnv("API_URL")
issueNumber := strconv.FormatInt(int64(issueIid), 10)
projectNumber := strconv.FormatInt(int64(projectId), 10)
hf.Log("🖐 issueNumber: " + issueNumber)
hf.Log("🖐 projectNumber: " + projectNumber)
noteApiUrl := apiUrl + "/projects/" + projectNumber + "/issues/" + issueNumber + "/notes"
headers := map[string]string{
"Content-Type": "application/json; charset=utf-8",
"PRIVATE-TOKEN": botToken,
}
jsondoc := `{"body": "` + message + `"}`
return hf.Http(noteApiUrl, "POST", headers, jsondoc)
}
func Handle(request hf.Request) (response hf.Response, errResp error) {
var resp string
var err error
object := gjson.Get(request.Body, "object_kind") // it should be issue or note
objectAttributes := gjson.Get(request.Body, "object_attributes") // issue or comment
projectId := gjson.Get(request.Body, "project.id").Num
userName := gjson.Get(request.Body, "user.username").Str
botName, _ := hf.GetEnv("BOT_NAME")
if object.Str == Issue {
action := objectAttributes.Get("action") // open close reopen update (only for issue)
if action.Str == Open || action.Str == Update {
issueDescription := objectAttributes.Get("description").Str
issueIid := objectAttributes.Get("iid").Num
if strings.Contains(issueDescription, botName) {
resp, err = addNoteToTheIssue(issueIid, projectId, "👋 @"+userName+" what's up? 😄")
}
}
}
// a comment is added to the issue or an existing comment is updated
if object.Str == Note {
note := objectAttributes.Get("note").Str
issueIid := gjson.Get(request.Body, "issue.iid").Num
if strings.Contains(note, botName) {
resp, err = addNoteToTheIssue(issueIid, projectId, "🤔 @"+userName+" are you talking to me?")
}
}
headersResp := map[string]string{
"Content-Type": "application/json; charset=utf-8",
}
if err != nil {
hf.Log("😡 error:" + err.Error())
}
return hf.Response{Body: resp, Headers: headersResp}, err
}
Build the Wasm Bot module
cd bob
cabu build . bob.go bob.wasm
Start the Wasm Bot module
BOT_NAME="@swannou" \
BOT_TOKEN="${SWANNOU_TOKEN}" \
API_URL="https://gitlab.com/api/v4" \
capsule -wasm=./bob.wasm -mode=http -httpPort=8080
Let's return to your GitLab project, and play!
Create an issue and notify the bot:
Add a note and notify the bot:
Of course, you can create more intelligent bots to analyze text, detect spelling mistakes, and provide help, ...
You can find the source code of the GitLab bot here: https://github.com/bots-garden/bob-the-bot/tree/main/bob-gitlab (and a GitHub bot version there: https://github.com/bots-garden/bob-the-bot/tree/main/bob-github)
That's it! And Have fun 🙂
If you want to read more about:
Photo by Kenny Eliason on Unsplash
Top comments (0)