Skip to content

Commit

Permalink
🚀 support googlechat
Browse files Browse the repository at this point in the history
  • Loading branch information
abahmed committed May 29, 2024
1 parent c837b83 commit 58f6b33
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,19 @@ If you want to enable Zenduty, provide IntegrationKey with optional alert type
| `alert.zenduty.integrationKey` | Zenduty integration Key |
| `alert.zenduty.alertType` | Optional alert type of incident: critical, acknowledged, resolved, error, warning, info (default: critical) |

#### Google Chat

<p>
<img src="./assets/googlechat.png" width="50%"/>
</p>

If you want to enable Rocket Chat, provide the webhook with optional text

| Parameter | Description |
|:---------------------------|:---------------------------------------|
| `alert.googlechat.webhook` | Google Chat webhook URL |
| `alert.rocketchat.text` | Customized text in Google Chat message |

#### Custom webhook

If you want to enable custom webhook, provide url with optional headers and
Expand Down
3 changes: 3 additions & 0 deletions alertmanager/alertmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/abahmed/kwatch/alertmanager/discord"
"github.com/abahmed/kwatch/alertmanager/email"
"github.com/abahmed/kwatch/alertmanager/feishu"
"github.com/abahmed/kwatch/alertmanager/googlechat"
"github.com/abahmed/kwatch/alertmanager/matrix"
"github.com/abahmed/kwatch/alertmanager/mattermost"
"github.com/abahmed/kwatch/alertmanager/opsgenie"
Expand Down Expand Up @@ -70,6 +71,8 @@ func (a *AlertManager) Init(
pvdr = webhook.NewWebhook(v, appCfg)
} else if lowerCaseKey == "zenduty" {
pvdr = zenduty.NewZenduty(v, appCfg)
} else if lowerCaseKey == "googlechat" {
pvdr = googlechat.NewGoogleChat(v, appCfg)
}

if !reflect.ValueOf(pvdr).IsNil() {
Expand Down
3 changes: 3 additions & 0 deletions alertmanager/alertmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ func TestGetProviders(t *testing.T) {
"zenduty": {
"integrationKey": "test",
},
"googlechat": {
"webhook": "test",
},
}

alertmanager := AlertManager{}
Expand Down
96 changes: 96 additions & 0 deletions alertmanager/googlechat/googlechat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package googlechat

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/abahmed/kwatch/config"
"github.com/abahmed/kwatch/event"
"github.com/sirupsen/logrus"
)

type GoogleChat struct {
webhook string
text string

// reference for general app configuration
appCfg *config.App
}

type payload struct {
Text string `json:"text"`
}

// NewGoogleChat returns new google chat instance
func NewGoogleChat(config map[string]interface{}, appCfg *config.App) *GoogleChat {
webhook, ok := config["webhook"].(string)
if !ok || len(webhook) == 0 {
logrus.Warnf("initializing Google Chat with empty webhook url")
return nil
}

logrus.Infof("initializing Google Chat with webhook url: %s", webhook)

text, _ := config["text"].(string)

return &GoogleChat{
webhook: webhook,
text: text,
appCfg: appCfg,
}
}

// Name returns name of the provider
func (r *GoogleChat) Name() string {
return "Google Chat"
}

// SendEvent sends event to the provider
func (r *GoogleChat) SendEvent(e *event.Event) error {
formattedMsg := e.FormatText(r.appCfg.ClusterName, r.text)
return r.sendAPI(r.buildRequestBody(formattedMsg))
}

func (r *GoogleChat) sendAPI(reqBody string) error {
client := &http.Client{}
buffer := bytes.NewBuffer([]byte(reqBody))
request, err := http.NewRequest(http.MethodPost, r.webhook, buffer)
if err != nil {
return err
}

request.Header.Set("Content-Type", "application/json")

response, err := client.Do(request)
if err != nil {
return err
}
defer response.Body.Close()

if response.StatusCode != 200 {
body, _ := io.ReadAll(response.Body)
return fmt.Errorf(
"call to google chat alert returned status code %d: %s",
response.StatusCode,
string(body))
}

return nil
}

// SendMessage sends text message to the provider
func (r *GoogleChat) SendMessage(msg string) error {
return r.sendAPI(r.buildRequestBody(msg))
}

func (r *GoogleChat) buildRequestBody(text string) string {
msgPayload := &payload{
Text: text,
}

jsonBytes, _ := json.Marshal(msgPayload)
return string(jsonBytes)
}
116 changes: 116 additions & 0 deletions alertmanager/googlechat/googlechat_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package googlechat

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/abahmed/kwatch/config"
"github.com/abahmed/kwatch/event"
"github.com/stretchr/testify/assert"
)

func TestEmptyConfig(t *testing.T) {
assert := assert.New(t)

c := NewGoogleChat(map[string]interface{}{}, &config.App{ClusterName: "dev"})
assert.Nil(c)
}

func TestGoogleChat(t *testing.T) {
assert := assert.New(t)

configMap := map[string]interface{}{
"webhook": "testtest",
}
c := NewGoogleChat(configMap, &config.App{ClusterName: "dev"})
assert.NotNil(c)

assert.Equal(c.Name(), "Google Chat")
}

func TestSendMessage(t *testing.T) {
assert := assert.New(t)

s := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"isOk": true}`))
}))

defer s.Close()

configMap := map[string]interface{}{
"webhook": s.URL,
}
c := NewGoogleChat(configMap, &config.App{ClusterName: "dev"})
assert.NotNil(c)

assert.Nil(c.SendMessage("test"))
}

func TestSendMessageError(t *testing.T) {
assert := assert.New(t)

s := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadGateway)
}))

defer s.Close()

configMap := map[string]interface{}{
"webhook": s.URL,
}
c := NewGoogleChat(configMap, &config.App{ClusterName: "dev"})
assert.NotNil(c)

assert.NotNil(c.SendMessage("test"))
}

func TestSendEvent(t *testing.T) {
assert := assert.New(t)

s := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"isOk": true}`))
}))

defer s.Close()

configMap := map[string]interface{}{
"webhook": s.URL,
}
c := NewGoogleChat(configMap, &config.App{ClusterName: "dev"})
assert.NotNil(c)

ev := event.Event{
Name: "test-pod",
Container: "test-container",
Namespace: "default",
Reason: "OOMKILLED",
Logs: "test\ntestlogs",
Events: "event1-event2-event3-event1-event2-event3-event1-event2-" +
"event3\nevent5\nevent6-event8-event11-event12",
}
assert.Nil(c.SendEvent(&ev))
}

func TestInvaildHttpRequest(t *testing.T) {
assert := assert.New(t)

configMap := map[string]interface{}{
"webhook": "h ttp://localhost",
}
c := NewGoogleChat(configMap, &config.App{ClusterName: "dev"})
assert.NotNil(c)

assert.NotNil(c.SendMessage("test"))

configMap = map[string]interface{}{
"webhook": "http://localhost:132323",
}
c = NewGoogleChat(configMap, &config.App{ClusterName: "dev"})
assert.NotNil(c)

assert.NotNil(c.SendMessage("test"))
}
Binary file added assets/googlechat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions event/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,45 @@ func (e *Event) FormatHtml(clusterName, text string) string {

return msg
}

func (e *Event) FormatText(clusterName, text string) string {
eventsText := constant.DefaultEvents
logsText := constant.DefaultLogs

// add events part if it exists
events := strings.TrimSpace(e.Events)
if len(events) > 0 {
eventsText = e.Events
}

// add logs part if it exists
logs := strings.TrimSpace(e.Logs)
if len(logs) > 0 {
logsText = e.Logs
}

// use custom text if it's provided, otherwise use default
if len(text) == 0 {
text = constant.DefaultText
}

msg := fmt.Sprintf(
"There is an issue with container in a pod!\n\n"+
"cluster: %s\n"+
"Pod Name: %s\n"+
"Container: %s\n"+
"Namespace: %s\n"+
"Reason: %s\n\n"+
"Events:\n%s\n\n"+
"Logs:\n%s\n\n",
clusterName,
e.Name,
e.Container,
e.Namespace,
e.Reason,
eventsText,
logsText,
)

return msg
}

0 comments on commit 58f6b33

Please sign in to comment.