Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added notification support for discord #157

Merged
merged 24 commits into from
Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f891a4b
added notification support for discord
wryonik Jun 9, 2019
1edf4d8
removed changes from go.sum
wryonik Jun 9, 2019
fedfd99
changed discord icon
wryonik Jun 10, 2019
812cdc0
tried to improve notification structure
wryonik Jun 14, 2019
eed16a0
tried to implement interface for notifier
wryonik Jun 15, 2019
4c1f818
tried to implement interface for notifier. Some errors are still ther…
wryonik Jun 15, 2019
3ee5816
tried to implement interface for notifier. Some errors are still ther…
wryonik Jun 15, 2019
e946b1f
solved some errors. Some remains with sending payload
wryonik Jun 18, 2019
7e19304
enumeration of notification providers
wryonik Jun 19, 2019
7dea82c
completed notification for discord
wryonik Jun 20, 2019
5fae9bf
completed notification for discord
wryonik Jun 20, 2019
e7b4349
removed changes from go.sum
wryonik Jun 20, 2019
4a840a4
Review changes fixed
wryonik Aug 3, 2019
c485521
Merge branch 'master' into added_more_notification_channels
wryonik Aug 3, 2019
2603ae6
Formatting errors fixed
wryonik Aug 3, 2019
4b5eb5e
Changed notification functions such that now both the notification fo…
wryonik Aug 4, 2019
e34c8b3
Minor review changes
wryonik Aug 5, 2019
c7b7ac6
Minor review changes
wryonik Aug 26, 2019
d13953b
Minor changes
wryonik Sep 1, 2019
681cead
Review changes fixed
wryonik Sep 25, 2019
d091dbf
Removed discord test server link from example.config.toml
wryonik Sep 27, 2019
6215179
Some changes fixed
wryonik Oct 9, 2019
ee0bbdb
Minor changes fixes
wryonik Oct 10, 2019
6e5fd76
Documentation update
wryonik Oct 10, 2019
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 _examples/example.config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ ssh_key = "/home/vsts/.beast/secret.key"
url = "[email protected]:sdslabs/nonexistent.git"
name = "nonexistent"
branch = "nonexistent"

[[webhooks]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add example for slack too.
Change the field status to active

url = ""
service_name= "discord"
active=true
23 changes: 14 additions & 9 deletions core/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,14 @@ import (
// ssh_key = "/home/fristonio/.beast/secrets/key.priv"
// ```
type BeastConfig struct {
AuthorizedKeysFile string `toml:"authorized_keys_file"`
BeastScriptsDir string `toml:"scripts_dir"`
AllowedBaseImages []string `toml:"allowed_base_images"`
AvailableSidecars []string `toml:"available_sidecars"`
GitRemote GitRemote `toml:"remote"`
JWTSecret string `toml:"jwt_secret"`
SlackWebHookURL string `toml:"slack_webhook"`

TickerFrequency int `toml:"ticker_frequency"`
AuthorizedKeysFile string `toml:"authorized_keys_file"`
BeastScriptsDir string `toml:"scripts_dir"`
AllowedBaseImages []string `toml:"allowed_base_images"`
AvailableSidecars []string `toml:"available_sidecars"`
GitRemote GitRemote `toml:"remote"`
JWTSecret string `toml:"jwt_secret"`
NotificationWebhooks []NotificationWebhook `toml:webhook`
TickerFrequency int `toml:"ticker_frequency"`

RemoteSyncPeriod time.Duration `toml:"-"`
Rsp string `toml:"remote_sync_period"`
Expand Down Expand Up @@ -216,6 +215,12 @@ func (config *GitRemote) ValidateGitConfig() error {
return nil
}

type NotificationWebhook struct {
URL string `toml:"url"`
ServiceName string `toml:"service_name"`
Active bool `toml:"active"`
}

// From the path of the config file provided as an arguement this function
// loads the parse the config file and load it into the BeastConfig
// structure. After parsing it validates the data in the config file and returns
Expand Down
6 changes: 3 additions & 3 deletions core/manager/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,14 +523,14 @@ func StartUndeployChallenge(challengeName string, purge bool) error {
if err != nil {
msg := fmt.Sprintf("UNDEPLOY ERROR: %s : %s", challengeName, err)
log.Error(msg)
notify.SendNotificationToSlack(notify.Error, msg)
notify.SendNotification(notify.Error, msg)
} else {
msg := fmt.Sprintf("UNDEPLOY SUCCESSFUL: %s", challengeName)
log.Info(msg)
notify.SendNotificationToSlack(notify.Success, msg)
notify.SendNotification(notify.Success, msg)
}

log.Infof("Notification for the event sent to slack.")
log.Info("Notification for the event sent.")
return err
}

Expand Down
2 changes: 1 addition & 1 deletion core/manager/health_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func ChallengesHealthProber(waitTime int) {
log.WithFields(log.Fields{
"ChallName": chall.Name,
}).Error(msg)
notify.SendNotificationToSlack(notify.Error, msg)
notify.SendNotification(notify.Error, msg)
} else {
log.WithFields(log.Fields{
"ChallName": chall.Name,
Expand Down
6 changes: 3 additions & 3 deletions core/manager/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,11 @@ func StartDeployPipeline(challengeDir string, skipStage bool, skipCommit bool) {

err := bootstrapDeployPipeline(challengeDir, skipStage, skipCommit)
if err != nil {
notify.SendNotificationToSlack(notify.Error, err.Error())
notify.SendNotification(notify.Error, err.Error())
} else {
msg := fmt.Sprintf("DEPLOY SUCCESS : %s : Challenge deployment pipeline successful.", challengeName)
notify.SendNotificationToSlack(notify.Success, msg)
notify.SendNotification(notify.Success, msg)
}

log.Debugf("%s: Notification sent to slack", challengeName)
log.Debugf("%s: Notification sent", challengeName)
}
13 changes: 11 additions & 2 deletions docs/Setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,18 @@ jwt_secret = "beast_jwt_secret_SUPER_STRONG_0x100010000100"


# To allow beast to send notification to a notification channel povide this webhook URL
# We are also working on implmeneting notification using Discord and IRC.
slack_webhook = ""
# We are also working on implmeneting notification using IRC.
[[webhooks]]

# The webhook URL of notification channel where notification should be sent
url = ""

# The service name to be used. It can be `discord` and `slack`
service_name = "discord"

# Status of webhook URL to be used.
# If it is false then notification will not be sent on this URL
active = true

# The sidecar that we support with beast, currently we only support two MySQL and
# MongoDB.
Expand Down
6 changes: 6 additions & 0 deletions pkg/notify/notification.go → pkg/notify/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ const (
WarningColor NotificationColor = "#FF4500"
SuccessColor NotificationColor = "#32CD32"
)

const (
USERNAME string = "Beast"
ICON_URL string = "https://i.ibb.co/sjC5dRY/beast-eye-39371.png"
CHANNEL_NAME string = "#beast"
)
68 changes: 68 additions & 0 deletions pkg/notify/discord.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package notify

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)

type DiscordNotificationProvider struct {
Request
}

func (d *DiscordNotificationProvider) SendNotification(nType NotificationType, msg string) error {
if d.Request.WebHookURL == "" {
return fmt.Errorf("Need a WebHookURL to send notification.")
}

d.Request.FillReqParams()

nAttachment := Attachment{
AuthorName: "Beast Notifier",
AuthorLink: "https://backdoor.sdslabs.co",
Footer: "Beast Discord API",
FooterIcon: "https://discordapp.com/assets/e05ead6e6ebc08df9291738d0aa6986d.png",
Timestamp: time.Now().Unix(),
Text: msg,
}

switch nType {
case Success:
nAttachment.Color = SuccessColor
nAttachment.Title = "Beast Deployment Success"
break
case Error:
nAttachment.Color = ErrorColor
nAttachment.Title = "Beast Deployment Error"
break
}

d.Request.PostPayload.Attachments = []Attachment{nAttachment}

if d.Request.PostPayload.Channel == "" || d.Request.PostPayload.Username == "" {
return fmt.Errorf("Username and Channel required to send the notification.")
}

payload, err := json.Marshal(d.PostPayload)
if err != nil {
return fmt.Errorf("Error while converting payload to JSON : %s", err)
}

payloadReader := bytes.NewReader(payload)
req, err := http.NewRequest("POST", d.Request.WebHookURL, payloadReader)
if err != nil {
return fmt.Errorf("Error while connecting to webhook url host : %s", err)
}

req.Header.Set("Content-Type", "application/json")
client := http.Client{}
_, err = client.Do(req)

if err != nil {
return fmt.Errorf("Error while posting payload for notification : %s", err)
}

return nil
}
116 changes: 116 additions & 0 deletions pkg/notify/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package notify

import (
"fmt"
"net/url"

"github.com/sdslabs/beastv4/core/config"
log "github.com/sirupsen/logrus"
)

type Attachment struct {
Fallback string `json:"fallback,omitempty"`
Color NotificationColor `json:"color,omitempty"`
PreText string `json:"pretext,omitempty"`
AuthorName string `json:"author_name,omitempty"`
AuthorLink string `json:"author_link,omitempty"`
AuthorIcon string `json:"author_icon,omitempty"`
Title string `json:"title,omitempty"`
TitleLink string `json:"title_link,omitempty"`
Text string `json:"text,omitempty"`
ImageUrl string `json:"image_url,omitempty"`
Footer string `json:"footer,omitempty"`
FooterIcon string `json:"footer_icon,omitempty"`
Timestamp int64 `json:"ts,omitempty"`
MarkdownIn []string `json:"mrkdwn_in,omitempty"`
CallbackID string `json:"callback_id,omitempty"`
ThumbnailUrl string `json:"thumb_url,omitempty"`
}

type PostPayload struct {
Parse string `json:"parse,omitempty"`
Username string `json:"username,omitempty"`
IconUrl string `json:"icon_url,omitempty"`
IconEmoji string `json:"icon_emoji,omitempty"`
Channel string `json:"channel,omitempty"`
Text string `json:"text,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
Markdown bool `json:"mrkdwn,omitempty"`
}

type Notifier interface {
SendNotification(nType NotificationType, msg string) error
}

type Request struct {
WebHookURL string
PostPayload
wryonik marked this conversation as resolved.
Show resolved Hide resolved
}

type ProviderTypeEnum int

const (
DiscordProvider ProviderTypeEnum = 1 + iota
SlackProvider
)

//In the Discord notification provider it was using the same payload which was used for slack.
//By writing "/slack" in the discord WebHookURL, it execute Slack-Compatible Webhook
func NewNotifier(URL string, ProviderType ProviderTypeEnum) Notifier {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validate URL here

url, err := url.ParseRequestURI(URL)
if url == nil || err != nil {
log.Error("Invalid notification webhook URL")
return nil
}
switch ProviderType {
case SlackProvider:
return &SlackNotificationProvider{
Request{
WebHookURL: url.String(),
},
}
case DiscordProvider:
return &DiscordNotificationProvider{
Request{
WebHookURL: url.String() + "/slack",
},
}
}
return nil
}

func (req *Request) FillReqParams() error {
req.PostPayload = PostPayload{
Username: USERNAME,
IconUrl: ICON_URL,
Channel: CHANNEL_NAME,
}
return nil
}

func SendNotification(nType NotificationType, message string) error {
for _, webhook := range config.Cfg.NotificationWebhooks {
if webhook.ServiceName != "" || webhook.Active == true {
var Provider ProviderTypeEnum
if webhook.ServiceName == "slack" {
Provider = SlackProvider
}
if webhook.ServiceName == "discord" {
Provider = DiscordProvider
}
Notifier := NewNotifier(webhook.URL, Provider)

err := Notifier.SendNotification(nType, message)
if err != nil {
log.Errorf("Error while sending notification to %s : %s", webhook.ServiceName, err)
fmt.Errorf("NOTIFICATION_SEND_ERROR: %s", err)
}

log.Infof("Notfication sent to %s.", webhook.ServiceName)
} else {
log.Warnf("No %s webhook url provided in beast config, cannot send notification.", webhook.ServiceName)
fmt.Errorf("No webhook URL in beast config.")
}
}
return nil
}
Loading