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

Feat notification #52

Merged
merged 4 commits into from
Jan 17, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ AtomCI 致力于让中小企业快速落地Kubernetes,代码均已开源, __
# conf/app.conf
[DB]
url = root:root@tcp(127.0.0.1:3306)/atomci?charset=utf8mb4

[notification]
dingEnable = 1 # 启用钉钉通知;0:不启用,1:启用
ding = 钉钉机器人

mailEnable = 1 # 启用邮件通知;0:不启用,1:启用
smtpHost = SMTP服务器
smtpPort = 465
smtpAccount = 邮件账号
smtpPassword = 邮件密码
```
> 注: 对于`[ldap]`,`[jwt]`, `[atomci]`可以参照附录-『配置说明』进行修改

Expand Down
13 changes: 12 additions & 1 deletion conf/app.conf
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,15 @@ configPath = ./conf/k8sconfig

# build/deploy callback
[atomci]
url = http://localhost:8080
url = http://localhost:8080

# notification config
[notification]
dingEnable = 1
ding = "https://oapi.dingtalk.com/robot/send?access_token=faketoken"

mailEnable = 1
smtpHost = "smtp.host"
smtpPort = 465
smtpAccount = "fake@mail.com"
smtpPassword = "pwd"
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ require (
github.com/ghodss/yaml v1.0.0
github.com/go-atomci/go-scm v1.13.2-0.20210629010829-147be8a9bdd3
github.com/go-atomci/workflow v0.0.0-20211126090842-208f180b47ab
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-sql-driver/mysql v1.5.0
github.com/golang/protobuf v1.4.3 // indirect
github.com/google/go-cmp v0.5.5 // indirect
Expand All @@ -57,7 +58,9 @@ require (
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
golang.org/x/text v0.3.5 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/api v0.18.0
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+s
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap/v3 v3.2.1 h1:mbP3BPfsULz5DuI3ejHuAypAbcg38Xv5T7eEHp3+XAE=
github.com/go-ldap/ldap/v3 v3.2.1/go.mod h1:phWI+JSJ/eGvABjJxU7bT7CBv03KfS0e16+bQxLtjMw=
Expand Down Expand Up @@ -1013,6 +1015,8 @@ google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand All @@ -1024,6 +1028,8 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U=
Expand Down
4 changes: 4 additions & 0 deletions internal/api/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package api

import (
"github.com/go-atomci/atomci/internal/core/notification/impl"
"github.com/go-atomci/atomci/internal/core/pipelinemgr"
"github.com/go-atomci/atomci/internal/core/publish"
"github.com/go-atomci/atomci/internal/middleware/log"
Expand Down Expand Up @@ -118,6 +119,9 @@ func (p *PipelineController) RunStepCallback() {
log.Log.Error("RunStep callback, update publish Order occur error: %s", err.Error())
return
}

go notification.Send(publishID, publishStatus)

p.Data["json"] = NewResult(true, nil, "")
p.ServeJSON()
}
Expand Down
88 changes: 88 additions & 0 deletions internal/core/notification/impl/dingrobot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package notification

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

"github.com/astaxie/beego"
messages "github.com/go-atomci/atomci/internal/core/notification/types"
)

type DingRobot struct{}

func DingRobotHandler() INotify {
notifyHandler := &DingRobot{}
return notifyHandler
}

func dingEventMessage(template INotifyTemplate, result messages.StepCallbackResult) messages.EventMessage {

robotHost := beego.AppConfig.String("notification::ding")

var buf bytes.Buffer
template.GenSubject(&buf, result)
template.GenContent(&buf, result)
template.GenFooter(&buf, result)

markdownText := &messages.MarkdownMessage{
MsgType: messages.MarkDown,
At: messages.MessageAt{
AtMobiles: []string{},
IsAtAll: false,
},
MarkdownText: messages.MessageMarkdown{
Title: "流水线通知",
Text: buf.String(),
},
}

dingMsg := &messages.DingMessage{
RobotHost: []string{robotHost},
EventMessage: markdownText,
}

msg := messages.EventMessage{
Ding: dingMsg,
}

return msg
}

func (dingtalk *DingRobot) Send(result messages.StepCallbackResult) error {

template := &DingRobotMarkdownTemplate{}

message := dingEventMessage(template, result)

body, err := json.Marshal(message.Ding.EventMessage)
if err != nil {
return fmt.Errorf("序列化消息失败 err:%s message:%+v", err, message)
}

for _, host := range message.Ding.RobotHost {
res, err := http.Post(host, "application/json", bytes.NewBuffer(body))
if err != nil {
fmt.Println("钉钉消息发送失败 ", err)
return fmt.Errorf("钉钉消息发送失败 err:%s", err)
}
if res != nil && res.Body != nil {
defer res.Body.Close()
content, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("读取钉钉响应失败 err:%s", err)
}
if res.StatusCode == http.StatusFound {
return fmt.Errorf("机器人可能已被限流 请注意发送频率不要过高 会自行恢复")
}
if res.StatusCode != http.StatusOK {
return fmt.Errorf("钉钉消息发送失败 err:%s", content)
}
return nil
}
}

return fmt.Errorf("钉钉响应为空")
}
78 changes: 78 additions & 0 deletions internal/core/notification/impl/email.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package notification

import (
"bytes"
"github.com/astaxie/beego/logs"
"strings"

"github.com/astaxie/beego"
messages "github.com/go-atomci/atomci/internal/core/notification/types"
"github.com/go-gomail/gomail"
)

type Email struct{}

func EmailHandler() INotify {
notifyHandler := &Email{}
return notifyHandler
}

func emailEventMessage(template INotifyTemplate, result messages.StepCallbackResult) messages.EventMessage {

smtpHost := beego.AppConfig.String("notification::smtpHost")
smtpAccount := beego.AppConfig.String("notification::smtpAccount")
smtpPassword := beego.AppConfig.String("notification::smtpPassword")
smtpPort, _ := beego.AppConfig.Int("notification::smtpPort")

var buf bytes.Buffer
subject := template.GenSubject(&buf, result)
buf.Reset()
template.GenContent(&buf, result)
template.GenFooter(&buf, result)

mailMessage := &messages.MailMessage{
SmtpPort: smtpPort,
SmtpHost: smtpHost,
SmtpAccount: smtpAccount,
SmtpPassword: smtpPassword,
Body: buf.String(),
Subject: subject,
}
msg := messages.EventMessage{
Mail: mailMessage,
}

return msg
}

func (email *Email) Send(result messages.StepCallbackResult) error {

template := &EmailTemplate{}

message := emailEventMessage(template, result)

body := message.Mail.Body
body = strings.Replace(body, "\n", "<br>", -1)

subject := message.Mail.Subject

m := gomail.NewMessage()
m.SetHeader("From", message.Mail.SmtpAccount)
m.SetHeader("To", message.Mail.SmtpAccount)
m.SetHeader("Subject", subject)
m.SetBody("text/html", body)

d := gomail.NewDialer(message.Mail.SmtpHost, message.Mail.SmtpPort, message.Mail.SmtpAccount, message.Mail.SmtpPassword)

defer func() {
if r := recover(); r != nil {
logs.Error("%v", r)
}
}()

if err := d.DialAndSend(m); err != nil {
panic(err)
}

return nil
}
51 changes: 51 additions & 0 deletions internal/core/notification/impl/notify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package notification

import (
"github.com/astaxie/beego"
messages "github.com/go-atomci/atomci/internal/core/notification/types"
"github.com/go-atomci/atomci/internal/core/publish"
)

type INotify interface {
Send(m messages.StepCallbackResult) error
}

func NewHandlers() []INotify {

dingEnable, _ := beego.AppConfig.Int("notification::dingEnable")
mailEnable, _ := beego.AppConfig.Int("notification::mailEnable")

var handlers []INotify

if dingEnable > 0 {
_ = append(handlers, DingRobotHandler())
}

if mailEnable > 0 {
_ = append(handlers, EmailHandler())
}
fanhousanbu marked this conversation as resolved.
Show resolved Hide resolved

return handlers
}

func Send(publishId int64, status int64) {

pm := publish.NewPublishManager()

pub, _ := pm.GetPublishInfo(publishId)

handlers := NewHandlers()

callbackResult := messages.StepCallbackResult{
PublishName: pub.Name,
StageName: pub.StageName,
StepName: pub.Step,
Status: status,
}

if handlers != nil {
for _, handler := range handlers {
go handler.Send(callbackResult)
}
}
}
75 changes: 75 additions & 0 deletions internal/core/notification/impl/notifyTemplate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package notification

import (
"bytes"

messages "github.com/go-atomci/atomci/internal/core/notification/types"
)

type INotifyTemplate interface {
GenSubject(buf *bytes.Buffer, m messages.StepCallbackResult) string
GenContent(buf *bytes.Buffer, m messages.StepCallbackResult) string
GenFooter(buf *bytes.Buffer, m messages.StepCallbackResult) string
}

type DingRobotMarkdownTemplate struct{}

func (temp *DingRobotMarkdownTemplate) GenSubject(buf *bytes.Buffer, m messages.StepCallbackResult) string {

buf.WriteString("## ")
buf.WriteString(messages.StatusCodeToChinese(m.Status))

return buf.String()
}

func (temp *DingRobotMarkdownTemplate) GenContent(buf *bytes.Buffer, m messages.StepCallbackResult) string {

buf.WriteString(m.PublishName)
buf.WriteString("\r\n\r\n")
buf.WriteString(m.StageName)
buf.WriteString("\r\n\r\n")
buf.WriteString(m.StepName)

return buf.String()
}

func (temp *DingRobotMarkdownTemplate) GenFooter(buf *bytes.Buffer, m messages.StepCallbackResult) string {

buf.WriteString("\r\n\r\n> by AtomCI")

return buf.String()
}

type EmailTemplate struct{}

func (temp *EmailTemplate) GenSubject(buf *bytes.Buffer, m messages.StepCallbackResult) string {

buf.WriteString("流水线")
buf.WriteString(m.PublishName)
buf.WriteString("构建")
buf.WriteString(messages.StatusCodeToChinese(m.Status))

return buf.String()
}

func (temp *EmailTemplate) GenContent(buf *bytes.Buffer, m messages.StepCallbackResult) string {

buf.WriteString("<p><h2><span>流水线: </span><b>")
buf.WriteString(m.PublishName)
buf.WriteString("</b></h2></p><p><h2><span>阶段: </span><b>")
buf.WriteString(m.StageName)
buf.WriteString("</b></h2></p><p><h2><span>步骤: </span><b>")
buf.WriteString(m.StepName)
buf.WriteString("</b></h2></p><p><h1>")
buf.WriteString(messages.StatusCodeToChinese(m.Status))
buf.WriteString("</h1>")

return buf.String()
}

func (temp *EmailTemplate) GenFooter(buf *bytes.Buffer, m messages.StepCallbackResult) string {

buf.WriteString("<p>by AtomCI</p>")

return buf.String()
}