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"`