272 lines
6.4 KiB
Go
272 lines
6.4 KiB
Go
// Package googlechat provides functionality to send google chat notifications
|
|
package googlechat
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/sasha-s/go-deadlock"
|
|
|
|
"quantex.com/qfixdpl/src/app/version"
|
|
common "quantex.com/qfixdpl/src/client/notify"
|
|
"quantex.com/qfixdpl/src/common/logger"
|
|
"quantex.com/qfixdpl/src/common/tracerr"
|
|
"quantex.com/qfixdpl/src/domain"
|
|
)
|
|
|
|
//revive:disable:line-length-limit It's just links
|
|
|
|
type SpaceID string
|
|
|
|
// spaces URLs
|
|
const googleChatURL = "https://chat.googleapis.com/v1/spaces/"
|
|
|
|
type Notify struct {
|
|
spaces map[domain.MessageChannel]SpaceID
|
|
messages map[string]time.Time
|
|
m deadlock.Mutex
|
|
}
|
|
|
|
//nolint:lll // It's just a link
|
|
func New(chls domain.Channels) *Notify {
|
|
if err := common.ValidateChannelConfig(chls); err != nil {
|
|
panic(tracerr.Errorf("google config error: %w", err))
|
|
}
|
|
|
|
messages := make(map[string]time.Time)
|
|
spaces := make(map[domain.MessageChannel]SpaceID)
|
|
|
|
spaces[domain.MessageChannelTest] = SpaceID(chls.Test)
|
|
spaces[domain.MessageChannelWeb] = SpaceID(chls.Web)
|
|
spaces[domain.MessageChannelPanic] = SpaceID(chls.Panic)
|
|
spaces[domain.MessageChannelError] = SpaceID(chls.Error)
|
|
|
|
return &Notify{
|
|
spaces: spaces,
|
|
messages: messages,
|
|
}
|
|
}
|
|
|
|
func (g *Notify) Example() {
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(3)
|
|
|
|
go g.SendMsg(domain.MessageChannelTest, "Hello World!", domain.MessageStatusGood, &wg)
|
|
go g.SendMsg(domain.MessageChannelTest, "Error", domain.MessageStatusStopper, &wg)
|
|
go g.SendMsg(domain.MessageChannelTest, "Warning", domain.MessageStatusWarning, &wg)
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func (g *Notify) SendMsg(chat domain.MessageChannel, text string, status domain.MessageStatus, waitgroup *sync.WaitGroup) {
|
|
var space SpaceID
|
|
|
|
if s, ok := g.spaces[chat]; !ok {
|
|
space = g.spaces[domain.MessageChannelError]
|
|
|
|
err := tracerr.Errorf("error sending google notification, there's no space id for chat: %s", chat)
|
|
|
|
logger.ErrorWoNotifier("%v", err.Error())
|
|
} else {
|
|
space = s
|
|
}
|
|
|
|
s := fmt.Sprintf("%s-%s-%s", text, status.String(), string(space))
|
|
|
|
if ok := g.shouldSendMessage(s); ok {
|
|
go common.SendWithRetry(text, status, waitgroup, googleChatURL+string(space), getMessage)
|
|
}
|
|
}
|
|
|
|
//nolint:lll // It's just a link
|
|
const (
|
|
errImg = "https://media.gettyimages.com/id/1359003186/es/foto/illustration-of-a-tick-or-an-x-indicating-right-and-wrong.jpg?s=612x612&w=0&k=20&c=FdYIXI1qCPpVcY6phcIJom8sRhZw4hgYVtwOC6lNlmA="
|
|
goodImg = "https://media.gettyimages.com/id/1352723074/es/foto/drawing-of-green-tick-check-mark.jpg?s=612x612&w=0&k=20&c=RgoCJ-n0OpZSClCWhJstoXAfiGrBZhnggUvOp2ooJqI="
|
|
warnImg = "https://media.gettyimages.com/id/1407160246/es/vector/icono-de-tri%C3%A1ngulo-de-peligro.jpg?s=612x612&w=0&k=20&c=rI0IYmZmkg62txtNQFhyTbHx5oW311_OupBkbWODfjg="
|
|
)
|
|
|
|
func getImage(status domain.MessageStatus) (string, error) {
|
|
switch status {
|
|
case domain.MessageStatusGood:
|
|
return goodImg, nil
|
|
case domain.MessageStatusWarning:
|
|
return warnImg, nil
|
|
case domain.MessageStatusStopper:
|
|
return errImg, nil
|
|
}
|
|
|
|
return "", tracerr.Errorf("unknown Message Status Type")
|
|
}
|
|
|
|
func getMessage(text string, status domain.MessageStatus) string {
|
|
image, err := getImage(status)
|
|
if err != nil {
|
|
err = tracerr.Errorf("error getting image at getMessage, error: %w", err)
|
|
|
|
logger.ErrorWoNotifier("%v", err.Error())
|
|
|
|
var e error
|
|
|
|
image, e = getImage(domain.MessageStatusWarning)
|
|
if e != nil {
|
|
e = tracerr.Errorf("error getting image at getMessage, error: %w", e)
|
|
|
|
logger.ErrorWoNotifier("%v", e.Error())
|
|
}
|
|
}
|
|
|
|
env := version.Environment()
|
|
appVersion := version.Base()
|
|
|
|
text = strings.ReplaceAll(text, "'", "\"")
|
|
|
|
msg := `
|
|
{
|
|
'cardsV2': [{
|
|
'cardId': 'createCardMessage',
|
|
'card': {
|
|
'header': {
|
|
'title': 'qfixdpl',
|
|
'subtitle': 'Notification',
|
|
'imageUrl': '%s',
|
|
'imageType': 'CIRCLE'
|
|
},
|
|
'sections': [
|
|
{
|
|
'widgets':[
|
|
{
|
|
'textParagraph': {
|
|
'text': '<b>Environment:</b> %s'
|
|
}
|
|
},
|
|
{
|
|
'textParagraph': {
|
|
'text': '<b>Message:</b> %s'
|
|
}
|
|
},
|
|
{
|
|
'textParagraph': {
|
|
'text': '<b>Build:</b> %s'
|
|
}
|
|
},
|
|
{
|
|
'textParagraph': {
|
|
'text': '<b>Time:</b> %s'
|
|
}
|
|
},
|
|
{
|
|
'textParagraph': {
|
|
'text': '<b>Hostname:</b> %s'
|
|
}
|
|
},
|
|
{
|
|
'textParagraph': {
|
|
'text': '<b>IP:</b> %s'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}]
|
|
}`
|
|
|
|
loc, err := time.LoadLocation("America/Argentina/Buenos_Aires")
|
|
if err != nil {
|
|
err := tracerr.Errorf("error loading timezone %v, setting default UTC", err)
|
|
|
|
logger.ErrorWoNotifier("%v", err.Error())
|
|
|
|
loc = time.UTC
|
|
}
|
|
|
|
now := time.Now().In(loc).Format("15:04:05 02-01-2006 -0700")
|
|
jsonMsg := fmt.Sprintf(msg, image, env, text, appVersion, now,
|
|
getHostname(), getOutboundIP())
|
|
|
|
return jsonMsg
|
|
}
|
|
|
|
func (g *Notify) shouldSendMessage(s string) (ok bool) {
|
|
sha, err := generateShaString(s)
|
|
if err != nil {
|
|
err = tracerr.Errorf("error generating sha string at shouldSendMessage, error: %w", err)
|
|
|
|
logger.ErrorWoNotifier("%v", err.Error())
|
|
|
|
return true
|
|
}
|
|
|
|
g.m.Lock()
|
|
defer g.m.Unlock()
|
|
|
|
if t, ok := g.messages[sha]; ok && time.Since(t) < time.Minute {
|
|
return false
|
|
}
|
|
|
|
g.messages[sha] = time.Now()
|
|
|
|
return true
|
|
}
|
|
|
|
func generateShaString(s string) (sha string, err error) {
|
|
hash := sha256.New()
|
|
if _, err := hash.Write([]byte(s)); err != nil {
|
|
err = tracerr.Errorf("error generating hash at generateShaString, error: %w", err)
|
|
logger.ErrorWoNotifier("%v", err.Error())
|
|
|
|
return sha, err
|
|
}
|
|
|
|
return hex.EncodeToString(hash.Sum(nil)), nil
|
|
}
|
|
|
|
//revive:enable:line-length-limit
|
|
|
|
//nolint:gochecknoglobals // need to be global
|
|
var outboundIP string
|
|
|
|
func getOutboundIP() string {
|
|
if outboundIP == "" {
|
|
conn, err := net.Dial("udp", "8.8.8.8:80")
|
|
if err != nil {
|
|
logger.ErrorWoNotifier("failed to get UDP address")
|
|
|
|
return ""
|
|
}
|
|
|
|
defer func(conn net.Conn) {
|
|
err = conn.Close()
|
|
if err != nil {
|
|
logger.ErrorWoNotifier("error closing net connection")
|
|
}
|
|
}(conn)
|
|
|
|
localAddr, ok := conn.LocalAddr().(*net.UDPAddr)
|
|
if !ok {
|
|
logger.ErrorWoNotifier("error: conn.LocalAddr() is not a *net.UDPAddr type")
|
|
}
|
|
|
|
outboundIP = localAddr.IP.String()
|
|
}
|
|
|
|
return outboundIP
|
|
}
|
|
|
|
func getHostname() string {
|
|
hostname, err := os.Hostname()
|
|
if err != nil {
|
|
logger.ErrorWoNotifier("failed to get hostname")
|
|
|
|
return ""
|
|
}
|
|
|
|
return hostname
|
|
}
|