2017-02-22 09:46:42 +01:00
|
|
|
package tasks
|
|
|
|
|
|
|
|
import (
|
2017-03-07 09:35:14 +01:00
|
|
|
"bytes"
|
2024-02-27 11:47:22 +01:00
|
|
|
"embed"
|
|
|
|
"fmt"
|
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"
|
2024-02-27 11:47:22 +01:00
|
|
|
"text/template"
|
2017-02-22 09:46:42 +01:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
"github.com/ansible-semaphore/semaphore/lib"
|
|
|
|
"github.com/ansible-semaphore/semaphore/util"
|
|
|
|
"github.com/ansible-semaphore/semaphore/util/mailer"
|
|
|
|
)
|
2017-03-22 08:22:09 +01:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
//go:embed templates/*.tmpl
|
|
|
|
var templates embed.FS
|
2022-04-11 10:29:48 +02:00
|
|
|
|
2023-10-21 11:50:08 +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",
|
2023-10-21 17:33:33 +02:00
|
|
|
"url": "{{ .TaskURL }}"
|
2023-10-21 11:50:08 +02:00
|
|
|
}
|
|
|
|
],
|
|
|
|
"msteams": {
|
|
|
|
"width": "Full"
|
|
|
|
},
|
|
|
|
"backgroundImage": {
|
|
|
|
"horizontalAlignment": "Center",
|
2023-10-21 17:03:30 +02:00
|
|
|
"url": "data:image/jpg;base64,iVBORw0KGgoAAAANSUhEUgAABSgAAAAFCAYAAABGmwLHAAAARklEQVR4nO3YMQEAIBDEsANPSMC/AbzwMm5JJHTseuf+AAAAAAAUbNEBAAAAgBaDEgAAAACoMSgBAAAAgBqDEgAAAADoSDL8RAJfcbcsoQAAAABJRU5ErkJggg==",
|
2023-10-21 11:50:08 +02:00
|
|
|
"fillMode": "RepeatHorizontally"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}`
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
// Alert represents an alert that will be templated and sent to the appropriate service
|
2017-03-07 09:35:14 +01:00
|
|
|
type Alert struct {
|
2024-02-27 11:47:22 +01:00
|
|
|
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
|
2017-03-07 09:35:14 +01:00
|
|
|
}
|
|
|
|
|
2022-01-29 19:00:21 +01:00
|
|
|
func (t *TaskRunner) sendMailAlert() {
|
2018-03-27 22:12:47 +02:00
|
|
|
if !util.Config.EmailAlert || !t.alert {
|
2017-03-13 03:30:48 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
body := bytes.NewBufferString("")
|
|
|
|
author, version := t.alertInfos()
|
2017-03-13 03:30:48 +01:00
|
|
|
|
2017-04-18 16:36:09 +02:00
|
|
|
alert := Alert{
|
2023-08-29 00:51:04 +02:00
|
|
|
Name: t.Template.Name,
|
2024-02-27 11:47:22 +01:00
|
|
|
Author: author,
|
|
|
|
Color: t.alertColor("email"),
|
|
|
|
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/email.tmpl")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Log("Can't parse email alert template!")
|
|
|
|
panic(err)
|
2017-04-18 16:36:09 +02:00
|
|
|
}
|
2017-03-13 03:30:48 +01:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
if err := tpl.Execute(body, alert); err != nil {
|
|
|
|
t.Log("Can't generate email alert template!")
|
|
|
|
panic(err)
|
|
|
|
}
|
2017-02-22 09:46:42 +01:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
for _, uid := range t.users {
|
|
|
|
user, err := t.pool.store.GetUser(uid)
|
2017-03-13 03:30:48 +01:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
if !user.Alert {
|
2023-06-21 22:16:26 +02:00
|
|
|
continue
|
2017-03-13 03:30:48 +01:00
|
|
|
}
|
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
if err != nil {
|
|
|
|
util.LogError(err)
|
2023-09-19 23:11:51 +02:00
|
|
|
continue
|
|
|
|
}
|
2022-10-02 19:10:56 +02:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
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)
|
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
|
|
|
|
2022-01-29 19:00:21 +01:00
|
|
|
func (t *TaskRunner) sendTelegramAlert() {
|
2018-03-27 22:12:47 +02:00
|
|
|
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 {
|
2022-02-12 13:15:15 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
chatID := util.Config.TelegramChat
|
2022-02-03 19:35:32 +01:00
|
|
|
if t.alertChat != nil && *t.alertChat != "" {
|
|
|
|
chatID = *t.alertChat
|
2017-05-03 06:27:58 +02:00
|
|
|
}
|
|
|
|
|
2023-09-19 23:11:51 +02:00
|
|
|
if chatID == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
body := bytes.NewBufferString("")
|
|
|
|
author, version := t.alertInfos()
|
2021-10-26 18:54:19 +02:00
|
|
|
|
2017-04-18 16:36:09 +02:00
|
|
|
alert := Alert{
|
2024-02-27 11:47:22 +01:00
|
|
|
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,
|
|
|
|
},
|
2017-04-18 16:36:09 +02:00
|
|
|
}
|
2021-08-31 14:03:52 +02:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
tpl, err := template.ParseFS(templates, "templates/telegram.tmpl")
|
2021-10-26 18:54:19 +02:00
|
|
|
|
|
|
|
if err != nil {
|
2024-02-27 11:47:22 +01:00
|
|
|
t.Log("Can't parse telegram alert template!")
|
2021-10-26 18:54:19 +02:00
|
|
|
panic(err)
|
|
|
|
}
|
2017-03-22 08:22:09 +01:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
if err := tpl.Execute(body, alert); err != nil {
|
|
|
|
t.Log("Can't generate telegram alert template!")
|
2021-10-26 18:54:19 +02:00
|
|
|
panic(err)
|
|
|
|
}
|
2017-03-22 08:22:09 +01:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
resp, err := http.Post(
|
|
|
|
fmt.Sprintf(
|
|
|
|
"https://api.telegram.org/bot%s/sendMessage",
|
|
|
|
util.Config.TelegramToken,
|
|
|
|
),
|
|
|
|
"application/json",
|
|
|
|
body,
|
|
|
|
)
|
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
|
|
|
|
}
|
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
body := bytes.NewBufferString("")
|
|
|
|
author, version := t.alertInfos()
|
2022-04-11 10:29:48 +02:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TaskRunner) alertInfos() (string, string) {
|
|
|
|
version := ""
|
2022-04-11 10:29:48 +02:00
|
|
|
|
2023-08-29 00:51:04 +02:00
|
|
|
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 = ""
|
|
|
|
}
|
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
author := ""
|
2022-04-11 10:29:48 +02:00
|
|
|
|
2023-08-29 00:51:04 +02:00
|
|
|
if t.Task.UserID != nil {
|
|
|
|
user, err := t.pool.store.GetUser(*t.Task.UserID)
|
2024-02-27 11:47:22 +01:00
|
|
|
|
2022-04-11 10:29:48 +02:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
author = user.Name
|
2022-04-11 10:29:48 +02:00
|
|
|
}
|
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
return version, author
|
|
|
|
}
|
2022-04-11 10:29:48 +02:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
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"
|
|
|
|
}
|
2022-04-11 10:29:48 +02:00
|
|
|
}
|
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
return ""
|
|
|
|
}
|
2022-04-11 10:29:48 +02:00
|
|
|
|
2024-02-27 11:47:22 +01:00
|
|
|
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,
|
|
|
|
)
|
2022-04-11 10:29:48 +02:00
|
|
|
}
|
2023-10-21 11:50:08 +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(µsoftTeamsBuffer, 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(µsoftTeamsBuffer, 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", µsoftTeamsBuffer)
|
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
2023-10-21 17:33:33 +02:00
|
|
|
|
|
|
|
t.Log("MicrosoftTeams alert sent successfully")
|
2023-10-21 11:53:35 +02:00
|
|
|
}
|