2015-10-11 15:24:49 +00:00
// Copyright 2015 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2015-09-29 13:12:31 +00:00
package notify
import (
"bytes"
2017-04-21 09:43:12 +00:00
"crypto/sha256"
2015-10-09 10:03:15 +00:00
"crypto/tls"
2015-09-29 13:12:31 +00:00
"encoding/json"
2016-07-04 14:09:38 +00:00
"errors"
2015-09-29 13:12:31 +00:00
"fmt"
2016-02-26 08:35:00 +00:00
"io/ioutil"
2015-10-29 13:34:24 +00:00
"mime"
2017-09-07 15:24:19 +00:00
"mime/multipart"
2015-10-09 10:03:15 +00:00
"net"
2015-09-29 13:12:31 +00:00
"net/http"
2015-10-29 13:34:24 +00:00
"net/mail"
2015-10-09 10:03:15 +00:00
"net/smtp"
2017-10-22 05:59:33 +00:00
"net/textproto"
2016-02-26 08:35:00 +00:00
"net/url"
2015-10-09 10:03:15 +00:00
"strings"
"time"
2015-09-29 13:12:31 +00:00
2017-10-22 05:59:33 +00:00
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
2015-10-01 13:46:10 +00:00
"github.com/prometheus/common/model"
2017-07-11 09:22:13 +00:00
"github.com/prometheus/common/version"
2015-09-29 13:12:31 +00:00
"golang.org/x/net/context"
"golang.org/x/net/context/ctxhttp"
"github.com/prometheus/alertmanager/config"
2015-10-11 11:32:24 +00:00
"github.com/prometheus/alertmanager/template"
2015-09-29 13:12:31 +00:00
"github.com/prometheus/alertmanager/types"
)
2015-12-17 12:05:06 +00:00
type notifierConfig interface {
SendResolved ( ) bool
}
2016-08-12 17:18:26 +00:00
// A Notifier notifies about alerts under constraints of the given context.
2016-09-05 15:51:03 +00:00
// It returns an error if unsuccessful and a flag whether the error is
// recoverable. This information is useful for a retry logic.
2016-08-12 17:18:26 +00:00
type Notifier interface {
2016-09-05 15:51:03 +00:00
Notify ( context . Context , ... * types . Alert ) ( bool , error )
2015-12-17 12:05:06 +00:00
}
2016-08-12 17:18:26 +00:00
// An Integration wraps a notifier and its config to be uniquely identified by
// name and index from its origin in the configuration.
type Integration struct {
notifier Notifier
conf notifierConfig
name string
idx int
2016-01-09 13:57:04 +00:00
}
2016-08-12 17:18:26 +00:00
// Notify implements the Notifier interface.
2016-09-05 15:51:03 +00:00
func ( i * Integration ) Notify ( ctx context . Context , alerts ... * types . Alert ) ( bool , error ) {
2016-10-05 14:28:04 +00:00
var res [ ] * types . Alert
// Resolved alerts have to be filtered only at this point, because they need
// to end up unfiltered in the SetNotifiesStage.
if i . conf . SendResolved ( ) {
res = alerts
} else {
for _ , a := range alerts {
if a . Status ( ) != model . AlertResolved {
res = append ( res , a )
}
}
}
if len ( res ) == 0 {
return false , nil
}
2017-01-04 12:50:40 +00:00
return i . notifier . Notify ( ctx , res ... )
2016-08-12 17:18:26 +00:00
}
2015-10-11 13:37:44 +00:00
2016-08-12 17:18:26 +00:00
// BuildReceiverIntegrations builds a list of integration notifiers off of a
// receivers config.
2017-10-22 05:59:33 +00:00
func BuildReceiverIntegrations ( nc * config . Receiver , tmpl * template . Template , logger log . Logger ) [ ] Integration {
2016-08-12 17:18:26 +00:00
var (
integrations [ ] Integration
add = func ( name string , i int , n Notifier , nc notifierConfig ) {
integrations = append ( integrations , Integration {
notifier : n ,
conf : nc ,
name : name ,
idx : i ,
} )
2016-02-26 08:35:00 +00:00
}
2016-08-12 17:18:26 +00:00
)
2015-10-11 13:37:44 +00:00
2016-08-12 17:18:26 +00:00
for i , c := range nc . WebhookConfigs {
2017-10-22 05:59:33 +00:00
n := NewWebhook ( c , tmpl , logger )
2016-08-12 17:18:26 +00:00
add ( "webhook" , i , n , c )
}
for i , c := range nc . EmailConfigs {
2017-10-22 05:59:33 +00:00
n := NewEmail ( c , tmpl , logger )
2016-08-12 17:18:26 +00:00
add ( "email" , i , n , c )
}
for i , c := range nc . PagerdutyConfigs {
2017-10-22 05:59:33 +00:00
n := NewPagerDuty ( c , tmpl , logger )
2016-08-12 17:18:26 +00:00
add ( "pagerduty" , i , n , c )
}
for i , c := range nc . OpsGenieConfigs {
2017-10-22 05:59:33 +00:00
n := NewOpsGenie ( c , tmpl , logger )
2016-08-12 17:18:26 +00:00
add ( "opsgenie" , i , n , c )
}
for i , c := range nc . SlackConfigs {
2017-10-22 05:59:33 +00:00
n := NewSlack ( c , tmpl , logger )
2016-08-12 17:18:26 +00:00
add ( "slack" , i , n , c )
}
for i , c := range nc . HipchatConfigs {
2017-10-22 05:59:33 +00:00
n := NewHipchat ( c , tmpl , logger )
2016-08-12 17:18:26 +00:00
add ( "hipchat" , i , n , c )
2015-10-11 13:37:44 +00:00
}
2016-08-12 17:18:26 +00:00
for i , c := range nc . VictorOpsConfigs {
2017-10-22 05:59:33 +00:00
n := NewVictorOps ( c , tmpl , logger )
2016-08-12 17:18:26 +00:00
add ( "victorops" , i , n , c )
}
for i , c := range nc . PushoverConfigs {
2017-10-22 05:59:33 +00:00
n := NewPushover ( c , tmpl , logger )
2016-08-12 17:18:26 +00:00
add ( "pushover" , i , n , c )
}
return integrations
2015-10-11 13:37:44 +00:00
}
2015-10-01 13:46:10 +00:00
const contentTypeJSON = "application/json"
2017-07-11 09:22:13 +00:00
var userAgentHeader = fmt . Sprintf ( "Alertmanager/%s" , version . Version )
2015-11-12 12:18:36 +00:00
// Webhook implements a Notifier for generic webhooks.
2015-09-29 13:12:31 +00:00
type Webhook struct {
2015-11-12 12:18:36 +00:00
// The URL to which notifications are sent.
2017-10-22 05:59:33 +00:00
URL string
tmpl * template . Template
logger log . Logger
2015-09-29 13:12:31 +00:00
}
2015-11-12 12:18:36 +00:00
// NewWebhook returns a new Webhook.
2017-10-22 05:59:33 +00:00
func NewWebhook ( conf * config . WebhookConfig , t * template . Template , l log . Logger ) * Webhook {
return & Webhook { URL : conf . URL , tmpl : t , logger : l }
2015-09-29 13:12:31 +00:00
}
2015-11-12 12:18:36 +00:00
// WebhookMessage defines the JSON object send to webhook endpoints.
2015-09-29 13:12:31 +00:00
type WebhookMessage struct {
2016-02-10 16:28:36 +00:00
* template . Data
2015-11-12 12:18:36 +00:00
// The protocol version.
2016-02-10 16:28:36 +00:00
Version string ` json:"version" `
2017-04-21 09:43:12 +00:00
GroupKey string ` json:"groupKey" `
2015-09-29 13:12:31 +00:00
}
2015-11-12 12:18:36 +00:00
// Notify implements the Notifier interface.
2016-09-05 15:51:03 +00:00
func ( w * Webhook ) Notify ( ctx context . Context , alerts ... * types . Alert ) ( bool , error ) {
2017-10-22 05:59:33 +00:00
data := w . tmpl . Data ( receiverName ( ctx , w . logger ) , groupLabels ( ctx , w . logger ) , alerts ... )
2015-12-07 16:55:37 +00:00
2016-02-10 16:28:36 +00:00
groupKey , ok := GroupKey ( ctx )
2016-02-09 13:36:40 +00:00
if ! ok {
2017-10-22 05:59:33 +00:00
level . Error ( w . logger ) . Log ( "msg" , "group key missing" )
2016-02-09 13:36:40 +00:00
}
2015-09-29 13:12:31 +00:00
msg := & WebhookMessage {
2017-04-21 09:43:12 +00:00
Version : "4" ,
2016-02-10 16:28:36 +00:00
Data : data ,
2017-04-21 09:43:12 +00:00
GroupKey : groupKey ,
2015-09-29 13:12:31 +00:00
}
var buf bytes . Buffer
if err := json . NewEncoder ( & buf ) . Encode ( msg ) ; err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2015-09-29 13:12:31 +00:00
}
2017-07-11 09:22:13 +00:00
req , err := http . NewRequest ( "POST" , w . URL , & buf )
if err != nil {
return true , err
}
req . Header . Set ( "Content-Type" , contentTypeJSON )
req . Header . Set ( "User-Agent" , userAgentHeader )
resp , err := ctxhttp . Do ( ctx , http . DefaultClient , req )
2015-09-29 13:12:31 +00:00
if err != nil {
2016-09-05 15:51:03 +00:00
return true , err
2015-09-29 13:12:31 +00:00
}
resp . Body . Close ( )
2016-09-05 15:51:03 +00:00
return w . retry ( resp . StatusCode )
}
func ( w * Webhook ) retry ( statusCode int ) ( bool , error ) {
// Webhooks are assumed to respond with 2xx response codes on a successful
// request and 5xx response codes are assumed to be recoverable.
if statusCode / 100 != 2 {
return ( statusCode / 100 == 5 ) , fmt . Errorf ( "unexpected status code %v from %s" , statusCode , w . URL )
2015-09-29 13:12:31 +00:00
}
2016-09-05 15:51:03 +00:00
return false , nil
2015-09-29 13:12:31 +00:00
}
2015-10-09 08:48:25 +00:00
2015-11-12 12:18:36 +00:00
// Email implements a Notifier for email notifications.
2015-10-09 10:03:15 +00:00
type Email struct {
2017-10-22 05:59:33 +00:00
conf * config . EmailConfig
tmpl * template . Template
logger log . Logger
2015-10-09 10:03:15 +00:00
}
2015-11-12 12:18:36 +00:00
// NewEmail returns a new Email notifier.
2017-10-22 05:59:33 +00:00
func NewEmail ( c * config . EmailConfig , t * template . Template , l log . Logger ) * Email {
2015-12-07 14:39:07 +00:00
if _ , ok := c . Headers [ "Subject" ] ; ! ok {
c . Headers [ "Subject" ] = config . DefaultEmailSubject
}
if _ , ok := c . Headers [ "To" ] ; ! ok {
c . Headers [ "To" ] = c . To
}
if _ , ok := c . Headers [ "From" ] ; ! ok {
c . Headers [ "From" ] = c . From
}
2017-10-22 05:59:33 +00:00
return & Email { conf : c , tmpl : t , logger : l }
2015-10-09 10:03:15 +00:00
}
2015-11-12 12:18:36 +00:00
// auth resolves a string of authentication mechanisms.
2016-03-04 18:42:06 +00:00
func ( n * Email ) auth ( mechs string ) ( smtp . Auth , error ) {
2016-04-15 03:12:47 +00:00
username := n . conf . AuthUsername
2015-10-09 10:03:15 +00:00
for _ , mech := range strings . Split ( mechs , " " ) {
switch mech {
case "CRAM-MD5" :
2016-04-15 03:12:47 +00:00
secret := string ( n . conf . AuthSecret )
2015-10-09 10:03:15 +00:00
if secret == "" {
continue
}
2016-03-04 18:42:06 +00:00
return smtp . CRAMMD5Auth ( username , secret ) , nil
2015-10-09 10:03:15 +00:00
case "PLAIN" :
2016-04-15 03:12:47 +00:00
password := string ( n . conf . AuthPassword )
2015-10-09 10:03:15 +00:00
if password == "" {
continue
}
2016-04-15 03:12:47 +00:00
identity := n . conf . AuthIdentity
2015-10-09 10:03:15 +00:00
// We need to know the hostname for both auth and TLS.
host , _ , err := net . SplitHostPort ( n . conf . Smarthost )
if err != nil {
2016-03-04 18:42:06 +00:00
return nil , fmt . Errorf ( "invalid address: %s" , err )
2015-10-09 10:03:15 +00:00
}
2016-03-04 18:42:06 +00:00
return smtp . PlainAuth ( identity , username , password , host ) , nil
2016-07-04 14:09:38 +00:00
case "LOGIN" :
password := string ( n . conf . AuthPassword )
if password == "" {
continue
}
return LoginAuth ( username , password ) , nil
2015-10-09 10:03:15 +00:00
}
}
2016-03-04 18:42:06 +00:00
return nil , nil
2015-10-09 10:03:15 +00:00
}
2015-11-12 12:18:36 +00:00
// Notify implements the Notifier interface.
2016-09-05 15:51:03 +00:00
func ( n * Email ) Notify ( ctx context . Context , as ... * types . Alert ) ( bool , error ) {
2016-03-04 18:42:06 +00:00
// We need to know the hostname for both auth and TLS.
2017-06-18 10:18:10 +00:00
var c * smtp . Client
host , port , err := net . SplitHostPort ( n . conf . Smarthost )
2016-03-04 18:42:06 +00:00
if err != nil {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "invalid address: %s" , err )
2016-03-04 18:42:06 +00:00
}
2017-06-18 10:18:10 +00:00
if port == "465" {
conn , err := tls . Dial ( "tcp" , n . conf . Smarthost , & tls . Config { ServerName : host } )
if err != nil {
return true , err
}
c , err = smtp . NewClient ( conn , n . conf . Smarthost )
if err != nil {
return true , err
}
} else {
// Connect to the SMTP smarthost.
c , err = smtp . Dial ( n . conf . Smarthost )
if err != nil {
return true , err
}
}
defer c . Quit ( )
2017-07-18 07:32:57 +00:00
if n . conf . Hello != "" {
err := c . Hello ( n . conf . Hello )
if err != nil {
return true , err
}
}
2016-09-27 09:00:21 +00:00
// Global Config guarantees RequireTLS is not nil
if * n . conf . RequireTLS {
2016-03-04 18:42:06 +00:00
if ok , _ := c . Extension ( "STARTTLS" ) ; ! ok {
2016-09-05 15:51:03 +00:00
return true , fmt . Errorf ( "require_tls: true (default), but %q does not advertise the STARTTLS extension" , n . conf . Smarthost )
2016-03-04 18:42:06 +00:00
}
tlsConf := & tls . Config { ServerName : host }
if err := c . StartTLS ( tlsConf ) ; err != nil {
2016-09-05 15:51:03 +00:00
return true , fmt . Errorf ( "starttls failed: %s" , err )
2016-03-04 18:42:06 +00:00
}
}
2015-10-09 10:03:15 +00:00
if ok , mech := c . Extension ( "AUTH" ) ; ok {
2016-03-04 18:42:06 +00:00
auth , err := n . auth ( mech )
2015-10-09 10:03:15 +00:00
if err != nil {
2016-09-05 15:51:03 +00:00
return true , err
2015-10-09 10:03:15 +00:00
}
if auth != nil {
if err := c . Auth ( auth ) ; err != nil {
2016-09-05 15:51:03 +00:00
return true , fmt . Errorf ( "%T failed: %s" , auth , err )
2015-10-09 10:03:15 +00:00
}
}
}
2015-11-25 14:49:26 +00:00
var (
2017-10-22 05:59:33 +00:00
data = n . tmpl . Data ( receiverName ( ctx , n . logger ) , groupLabels ( ctx , n . logger ) , as ... )
2015-11-25 14:49:26 +00:00
tmpl = tmplText ( n . tmpl , data , & err )
from = tmpl ( n . conf . From )
to = tmpl ( n . conf . To )
)
2015-10-29 13:34:24 +00:00
if err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2015-10-11 10:34:05 +00:00
}
2015-11-25 14:49:26 +00:00
2015-10-29 13:34:24 +00:00
addrs , err := mail . ParseAddressList ( from )
if err != nil {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "parsing from addresses: %s" , err )
2015-10-29 13:34:24 +00:00
}
if len ( addrs ) != 1 {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "must be exactly one from address" )
2015-10-29 13:34:24 +00:00
}
if err := c . Mail ( addrs [ 0 ] . Address ) ; err != nil {
2016-09-05 15:51:03 +00:00
return true , fmt . Errorf ( "sending mail from: %s" , err )
2015-10-29 13:34:24 +00:00
}
addrs , err = mail . ParseAddressList ( to )
if err != nil {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "parsing to addresses: %s" , err )
2015-10-29 13:34:24 +00:00
}
for _ , addr := range addrs {
if err := c . Rcpt ( addr . Address ) ; err != nil {
2016-09-05 15:51:03 +00:00
return true , fmt . Errorf ( "sending rcpt to: %s" , err )
2015-10-29 13:34:24 +00:00
}
2015-10-11 10:34:05 +00:00
}
2015-10-09 10:03:15 +00:00
// Send the email body.
wc , err := c . Data ( )
if err != nil {
2016-09-05 15:51:03 +00:00
return true , err
2015-10-09 10:03:15 +00:00
}
defer wc . Close ( )
2015-12-07 14:39:07 +00:00
for header , t := range n . conf . Headers {
value , err := n . tmpl . ExecuteTextString ( t , data )
2015-10-29 13:34:24 +00:00
if err != nil {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "executing %q header template: %s" , header , err )
2015-10-29 13:34:24 +00:00
}
fmt . Fprintf ( wc , "%s: %s\r\n" , header , mime . QEncoding . Encode ( "utf-8" , value ) )
2015-10-11 10:34:05 +00:00
}
2015-11-25 14:49:26 +00:00
2017-09-07 15:24:19 +00:00
buffer := & bytes . Buffer { }
multipartWriter := multipart . NewWriter ( buffer )
2015-10-29 13:34:24 +00:00
fmt . Fprintf ( wc , "Date: %s\r\n" , time . Now ( ) . Format ( time . RFC1123Z ) )
2017-09-07 15:24:19 +00:00
fmt . Fprintf ( wc , "Content-Type: multipart/alternative; boundary=%s\r\n" , multipartWriter . Boundary ( ) )
2017-09-29 09:22:38 +00:00
fmt . Fprintf ( wc , "MIME-Version: 1.0\r\n" )
2017-09-07 15:24:19 +00:00
2015-10-29 13:34:24 +00:00
// TODO: Add some useful headers here, such as URL of the alertmanager
// and active/resolved.
fmt . Fprintf ( wc , "\r\n" )
2015-10-11 10:34:05 +00:00
2017-09-29 09:22:38 +00:00
if len ( n . conf . Text ) > 0 {
// Text template
w , err := multipartWriter . CreatePart ( textproto . MIMEHeader { "Content-Type" : { "text/plain; charset=UTF-8" } } )
if err != nil {
return false , fmt . Errorf ( "creating part for text template: %s" , err )
}
body , err := n . tmpl . ExecuteTextString ( n . conf . Text , data )
if err != nil {
return false , fmt . Errorf ( "executing email text template: %s" , err )
}
_ , err = w . Write ( [ ] byte ( body ) )
if err != nil {
return true , err
}
2017-09-07 15:24:19 +00:00
}
2017-09-29 09:22:38 +00:00
if len ( n . conf . HTML ) > 0 {
// Html template
// Preferred alternative placed last per section 5.1.4 of RFC 2046
// https://www.ietf.org/rfc/rfc2046.txt
w , err := multipartWriter . CreatePart ( textproto . MIMEHeader { "Content-Type" : { "text/html; charset=UTF-8" } } )
if err != nil {
return false , fmt . Errorf ( "creating part for html template: %s" , err )
}
body , err := n . tmpl . ExecuteHTMLString ( n . conf . HTML , data )
if err != nil {
return false , fmt . Errorf ( "executing email html template: %s" , err )
}
_ , err = w . Write ( [ ] byte ( body ) )
if err != nil {
return true , err
}
2016-09-05 15:51:03 +00:00
}
2015-11-25 14:49:26 +00:00
2017-09-07 15:24:19 +00:00
multipartWriter . Close ( )
wc . Write ( buffer . Bytes ( ) )
2016-09-05 15:51:03 +00:00
return false , nil
2015-10-09 10:03:15 +00:00
}
2015-11-12 12:18:36 +00:00
// PagerDuty implements a Notifier for PagerDuty notifications.
2015-10-19 09:45:52 +00:00
type PagerDuty struct {
2017-10-22 05:59:33 +00:00
conf * config . PagerdutyConfig
tmpl * template . Template
logger log . Logger
2015-10-19 09:45:52 +00:00
}
2015-11-12 12:18:36 +00:00
// NewPagerDuty returns a new PagerDuty notifier.
2017-10-22 05:59:33 +00:00
func NewPagerDuty ( c * config . PagerdutyConfig , t * template . Template , l log . Logger ) * PagerDuty {
return & PagerDuty { conf : c , tmpl : t , logger : l }
2015-10-19 09:45:52 +00:00
}
const (
pagerDutyEventTrigger = "trigger"
pagerDutyEventResolve = "resolve"
)
type pagerDutyMessage struct {
2017-11-07 10:07:27 +00:00
RoutingKey string ` json:"routing_key,omitempty" `
ServiceKey string ` json:"service_key,omitempty" `
DedupKey string ` json:"dedup_key,omitempty" `
IncidentKey string ` json:"incident_key,omitempty" `
EventType string ` json:"event_type,omitempty" `
Description string ` json:"description,omitempty" `
EventAction string ` json:"event_action" `
Payload * pagerDutyPayload ` json:"payload" `
2015-10-19 09:45:52 +00:00
Client string ` json:"client,omitempty" `
ClientURL string ` json:"client_url,omitempty" `
2015-11-26 17:19:46 +00:00
Details map [ string ] string ` json:"details,omitempty" `
2015-10-19 09:45:52 +00:00
}
2017-11-07 10:07:27 +00:00
type pagerDutyPayload struct {
Summary string ` json:"summary" `
Source string ` json:"source" `
Severity string ` json:"severity" `
Timestamp string ` json:"timestamp,omitempty" `
Component string ` json:"component,omitempty" `
Group string ` json:"group,omitempty" `
CustomDetails map [ string ] string ` json:"custom_details,omitempty" `
}
2017-11-07 10:32:37 +00:00
func ( n * PagerDuty ) notifyV1 ( ctx context . Context , eventType , key string , tmpl func ( string ) string , details map [ string ] string , as ... * types . Alert ) ( bool , error ) {
level . Info ( n . logger ) . Log ( "msg" , "PagerDuty v1 API will no longer be supported: https://v2.developer.pagerduty.com/v2/docs/api-v2-frequently-asked-questions" )
2017-11-07 10:07:27 +00:00
2017-11-07 10:32:37 +00:00
msg := & pagerDutyMessage {
ServiceKey : string ( n . conf . ServiceKey ) ,
EventType : eventType ,
IncidentKey : hashKey ( key ) ,
Description : tmpl ( n . conf . Description ) ,
Details : details ,
}
2017-11-07 10:07:27 +00:00
n . conf . URL = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"
if eventType == pagerDutyEventTrigger {
msg . Client = tmpl ( n . conf . Client )
msg . ClientURL = tmpl ( n . conf . ClientURL )
}
var buf bytes . Buffer
if err := json . NewEncoder ( & buf ) . Encode ( msg ) ; err != nil {
return false , err
}
resp , err := ctxhttp . Post ( ctx , http . DefaultClient , n . conf . URL , contentTypeJSON , & buf )
if err != nil {
return true , err
}
2017-11-07 10:32:37 +00:00
defer resp . Body . Close ( )
2017-11-07 10:07:27 +00:00
return n . retryV1 ( resp . StatusCode )
}
2017-11-07 10:32:37 +00:00
func ( n * PagerDuty ) notifyV2 ( ctx context . Context , eventType , key string , tmpl func ( string ) string , details map [ string ] string , as ... * types . Alert ) ( bool , error ) {
2017-11-07 10:07:27 +00:00
if n . conf . Severity == "" {
n . conf . Severity = "error"
}
2017-11-07 10:32:37 +00:00
var payload * pagerDutyPayload
2017-11-07 10:07:27 +00:00
if eventType == pagerDutyEventTrigger {
2017-11-07 10:32:37 +00:00
payload = & pagerDutyPayload {
2017-11-07 10:07:27 +00:00
Summary : tmpl ( n . conf . Description ) ,
Source : n . conf . Client ,
Severity : n . conf . Severity ,
CustomDetails : details ,
Component : n . conf . Component ,
Group : n . conf . Group ,
}
2017-11-07 10:32:37 +00:00
}
msg := & pagerDutyMessage {
RoutingKey : string ( n . conf . RoutingKey ) ,
EventAction : eventType ,
DedupKey : hashKey ( key ) ,
Payload : payload ,
2017-11-07 10:07:27 +00:00
}
if eventType == pagerDutyEventTrigger {
msg . Client = tmpl ( n . conf . Client )
msg . ClientURL = tmpl ( n . conf . ClientURL )
}
var buf bytes . Buffer
if err := json . NewEncoder ( & buf ) . Encode ( msg ) ; err != nil {
return false , err
}
resp , err := ctxhttp . Post ( ctx , http . DefaultClient , n . conf . URL , contentTypeJSON , & buf )
if err != nil {
return true , err
}
2017-11-07 10:32:37 +00:00
defer resp . Body . Close ( )
2017-11-07 10:07:27 +00:00
return n . retryV2 ( resp . StatusCode )
}
2015-11-12 12:18:36 +00:00
// Notify implements the Notifier interface.
2015-11-25 14:49:26 +00:00
//
2017-11-07 10:07:27 +00:00
// https://v2.developer.pagerduty.com/docs/events-api-v2
2016-09-05 15:51:03 +00:00
func ( n * PagerDuty ) Notify ( ctx context . Context , as ... * types . Alert ) ( bool , error ) {
2015-10-21 11:08:53 +00:00
key , ok := GroupKey ( ctx )
if ! ok {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "group key missing" )
2015-10-21 11:08:53 +00:00
}
var err error
2015-11-25 14:49:26 +00:00
var (
alerts = types . Alerts ( as ... )
2017-10-22 05:59:33 +00:00
data = n . tmpl . Data ( receiverName ( ctx , n . logger ) , groupLabels ( ctx , n . logger ) , as ... )
2015-11-25 14:49:26 +00:00
tmpl = tmplText ( n . tmpl , data , & err )
eventType = pagerDutyEventTrigger
)
if alerts . Status ( ) == model . AlertResolved {
eventType = pagerDutyEventResolve
2015-10-21 11:08:53 +00:00
}
2015-11-25 14:49:26 +00:00
2017-10-22 05:59:33 +00:00
level . Debug ( n . logger ) . Log ( "msg" , "Notifying PagerDuty" , "incident" , key , "eventType" , eventType )
2015-11-25 14:49:26 +00:00
2015-10-29 13:34:24 +00:00
details := make ( map [ string ] string , len ( n . conf . Details ) )
for k , v := range n . conf . Details {
details [ k ] = tmpl ( v )
}
2015-10-21 11:08:53 +00:00
if err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2015-10-21 11:08:53 +00:00
}
2015-10-19 09:45:52 +00:00
2017-11-07 10:07:27 +00:00
if n . conf . ServiceKey != "" {
return n . notifyV1 ( ctx , eventType , key , tmpl , details , as ... )
2015-10-19 09:45:52 +00:00
}
2017-11-07 10:07:27 +00:00
return n . notifyV2 ( ctx , eventType , key , tmpl , details , as ... )
2016-09-05 15:51:03 +00:00
}
2017-11-07 10:07:27 +00:00
func ( n * PagerDuty ) retryV1 ( statusCode int ) ( bool , error ) {
2016-09-05 15:51:03 +00:00
// Retrying can solve the issue on 403 (rate limiting) and 5xx response codes.
// 2xx response codes indicate a successful request.
// https://v2.developer.pagerduty.com/docs/trigger-events
if statusCode / 100 != 2 {
return ( statusCode == 403 || statusCode / 100 == 5 ) , fmt . Errorf ( "unexpected status code %v" , statusCode )
2015-10-19 09:45:52 +00:00
}
2016-09-05 15:51:03 +00:00
return false , nil
2015-10-19 09:45:52 +00:00
}
2017-11-07 10:07:27 +00:00
func ( n * PagerDuty ) retryV2 ( statusCode int ) ( bool , error ) {
// Retrying can solve the issue on 429 (rate limiting) and 5xx response codes.
// 2xx response codes indicate a successful request.
// https://v2.developer.pagerduty.com/docs/events-api-v2#api-response-codes--retry-logic
if statusCode / 100 != 2 {
return ( statusCode == 429 || statusCode / 100 == 5 ) , fmt . Errorf ( "unexpected status code %v" , statusCode )
}
return false , nil
}
2015-11-12 12:18:36 +00:00
// Slack implements a Notifier for Slack notifications.
2015-10-09 08:48:25 +00:00
type Slack struct {
2017-10-22 05:59:33 +00:00
conf * config . SlackConfig
tmpl * template . Template
logger log . Logger
2015-10-09 08:48:25 +00:00
}
2015-11-30 12:52:41 +00:00
// NewSlack returns a new Slack notification handler.
2017-10-22 05:59:33 +00:00
func NewSlack ( c * config . SlackConfig , t * template . Template , l log . Logger ) * Slack {
2015-11-30 12:52:41 +00:00
return & Slack {
2017-10-22 05:59:33 +00:00
conf : c ,
tmpl : t ,
logger : l ,
2015-11-30 12:52:41 +00:00
}
}
2015-10-09 08:48:25 +00:00
// slackReq is the request for sending a slack notification.
type slackReq struct {
Channel string ` json:"channel,omitempty" `
2015-11-25 14:49:26 +00:00
Username string ` json:"username,omitempty" `
2016-02-16 10:54:30 +00:00
IconEmoji string ` json:"icon_emoji,omitempty" `
2016-05-12 18:51:22 +00:00
IconURL string ` json:"icon_url,omitempty" `
2017-07-25 07:21:33 +00:00
LinkNames bool ` json:"link_names,omitempty" `
2015-10-09 08:48:25 +00:00
Attachments [ ] slackAttachment ` json:"attachments" `
}
// slackAttachment is used to display a richly-formatted message block.
type slackAttachment struct {
Title string ` json:"title,omitempty" `
TitleLink string ` json:"title_link,omitempty" `
Pretext string ` json:"pretext,omitempty" `
Text string ` json:"text" `
Fallback string ` json:"fallback" `
2015-11-30 12:52:41 +00:00
Color string ` json:"color,omitempty" `
MrkdwnIn [ ] string ` json:"mrkdwn_in,omitempty" `
2015-10-09 08:48:25 +00:00
}
// slackAttachmentField is displayed in a table inside the message attachment.
type slackAttachmentField struct {
Title string ` json:"title" `
Value string ` json:"value" `
Short bool ` json:"short,omitempty" `
}
2015-11-12 12:18:36 +00:00
// Notify implements the Notifier interface.
2016-09-05 15:51:03 +00:00
func ( n * Slack ) Notify ( ctx context . Context , as ... * types . Alert ) ( bool , error ) {
2015-10-11 11:32:24 +00:00
var err error
2015-11-25 14:49:26 +00:00
var (
2017-10-22 05:59:33 +00:00
data = n . tmpl . Data ( receiverName ( ctx , n . logger ) , groupLabels ( ctx , n . logger ) , as ... )
2015-11-25 14:49:26 +00:00
tmplText = tmplText ( n . tmpl , data , & err )
)
2015-10-09 08:48:25 +00:00
attachment := & slackAttachment {
2015-10-29 13:34:24 +00:00
Title : tmplText ( n . conf . Title ) ,
TitleLink : tmplText ( n . conf . TitleLink ) ,
Pretext : tmplText ( n . conf . Pretext ) ,
2016-02-08 21:17:44 +00:00
Text : tmplText ( n . conf . Text ) ,
2015-10-29 13:34:24 +00:00
Fallback : tmplText ( n . conf . Fallback ) ,
2015-11-30 12:52:41 +00:00
Color : tmplText ( n . conf . Color ) ,
2016-02-04 10:42:55 +00:00
MrkdwnIn : [ ] string { "fallback" , "pretext" , "text" } ,
2015-10-09 08:48:25 +00:00
}
req := & slackReq {
2015-10-29 13:34:24 +00:00
Channel : tmplText ( n . conf . Channel ) ,
2015-11-30 12:52:41 +00:00
Username : tmplText ( n . conf . Username ) ,
2016-02-16 10:54:30 +00:00
IconEmoji : tmplText ( n . conf . IconEmoji ) ,
2016-05-12 18:51:22 +00:00
IconURL : tmplText ( n . conf . IconURL ) ,
2017-07-25 07:21:33 +00:00
LinkNames : n . conf . LinkNames ,
2015-10-09 08:48:25 +00:00
Attachments : [ ] slackAttachment { * attachment } ,
}
2015-10-29 13:34:24 +00:00
if err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2015-10-29 13:34:24 +00:00
}
2015-10-09 08:48:25 +00:00
var buf bytes . Buffer
if err := json . NewEncoder ( & buf ) . Encode ( req ) ; err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2015-10-09 08:48:25 +00:00
}
2015-12-03 11:40:50 +00:00
resp , err := ctxhttp . Post ( ctx , http . DefaultClient , string ( n . conf . APIURL ) , contentTypeJSON , & buf )
2015-10-09 08:48:25 +00:00
if err != nil {
2016-09-05 15:51:03 +00:00
return true , err
2015-10-09 08:48:25 +00:00
}
resp . Body . Close ( )
2016-09-05 15:51:03 +00:00
return n . retry ( resp . StatusCode )
}
func ( n * Slack ) retry ( statusCode int ) ( bool , error ) {
// Only 5xx response codes are recoverable and 2xx codes are successful.
// https://api.slack.com/incoming-webhooks#handling_errors
// https://api.slack.com/changelog/2016-05-17-changes-to-errors-for-incoming-webhooks
if statusCode / 100 != 2 {
return ( statusCode / 100 == 5 ) , fmt . Errorf ( "unexpected status code %v" , statusCode )
2015-10-09 08:48:25 +00:00
}
2016-09-05 15:51:03 +00:00
return false , nil
2015-10-09 08:48:25 +00:00
}
2015-11-24 22:29:25 +00:00
2016-01-05 19:52:08 +00:00
// Hipchat implements a Notifier for Hipchat notifications.
type Hipchat struct {
2017-10-22 05:59:33 +00:00
conf * config . HipchatConfig
tmpl * template . Template
logger log . Logger
2016-01-05 19:52:08 +00:00
}
// NewHipchat returns a new Hipchat notification handler.
2017-10-22 05:59:33 +00:00
func NewHipchat ( c * config . HipchatConfig , t * template . Template , l log . Logger ) * Hipchat {
2016-01-05 19:52:08 +00:00
return & Hipchat {
2017-10-22 05:59:33 +00:00
conf : c ,
tmpl : t ,
logger : l ,
2016-01-05 19:52:08 +00:00
}
}
type hipchatReq struct {
From string ` json:"from" `
Notify bool ` json:"notify" `
Message string ` json:"message" `
MessageFormat string ` json:"message_format" `
Color string ` json:"color" `
}
// Notify implements the Notifier interface.
2016-09-05 15:51:03 +00:00
func ( n * Hipchat ) Notify ( ctx context . Context , as ... * types . Alert ) ( bool , error ) {
2016-01-05 19:52:08 +00:00
var err error
var msg string
var (
2017-10-22 05:59:33 +00:00
data = n . tmpl . Data ( receiverName ( ctx , n . logger ) , groupLabels ( ctx , n . logger ) , as ... )
2016-01-05 19:52:08 +00:00
tmplText = tmplText ( n . tmpl , data , & err )
tmplHTML = tmplHTML ( n . tmpl , data , & err )
url = fmt . Sprintf ( "%sv2/room/%s/notification?auth_token=%s" , n . conf . APIURL , n . conf . RoomID , n . conf . AuthToken )
)
if n . conf . MessageFormat == "html" {
msg = tmplHTML ( n . conf . Message )
} else {
msg = tmplText ( n . conf . Message )
}
req := & hipchatReq {
From : tmplText ( n . conf . From ) ,
Notify : n . conf . Notify ,
Message : msg ,
MessageFormat : n . conf . MessageFormat ,
Color : tmplText ( n . conf . Color ) ,
}
if err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2016-01-05 19:52:08 +00:00
}
var buf bytes . Buffer
if err := json . NewEncoder ( & buf ) . Encode ( req ) ; err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2016-01-05 19:52:08 +00:00
}
resp , err := ctxhttp . Post ( ctx , http . DefaultClient , url , contentTypeJSON , & buf )
if err != nil {
2016-09-05 15:51:03 +00:00
return true , err
2016-01-05 19:52:08 +00:00
}
defer resp . Body . Close ( )
2016-09-05 15:51:03 +00:00
return n . retry ( resp . StatusCode )
}
func ( n * Hipchat ) retry ( statusCode int ) ( bool , error ) {
// Response codes 429 (rate limiting) and 5xx can potentially recover. 2xx
// responce codes indicate successful requests.
// https://developer.atlassian.com/hipchat/guide/hipchat-rest-api/api-response-codes
if statusCode / 100 != 2 {
return ( statusCode == 429 || statusCode / 100 == 5 ) , fmt . Errorf ( "unexpected status code %v" , statusCode )
2016-01-05 19:52:08 +00:00
}
2016-09-05 15:51:03 +00:00
return false , nil
2016-01-05 19:52:08 +00:00
}
2015-11-24 22:29:25 +00:00
// OpsGenie implements a Notifier for OpsGenie notifications.
type OpsGenie struct {
2017-10-22 05:59:33 +00:00
conf * config . OpsGenieConfig
tmpl * template . Template
logger log . Logger
2015-11-24 22:29:25 +00:00
}
2016-03-11 14:14:55 +00:00
// NewOpsGenie returns a new OpsGenie notifier.
2017-10-22 05:59:33 +00:00
func NewOpsGenie ( c * config . OpsGenieConfig , t * template . Template , l log . Logger ) * OpsGenie {
return & OpsGenie { conf : c , tmpl : t , logger : l }
2015-11-24 22:29:25 +00:00
}
type opsGenieCreateMessage struct {
2017-10-29 15:19:17 +00:00
Alias string ` json:"alias" `
2016-07-12 12:36:20 +00:00
Message string ` json:"message" `
Description string ` json:"description,omitempty" `
Details map [ string ] string ` json:"details" `
Source string ` json:"source" `
Teams string ` json:"teams,omitempty" `
Tags string ` json:"tags,omitempty" `
Note string ` json:"note,omitempty" `
2015-11-24 22:29:25 +00:00
}
type opsGenieCloseMessage struct {
2017-10-29 15:19:17 +00:00
Source string ` json:"source" `
2016-03-11 14:14:55 +00:00
}
2015-11-24 22:29:25 +00:00
// Notify implements the Notifier interface.
2016-09-05 15:51:03 +00:00
func ( n * OpsGenie ) Notify ( ctx context . Context , as ... * types . Alert ) ( bool , error ) {
2015-11-24 22:29:25 +00:00
key , ok := GroupKey ( ctx )
if ! ok {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "group key missing" )
2015-11-24 22:29:25 +00:00
}
2017-10-22 05:59:33 +00:00
data := n . tmpl . Data ( receiverName ( ctx , n . logger ) , groupLabels ( ctx , n . logger ) , as ... )
2015-11-24 22:29:25 +00:00
2017-10-22 05:59:33 +00:00
level . Debug ( n . logger ) . Log ( "msg" , "Notifying OpsGenie" , "incident" , key )
2015-11-24 22:29:25 +00:00
var err error
2015-11-25 14:49:26 +00:00
tmpl := tmplText ( n . tmpl , data , & err )
2015-11-24 22:29:25 +00:00
details := make ( map [ string ] string , len ( n . conf . Details ) )
for k , v := range n . conf . Details {
details [ k ] = tmpl ( v )
}
var (
msg interface { }
apiURL string
2017-10-29 15:19:17 +00:00
alias = hashKey ( key )
2015-11-25 14:49:26 +00:00
alerts = types . Alerts ( as ... )
)
2015-11-24 22:29:25 +00:00
switch alerts . Status ( ) {
case model . AlertResolved :
2017-10-29 15:19:17 +00:00
apiURL = fmt . Sprintf ( "%sv2/alerts/%s/close?identifierType=alias" , n . conf . APIHost , alias )
msg = & opsGenieCloseMessage { Source : tmpl ( n . conf . Source ) }
2015-11-24 22:29:25 +00:00
default :
2017-10-26 09:09:23 +00:00
message := tmpl ( n . conf . Message )
if len ( message ) > 130 {
2017-10-29 15:19:17 +00:00
message = message [ : 127 ] + "..."
2017-10-26 09:09:23 +00:00
level . Debug ( n . logger ) . Log ( "msg" , "Truncated message to %q due to OpsGenie message limit" , "truncated_message" , message , "incident" , key )
}
2017-10-29 15:19:17 +00:00
apiURL = n . conf . APIHost + "v2/alerts"
2015-11-24 22:29:25 +00:00
msg = & opsGenieCreateMessage {
2017-10-29 15:19:17 +00:00
Alias : alias ,
Message : message ,
Description : tmpl ( n . conf . Description ) ,
Details : details ,
Source : tmpl ( n . conf . Source ) ,
Teams : tmpl ( n . conf . Teams ) ,
Tags : tmpl ( n . conf . Tags ) ,
Note : tmpl ( n . conf . Note ) ,
2015-11-24 22:29:25 +00:00
}
}
2015-11-25 14:49:26 +00:00
if err != nil {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "templating error: %s" , err )
2015-11-25 14:49:26 +00:00
}
2015-11-24 22:29:25 +00:00
var buf bytes . Buffer
if err := json . NewEncoder ( & buf ) . Encode ( msg ) ; err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2015-11-24 22:29:25 +00:00
}
2017-10-29 15:19:17 +00:00
req , err := http . NewRequest ( "POST" , apiURL , & buf )
if err != nil {
return true , err
}
req . Header . Set ( "Content-Type" , contentTypeJSON )
req . Header . Set ( "Authorization" , fmt . Sprintf ( "GenieKey %s" , n . conf . APIKey ) )
resp , err := ctxhttp . Do ( ctx , http . DefaultClient , req )
2015-11-24 22:29:25 +00:00
if err != nil {
2016-09-05 15:51:03 +00:00
return true , err
2015-11-24 22:29:25 +00:00
}
2016-03-11 14:14:55 +00:00
defer resp . Body . Close ( )
2015-11-24 22:29:25 +00:00
2017-10-29 15:19:17 +00:00
// https://docs.opsgenie.com/docs/response#section-response-codes
// Response codes 429 (rate limiting) and 5xx are potentially recoverable
if resp . StatusCode / 100 == 5 || resp . StatusCode == 429 {
2016-09-05 15:51:03 +00:00
return true , fmt . Errorf ( "unexpected status code %v" , resp . StatusCode )
} else if resp . StatusCode / 100 == 4 {
return false , fmt . Errorf ( "unexpected status code %v" , resp . StatusCode )
2016-03-11 14:14:55 +00:00
} else if resp . StatusCode / 100 != 2 {
2016-03-09 01:04:20 +00:00
body , _ := ioutil . ReadAll ( resp . Body )
2017-10-22 05:59:33 +00:00
level . Debug ( n . logger ) . Log (
"msg" , "Unexpected OpsGenie response" ,
"incident" , key ,
"url" , apiURL ,
"posted_message" , msg ,
"status" , resp . Status ,
"response_body" , body ,
)
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "unexpected status code %v" , resp . StatusCode )
2015-11-24 22:29:25 +00:00
}
2016-09-05 15:51:03 +00:00
return false , nil
2015-11-24 22:29:25 +00:00
}
2015-11-25 14:49:26 +00:00
2016-07-03 15:33:44 +00:00
// VictorOps implements a Notifier for VictorOps notifications.
type VictorOps struct {
2017-10-22 05:59:33 +00:00
conf * config . VictorOpsConfig
tmpl * template . Template
logger log . Logger
2016-07-03 15:33:44 +00:00
}
// NewVictorOps returns a new VictorOps notifier.
2017-10-22 05:59:33 +00:00
func NewVictorOps ( c * config . VictorOpsConfig , t * template . Template , l log . Logger ) * VictorOps {
2016-07-03 15:33:44 +00:00
return & VictorOps {
2017-10-22 05:59:33 +00:00
conf : c ,
tmpl : t ,
logger : l ,
2016-07-03 15:33:44 +00:00
}
}
const (
victorOpsEventTrigger = "CRITICAL"
victorOpsEventResolve = "RECOVERY"
)
type victorOpsMessage struct {
2017-07-03 09:44:53 +00:00
MessageType string ` json:"message_type" `
EntityID string ` json:"entity_id" `
EntityDisplayName string ` json:"entity_display_name" `
StateMessage string ` json:"state_message" `
MonitoringTool string ` json:"monitoring_tool" `
2016-07-03 15:33:44 +00:00
}
type victorOpsErrorResponse struct {
Result string ` json:"result" `
Message string ` json:"message" `
}
// Notify implements the Notifier interface.
2016-09-05 15:51:03 +00:00
func ( n * VictorOps ) Notify ( ctx context . Context , as ... * types . Alert ) ( bool , error ) {
2016-07-03 15:33:44 +00:00
victorOpsAllowedEvents := map [ string ] bool {
"INFO" : true ,
"WARNING" : true ,
"CRITICAL" : true ,
}
key , ok := GroupKey ( ctx )
if ! ok {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "group key missing" )
2016-07-03 15:33:44 +00:00
}
var err error
var (
2017-07-03 09:44:53 +00:00
alerts = types . Alerts ( as ... )
2017-10-22 05:59:33 +00:00
data = n . tmpl . Data ( receiverName ( ctx , n . logger ) , groupLabels ( ctx , n . logger ) , as ... )
2017-07-03 09:44:53 +00:00
tmpl = tmplText ( n . tmpl , data , & err )
2017-11-07 09:38:46 +00:00
apiURL = fmt . Sprintf ( "%s%s/%s" , n . conf . APIURL , n . conf . APIKey , tmpl ( n . conf . RoutingKey ) )
2017-10-11 13:42:10 +00:00
messageType = tmpl ( n . conf . MessageType )
2017-07-03 09:44:53 +00:00
stateMessage = tmpl ( n . conf . StateMessage )
2016-07-03 15:33:44 +00:00
)
if alerts . Status ( ) == model . AlertFiring && ! victorOpsAllowedEvents [ messageType ] {
messageType = victorOpsEventTrigger
}
if alerts . Status ( ) == model . AlertResolved {
messageType = victorOpsEventResolve
}
2017-07-03 09:44:53 +00:00
if len ( stateMessage ) > 20480 {
stateMessage = stateMessage [ 0 : 20475 ] + "\n..."
2017-10-26 09:09:54 +00:00
level . Debug ( n . logger ) . Log ( "msg" , "Truncated stateMessage due to VictorOps stateMessage limit" , "truncated_state_message" , stateMessage , "incident" , key )
2017-07-03 09:44:53 +00:00
}
2016-07-03 15:33:44 +00:00
msg := & victorOpsMessage {
2017-07-03 09:44:53 +00:00
MessageType : messageType ,
EntityID : hashKey ( key ) ,
EntityDisplayName : tmpl ( n . conf . EntityDisplayName ) ,
StateMessage : stateMessage ,
MonitoringTool : tmpl ( n . conf . MonitoringTool ) ,
2016-07-03 15:33:44 +00:00
}
if err != nil {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "templating error: %s" , err )
2016-07-03 15:33:44 +00:00
}
var buf bytes . Buffer
if err := json . NewEncoder ( & buf ) . Encode ( msg ) ; err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2016-07-03 15:33:44 +00:00
}
resp , err := ctxhttp . Post ( ctx , http . DefaultClient , apiURL , contentTypeJSON , & buf )
if err != nil {
2016-09-05 15:51:03 +00:00
return true , err
2016-07-03 15:33:44 +00:00
}
defer resp . Body . Close ( )
2016-09-05 15:51:03 +00:00
// Missing documentation therefore assuming only 5xx response codes are
// recoverable.
if resp . StatusCode / 100 == 5 {
return true , fmt . Errorf ( "unexpected status code %v" , resp . StatusCode )
}
2016-07-03 15:33:44 +00:00
if resp . StatusCode / 100 != 2 {
body , _ := ioutil . ReadAll ( resp . Body )
var responseMessage victorOpsErrorResponse
if err := json . Unmarshal ( body , & responseMessage ) ; err != nil {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "could not parse error response %q" , body )
2016-07-03 15:33:44 +00:00
}
2017-10-22 05:59:33 +00:00
level . Debug ( n . logger ) . Log (
"msg" , "Unexpected VictorOps response" ,
"incident" , key ,
"url" , apiURL ,
"posted_message" , msg ,
"status" , resp . Status ,
"response_body" , body ,
)
2016-07-03 15:33:44 +00:00
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "error when posting alert: result %q, message %q" ,
2016-07-03 15:33:44 +00:00
responseMessage . Result , responseMessage . Message )
}
2016-09-05 15:51:03 +00:00
return false , nil
2016-07-03 15:33:44 +00:00
}
2016-02-26 08:35:00 +00:00
// Pushover implements a Notifier for Pushover notifications.
type Pushover struct {
2017-10-22 05:59:33 +00:00
conf * config . PushoverConfig
tmpl * template . Template
logger log . Logger
2016-02-26 08:35:00 +00:00
}
// NewPushover returns a new Pushover notifier.
2017-10-22 05:59:33 +00:00
func NewPushover ( c * config . PushoverConfig , t * template . Template , l log . Logger ) * Pushover {
return & Pushover { conf : c , tmpl : t , logger : l }
2016-02-26 08:35:00 +00:00
}
// Notify implements the Notifier interface.
2016-09-05 15:51:03 +00:00
func ( n * Pushover ) Notify ( ctx context . Context , as ... * types . Alert ) ( bool , error ) {
2016-02-26 08:35:00 +00:00
key , ok := GroupKey ( ctx )
if ! ok {
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "group key missing" )
2016-02-26 08:35:00 +00:00
}
2017-10-22 05:59:33 +00:00
data := n . tmpl . Data ( receiverName ( ctx , n . logger ) , groupLabels ( ctx , n . logger ) , as ... )
2016-02-26 08:35:00 +00:00
2017-10-22 05:59:33 +00:00
level . Debug ( n . logger ) . Log ( "msg" , "Notifying Pushover" , "incident" , key )
2016-02-26 08:35:00 +00:00
var err error
tmpl := tmplText ( n . tmpl , data , & err )
parameters := url . Values { }
parameters . Add ( "token" , tmpl ( string ( n . conf . Token ) ) )
parameters . Add ( "user" , tmpl ( string ( n . conf . UserKey ) ) )
2017-10-25 12:44:35 +00:00
2016-02-26 08:35:00 +00:00
title := tmpl ( n . conf . Title )
2017-10-25 12:44:35 +00:00
if len ( title ) > 250 {
title = title [ : 247 ] + "..."
level . Debug ( n . logger ) . Log ( "msg" , "Truncated title due to Pushover title limit" , "truncated_title" , title , "incident" , key )
2016-04-16 11:00:46 +00:00
}
2017-10-25 12:44:35 +00:00
parameters . Add ( "title" , title )
message := tmpl ( n . conf . Message )
if len ( message ) > 1024 {
message = message [ : 1021 ] + "..."
2017-10-22 05:59:33 +00:00
level . Debug ( n . logger ) . Log ( "msg" , "Truncated message due to Pushover message limit" , "truncated_message" , message , "incident" , key )
2016-02-26 08:35:00 +00:00
}
2016-04-27 08:46:17 +00:00
message = strings . TrimSpace ( message )
2016-02-26 08:35:00 +00:00
if message == "" {
// Pushover rejects empty messages.
message = "(no details)"
}
parameters . Add ( "message" , message )
2017-10-25 12:44:35 +00:00
2017-11-01 22:08:34 +00:00
supplementaryURL := tmpl ( n . conf . URL )
if len ( supplementaryURL ) > 512 {
supplementaryURL = supplementaryURL [ : 509 ] + "..."
level . Debug ( n . logger ) . Log ( "msg" , "Truncated URL due to Pushover url limit" , "truncated_url" , supplementaryURL , "incident" , key )
2017-10-25 12:44:35 +00:00
}
2017-11-01 22:08:34 +00:00
parameters . Add ( "url" , supplementaryURL )
2017-10-25 12:44:35 +00:00
2016-02-26 08:35:00 +00:00
parameters . Add ( "priority" , tmpl ( n . conf . Priority ) )
parameters . Add ( "retry" , fmt . Sprintf ( "%d" , int64 ( time . Duration ( n . conf . Retry ) . Seconds ( ) ) ) )
parameters . Add ( "expire" , fmt . Sprintf ( "%d" , int64 ( time . Duration ( n . conf . Expire ) . Seconds ( ) ) ) )
2016-09-05 15:51:03 +00:00
if err != nil {
return false , err
}
2016-02-26 08:35:00 +00:00
apiURL := "https://api.pushover.net/1/messages.json"
u , err := url . Parse ( apiURL )
if err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2016-02-26 08:35:00 +00:00
}
u . RawQuery = parameters . Encode ( )
2017-10-22 05:59:33 +00:00
level . Debug ( n . logger ) . Log ( "msg" , "Sending Pushover message" , "incident" , key , "url" , u . String ( ) )
2016-02-26 08:35:00 +00:00
resp , err := ctxhttp . Post ( ctx , http . DefaultClient , u . String ( ) , "text/plain" , nil )
if err != nil {
2016-09-05 15:51:03 +00:00
return true , err
2016-02-26 08:35:00 +00:00
}
defer resp . Body . Close ( )
2016-09-05 15:51:03 +00:00
// Only documented behaviour is that 2xx response codes are successful and
// 4xx are unsuccessful, therefore assuming only 5xx are recoverable.
// https://pushover.net/api#response
if resp . StatusCode / 100 == 5 {
return true , fmt . Errorf ( "unexpected status code %v" , resp . StatusCode )
}
2016-02-26 08:35:00 +00:00
if resp . StatusCode / 100 != 2 {
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
2016-09-05 15:51:03 +00:00
return false , err
2016-02-26 08:35:00 +00:00
}
2016-09-05 15:51:03 +00:00
return false , fmt . Errorf ( "unexpected status code %v (body: %s)" , resp . StatusCode , string ( body ) )
2016-02-26 08:35:00 +00:00
}
2016-09-05 15:51:03 +00:00
return false , nil
2016-02-26 08:35:00 +00:00
}
2015-11-25 14:49:26 +00:00
func tmplText ( tmpl * template . Template , data * template . Data , err * error ) func ( string ) string {
return func ( name string ) ( s string ) {
if * err != nil {
return
}
s , * err = tmpl . ExecuteTextString ( name , data )
return s
}
}
func tmplHTML ( tmpl * template . Template , data * template . Data , err * error ) func ( string ) string {
return func ( name string ) ( s string ) {
if * err != nil {
return
}
s , * err = tmpl . ExecuteHTMLString ( name , data )
return s
}
}
2016-07-04 14:09:38 +00:00
type loginAuth struct {
username , password string
}
func LoginAuth ( username , password string ) smtp . Auth {
return & loginAuth { username , password }
}
func ( a * loginAuth ) Start ( server * smtp . ServerInfo ) ( string , [ ] byte , error ) {
return "LOGIN" , [ ] byte { } , nil
}
// Used for AUTH LOGIN. (Maybe password should be encrypted)
func ( a * loginAuth ) Next ( fromServer [ ] byte , more bool ) ( [ ] byte , error ) {
if more {
2016-08-08 09:57:08 +00:00
switch strings . ToLower ( string ( fromServer ) ) {
case "username:" :
2016-07-04 14:09:38 +00:00
return [ ] byte ( a . username ) , nil
2016-08-08 09:57:08 +00:00
case "password:" :
2016-07-04 14:09:38 +00:00
return [ ] byte ( a . password ) , nil
default :
return nil , errors . New ( "unexpected server challenge" )
}
}
return nil , nil
}
2017-04-21 09:43:12 +00:00
// hashKey returns the sha256 for a group key as integrations may have
// maximum length requirements on deduplication keys.
func hashKey ( s string ) string {
h := sha256 . New ( )
h . Write ( [ ] byte ( s ) )
2017-04-27 11:10:43 +00:00
return fmt . Sprintf ( "%x" , h . Sum ( nil ) )
2017-04-21 09:43:12 +00:00
}