Semaphore/services/tasks/alert.go

435 lines
8.7 KiB
Go

package tasks
import (
"bytes"
"embed"
"fmt"
"net/http"
"strconv"
"strings"
"text/template"
"github.com/ansible-semaphore/semaphore/lib"
"github.com/ansible-semaphore/semaphore/util"
"github.com/ansible-semaphore/semaphore/util/mailer"
)
//go:embed templates/*.tmpl
var templates embed.FS
// Alert represents an alert that will be templated and sent to the appropriate service
type Alert struct {
Name string
Author string
Color string
Task alertTask
Chat alertChat
}
type alertTask struct {
ID string
URL string
Result string
Desc string
Version string
}
type alertChat struct {
ID string
}
func (t *TaskRunner) sendMailAlert() {
if !util.Config.EmailAlert || !t.alert {
return
}
body := bytes.NewBufferString("")
author, version := t.alertInfos()
alert := Alert{
Name: t.Template.Name,
Author: author,
Color: t.alertColor("email"),
Task: alertTask{
ID: strconv.Itoa(t.Task.ID),
URL: t.taskLink(),
Result: t.Task.Status.Format(),
Version: version,
Desc: t.Task.Message,
},
}
tpl, err := template.ParseFS(templates, "templates/email.tmpl")
if err != nil {
t.Log("Can't parse email alert template!")
panic(err)
}
if err := tpl.Execute(body, alert); err != nil {
t.Log("Can't generate email alert template!")
panic(err)
}
if body.Len() == 0 {
t.Log("Buffer for email alert is empty")
return
}
for _, uid := range t.users {
user, err := t.pool.store.GetUser(uid)
if !user.Alert {
continue
}
if err != nil {
util.LogError(err)
continue
}
t.Logf("Attempting to send email alert to %s", user.Email)
if err := mailer.Send(
util.Config.EmailSecure,
util.Config.EmailHost,
util.Config.EmailPort,
util.Config.EmailUsername,
util.Config.EmailPassword,
util.Config.EmailSender,
user.Email,
fmt.Sprintf("Task '%s' failed", t.Template.Name),
body.String(),
); err != nil {
util.LogError(err)
continue
}
t.Logf("Sent successfully email alert to %s", user.Email)
}
}
func (t *TaskRunner) sendTelegramAlert() {
if !util.Config.TelegramAlert || !t.alert {
return
}
if t.Template.SuppressSuccessAlerts && t.Task.Status == lib.TaskSuccessStatus {
return
}
chatID := util.Config.TelegramChat
if t.alertChat != nil && *t.alertChat != "" {
chatID = *t.alertChat
}
if chatID == "" {
return
}
body := bytes.NewBufferString("")
author, version := t.alertInfos()
alert := Alert{
Name: t.Template.Name,
Author: author,
Color: t.alertColor("telegram"),
Task: alertTask{
ID: strconv.Itoa(t.Task.ID),
URL: t.taskLink(),
Result: strings.ToUpper(string(t.Task.Status)),
Version: version,
Desc: t.Task.Message,
},
Chat: alertChat{
ID: chatID,
},
}
tpl, err := template.ParseFS(templates, "templates/telegram.tmpl")
if err != nil {
t.Log("Can't parse telegram alert template!")
panic(err)
}
if err := tpl.Execute(body, alert); err != nil {
t.Log("Can't generate telegram alert template!")
panic(err)
}
if body.Len() == 0 {
t.Log("Buffer for telegram alert is empty")
return
}
t.Log("Attempting to send telegram alert")
resp, err := http.Post(
fmt.Sprintf(
"https://api.telegram.org/bot%s/sendMessage",
util.Config.TelegramToken,
),
"application/json",
body,
)
if err != nil {
t.Log("Can't send telegram alert! Error: " + err.Error())
} else if resp.StatusCode != 200 {
t.Log("Can't send telegram alert! Response code: " + strconv.Itoa(resp.StatusCode))
}
t.Log("Sent successfully telegram alert")
}
func (t *TaskRunner) sendSlackAlert() {
if !util.Config.SlackAlert || !t.alert {
return
}
if t.Template.SuppressSuccessAlerts && t.Task.Status == lib.TaskSuccessStatus {
return
}
body := bytes.NewBufferString("")
author, version := t.alertInfos()
alert := Alert{
Name: t.Template.Name,
Author: author,
Color: t.alertColor("slack"),
Task: alertTask{
ID: strconv.Itoa(t.Task.ID),
URL: t.taskLink(),
Result: strings.ToUpper(string(t.Task.Status)),
Version: version,
Desc: t.Task.Message,
},
}
tpl, err := template.ParseFS(templates, "templates/slack.tmpl")
if err != nil {
t.Log("Can't parse slack alert template!")
panic(err)
}
if err := tpl.Execute(body, alert); err != nil {
t.Log("Can't generate slack alert template!")
panic(err)
}
if body.Len() == 0 {
t.Log("Buffer for slack alert is empty")
return
}
t.Log("Attempting to send slack alert")
resp, err := http.Post(
util.Config.SlackUrl,
"application/json",
body,
)
if err != nil {
t.Log("Can't send slack alert! Error: " + err.Error())
} else if resp.StatusCode != 200 {
t.Log("Can't send slack alert! Response code: " + strconv.Itoa(resp.StatusCode))
}
t.Log("Sent successfully slack alert")
}
func (t *TaskRunner) sendRocketChatAlert() {
if !util.Config.RocketChatAlert || !t.alert {
return
}
if t.Template.SuppressSuccessAlerts && t.Task.Status == lib.TaskSuccessStatus {
return
}
body := bytes.NewBufferString("")
author, version := t.alertInfos()
alert := Alert{
Name: t.Template.Name,
Author: author,
Color: t.alertColor("rocketchat"),
Task: alertTask{
ID: strconv.Itoa(t.Task.ID),
URL: t.taskLink(),
Result: strings.ToUpper(string(t.Task.Status)),
Version: version,
Desc: t.Task.Message,
},
}
tpl, err := template.ParseFS(templates, "templates/rocketchat.tmpl")
if err != nil {
t.Log("Can't parse rocketchat alert template!")
panic(err)
}
if err := tpl.Execute(body, alert); err != nil {
t.Log("Can't generate rocketchat alert template!")
panic(err)
}
if body.Len() == 0 {
t.Log("Buffer for rocketchat alert is empty")
return
}
t.Log("Attempting to send rocketchat alert")
resp, err := http.Post(
util.Config.RocketChatUrl,
"application/json",
body,
)
if err != nil {
t.Log("Can't send rocketchat alert! Error: " + err.Error())
} else if resp.StatusCode != 200 {
t.Log("Can't send rocketchat alert! Response code: " + strconv.Itoa(resp.StatusCode))
}
t.Log("Sent successfully rocketchat alert")
}
func (t *TaskRunner) sendMicrosoftTeamsAlert() {
if !util.Config.MicrosoftTeamsAlert || !t.alert {
return
}
if t.Template.SuppressSuccessAlerts && t.Task.Status == lib.TaskSuccessStatus {
return
}
body := bytes.NewBufferString("")
author, version := t.alertInfos()
alert := Alert{
Name: t.Template.Name,
Author: author,
Color: t.alertColor("micorsoft-teams"),
Task: alertTask{
ID: strconv.Itoa(t.Task.ID),
URL: t.taskLink(),
Result: strings.ToUpper(string(t.Task.Status)),
Version: version,
Desc: t.Task.Message,
},
}
tpl, err := template.ParseFS(templates, "templates/microsoft-teams.tmpl")
if err != nil {
t.Log("Can't parse microsoft teams alert template!")
panic(err)
}
if err := tpl.Execute(body, alert); err != nil {
t.Log("Can't generate microsoft teams alert template!")
panic(err)
}
if body.Len() == 0 {
t.Log("Buffer for microsoft teams alert is empty")
return
}
t.Log("Attempting to send microsoft teams alert")
resp, err := http.Post(
util.Config.MicrosoftTeamsUrl,
"application/json",
body,
)
if err != nil {
t.Log("Can't send microsoft teams alert! Error: " + err.Error())
} else if resp.StatusCode != 200 {
t.Log("Can't send microsoft teams alert! Response code: " + strconv.Itoa(resp.StatusCode))
}
t.Log("Sent successfully microsoft teams alert")
}
func (t *TaskRunner) alertInfos() (string, string) {
version := ""
if t.Task.Version != nil {
version = *t.Task.Version
} else if t.Task.BuildTaskID != nil {
version = "build " + strconv.Itoa(*t.Task.BuildTaskID)
} else {
version = ""
}
author := ""
if t.Task.UserID != nil {
user, err := t.pool.store.GetUser(*t.Task.UserID)
if err != nil {
panic(err)
}
author = user.Name
}
return version, author
}
func (t *TaskRunner) alertColor(kind string) string {
switch kind {
case "slack":
switch t.Task.Status {
case lib.TaskSuccessStatus:
return "good"
case lib.TaskFailStatus:
return "danger"
case lib.TaskRunningStatus:
return "#333CFF"
case lib.TaskWaitingStatus:
return "#FFFC33"
case lib.TaskStoppingStatus:
return "#BEBEBE"
case lib.TaskStoppedStatus:
return "#5B5B5B"
}
case "rocketchat":
switch t.Task.Status {
case lib.TaskSuccessStatus:
return "#00EE00"
case lib.TaskFailStatus:
return "#EE0000"
case lib.TaskRunningStatus:
return "#333CFF"
case lib.TaskWaitingStatus:
return "#FFFC33"
case lib.TaskStoppingStatus:
return "#BEBEBE"
case lib.TaskStoppedStatus:
return "#5B5B5B"
}
}
return ""
}
func (t *TaskRunner) taskLink() string {
return fmt.Sprintf(
"%s/project/%d/templates/%d?t=%d",
util.Config.WebHost,
t.Template.ProjectID,
t.Template.ID,
t.Task.ID,
)
}