Semaphore/services/tasks/alert.go

396 lines
10 KiB
Go
Raw Normal View History

2017-02-22 09:46:42 +01:00
package tasks
import (
"bytes"
2023-09-23 17:12:35 +02:00
"github.com/ansible-semaphore/semaphore/lib"
2022-04-19 14:32:03 +02:00
"github.com/ansible-semaphore/semaphore/util"
"html/template"
2017-03-22 08:22:09 +01:00
"net/http"
2017-02-22 09:46:42 +01:00
"strconv"
2021-10-26 18:54:19 +02:00
"strings"
2017-02-22 09:46:42 +01:00
)
2022-10-02 19:10:56 +02:00
const emailTemplate = "Subject: Task '{{ .Name }}' failed\r\n" +
"From: {{ .From }}\r\n" +
"\r\n" +
"Task {{ .TaskID }} with template '{{ .Name }}' has failed!`\n" +
"Task Log: {{ .TaskURL }}"
2022-02-03 08:05:13 +01:00
const telegramTemplate = `{"chat_id": "{{ .ChatID }}","parse_mode":"HTML","text":"<code>{{ .Name }}</code>\n#{{ .TaskID }} <b>{{ .TaskResult }}</b> <code>{{ .TaskVersion }}</code> {{ .TaskDescription }}\nby {{ .Author }}\n{{ .TaskURL }}"}`
2017-03-22 08:22:09 +01:00
2022-04-19 14:32:03 +02:00
const slackTemplate = `{ "attachments": [ { "title": "Task: {{ .Name }}", "title_link": "{{ .TaskURL }}", "text": "execution ID #{{ .TaskID }}, status: {{ .TaskResult }}!", "color": "{{ .Color }}", "mrkdwn_in": ["text"], "fields": [ { "title": "Author", "value": "{{ .Author }}", "short": true }] } ]}`
2022-04-11 10:29:48 +02:00
const microsoftTeamsTemplate = `{
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{
"type": "TextBlock",
"text": "Ansible Task Template Execution by: {{ .Author }}",
},
{
"type": "FactSet",
"facts": [
{
"title": "Task:",
"value": "{{ .Name }}"
},
{
"title": "Status:",
"value": "{{ .TaskResult }}"
},
{
"title": "Task ID:",
"value": "{{ .TaskID }}"
}
],
"separator": true
}
],
"actions": [
{
"type": "Action.OpenUrl",
"title": "Task URL",
"url": "http://localhost:3000{{ .TaskURL }}"
}
],
"msteams": {
"width": "Full"
},
"backgroundImage": {
"horizontalAlignment": "Center",
"url": "data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAABSgAAAAFCAYAAABGmwLHAAAARUlEQVR4nO3YMQEAIBDEsAM1GEErMnkZtyQSOnadd38AAAAAAAq26AAAAABAi0EJAAAAANQYlAAAAABAjUEJAAAAAHQkGWMEAh35OF6mAAAAAElFTkSuQmCC",
"fillMode": "RepeatHorizontally"
}
}
}
]
}`
// Alert represents an alert that will be templated and sent to the appropriate service
type Alert struct {
2021-10-26 18:54:19 +02:00
TaskID string
2022-02-03 08:05:13 +01:00
Name string
2021-10-26 18:54:19 +02:00
TaskURL string
ChatID string
TaskResult string
TaskDescription string
TaskVersion string
Author string
2022-04-19 14:32:03 +02:00
Color string
2022-10-02 19:10:56 +02:00
From string
}
func (t *TaskRunner) sendMailAlert() {
if !util.Config.EmailAlert || !t.alert {
2017-03-13 03:30:48 +01:00
return
}
mailHost := util.Config.EmailHost + ":" + util.Config.EmailPort
var mailBuffer bytes.Buffer
2017-04-18 16:36:09 +02:00
alert := Alert{
TaskID: strconv.Itoa(t.Task.ID),
Name: t.Template.Name,
TaskURL: util.Config.WebHost + "/project/" + strconv.Itoa(t.Template.ProjectID) +
"/templates/" + strconv.Itoa(t.Template.ID) +
"?t=" + strconv.Itoa(t.Task.ID),
2022-10-02 19:10:56 +02:00
From: util.Config.EmailSender,
2017-04-18 16:36:09 +02:00
}
2017-03-13 03:30:48 +01:00
tpl := template.New("mail body template")
tpl, err := tpl.Parse(emailTemplate)
util.LogError(err)
2017-03-13 03:30:48 +01:00
t.panicOnError(tpl.Execute(&mailBuffer, alert), "Can't generate alert template!")
2017-02-22 09:46:42 +01:00
2017-03-13 03:30:48 +01:00
for _, user := range t.users {
2023-09-19 23:11:51 +02:00
userObj, err2 := t.pool.store.GetUser(user)
2017-03-13 03:30:48 +01:00
if !userObj.Alert {
2023-06-21 22:16:26 +02:00
continue
2017-03-13 03:30:48 +01:00
}
2023-09-19 23:11:51 +02:00
if err2 != nil {
2023-09-19 23:14:21 +02:00
util.LogError(err2)
2023-09-19 23:11:51 +02:00
continue
}
2022-10-02 19:10:56 +02:00
2021-09-22 05:43:19 +02:00
if util.Config.EmailSecure {
2023-09-19 23:11:51 +02:00
err2 = util.SendSecureMail(util.Config.EmailHost, util.Config.EmailPort,
2022-10-02 19:10:56 +02:00
util.Config.EmailSender, util.Config.EmailUsername, util.Config.EmailPassword,
userObj.Email, mailBuffer)
2021-09-22 05:43:19 +02:00
} else {
2023-09-19 23:11:51 +02:00
err2 = util.SendMail(mailHost, util.Config.EmailSender, userObj.Email, mailBuffer)
2021-09-22 05:43:19 +02:00
}
2022-10-02 19:10:56 +02:00
2023-09-19 23:11:51 +02:00
if err2 != nil {
2023-09-19 23:14:21 +02:00
util.LogError(err2)
2023-09-19 23:11:51 +02:00
}
2017-03-13 03:30:48 +01:00
}
2017-02-22 09:46:42 +01:00
}
2017-03-22 08:22:09 +01:00
func (t *TaskRunner) sendTelegramAlert() {
if !util.Config.TelegramAlert || !t.alert {
2017-03-22 08:22:09 +01:00
return
}
2023-09-23 17:12:35 +02:00
if t.Template.SuppressSuccessAlerts && t.Task.Status == lib.TaskSuccessStatus {
return
}
chatID := util.Config.TelegramChat
if t.alertChat != nil && *t.alertChat != "" {
chatID = *t.alertChat
}
2023-09-19 23:11:51 +02:00
if chatID == "" {
return
}
2017-03-22 08:22:09 +01:00
var telegramBuffer bytes.Buffer
2021-10-26 18:54:19 +02:00
var version string
if t.Task.Version != nil {
version = *t.Task.Version
} else if t.Task.BuildTaskID != nil {
buildVer := t.Task.GetIncomingVersion(t.pool.store)
if buildVer != nil {
version = *buildVer
}
2021-10-26 18:54:19 +02:00
} else {
version = ""
}
var message string
if t.Task.Message != "" {
message = "- " + t.Task.Message
2021-10-26 18:54:19 +02:00
}
var author string
if t.Task.UserID != nil {
user, err := t.pool.store.GetUser(*t.Task.UserID)
2021-10-26 18:54:19 +02:00
if err != nil {
panic(err)
}
author = user.Name
}
2017-04-18 16:36:09 +02:00
alert := Alert{
TaskID: strconv.Itoa(t.Task.ID),
Name: t.Template.Name,
TaskURL: util.Config.WebHost + "/project/" + strconv.Itoa(t.Template.ProjectID) + "/templates/" + strconv.Itoa(t.Template.ID) + "?t=" + strconv.Itoa(t.Task.ID),
2021-10-26 18:54:19 +02:00
ChatID: chatID,
TaskResult: strings.ToUpper(string(t.Task.Status)),
2021-10-26 18:54:19 +02:00
TaskVersion: version,
TaskDescription: message,
Author: author,
2017-04-18 16:36:09 +02:00
}
2021-08-31 14:03:52 +02:00
2017-03-22 08:22:09 +01:00
tpl := template.New("telegram body template")
2021-10-26 18:54:19 +02:00
2017-03-22 08:22:09 +01:00
tpl, err := tpl.Parse(telegramTemplate)
2021-10-26 18:54:19 +02:00
if err != nil {
t.Log("Can't parse telegram template!")
2021-10-26 18:54:19 +02:00
panic(err)
}
2017-03-22 08:22:09 +01:00
2021-10-26 18:54:19 +02:00
err = tpl.Execute(&telegramBuffer, alert)
if err != nil {
t.Log("Can't generate alert template!")
2021-10-26 18:54:19 +02:00
panic(err)
}
2017-03-22 08:22:09 +01:00
2017-03-24 08:50:26 +01:00
resp, err := http.Post("https://api.telegram.org/bot"+util.Config.TelegramToken+"/sendMessage", "application/json", &telegramBuffer)
2017-03-22 08:22:09 +01:00
2021-10-26 18:54:19 +02:00
if err != nil {
2022-10-02 19:10:56 +02:00
t.Log("Can't send telegram alert! Error: " + err.Error())
2021-10-26 18:54:19 +02:00
} else if resp.StatusCode != 200 {
2022-10-02 19:10:56 +02:00
t.Log("Can't send telegram alert! Response code: " + strconv.Itoa(resp.StatusCode))
2017-03-24 08:50:26 +01:00
}
2017-03-22 08:22:09 +01:00
}
2022-04-11 10:29:48 +02:00
func (t *TaskRunner) sendSlackAlert() {
if !util.Config.SlackAlert || !t.alert {
return
}
2023-09-23 17:12:35 +02:00
if t.Template.SuppressSuccessAlerts && t.Task.Status == lib.TaskSuccessStatus {
2022-04-11 10:29:48 +02:00
return
}
slackUrl := util.Config.SlackUrl
var slackBuffer bytes.Buffer
var version string
if t.Task.Version != nil {
version = *t.Task.Version
} else if t.Task.BuildTaskID != nil {
version = "build " + strconv.Itoa(*t.Task.BuildTaskID)
2022-04-11 10:29:48 +02:00
} else {
version = ""
}
var message string
if t.Task.Message != "" {
message = "- " + t.Task.Message
2022-04-11 10:29:48 +02:00
}
var author string
if t.Task.UserID != nil {
user, err := t.pool.store.GetUser(*t.Task.UserID)
2022-04-11 10:29:48 +02:00
if err != nil {
panic(err)
}
author = user.Name
}
var color string
2023-09-23 17:12:35 +02:00
if t.Task.Status == lib.TaskSuccessStatus {
2022-04-11 10:29:48 +02:00
color = "good"
2023-09-23 17:12:35 +02:00
} else if t.Task.Status == lib.TaskFailStatus {
2022-04-11 10:29:48 +02:00
color = "bad"
2023-09-23 17:12:35 +02:00
} else if t.Task.Status == lib.TaskRunningStatus {
2022-04-19 14:32:03 +02:00
color = "#333CFF"
2023-09-23 17:12:35 +02:00
} else if t.Task.Status == lib.TaskWaitingStatus {
2022-04-19 14:32:03 +02:00
color = "#FFFC33"
2023-09-23 17:12:35 +02:00
} else if t.Task.Status == lib.TaskStoppingStatus {
2022-04-19 14:32:03 +02:00
color = "#BEBEBE"
2023-09-23 17:12:35 +02:00
} else if t.Task.Status == lib.TaskStoppedStatus {
2022-04-19 14:32:03 +02:00
color = "#5B5B5B"
2022-04-11 10:29:48 +02:00
}
alert := Alert{
TaskID: strconv.Itoa(t.Task.ID),
Name: t.Template.Name,
TaskURL: util.Config.WebHost + "/project/" + strconv.Itoa(t.Template.ProjectID) + "/templates/" + strconv.Itoa(t.Template.ID) + "?t=" + strconv.Itoa(t.Task.ID),
TaskResult: strings.ToUpper(string(t.Task.Status)),
2022-04-11 10:29:48 +02:00
TaskVersion: version,
TaskDescription: message,
Author: author,
2022-04-19 14:32:03 +02:00
Color: color,
2022-04-11 10:29:48 +02:00
}
tpl := template.New("slack body template")
tpl, err := tpl.Parse(slackTemplate)
if err != nil {
t.Log("Can't parse slack template!")
panic(err)
}
err = tpl.Execute(&slackBuffer, alert)
if err != nil {
t.Log("Can't generate alert template!")
panic(err)
}
resp, err := http.Post(slackUrl, "application/json", &slackBuffer)
if err != nil {
2022-10-02 19:10:56 +02:00
t.Log("Can't send slack alert! Error: " + err.Error())
2022-04-11 10:29:48 +02:00
} else if resp.StatusCode != 200 {
2022-10-02 19:10:56 +02:00
t.Log("Can't send slack alert! Response code: " + strconv.Itoa(resp.StatusCode))
2022-04-11 10:29:48 +02:00
}
}
func (t *TaskRunner) sendMicrosoftTeamsAlert() {
if !util.Config.MicrosoftTeamsAlert || !t.alert {
return
}
if t.Template.SuppressSuccessAlerts && t.Task.Status == lib.TaskSuccessStatus {
return
}
MicrosoftTeamsUrl := util.Config.MicrosoftTeamsUrl
var microsoftTeamsBuffer bytes.Buffer
var version string
if t.Task.Version != nil {
version = *t.Task.Version
} else if t.Task.BuildTaskID != nil {
version = "build " + strconv.Itoa(*t.Task.BuildTaskID)
} else {
version = ""
}
var message string
if t.Task.Message != "" {
message = "- " + t.Task.Message
}
var author string
if t.Task.UserID != nil {
user, err := t.pool.store.GetUser(*t.Task.UserID)
if err != nil {
panic(err)
}
author = user.Name
}
var color string
if t.Task.Status == lib.TaskSuccessStatus {
color = "good"
} else if t.Task.Status == lib.TaskFailStatus {
color = "bad"
} else if t.Task.Status == lib.TaskRunningStatus {
color = "#333CFF"
} else if t.Task.Status == lib.TaskWaitingStatus {
color = "#FFFC33"
} else if t.Task.Status == lib.TaskStoppingStatus {
color = "#BEBEBE"
} else if t.Task.Status == lib.TaskStoppedStatus {
color = "#5B5B5B"
}
// Instantiate an alert object
alert := Alert{
TaskID: strconv.Itoa(t.Task.ID),
Name: t.Template.Name,
TaskURL: util.Config.WebHost + "/project/" + strconv.Itoa(t.Template.ProjectID) + "/templates/" + strconv.Itoa(t.Template.ID) + "?t=" + strconv.Itoa(t.Task.ID),
TaskResult: strings.ToUpper(string(t.Task.Status)),
TaskVersion: version,
TaskDescription: message,
Author: author,
Color: color,
}
tpl := template.New("MicrosoftTeams body template")
tpl, err := tpl.Parse(microsoftTeamsTemplate)
if err != nil {
t.Log("Can't parse MicrosoftTeams template!")
panic(err)
}
// The tpl.Execute(&microsoftTeamsBuffer, alert) line is used to apply the data from the alert struct to the template.
// This operation fills in the placeholders in the template with the corresponding values from the alert struct
// and writes the result to the microsoftTeamsBuffer. In essence, it generates a JSON message based on the template and the data in the alert struct.
err = tpl.Execute(&microsoftTeamsBuffer, alert)
if err != nil {
t.Log("Can't generate alert template!")
panic(err)
}
// test if buffer is empty
if microsoftTeamsBuffer.Len() == 0 {
t.Log("MicrosoftTeams buffer is empty!")
return
}
t.Log("Attempting to send MicrosoftTeams alert")
resp, err := http.Post(MicrosoftTeamsUrl, "application/json", &microsoftTeamsBuffer)
if err != nil {
t.Log("Can't send MicrosoftTeams alert! Error: " + err.Error())
} else if resp.StatusCode != 200 {
t.Log("Can't send MicrosoftTeams alert! Response code: " + strconv.Itoa(resp.StatusCode))
}
}