Skip to content

Instantly share code, notes, and snippets.

@lsjostro
Created December 8, 2021 08:30
Show Gist options
  • Save lsjostro/295eb3436cf09dda1791acd9fa1d0c0a to your computer and use it in GitHub Desktop.
Save lsjostro/295eb3436cf09dda1791acd9fa1d0c0a to your computer and use it in GitHub Desktop.
Cloud function listening on a pubsub cloud-build pubsub topic for GCB run events and notify on slack
package main
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/apsystole/log"
"github.com/sethvargo/go-envconfig"
"github.com/slack-go/slack"
"google.golang.org/api/cloudbuild/v1"
"google.golang.org/api/pubsub/v1"
)
var config struct {
Port string `env:"PORT,default=8080"`
SlackChannelID string `env:"SLACK_CHANNEL_ID,required"`
SlackToken string `env:"SLACK_TOKEN,required"`
}
type pushRequest struct {
Message pubsub.PubsubMessage `json:"message"`
Subscription string `json:"subscription"`
}
func receiveMessage(w http.ResponseWriter, r *http.Request) {
var pr pushRequest
err := json.NewDecoder(r.Body).Decode(&pr)
if err != nil {
http.Error(w, fmt.Sprintf("Could not decode body: %v", err), http.StatusBadRequest)
return
}
data := base64.NewDecoder(base64.StdEncoding, strings.NewReader(pr.Message.Data))
var b cloudbuild.Build
err = json.NewDecoder(data).Decode(&b)
if err != nil {
log.Error(err)
http.Error(w, err.Error(), http.StatusBadRequest)
}
if notifyBuild(&b) {
err = slackPostMessage(&b)
if err != nil {
log.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}
func notifyBuild(b *cloudbuild.Build) bool {
switch b.Status {
case
"EXPIRED",
"FAILURE",
"INTERNAL_ERROR",
"SUCCESS",
"TIMEOUT":
return true
}
return false
}
func slackPostMessage(b *cloudbuild.Build) error {
api := slack.New(config.SlackToken)
trigger := b.Substitutions["TRIGGER_NAME"]
commit := b.Substitutions["SHORT_SHA"]
repo := b.Substitutions["REPO_NAME"]
branch := b.Substitutions["BRANCH_NAME"]
srcText := ":github: Source"
var srcLink string
// GitHub Repo
if repo != "" {
srcLink = fmt.Sprintf("https://github.com/Tocaboca/%s/commit/%s", repo, commit)
repo = fmt.Sprintf("<%s|%s:%s@%s>", srcLink, repo, branch, commit)
repo = " for " + repo
}
var message string
if b.Status == "SUCCESS" {
message = fmt.Sprintf(":large_green_circle: *%s* successful%s :rocket:", trigger, repo)
} else {
message = fmt.Sprintf(
":octagonal_sign: *%s* failed with status `%s` %s",
trigger,
b.Status,
repo,
)
if b.FailureInfo != nil {
message = fmt.Sprintf("%s\n```%s```", message, b.FailureInfo.Detail)
}
}
headerText := slack.NewTextBlockObject("mrkdwn", message, false, false)
headerSection := slack.NewSectionBlock(headerText, nil, nil)
logsBtn := &slack.ButtonBlockElement{
Type: slack.METButton,
Text: slack.NewTextBlockObject("plain_text", ":mag: View Logs", false, false),
URL: b.LogUrl,
Value: "view_logs",
}
srcBtn := &slack.ButtonBlockElement{
Type: slack.METButton,
Text: slack.NewTextBlockObject("plain_text", srcText, true, false),
URL: srcLink,
Value: "view_src",
}
actions := []slack.BlockElement{
logsBtn,
srcBtn,
}
pr := b.Substitutions["_PR_NUMBER"]
if pr != "" {
prBtn := &slack.ButtonBlockElement{
Type: slack.METButton,
Text: slack.NewTextBlockObject("plain_text", ":github: Pull Request", true, false),
URL: fmt.Sprintf("https://github.com/Tocaboca/%s/pull/%s", b.Substitutions["REPO_NAME"], pr),
Value: "view_pr",
}
actions = append(actions, prBtn)
}
actionBlock := slack.NewActionBlock("", actions...)
_, _, err := api.PostMessage(config.SlackChannelID,
slack.MsgOptionBlocks(
headerSection,
actionBlock,
slack.NewDividerBlock(),
),
)
return err
}
func main() {
ctx := context.Background()
if err := envconfig.Process(ctx, &config); err != nil {
log.Fatal(err)
}
http.HandleFunc("/", receiveMessage)
log.Printf("Listening on port %s", config.Port)
if err := http.ListenAndServe(":"+config.Port, nil); err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment