Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ kubectl apply -f https://raw.githubusercontent.com/abahmed/kwatch/v0.0.4/deploy/
| `providers.slack.webhook` | Slack webhook URL | Yes |
| `providers.slack.title` | Customized title in slack message | No |
| `providers.slack.text` | Customized text in slack message | No |
| `providers.discord.webhook` | Discord webhook URL | Yes |
| `providers.discord.title` | Customized title in discord message | No |
| `providers.discord.text` | Customized text in discord message | No |
| `providers.pagerduty.integrationKey` | PagerDuty integration key [more info](https://support.pagerduty.com/docs/services-and-integrations) | Yes |

Each provider can be enabled or disabled separately using the boolean property `enabled`.

### Cleanup

Expand Down
5 changes: 5 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
maxRecentLogLines: 0
providers:
slack:
enabled: true
webhook: ""
pagerduty:
enabled: true
integrationKey: ""
discord:
enabled: true
webhook: ""
2 changes: 1 addition & 1 deletion controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func Start() {
indexer: indexer,
queue: queue,
kclient: kclient,
providers: []provider.Provider{provider.NewSlack(), provider.NewDiscord()},
providers: util.GetProviders(),
}

stopCh := make(chan struct{})
Expand Down
4 changes: 1 addition & 3 deletions provider/discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ type discord struct {
}

// NewDiscord returns new Discord object
func NewDiscord() Provider {
url := viper.GetString("providers.discord.webhook")

func NewDiscord(url string) Provider {
if len(url) == 0 {
logrus.Warnf("initializing discord with empty webhook url")
} else {
Expand Down
117 changes: 117 additions & 0 deletions provider/pagerduty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package provider

import (
"bytes"
"errors"
"fmt"
"net/http"
"strings"

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

const (
pagerdutyAPIURL = "https://events.pagerduty.com/v2/enqueue"
defaultEventTitle = "[%s] There is an issue with a container in a pod"
)

type pagerduty struct {
integrationKey string
}

func NewPagerDuty(integrationKey string) Provider {
if len(integrationKey) == 0 {
logrus.Warnf("initializing pagerduty with an empty integration key")
} else {
logrus.Infof("initializing pagerduty with the provided integration key")
}

return &pagerduty{
integrationKey: integrationKey,
}
}

// Name returns name of the provider
func (s *pagerduty) Name() string {
return "PagerDuty"
}

// SendEvent sends event to the provider
func (s *pagerduty) SendEvent(ev *event.Event) error {
logrus.Debugf("sending to pagerduty event: %v", ev)

if len(s.integrationKey) == 0 {
return errors.New("integration key is empty")
}

client := &http.Client{}

reqBody := buildRequestBody(ev, s.integrationKey)
buffer := bytes.NewBuffer([]byte(reqBody))

request, err := http.NewRequest(http.MethodPost, pagerdutyAPIURL, buffer)
if err != nil {
return err
}

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

response, err := client.Do(request)
if err != nil || response.StatusCode > 202 {
return err
}

return nil
}

// SendMessage sends text message to the provider
func (s *pagerduty) SendMessage(msg string) error {
return nil
}

func buildRequestBody(ev *event.Event, key string) string {
eventsText := "No events captured"
logsText := "No logs captured"

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

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

reqBody := fmt.Sprintf(`{
"routing_key": "%s",
"event_action": "trigger",
"payload": {
"summary": "%s",
"source": "%s",
"severity": "critical",
"custom_details": {
"Name": "%s",
"Container": "%s",
"Namespace": "%s",
"Reason": "%s",
"Events": "%s",
"Logs": "%s"
}
}
}`,
key,
fmt.Sprintf(defaultEventTitle, ev.Container),
ev.Container,
ev.Name,
ev.Container,
ev.Namespace,
ev.Reason,
eventsText,
logsText)

return reqBody
}
4 changes: 1 addition & 3 deletions provider/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ type slack struct {
}

// NewSlack returns new Slack object
func NewSlack() Provider {
url := viper.GetString("providers.slack.webhook")

func NewSlack(url string) Provider {
if len(url) == 0 {
logrus.Warnf("initializing slack with empty webhook url")
} else {
Expand Down
23 changes: 23 additions & 0 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"strings"

"github.com/abahmed/kwatch/provider"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -90,3 +91,25 @@ func getPodEvents(c kubernetes.Interface, name, namespace string) (*v1.EventList
FieldSelector: "involvedObject.name=" + name,
})
}

func GetProviders() []provider.Provider {
var providers []provider.Provider

for key, value := range viper.Get("providers").(map[string]interface{}) {
for e, b := range value.(map[string]interface{}) {
if e == "enabled" && b == true {
if key == "slack" {
providers = append(providers, provider.NewSlack(viper.GetString("providers.slack.webhook")))
}
if key == "pagerduty" {
providers = append(providers, provider.NewPagerDuty(viper.GetString("providers.pagerduty.integrationKey")))
}
if key == "discord" {
providers = append(providers, provider.NewDiscord(viper.GetString("providers.discord.webhook")))
}
}
}
}

return providers
}