8000 Add Notifier by huacnlee · Pull Request #111 · gobackup/gobackup · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add Notifier #111

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

Merged
merged 6 commits into from
Dec 12, 2022
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
16 changes: 16 additions & 0 deletions config/config.go
10000
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type ModelConfig struct {
Splitter *viper.Viper
Databases map[string]SubConfig
Storages map[string]SubConfig
Notifiers map[string]SubConfig
Viper *viper.Viper
}

Expand Down Expand Up @@ -164,6 +165,8 @@ func loadModel(key string) (model ModelConfig) {
logger.Fatalf("No storage found in model %s", model.Name)
}

loadNotifiersConfig(&model)

return
}

Expand Down Expand Up @@ -220,6 +223,19 @@ func loadStoragesConfig(model *ModelConfig) {
model.Storages = storageConfigs
}

func loadNotifiersConfig(model *ModelConfig) {
subViper := model.Viper.Sub("notifiers")
model.Notifiers = map[string]SubConfig{}
for key := range model.Viper.GetStringMap("notifiers") {
dbViper := subViper.Sub(key)
model.Notifiers[key] = SubConfig{
Name: key,
Type: dbViper.GetString("type"),
Viper: dbViper,
}
}
}

// GetModelConfigByName get model config by name
func GetModelConfigByName(name string) (model *ModelConfig) {
for _, m := range Models {
Expand Down
2 changes: 1 addition & 1 deletion config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func init() {

func TestModelsLength(t *testing.T) {
assert.Equal(t, Exist, true)
assert.Equal(t, len(Models), 5)
assert.Equal(t, len(Models), 4)
}

func TestModel(t *testing.T) {
Expand Down
29 changes: 21 additions & 8 deletions gobackup_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,28 @@ models:
password: 123456
salt: false
openssl: true
notifiers:
feishu:
type: feishu
url: https://open.feishu.cn/open-apis/bot/v2/hook/a8fb64fa-8f28-46f5-ba7a-c2d0a542a78711
dingtalk:
type: dingtalk
url: https://oapi.dingtalk.com/robot/send?access_token=102f9b03e717078acf830a692e78a07092aca8b6a7fc3f5c2c048baf25de1370r
discord:
type: discord
url: https://discordapp.com/api/webhooks/1051827238108135424/lWrAw9okY-6LCimnWlHn3pKMr-e3rr4fWn5rKgcjfn92n_RiZbRK9M7Kse-esKDBepV21
slack:
type: slack
url: https://hooks.slack.com/services/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o2
github:
type: github
url: https://github.com/gobackup/gobackup/pull/111
access_token: xxxxxxxxxxxx
storages:
azure:
type: azure
keep: 20
account: my-storage-account
timeout: 300
tenant_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
client_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
client_secret: xxxxxxxx
local:
type: local
keep: 10
path: /Users/jason/Downloads/backup1
archive:
includes:
- /Users/jason/work/imageproxy
Expand Down
21 changes: 13 additions & 8 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/gobackup/gobackup/database"
"github.com/gobackup/gobackup/encryptor"
"github.com/gobackup/gobackup/logger"
"github.com/gobackup/gobackup/notifier"
"github.com/gobackup/gobackup/splitter"
"github.com/gobackup/gobackup/storage"
)
Expand All @@ -22,9 +23,18 @@ type Model struct {
}

// Perform model
func (m Model) Perform() {
func (m Model) Perform() (err error) {
logger := logger.Tag(fmt.Sprintf("Model: %s", m.Config.Name))

defer func() {
if err != nil {
logger.Error(err)
notifier.Failure(m.Config, err.Error())
} else {
notifier.Success(m.Config)
}
}()

logger.Info("WorkDir:", m.Config.DumpPath)

defer func() {
Expand All @@ -35,44 +45,39 @@ func (m Model) Perform() {
m.cleanup()
}()

err := database.Run(m.Config)
err = database.Run(m.Config)
if err != nil {
logger.Error(err)
return
}

if m.Config.Archive != nil {
err = archive.Run(m.Config)
if err != nil {
logger.Error(err)
return
}
}

archivePath, err := compressor.Run(m.Config)
if err != nil {
logger.Error(err)
return
}

archivePath, err = encryptor.Run(archivePath, m.Config)
if err != nil {
logger.Error(err)
return
}

archivePath, err = splitter.Run(archivePath, m.Config)
if err != nil {
logger.Error(err)
return
}

err = storage.Run(m.Config, archivePath)
if err != nil {
logger.Error(err)
return
}

return nil
}

// Cleanup model temp files
Expand Down
97 changes: 97 additions & 0 deletions notifier/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package notifier

import (
"fmt"
"time"

"github.com/gobackup/gobackup/config"
"github.com/gobackup/gobackup/logger"
"github.com/spf13/viper"
)

type Base struct {
viper *viper.Viper
Name string
onSuccess bool
onFailure bool
}

type Notifier interface {
notify(title, message string) error
}

var (
notifyTypeSuccess = 1
notifyTypeFailure = 2
)

func newNotifier(name string, config config.SubConfig) (Notifier, *Base, error) {
base := &Base{
viper: config.Viper,
Name: name,
}
base.viper.SetDefault("on_success", true)
base.viper.SetDefault("on_failure", true)

base.>
base.>

switch config.Type {
case "webhook":
return &Webhook{Base: *base}, base, nil
case "feishu":
return NewFeishu(base), base, nil
case "dingtalk":
return NewDingtalk(base), base, nil
case "discord":
return NewDiscord(base), base, nil
case "slack":
return NewSlack(base), base, nil
case "github":
return NewGitHub(base), base, nil
}

return nil, nil, fmt.Errorf("Notifier: %s is not supported", name)
}

func notify(model config.ModelConfig, title, message string, notifyType int) error {
logger := logger.Tag("Notifier")

logger.Infof("Running %d Notifiers", len(model.Notifiers))
for name, config := range model.Notifiers {
notifier, base, err := newNotifier(name, config)
if err != nil {
logger.Error(err)
continue
}

if notifyType == notifyTypeSuccess {
if base.onSuccess {
if err := notifier.notify(title, message); err != nil {
logger.Error(err)
}
}
} else if notifyType == notifyTypeFailure {
if base.onFailure {
if err := notifier.notify(title, message); err != nil {
logger.Error(err)
}
}
}
}

return nil
}

func Success(model config.ModelConfig) error {
title := fmt.Sprintf("[GoBackup] Backup of %s completed successfully", model.Name)
message := fmt.Sprintf("Backup of %s completed successfully at %s", model.Name, time.Now().Local())
return notify(model, title, message, notifyTypeSuccess)
}

func Failure(model config.ModelConfig, reason string) error {
title := fmt.Sprintf("[GoBackup] Backup of %s failed", model.Name)
message := fmt.Sprintf("Backup of %s failed at %s:\n\n%s", model.Name, time.Now().Local(), reason)

return notify(model, title, message, notifyTypeFailure)
}
52 changes: 52 additions & 0 deletions notifier/dingtalk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package notifier

import (
"encoding/json"
"fmt"
)

type dingtalkResult struct {
Code int `json:"errcode"`
Message string `json:"errmsg"`
}

type dingtalkPayload struct {
MsgType string `json:"msgtype"`
Text dingtalkPayloadText `json:"text"`
}

type dingtalkPayloadText struct {
Content string `json:"content"`
}

func NewDingtalk(base *Base) *Webhook {
return &Webhook{
Base: *base,
Service: "DingTalk",
method: "POST",
contentType: "application/json",
buildBody: func(title, message string) ([]byte, error) {
payload := dingtalkPayload{
MsgType: "text",
Text: dingtalkPayloadText{
Content: fmt.Sprintf("%s\n\n%s", title, message),
},
}

return json.Marshal(payload)
},
checkResult: func(status int, body []byte) error {
var result dingtalkResult
err := json.Unmarshal(body, &result)
if err != nil {
return err
}

if result.Code != 0 || status != 200 {
return fmt.Errorf("status: %d, body: %s", status, string(body))
}

return nil
},
}
}
28 changes: 28 additions & 0 deletions notifier/dingtalk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package notifier

import (
"fmt"
"testing"

"github.com/longbridgeapp/assert"
)

func Test_Dingtalk(t *testing.T) {
base := &Base{}

s := NewDingtalk(base)
assert.Equal(t, "DingTalk", s.Service)
assert.Equal(t, "POST", s.method)
assert.Equal(t, "application/json", s.contentType)

body, err := s.buildBody("This is title", "This is body")
assert.NoError(t, err)
assert.Equal(t, `{"msgtype":"text","text":{"content":"This is title\n\nThis is body"}}`, string(body))

err = s.checkResult(200, []byte(`{"errcode":0,"errmsg":"ok"}`))
assert.NoError(t, err)

respBody := `{"errcode":300001,"errmsg":"Invalid token"}`
err = s.checkResult(403, []byte(respBody))
assert.EqualError(t, err, fmt.Sprintf("status: %d, body: %s", 403, respBody))
}
33 changes: 33 additions & 0 deletions notifier/discord.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package notifier

import (
"encoding/json"
"fmt"
)

type discordPayload struct {
Content string `json:"content"`
}

func NewDiscord(base *Base) *Webhook {
return &Webhook{
Base: *base,
Service: "Discord",
method: "POST",
contentType: "application/json",
buildBody: func(title, message string) ([]byte, error) {
payload := discordPayload{
Content: fmt.Sprintf("%s\n\n%s", title, message),
}

return json.Marshal(payload)
},
checkResult: func(status int, body []byte) error {
if status != 200 {
return fmt.Errorf("status: %d, body: %s", status, string(body))
}

return nil
},
}
}
20 changes: 20 additions & 0 deletions notifier/discord_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package notifier

import (
"testing"

"github.com/longbridgeapp/assert"
)

func Test_Discord(t *testing.T) {
base := &Base{}

s := NewDiscord(base)
assert.Equal(t, "Discord", s.Service)
assert.Equal(t, "POST", s.method)
assert.Equal(t, "application/json", s.contentType)

body, err := s.buildBody("This is title", "This is body")
assert.NoError(t, err)
assert.Equal(t, `{"content":"This is title\n\nThis is body"}`, string(body))
}
Loading
0