Skip to content

Commit

Permalink
feat: add go code (#1)
Browse files Browse the repository at this point in the history
* feat: add go code

* add file header

* fix nits
  • Loading branch information
sailorlqh authored Jul 25, 2023
1 parent b129c4b commit 1c48c9a
Show file tree
Hide file tree
Showing 4 changed files with 505 additions and 0 deletions.
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/drevell/hackathon

go 1.20

require (
github.com/abcxyz/pkg v0.3.0
github.com/google/go-cmp v0.5.9
)

require (
github.com/kr/text v0.1.0 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
golang.org/x/sys v0.6.0 // indirect
)
31 changes: 31 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
github.com/abcxyz/pkg v0.3.0 h1:xygbuwjJBmiC90ieYXQyRdHqG8HzNrwgDrqMxojOsGE=
github.com/abcxyz/pkg v0.3.0/go.mod h1:9x3KtJ6HorqpFPHd0AyS4g72SMsbmvbVoJOno4+mhbg=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
github.com/goccy/go-json v0.10.1 h1:lEs5Ob+oOG/Ze199njvzHbhn6p9T+h64F5hRj69iTTo=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/jwx/v2 v2.0.8 h1:jCFT8oc0hEDVjgUgsBy1F9cbjsjAVZSXNi7JaU9HR/Q=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
240 changes: 240 additions & 0 deletions src/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// Copyright 2023 The Authors (see AUTHORS file)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/abcxyz/pkg/cli"
)

const (
githubContextEnv = "GITHUB_CONTEXT"
jobContextEnv = "JOB_CONTEXT"
)

var rootCmd = func() cli.Command {
return &cli.RootCommand{
Name: "send-google-chat-webhook",
Commands: map[string]cli.CommandFactory{
"chat": func() cli.Command {
return &cli.RootCommand{
Name: "workflownotification",
Description: "notification for workflow",
Commands: map[string]cli.CommandFactory{
"workflownotification": func() cli.Command {
return &WorkflowNotificationCommand{}
},
},
}
},
},
}
}

type WorkflowNotificationCommand struct {
cli.BaseCommand
flagWebhookUrl string
}

func (c *WorkflowNotificationCommand) Desc() string {
return "Send a message to a Google Chat space"
}

func (c *WorkflowNotificationCommand) Help() string {
return `
Usage: {{ COMMAND }} [options]
The chat command sends messages to Google Chat spaces.
`
}

func (c *WorkflowNotificationCommand) Flags() *cli.FlagSet {
set := cli.NewFlagSet()

f := set.NewSection("Chat space options")

f.StringVar(&cli.StringVar{
Name: "webhook-url",
Example: "https://chat.googleapis.com/v1/spaces/<SPACE_ID>/messages?key=<KEY>&token=<TOKEN>",
Default: "",
Target: &c.flagWebhookUrl,
Usage: `Webhook URL from google chat`,
})

return set
}

func (c *WorkflowNotificationCommand) Run(ctx context.Context, args []string) error {
f := c.Flags()
if err := f.Parse(args); err != nil {
return fmt.Errorf("failed to parse flags: %w", err)
}

args = f.Args()
if len(args) != 0 {
return fmt.Errorf("expected 0 arguments, got %q", args)
}

ghJsonStr := os.Getenv(githubContextEnv)
if ghJsonStr == "" {
return fmt.Errorf("environment var %s not set", githubContextEnv)
}
jobJsonStr := os.Getenv(jobContextEnv)
if jobJsonStr == "" {
return fmt.Errorf("environment var %s not set", jobContextEnv)
}

ghJson := map[string]any{}
jobJson := map[string]any{}
if err := json.Unmarshal([]byte(ghJsonStr), &ghJson); err != nil {
return fmt.Errorf("failed unmarshaling %s: %w", githubContextEnv, err)
}
if err := json.Unmarshal([]byte(jobJsonStr), &jobJson); err != nil {
return fmt.Errorf("failed unmarshaling %s: %w", jobContextEnv, err)
}

b, err := generateMessageBody(ghJson, jobJson, time.Now())
if err != nil {
return fmt.Errorf("failed to generate message body: %w", err)
}

url := c.flagWebhookUrl

request, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(b))
if err != nil {
return fmt.Errorf("creating http request failed: %w", err)
}

client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
return fmt.Errorf("sending http request failed: %w", err)
}
defer resp.Body.Close()

if got, want := resp.StatusCode, http.StatusOK; got != want {
return fmt.Errorf("unexpected HTTP status code %d (%s)", got, http.StatusText(got))
}

return nil
}

func main() {
ctx, done := signal.NotifyContext(context.Background(),
syscall.SIGINT, syscall.SIGTERM)
defer done()

if err := realMain(ctx); err != nil {
done()
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
}

func realMain(ctx context.Context) error {
return rootCmd().Run(ctx, os.Args[1:]) //nolint:wrapcheck // Want passthrough
}

func generateMessageBody(ghJson, jobJson map[string]any, timestamp time.Time) ([]byte, error) {
timezoneLoc, _ := time.LoadLocation("America/Los_Angeles")

var iconUrl string
switch jobJson["status"] {
case "success":
iconUrl = "/~https://github.githubassets.com/favicons/favicon.png"
default:
iconUrl = "/~https://github.githubassets.com/favicons/favicon-failure.png"
}

jsonData := map[string]any{
"cardsV2": map[string]any{
"cardId": "createCardMessage",
"card": map[string]any{
"header": map[string]any{
"title": fmt.Sprintf("GitHub workflow %s", jobJson["status"]),
"subtitle": fmt.Sprintf("Workflow: <b>%s</b>", ghJson["workflow"]),
"imageUrl": iconUrl,
},
"sections": []any{
map[string]any{
// "header": "This is the section header",
"collapsible": true,
"uncollapsibleWidgetsCount": 1,
"widgets": []map[string]any{
{
"decoratedText": map[string]any{
"startIcon": map[string]any{
"iconUrl": "https://fonts.gstatic.com/s/i/short-term/release/googlesymbols/quick_reference/default/48px.svg",
},
"text": fmt.Sprintf("<b>Ref:</b> %s", ghJson["ref"]),
},
},
{
"decoratedText": map[string]any{
"startIcon": map[string]any{
"knownIcon": "PERSON",
},
"text": fmt.Sprintf("<b>Run by:</b> %s", ghJson["triggering_actor"]),
},
},
{
"decoratedText": map[string]any{
"startIcon": map[string]any{
"knownIcon": "CLOCK",
},
"text": fmt.Sprintf("<b>Pacific:</b> %s", timestamp.In(timezoneLoc).Format(time.DateTime)),
},
},
{
"decoratedText": map[string]any{
"startIcon": map[string]any{
"knownIcon": "CLOCK",
},
"text": fmt.Sprintf("<b>UTC:</b> %s", timestamp.UTC().Format(time.DateTime)),
},
},
{
"buttonList": map[string]any{
"buttons": []any{
map[string]any{
"text": "Open",
"onClick": map[string]any{
"openLink": map[string]any{
"url": fmt.Sprintf("/~https://github.com/%s/actions/runs/%s",
ghJson["repository"], ghJson["run_id"]),
},
},
},
},
},
},
},
},
},
},
},
}

return json.Marshal(jsonData)
}
Loading

0 comments on commit 1c48c9a

Please sign in to comment.