diff --git a/cli/setup/setup.go b/cli/setup/setup.go index 60b04119..de76abac 100644 --- a/cli/setup/setup.go +++ b/cli/setup/setup.go @@ -65,6 +65,11 @@ func InteractiveSetup(conf *util.ConfigType) { askValue("Telegram chat ID", "", &conf.TelegramChat) } + askConfirmation("Enable slack alerts?", false, &conf.SlackAlert) + if conf.SlackAlert { + askValue("Slack Webhook URL", "", &conf.SlackUrl) + } + askConfirmation("Enable LDAP authentication?", false, &conf.LdapEnable) if conf.LdapEnable { askValue("LDAP server host", "localhost:389", &conf.LdapServer) diff --git a/deployment/docker/common/semaphore-wrapper b/deployment/docker/common/semaphore-wrapper index 4cb0d53b..5619b21a 100755 --- a/deployment/docker/common/semaphore-wrapper +++ b/deployment/docker/common/semaphore-wrapper @@ -81,6 +81,7 @@ ${SEMAPHORE_TMP_PATH} ${SEMAPHORE_WEB_ROOT} no no +no ${SEMAPHORE_LDAP_ACTIVATED} EOF diff --git a/services/tasks/alert.go b/services/tasks/alert.go index 5b61b42e..4e7e233a 100644 --- a/services/tasks/alert.go +++ b/services/tasks/alert.go @@ -3,12 +3,11 @@ package tasks import ( "bytes" "github.com/ansible-semaphore/semaphore/db" + "github.com/ansible-semaphore/semaphore/util" "html/template" "net/http" "strconv" "strings" - - "github.com/ansible-semaphore/semaphore/util" ) const emailTemplate = `Subject: Task '{{ .Name }}' failed @@ -18,6 +17,8 @@ Task log: {{ .TaskURL }}` const telegramTemplate = `{"chat_id": "{{ .ChatID }}","parse_mode":"HTML","text":"{{ .Name }}\n#{{ .TaskID }} {{ .TaskResult }} {{ .TaskVersion }} {{ .TaskDescription }}\nby {{ .Author }}\n{{ .TaskURL }}"}` +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 }] } ]}` + // Alert represents an alert that will be templated and sent to the appropriate service type Alert struct { TaskID string @@ -28,6 +29,7 @@ type Alert struct { TaskDescription string TaskVersion string Author string + Color string } func (t *TaskRunner) sendMailAlert() { @@ -139,3 +141,86 @@ func (t *TaskRunner) sendTelegramAlert() { t.Log("Can't send telegram alert! Response code not 200!") } } + +func (t *TaskRunner) sendSlackAlert() { + if !util.Config.SlackAlert || !t.alert { + return + } + + if t.template.SuppressSuccessAlerts && t.task.Status == db.TaskSuccessStatus { + 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) + } 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 == db.TaskSuccessStatus { + color = "good" + } else if t.task.Status == db.TaskFailStatus { + color = "bad" + } else if t.task.Status == db.TaskRunningStatus { + color = "#333CFF" + } else if t.task.Status == db.TaskWaitingStatus { + color = "#FFFC33" + } else if t.task.Status == db.TaskStoppingStatus { + color = "#BEBEBE" + } else if t.task.Status == db.TaskStoppedStatus { + color = "#5B5B5B" + } + 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("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 { + t.Log("Can't send slack alert! Response code not 200!") + } else if resp.StatusCode != 200 { + t.Log("Can't send slack alert! Response code not 200!") + } +} diff --git a/services/tasks/runner.go b/services/tasks/runner.go index edd0ba94..1c360bf3 100644 --- a/services/tasks/runner.go +++ b/services/tasks/runner.go @@ -72,6 +72,8 @@ func (t *TaskRunner) setStatus(status db.TaskStatus) { t.updateStatus() + t.sendSlackAlert() + if status == db.TaskFailStatus { t.sendMailAlert() } diff --git a/util/config.go b/util/config.go index e8926ede..fe4b8f08 100644 --- a/util/config.go +++ b/util/config.go @@ -98,6 +98,9 @@ type ConfigType struct { TelegramChat string `json:"telegram_chat"` TelegramToken string `json:"telegram_token"` + // slack alerting + SlackUrl string `json:"slack_url"` + // task concurrency MaxParallelTasks int `json:"max_parallel_tasks"` @@ -108,6 +111,7 @@ type ConfigType struct { EmailAlert bool `json:"email_alert"` EmailSecure bool `json:"email_secure"` TelegramAlert bool `json:"telegram_alert"` + SlackAlert bool `json:"slack_alert"` LdapEnable bool `json:"ldap_enable"` LdapNeedTLS bool `json:"ldap_needtls"`