Simple Slack Notification with golang

arunx2

Arunachalam Lakshmanan

Posted on July 20, 2020

Simple Slack Notification with golang

A slack notification is useful when

  • A long-running job is started
  • When some critical error
  • A specific request comes through the process as cache invalidate, critical delete request, etc...

However slack provides simple webhooks to notify a channel, it has more features like action, workflow, poll, etc... So the slack go libraries need to comply with these features and that makes the library heavy by bringing internal dependencies. We can write a small tool in Go just for webhooks. It just uses standard libraries.

Usage

Create a slack client with an optional user name one per process. The SendSlackNotification() can send a simple notification to the given channel.

Options: Username - the name of the bot in a slack message
Timeout - default would be 5 seconds
Icon_emoji - the name of the icon image, otherwise it is a default image of incoming webhook.

Sample

    sc := SlackClient{
        WebHookUrl: "https://WEB_HOOK_URL",
        UserName:   "USER_NAME",
        Channel:    "CHANNEL_NAME",
    }
    sr := SimpleSlackRequest{
        Text:      "This is test message",
        IconEmoji: ":ghost:",
    }
    err := sc.SendSlackNotification(sr)
    if err != nil {
        log.Fatal(err)
    }

    To send a notification with status (slack attachments)
    sr := SlackJobNotification{
        Text:  "This is attachment message",
        Details: "details of the jobs",
        Color: "warning",
        IconEmoji: ":hammer_and_wrench",
    }
    err := sc.SendJobNotification(sr)
    if err != nil {
        log.Fatal(err)
    }

Source

package notification

import (
    "bytes"
    "encoding/json"
    "errors"
    "net/http"
    "strconv"
    "time"
)

const DefaultSlackTimeout = 5 * time.Second

type SlackClient struct {
    WebHookUrl string
    UserName   string
    Channel    string
    TimeOut    time.Duration
}

type SimpleSlackRequest struct {
    Text      string
    IconEmoji string
}

type SlackJobNotification struct {
    Color     string
    IconEmoji string
    Details   string
    Text      string
}

type SlackMessage struct {
    Username    string       `json:"username,omitempty"`
    IconEmoji   string       `json:"icon_emoji,omitempty"`
    Channel     string       `json:"channel,omitempty"`
    Text        string       `json:"text,omitempty"`
    Attachments []Attachment `json:"attachments,omitempty"`
}

type Attachment struct {
    Color         string `json:"color,omitempty"`
    Fallback      string `json:"fallback,omitempty"`
    CallbackID    string `json:"callback_id,omitempty"`
    ID            int    `json:"id,omitempty"`
    AuthorID      string `json:"author_id,omitempty"`
    AuthorName    string `json:"author_name,omitempty"`
    AuthorSubname string `json:"author_subname,omitempty"`
    AuthorLink    string `json:"author_link,omitempty"`
    AuthorIcon    string `json:"author_icon,omitempty"`
    Title         string `json:"title,omitempty"`
    TitleLink     string `json:"title_link,omitempty"`
    Pretext       string `json:"pretext,omitempty"`
    Text          string `json:"text,omitempty"`
    ImageURL      string `json:"image_url,omitempty"`
    ThumbURL      string `json:"thumb_url,omitempty"`
    // Fields and actions are not defined.
    MarkdownIn []string    `json:"mrkdwn_in,omitempty"`
    Ts         json.Number `json:"ts,omitempty"`
}

// SendSlackNotification will post to an 'Incoming Webook' url setup in Slack Apps. It accepts
// some text and the slack channel is saved within Slack.
func (sc SlackClient) SendSlackNotification(sr SimpleSlackRequest) error {
    slackRequest := SlackMessage{
        Text:      sr.Text,
        Username:  sc.UserName,
        IconEmoji: sr.IconEmoji,
        Channel:   sc.Channel,
    }
    return sc.sendHttpRequest(slackRequest)
}

func (sc SlackClient) SendJobNotification(job SlackJobNotification) error {
    attachment := Attachment{
        Color: job.Color,
        Text:  job.Details,
        Ts:    json.Number(strconv.FormatInt(time.Now().Unix(), 10)),
    }
    slackRequest := SlackMessage{
        Text:        job.Text,
        Username:    sc.UserName,
        IconEmoji:   job.IconEmoji,
        Channel:     sc.Channel,
        Attachments: []Attachment{attachment},
    }
    return sc.sendHttpRequest(slackRequest)
}

func (sc SlackClient) SendError(message string, options ...string) (err error) {
    return sc.funcName("danger", message, options)
}

func (sc SlackClient) SendInfo(message string, options ...string) (err error) {
    return sc.funcName("good", message, options)
}

func (sc SlackClient) SendWarning(message string, options ...string) (err error) {
    return sc.funcName("warning", message, options)
}

func (sc SlackClient) funcName(color string, message string, options []string) error {
    emoji := ":hammer_and_wrench"
    if len(options) > 0 {
        emoji = options[0]
    }
    sjn := SlackJobNotification{
        Color:     color,
        IconEmoji: emoji,
        Details:   message,
    }
    return sc.SendJobNotification(sjn)
}
func (sc SlackClient) sendHttpRequest(slackRequest SlackMessage) error {
    slackBody, _ := json.Marshal(slackRequest)
    req, err := http.NewRequest(http.MethodPost, sc.WebHookUrl, bytes.NewBuffer(slackBody))
    if err != nil {
        return err
    }
    req.Header.Add("Content-Type", "application/json")
    if sc.TimeOut == 0 {
        sc.TimeOut = DefaultSlackTimeout
    }
    client := &http.Client{Timeout: sc.TimeOut}
    resp, err := client.Do(req)
    if err != nil {
        return err
    }

    buf := new(bytes.Buffer)
    _, err = buf.ReadFrom(resp.Body)
    if err != nil {
        return err
    }
    if buf.String() != "ok" {
        return errors.New("Non-ok response returned from Slack")
    }
    return nil
}
💖 💪 🙅 🚩
arunx2
Arunachalam Lakshmanan

Posted on July 20, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related