Get Zapping with Teea’s example code of how to use Zapier to connect services and run actions when events are triggered.

What is Zapier?

Zapier is a web service for connecting different services. It’s free to use in most cases (you pay only for connectivity to services such as Facebook or Salesforce) and offers an easy way to connect your applications or APIs to other systems. In our example we’ll be using a simple program written in Go to be run as a cronjob on your Exoscale instance and send an alert to Slack if disk space or server loads exceed defined values.

Example App

Our example code can be found in the examples repository. Use git to clone it and run with go run yeller.go. There is a dependency to jconfig which you can get by executing go get stathat.com/c/jconfig. The configuration file yeller.conf has values for percentages of disk space and load average as well as a URL for our webhook:

{
    "diskfree": 15,
    "load": 2,
    "webhook": "<Your webhook URL here>"
}

The contents of the yeller.go are as follows:

package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "os/exec"
    "regexp"
    "strconv"
    "strings"
    "syscall"

    "stathat.com/c/jconfig"
)

var conf *jconfig.Config

func main() {
    conf = jconfig.LoadConfig("yeller.conf")

    var errors []string

    error := checkLoad()
    if error != "" {
        errors = append(errors, error)
    }

    error = checkDiskSpace()
    if error != "" {
        errors = append(errors, error)
    }

    if len(errors) > 0 {
        reportErrors(errors)
    }
}

func pushMessage(message string) {
    client := &http.Client{}
    reader := strings.NewReader("{\"message\": \"" + message + "\"}")
    req, err := http.NewRequest("POST", conf.GetString("webhook"), reader)
    if err != nil {
        log.Fatal(err)
    }

    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }

    defer resp.Body.Close()
    respBody, err := ioutil.ReadAll(resp.Body)
    body := string(respBody)

    if !strings.Contains(body, "status\": \"success") {
        log.Fatal(body)
    }
}

func reportErrors(errors []string) {
    for i := 0; i < len(errors); i++ {
        pushMessage(errors[i])
    }
}

func checkDiskSpace() string {
    wd, err := os.Getwd()

    if err != nil {
        log.Fatal(err)
    }

    fs := syscall.Statfs_t{}
    err = syscall.Statfs(wd, &fs)

    if err != nil {
        log.Fatal(err)
    }

    var disksize = fs.Blocks * uint64(fs.Bsize)
    var free = fs.Bfree * uint64(fs.Bsize)
    var freepercent = float64(100) / float64(disksize) * float64(free)
    var percentage = conf.GetFloat("diskfree")

    if freepercent <= float64(percentage) {
        return fmt.Sprintf("Diskspace limit reached (free %f%%, limit %f%%)", freepercent, percentage)
    }

    return ""
}

func checkLoad() string {
    var out bytes.Buffer
    cmd := exec.Command("uptime")
    cmd.Stdout = &out
    err := cmd.Run()
    if err != nil {
        log.Fatal(err)
    }

    var load = out.String()
    r := regexp.MustCompile("load averages: (?P<now>[0-9]*.[0-9]*) (?P<5min>[0-9]*.[0-9]*) (?P<15min>[0-9]*.[0-9]*)")

    match := r.FindStringSubmatch(load)

    loadavg, err := strconv.ParseFloat(match[2], 64)
    loadmax := conf.GetFloat("load")

    if err != nil {
        log.Fatal(err)
    }

    if loadavg >= loadmax {
        return fmt.Sprintf("Server load too high (current: %f, max %f)", loadavg, loadmax)
    }

    return ""
}

The Zapier Side

Create an account and start with creating your own Zap by clicking “Make a Zap!”. Each Zap has a trigger which initiates it and then one or more actions that run when your Zap is triggered. Our Zap will be triggered when the constraints are exceeded by a webhook, so pick Webhooks by Zapier from the list of triggers.

Zapier webhook interface

As type of hook we will use Catch hook which will offer us a URL used for triggering the action. Leave the Child key part empty and proceed to the next step which offers you the URL. Copy this URL to the webhook part in the yeller.conf file. After clicking OK, I did this, Zapier will wait for the webhook to be triggered. Run the yeller program once (edit the configuration to have values that should be triggered, such as load being zero) and Zapier should report a successful test to you:

test successful

We want our Zap to give us a message in our Slack channel when it is triggered, so choose the Action App to be Slack.

Slack interface

Set the action to send a channel message and connect Zapier Slack app to your team and Slack account. On the next page define the channel and the message you want to send (click on the file/message icon in the corner) and you’re all set up.

Slack channel

Fill the rest of the values as you see fit (or leave them as they are), test it, and you’re done on the Zapier side. Your Slack should get a message about loads being too high.

server alarm

The Server Side

On the server side of things set yeller.go to be run for example every 10 minutes (don’t forget to edit the load and disk space values to your liking!) and you’re all set! For an execution interval of 10 minutes, add the following line to your crontab:

10 * * * * go run yeller.go >/dev/null 2>&1

You can also do the application in your favorite language or just as a shell script, the example Go program doesn’t use any Zapier libraries or such and the code should be fairly self-explanatory even if you have no experience in Go.

Have fun Zapping!

Want to write on our blog about some specific code or share a tutorial you wrote? Got your own dev or ops story to share? Get in touch with us at hello@exoscale.ch.