Edited at

待望のCloud FunctionsのGo対応! Cloud Buildの通知をGoで実装!!


tl;dr


Cloud Functions Go

つい先日(2019/1/17)、Cloud FunctionsでGoが使えるようになりました:tada:

Cloud Functions: Go 1.11 is now a supported language | Google Cloud Blog

いまのところベータ版ですが、GCPのベータはプロダクションレベルなんでいろいろ使っていきたいですね。


Cloud Buildの通知サンプル

Cloud Buildの実行結果を、Cloud Functionsを使ってSlackに通知するサンプルがオフィシャルドキュメントにあります。

Configuring notifications for third-party services  |  Cloud Build Documentation  |  Google Cloud

Node.jsで書かれたものですが、こちらを今回Goで書き直してみました。


Go版のCloud Functions

ソースは以下です。1


function.go

package function

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"

"cloud.google.com/go/functions/metadata"
slack "github.com/ashwanthkumar/slack-go-webhook"
"github.com/pkg/errors"
"google.golang.org/api/cloudbuild/v1"
)

const SlackWebhookURL = "[SLACK_WEBHOOK]"

var (
projectID string
resource string
// Skip if the current status is not in the status list.
// Add additional statues to list if you'd like:
// QUEUED, WORKING, SUCCESS, FAILURE,
// INTERNAL_ERROR, TIMEOUT, CANCELLED
status = map[string]bool{
"SUCCESS": true,
"FAILURE": true,
"INTERNAL_ERROR": true,
"TIMEOUT": true,
}
)

func init() {
projectID = os.Getenv("GCP_PROJECT")
resource = fmt.Sprintf("projects/%s/topics/cloud-builds", projectID)
}

type PubSubMessage struct {
Data string `json:"data"`
}

// Subscribe is the main function called by Cloud Functions.
func Subscribe(ctx context.Context, m PubSubMessage) error {
meta, err := metadata.FromContext(ctx)
if err != nil {
return errors.Wrap(err, "Failed to get metadata")
}
if meta.Resource.Name != resource {
fmt.Printf("%s is not wathing resource\n", meta.Resource.Name)
return nil
}

build, err := eventToBuild(m.Data)
if err != nil {
return errors.Wrap(err, "Failed to decode event data")
}

if _, ok := status[build.Status]; !ok {
fmt.Printf("%s status is skipped\n", build.Status)
return nil
}

// Send message to Slack.
message := createSlackMessage(build)
errs := slack.Send(SlackWebhookURL, "", message)
if len(errs) > 0 {
return errors.Errorf("Failed to send a message to Slack: %s", errs)
}

return nil
}

// eventToBuild transforms pubsub event message to a Build struct.
func eventToBuild(data string) (*cloudbuild.Build, error) {
d, err := base64.StdEncoding.DecodeString(data)
if err != nil {
return nil, errors.Wrap(err, "Failed to decode base64 data")
}

build := cloudbuild.Build{}
err = json.Unmarshal(d, &build)
if err != nil {
return nil, errors.Wrap(err, "Failed to decode to JSON")
}
return &build, nil
}

// createSlackMessage creates a message from a build object.
func createSlackMessage(build *cloudbuild.Build) slack.Payload {
title := "Build Logs"
a := slack.Attachment{
Title: &title,
TitleLink: &build.LogUrl,
}
a.AddField(slack.Field{
Title: "Status",
Value: build.Status,
})
p := slack.Payload{
Text: fmt.Sprintf("Build `%s`", build.Id),
Markdown: true,
Attachments: []slack.Attachment{a},
}
return p
}


追い易いように元のNode.jsのソースのコメントほぼそのまま載せました。

Node.js版をみるとわかるように、Cloud Buildから渡ってくるPubSub Messageはdataフィールドをもっており、その中身はJSONをbase64エンコードしたものになっています。

ここからデータを取り出すために、Goでもbase64デコード→JSONデコードの処理をeventToBuildで実装しています。

ビルドデータの構造体はGoogleのcloudbuild.v1パッケージのBuild構造体で定義されているものを使っています。

statusをチェックしているところは、NodeだとArray.prototype.indexOfを使っていますが、Goのスライスだとそういうメソッドはなく、プリミティブにforを書くことになるので、mapのキーとして有効なstatusを定義しました。

SlackにInCommingWebhook経由で通知する部分は、ashwanthkumar/slack-go-webhook2を使いました。

これで以下のようなコマンドでデプロイすれば使えるはず…です!3

$ gcloud functions deploy Subscribe --runtime go111 \

--trigger-topic cloud-builds





  1. ソースはGithubにもあげてあります。 



  2. パッと調べた限りこれ以外ライブラリなさそうだった 



  3. 実際にこのソースで試してはないですが、いまやっているプロジェクトでは似たようなコードで動いています。