/ notifier / mattermost-notifier.go
mattermost-notifier.go
  1  package notifier
  2  
  3  import (
  4  	"bytes"
  5  	"encoding/json"
  6  	"fmt"
  7  	log "github.com/AcalephStorage/consul-alerts/Godeps/_workspace/src/github.com/Sirupsen/logrus"
  8  	"net/http"
  9  	"strconv"
 10  	"strings"
 11  )
 12  
 13  type StringMap map[string]string
 14  
 15  type MattermostLoginInfo struct {
 16  	LoginID  string `json:"login_id"`
 17  	Password string `json:"password"`
 18  }
 19  
 20  type MattermostAuthInfo struct {
 21  	UserID             string    `json:"id"`
 22  	CreateAt           int64     `json:"create_at"`
 23  	UpdateAt           int64     `json:"update_at"`
 24  	DeleteAt           int64     `json:"delete_at"`
 25  	UserName           string    `json:"username"`
 26  	AuthData           string    `json:"auth_data"`
 27  	AuthService        string    `json:"auth_service"`
 28  	Email              string    `json:"email"`
 29  	EmailVerified      bool      `json:"email_verified"`
 30  	NickName           string    `json:"nickname"`
 31  	FirstName          string    `json:"first_name"`
 32  	LastName           string    `json:"last_name"`
 33  	Roles              string    `json:"roles"`
 34  	AllowMarketing     bool      `json:"allow_marketing"`
 35  	NotifyProps        StringMap `json:"notify_props"`
 36  	Props              StringMap `json:"props"`
 37  	LastPasswordUpdate int64     `json:"last_password_update"`
 38  	LastPictureUpdate  int64     `json:"last_picture_update"`
 39  }
 40  
 41  type MattermostUserInfo struct {
 42  	UserID             string    `json:"id"`
 43  	CreateAt           int64     `json:"create_at"`
 44  	UpdateAt           int64     `json:"update_at"`
 45  	DeleteAt           int64     `json:"delete_at"`
 46  	Username           string    `json:"username"`
 47  	FirstName          string    `json:"first_name"`
 48  	LastName           string    `json:"last_name"`
 49  	Nickname           string    `json:"nickname"`
 50  	Email              string    `json:"email"`
 51  	EmailVerified      bool      `json:"email_verified"`
 52  	Password           string    `json:"password"`
 53  	AuthData           *string   `json:"auth_data"`
 54  	AuthService        string    `json:"auth_service"`
 55  	Roles              string    `json:"roles"`
 56  	NotifyProps        StringMap `json:"notify_props"`
 57  	Props              StringMap `json:"props,omitempty"`
 58  	LastPasswordUpdate int64     `json:"last_password_update"`
 59  	LastPictureUpdate  int64     `json:"last_picture_update"`
 60  	FailedAttempts     int       `json:"failed_attempts"`
 61  	MfaActive          bool      `json:"mfa_active"`
 62  	MfaSecret          string    `json:"mfa_secret"`
 63  }
 64  
 65  type MattermostTeamInfo struct {
 66  	TeamID          string `json:"id"`
 67  	CreateAt        int64  `json:"create_at"`
 68  	UpdateAt        int64  `json:"update_at"`
 69  	DeleteAt        int64  `json:"delete_at"`
 70  	DisplayName     string `json:"display_name"`
 71  	Name            string `json:"name"`
 72  	Email           string `json:"email"`
 73  	Type            string `json:"type"`
 74  	AllowedDomains  string `json:"allowed_domains"`
 75  	InviteID        string `json:"invite_id"`
 76  	AllowOpenInvite bool   `json:"allow_open_invite"`
 77  }
 78  
 79  type MattermostChannelInfo struct {
 80  	ChannelID     string `json:"id"`
 81  	CreateAt      int64  `json:"create_at"`
 82  	UpdateAt      int64  `json:"update_at"`
 83  	DeleteAt      int64  `json:"delete_at"`
 84  	TeamID        string `json:"team_id"`
 85  	Type          string `json:"type"`
 86  	DisplayName   string `json:"display_name"`
 87  	Name          string `json:"name"`
 88  	Header        string `json:"header"`
 89  	Purpose       string `json:"purpose"`
 90  	LastPostAt    int64  `json:"last_post_at"`
 91  	TotalMsgCount int64  `json:"total_msg_count"`
 92  	ExtraUpdateAt int64  `json:"extra_update_at"`
 93  	CreatorID     string `json:"creator_id"`
 94  }
 95  
 96  type MattermostChannelList struct {
 97  	Channels []MattermostChannelInfo
 98  }
 99  
100  type MattermostPostInfo struct {
101  	PostID        string    `json:"id"`
102  	CreateAt      int64     `json:"create_at"`
103  	UpdateAt      int64     `json:"update_at"`
104  	DeleteAt      int64     `json:"delete_at"`
105  	UserID        string    `json:"user_id"`
106  	ChannelID     string    `json:"channel_id"`
107  	RootID        string    `json:"root_id"`
108  	ParentID      string    `json:"parent_id"`
109  	OriginalID    string    `json:"original_id"`
110  	Message       string    `json:"message"`
111  	Type          string    `json:"type"`
112  	Props         StringMap `json:"props"`
113  	Hashtags      string    `json:"hashtags"`
114  	Filenames     StringMap `json:"filenames"`
115  	PendingPostID string    `json:"pending_post_id"`
116  }
117  
118  type MattermostNotifier struct {
119  	ClusterName string
120  	Url         string
121  	UserName    string
122  	Password    string
123  	Team        string
124  	Channel     string
125  	Detailed    bool
126  	NotifName   string
127  	Enabled     bool
128  
129  	/* Filled in after authentication */
130  	Initialized bool
131  	Token       string
132  	TeamID      string
133  	UserID      string
134  	ChannelID   string
135  	Text        string
136  }
137  
138  func (mattermost *MattermostNotifier) GetURL() string {
139  
140  	proto := "http"
141  	u := strings.TrimSpace(strings.ToLower(mattermost.Url))
142  	if u[:5] == "https" && u[5] == ':' {
143  		proto = "https"
144  	}
145  
146  	host := ""
147  	port := 0
148  	buf := strings.Split(u, ":")
149  	if (u[:4] == "http" && u[4] == ':') ||
150  		(u[:5] == "https" && u[5] == ':') {
151  
152  		host = strings.Trim(buf[1], "/")
153  		if len(buf) == 3 {
154  			port, _ = strconv.Atoi(strings.TrimSpace(buf[2]))
155  		}
156  
157  	} else if len(buf) == 2 {
158  		host = strings.Trim(buf[0], "/")
159  		port, _ = strconv.Atoi(strings.TrimSpace(buf[1]))
160  
161  	} else {
162  		host = strings.TrimSpace(buf[0])
163  	}
164  
165  	portstr := ""
166  	if port > 0 {
167  		portstr = fmt.Sprintf(":%d", port)
168  	}
169  
170  	return fmt.Sprintf("%s://%s%s/api/v3", proto, host, portstr)
171  }
172  
173  func (mattermost *MattermostNotifier) Authenticate() bool {
174  
175  	loginURL := fmt.Sprintf("%s/users/login", mattermost.GetURL())
176  	loginInfo := MattermostLoginInfo{LoginID: mattermost.UserName,
177  		Password: mattermost.Password}
178  
179  	buf := new(bytes.Buffer)
180  	json.NewEncoder(buf).Encode(loginInfo)
181  
182  	req, err := http.NewRequest("POST", loginURL, buf)
183  	if err != nil {
184  		log.Error("NewRequest: ", err)
185  		return false
186  	}
187  
188  	req.Header.Set("Content-Type", "application/json; charset=utf-8")
189  
190  	client := &http.Client{}
191  	resp, err := client.Do(req)
192  	if err != nil {
193  		log.Error("Do: ", err)
194  		return false
195  	}
196  
197  	defer resp.Body.Close()
198  
199  	decoder := json.NewDecoder(resp.Body)
200  	var a MattermostAuthInfo
201  	err = decoder.Decode(&a)
202  	if err != nil {
203  		log.Error("Decode: ", err)
204  		return false
205  	}
206  
207  	if buf, ok := resp.Header["Token"]; ok {
208  		if len(buf) > 0 {
209  			mattermost.Token = buf[0]
210  			return true
211  		}
212  	}
213  
214  	return false
215  }
216  
217  func (mattermost *MattermostNotifier) GetAllTeams(teams *[]MattermostTeamInfo) bool {
218  
219  	teamURL := fmt.Sprintf("%s/teams/all", mattermost.GetURL())
220  	req, err := http.NewRequest("GET", teamURL, nil)
221  	if err != nil {
222  		log.Error("NewRequest: ", err)
223  		return false
224  	}
225  
226  	authorization := fmt.Sprintf("Bearer %s", mattermost.Token)
227  	req.Header.Set("Authorization", authorization)
228  	req.Header.Set("Content-Type", "application/json; charset=utf-8")
229  
230  	client := &http.Client{}
231  	resp, err := client.Do(req)
232  	if err != nil {
233  		log.Error("Do: ", err)
234  		return false
235  	}
236  
237  	defer resp.Body.Close()
238  
239  	decoder := json.NewDecoder(resp.Body)
240  	var buf map[string]*MattermostTeamInfo
241  	err = decoder.Decode(&buf)
242  	if err != nil {
243  		log.Error("Decode: ", err)
244  		return false
245  	}
246  
247  	if len(buf) > 0 {
248  		for _, value := range buf {
249  			*teams = append(*teams, *value)
250  		}
251  		return true
252  	}
253  
254  	return false
255  }
256  
257  func (mattermost *MattermostNotifier) GetUser(userID string, userInfo *MattermostUserInfo) bool {
258  
259  	if userID == "" || userInfo == nil {
260  		return false
261  	}
262  
263  	userURL := fmt.Sprintf("%s/users/%s/get", mattermost.GetURL(), userID)
264  
265  	req, err := http.NewRequest("GET", userURL, nil)
266  	if err != nil {
267  		log.Error("NewRequest: ", err)
268  		return false
269  	}
270  
271  	authorization := fmt.Sprintf("Bearer %s", mattermost.Token)
272  	req.Header.Set("Authorization", authorization)
273  	req.Header.Set("Content-Type", "application/json; charset=utf-8")
274  
275  	client := &http.Client{}
276  	resp, err := client.Do(req)
277  	if err != nil {
278  		log.Error("Do: ", err)
279  		return false
280  	}
281  
282  	defer resp.Body.Close()
283  
284  	decoder := json.NewDecoder(resp.Body)
285  	err = decoder.Decode(userInfo)
286  	if err != nil {
287  		log.Error("Decode: ", err)
288  		return false
289  	}
290  
291  	return true
292  }
293  
294  func (mattermost *MattermostNotifier) GetMe(me *MattermostUserInfo) bool {
295  
296  	if me == nil {
297  		return false
298  	}
299  
300  	userURL := fmt.Sprintf("%s/users/me", mattermost.GetURL())
301  
302  	req, err := http.NewRequest("GET", userURL, nil)
303  	if err != nil {
304  		log.Error("NewRequest: ", err)
305  		return false
306  	}
307  
308  	authorization := fmt.Sprintf("Bearer %s", mattermost.Token)
309  	req.Header.Set("Authorization", authorization)
310  	req.Header.Set("Content-Type", "application/json; charset=utf-8")
311  
312  	client := &http.Client{}
313  	resp, err := client.Do(req)
314  	if err != nil {
315  		log.Error("Do: ", err)
316  		return false
317  	}
318  
319  	defer resp.Body.Close()
320  
321  	decoder := json.NewDecoder(resp.Body)
322  	err = decoder.Decode(me)
323  	if err != nil {
324  		log.Error("Decode: ", err)
325  		return false
326  	}
327  
328  	return true
329  }
330  
331  func (mattermost *MattermostNotifier) GetTeam(teamID string, teamInfo *MattermostTeamInfo) bool {
332  
333  	if teamID == "" || teamInfo == nil {
334  		return false
335  	}
336  
337  	teamURL := fmt.Sprintf("%s/teams/%s/me", mattermost.GetURL(), teamID)
338  
339  	req, err := http.NewRequest("GET", teamURL, nil)
340  	if err != nil {
341  		log.Error("NewRequest: ", err)
342  		return false
343  	}
344  
345  	authorization := fmt.Sprintf("Bearer %s", mattermost.Token)
346  	req.Header.Set("Authorization", authorization)
347  	req.Header.Set("Content-Type", "application/json; charset=utf-8")
348  
349  	client := &http.Client{}
350  	resp, err := client.Do(req)
351  	if err != nil {
352  		log.Error("Do: ", err)
353  		return false
354  	}
355  
356  	defer resp.Body.Close()
357  
358  	decoder := json.NewDecoder(resp.Body)
359  	err = decoder.Decode(teamInfo)
360  	if err != nil {
361  		log.Error("Decode: ", err)
362  		return false
363  	}
364  
365  	return true
366  }
367  
368  func (mattermost *MattermostNotifier) GetChannels(teamID string, channels *[]MattermostChannelInfo) bool {
369  
370  	if teamID == "" || channels == nil {
371  		return false
372  	}
373  
374  	channelURL := fmt.Sprintf("%s/teams/%s/channels/", mattermost.GetURL(), teamID)
375  	req, err := http.NewRequest("GET", channelURL, nil)
376  	if err != nil {
377  		log.Error("NewRequest: ", err)
378  		return false
379  	}
380  
381  	authorization := fmt.Sprintf("Bearer %s", mattermost.Token)
382  	req.Header.Set("Authorization", authorization)
383  	req.Header.Set("Content-Type", "application/json; charset=utf-8")
384  
385  	client := &http.Client{}
386  	resp, err := client.Do(req)
387  	if err != nil {
388  		log.Error("Do: ", err)
389  		return false
390  	}
391  
392  	defer resp.Body.Close()
393  
394  	decoder := json.NewDecoder(resp.Body)
395  	fc := &MattermostChannelList{}
396  	err = decoder.Decode(&fc)
397  	if err != nil {
398  		log.Error("Decode: ", err)
399  		return false
400  	}
401  	*channels = fc.Channels
402  
403  	return true
404  }
405  
406  func (mattermost *MattermostNotifier) PostMessage(teamID string, channelID string, postInfo *MattermostPostInfo) bool {
407  
408  	if teamID == "" || channelID == "" || postInfo == nil {
409  		return false
410  	}
411  
412  	postURL := fmt.Sprintf("%s/teams/%s/channels/%s/posts/create",
413  		mattermost.GetURL(), teamID, channelID)
414  
415  	buf := new(bytes.Buffer)
416  	encoder := json.NewEncoder(buf)
417  	err := encoder.Encode(*postInfo)
418  
419  	req, err := http.NewRequest("POST", postURL, buf)
420  	if err != nil {
421  		log.Error("NewRequest: ", err)
422  		return false
423  	}
424  
425  	authorization := fmt.Sprintf("Bearer %s", mattermost.Token)
426  	req.Header.Set("Authorization", authorization)
427  	req.Header.Set("Content-Type", "application/json; charset=utf-8")
428  
429  	client := &http.Client{}
430  	resp, err := client.Do(req)
431  	if err != nil {
432  		log.Error("Do: ", err)
433  		return false
434  	}
435  
436  	defer resp.Body.Close()
437  
438  	decoder := json.NewDecoder(resp.Body)
439  	var p MattermostPostInfo
440  	err = decoder.Decode(&p)
441  	if err != nil {
442  		log.Error("Decode: ", err)
443  		return false
444  	}
445  	*postInfo = p
446  
447  	return false
448  }
449  
450  func (mattermost *MattermostNotifier) Init() bool {
451  	if mattermost.Initialized == true {
452  		return true
453  	}
454  
455  	if mattermost.Token == "" && !mattermost.Authenticate() {
456  		log.Println("Mattermost: Unable to authenticate!")
457  		return false
458  	}
459  
460  	if mattermost.TeamID == "" {
461  		var teams []MattermostTeamInfo
462  
463  		if !mattermost.GetAllTeams(&teams) {
464  			log.Println("Mattermost: Unable to get teams!")
465  			return false
466  		}
467  
468  		for i := 0; i < len(teams); i++ {
469  			if teams[i].Name == mattermost.Team {
470  				mattermost.TeamID = teams[i].TeamID
471  				break
472  			}
473  		}
474  
475  		if mattermost.TeamID == "" {
476  			log.Println("Mattermost: Unable to find team!")
477  			return false
478  		}
479  	}
480  
481  	if mattermost.UserID == "" {
482  		var me MattermostUserInfo
483  
484  		if !mattermost.GetMe(&me) {
485  			log.Println("Mattermost: Unable to get user!")
486  			return false
487  		}
488  
489  		if me.UserID == "" {
490  			log.Println("Mattermost: Unable to get user ID!")
491  			return false
492  		}
493  
494  		mattermost.UserID = me.UserID
495  	}
496  
497  	if mattermost.ChannelID == "" {
498  		var channels []MattermostChannelInfo
499  
500  		if !mattermost.GetChannels(mattermost.TeamID, &channels) {
501  			log.Println("Mattermost: Unable to get channels!")
502  			return false
503  		}
504  
505  		for i := 0; i < len(channels); i++ {
506  			if channels[i].Name == mattermost.Channel {
507  				mattermost.ChannelID = channels[i].ChannelID
508  				break
509  			}
510  		}
511  
512  		if mattermost.ChannelID == "" {
513  			log.Println("Mattermost: Unable to find channel!")
514  			return false
515  		}
516  	}
517  
518  	mattermost.Initialized = true
519  	return true
520  }
521  
522  // NotifierName provides name for notifier selection
523  func (mattermost *MattermostNotifier) NotifierName() string {
524  	return "mattermost"
525  }
526  
527  func (mattermost *MattermostNotifier) Copy() Notifier {
528  	notifier := *mattermost
529  	return &notifier
530  }
531  
532  //Notify sends messages to the endpoint notifier
533  func (mattermost *MattermostNotifier) Notify(messages Messages) bool {
534  	if !mattermost.Init() {
535  		return false
536  	}
537  
538  	if mattermost.Detailed {
539  		return mattermost.notifyDetailed(messages)
540  	}
541  
542  	return mattermost.notifySimple(messages)
543  }
544  
545  func (mattermost *MattermostNotifier) notifySimple(messages Messages) bool {
546  	overallStatus, pass, warn, fail := messages.Summary()
547  
548  	text := fmt.Sprintf(header, mattermost.ClusterName, overallStatus, fail, warn, pass)
549  
550  	for _, message := range messages {
551  		text += fmt.Sprintf("\n%s:%s:%s is %s.",
552  			message.Node, message.Service, message.Check, message.Status)
553  		text += fmt.Sprintf("\n%s\n\n", message.Output)
554  	}
555  
556  	mattermost.Text = text
557  
558  	return mattermost.postToMattermost()
559  }
560  
561  func (mattermost *MattermostNotifier) notifyDetailed(messages Messages) bool {
562  
563  	overallStatus, pass, warn, fail := messages.Summary()
564  
565  	var emoji string
566  	switch overallStatus {
567  	case SYSTEM_HEALTHY:
568  		emoji = ":white_check_mark:"
569  	case SYSTEM_UNSTABLE:
570  		emoji = ":question:"
571  	case SYSTEM_CRITICAL:
572  		emoji = ":x:"
573  	default:
574  		emoji = ":question:"
575  	}
576  	title := "Consul monitoring report"
577  	pretext := fmt.Sprintf("%s %s is *%s*", emoji, mattermost.ClusterName, overallStatus)
578  
579  	detailedBody := ""
580  	detailedBody += fmt.Sprintf("*Changes:* Fail = %d, Warn = %d, Pass = %d",
581  		fail, warn, pass)
582  	detailedBody += fmt.Sprintf("\n")
583  
584  	for _, message := range messages {
585  		detailedBody += fmt.Sprintf("\n*[%s:%s]* %s is *%s.*",
586  			message.Node, message.Service, message.Check, message.Status)
587  		detailedBody += fmt.Sprintf("\n`%s`", strings.TrimSpace(message.Output))
588  	}
589  
590  	mattermost.Text = fmt.Sprintf("%s\n%s\n%s\n\n", title, pretext, detailedBody)
591  
592  	return mattermost.postToMattermost()
593  
594  }
595  
596  func (mattermost *MattermostNotifier) postToMattermost() bool {
597  	var postInfo = MattermostPostInfo{
598  		ChannelID: mattermost.ChannelID,
599  		Message:   mattermost.Text}
600  
601  	return mattermost.PostMessage(mattermost.TeamID, mattermost.ChannelID, &postInfo)
602  }