Skip to content

Commit

Permalink
Reintroduce AWS SNS notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
tyrken committed Apr 18, 2017
1 parent cb8729a commit 9264491
Show file tree
Hide file tree
Showing 112 changed files with 29,381 additions and 48 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/data/
/alertmanager
/alertmanager.exe
*.yml
*.yaml
/.build
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ type Receiver struct {
OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"`
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
AmazonSNSConfigs []*AmazonSNSConfig `yaml:"amazon_sns_configs,omitempty" json:"amazon_sns_configs,omitempty"`

// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
Expand Down
35 changes: 35 additions & 0 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ var (
Retry: duration(1 * time.Minute),
Expire: duration(1 * time.Hour),
}

// DefaultAmazonSNSConfig defines default values for Amazon SNS configurations.
DefaultAmazonSNSConfig = AmazonSNSConfig{
NotifierConfig: NotifierConfig{
VSendResolved: true,
},
Subject: `{{ template "amazon_sns.default.subject" . }}`,
Message: `{{ template "amazon_sns.default.message" . }}`,
}
)

// NotifierConfig contains base options common across all notifier configurations.
Expand Down Expand Up @@ -392,3 +401,29 @@ func (c *PushoverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
}
return checkOverflow(c.XXX, "pushover config")
}

// AmazonSNSConfig configures notifications via Amazon SNS.
type AmazonSNSConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`

AWSRegion string `yaml:"aws_region" json:"aws_region"`
TopicARN string `yaml:"topic_arn" json:"topic_arn"`
Subject string `yaml:"subject" json:"subject"`
Message string `yaml:"message" json:"message"`

XXX map[string]interface{} `yaml:",inline" json:"-"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *AmazonSNSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultAmazonSNSConfig
type plain AmazonSNSConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
// Allow blank region, but not missing ARN
if c.TopicARN == "" {
return fmt.Errorf("missing topic_arn key in amazon_sns_config")
}
return checkOverflow(c.XXX, "amazon_sns_config")
}
113 changes: 113 additions & 0 deletions notify/amazonsns_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// 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.
package notify

import (
"errors"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
"net/url"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/sns"

"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
)

func dummySNSSetup(t *testing.T) (context.Context, []*types.Alert, *AmazonSNS) {
ctx := WithReceiverName(context.Background(), "name")
lset := model.LabelSet{
"group_label_key": "group_label_value",
}
ctx = WithGroupLabels(ctx, lset)

alerts := []*types.Alert{{}, {}}

c := &config.AmazonSNSConfig{
TopicARN: "arn:aws:sns:no-region:1234567890:topic",
Subject: `{{ template "amazon_sns.default.subject" . }}`,
Message: `{{ template "amazon_sns.default.message" . }}`,
}
tmpl, err := template.FromGlobs()
require.NoError(t, err, "Failed template setup")
tmpl.ExternalURL, err = url.Parse("http://localhost/")
require.NoError(t, err, "Failed template URL setup")

n := NewAmazonSNS(c, tmpl)

return ctx, alerts, n
}

func TestAmazonSNSIntegration(t *testing.T) {
ctx, alerts, n := dummySNSSetup(t)

n.testPublisher = func(c *aws.Config, pi *sns.PublishInput) error {
require.Contains(t, aws.StringValue(c.Region), "no-region", "AWS Config not set to correct region")
subj := aws.StringValue(pi.Subject)
require.Contains(t, subj, "[FIRING:2]", "Default Subject missing alerts raised summary")
require.Contains(t, subj, "group_label_value", "Default Subject missing Context label")
mess := aws.StringValue(pi.Message)
require.Contains(t, mess, "Source", "Default Message missing structure")
return nil
}

retry, err := n.Notify(ctx, alerts...)
require.NoError(t, err, "Happy path")
require.Equal(t, false, retry, "Happy path no need to retry")
}

func TestAmazonSNSBadARN(t *testing.T) {
ctx, alerts, n := dummySNSSetup(t)

n.conf.TopicARN = "fdsfds"
n.testPublisher = func(c *aws.Config, pi *sns.PublishInput) error {
t.Fatal("Shouldn't attempt to publish to a bad TopicARN")
return nil
}

retry, err := n.Notify(ctx, alerts...)
require.Error(t, err, "Bad ARN should error")
require.Contains(t, err.Error(), "fdsfds", "Incorrect bad ARN message")
require.Equal(t, false, retry, "Bad ARN shouldn't retry")
}

func TestAmazonSNSRegionOverride(t *testing.T) {
ctx, alerts, n := dummySNSSetup(t)

n.conf.AWSRegion = "somewhere"
n.testPublisher = func(c *aws.Config, pi *sns.PublishInput) error {
require.Contains(t, aws.StringValue(c.Region), "somewhere", "Failed to override AWS Region")
return nil
}

retry, err := n.Notify(ctx, alerts...)
require.NoError(t, err, "SNS Override should work")
require.Equal(t, false, retry, "SNS Override no need to retry")
}

func TestAmazonSNSPublishFailRetry(t *testing.T) {
ctx, alerts, n := dummySNSSetup(t)

n.testPublisher = func(c *aws.Config, pi *sns.PublishInput) error {
return errors.New("Dummy failure")
}

retry, err := n.Notify(ctx, alerts...)
require.Error(t, err, "Publish error should propagate")
require.Contains(t, err.Error(), "Dummy failure", "Need to see the error message from SNS publish upon failure")
require.Equal(t, true, retry, "Should retry on publish failure")
}
75 changes: 75 additions & 0 deletions notify/impl.go
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import (
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sns"

"github.com/prometheus/common/log"
"github.com/prometheus/common/model"
"golang.org/x/net/context"
Expand Down Expand Up @@ -129,6 +133,10 @@ func BuildReceiverIntegrations(nc *config.Receiver, tmpl *template.Template) []I
n := NewPushover(c, tmpl)
add("pushover", i, n, c)
}
for i, c := range nc.AmazonSNSConfigs {
n := NewAmazonSNS(c, tmpl)
add("amazon_sns", i, n, c)
}
return integrations
}

Expand Down Expand Up @@ -934,6 +942,73 @@ func (n *Pushover) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
return false, nil
}

// AmazonSNS implements a Notifier for Amazon SNS notifications.
type AmazonSNS struct {
conf *config.AmazonSNSConfig
tmpl *template.Template

// For testing only
testPublisher func(*aws.Config, *sns.PublishInput) error
}

// NewAmazonSNS returns a new AmazonSNS notifier.
func NewAmazonSNS(c *config.AmazonSNSConfig, t *template.Template) *AmazonSNS {
return &AmazonSNS{
conf: c,
tmpl: t,
testPublisher: nil,
}
}

// Notify implements the Notifier interface.
func (n *AmazonSNS) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
var err error
var (
data = n.tmpl.Data(receiverName(ctx), groupLabels(ctx), as...)
tmpl = tmplText(n.tmpl, data, &err)
region = tmpl(n.conf.AWSRegion)
topicARN = tmpl(n.conf.TopicARN)
subject = tmpl(n.conf.Subject)
message = tmpl(n.conf.Message)
)
if err != nil {
return false, fmt.Errorf("Amazon SNS templating error: %s", err)
}

// SNS ARNs are of the form arn:aws:sns:<region>:<account-id>:<topicname>
parts := strings.SplitN(topicARN, ":", 6)
if len(parts) < 6 {
return false, fmt.Errorf("Amazon SNS topic_arn not a valid ARN: '%s'", topicARN)
}
if region == "" {
region = parts[3]
}
awsConfig := aws.NewConfig()
if region != "" {
awsConfig = awsConfig.WithRegion(region)
}

params := &sns.PublishInput{
Message: aws.String(message),
MessageStructure: aws.String("string"),
Subject: aws.String(subject),
TopicArn: aws.String(topicARN),
}

if n.testPublisher != nil {
err = n.testPublisher(awsConfig, params)
} else {
snsAPI := sns.New(session.New(awsConfig))
_, err = snsAPI.Publish(params)
}

if err != nil {
return true, fmt.Errorf("Amazon SNS publishing error: %s", err)
}

return false, nil
}

func tmplText(tmpl *template.Template, data *template.Data, err *error) func(string) string {
return func(name string) (s string) {
if *err != nil {
Expand Down
12 changes: 12 additions & 0 deletions template/default.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,15 @@ Alerts Resolved:
{{ end }}
{{ end }}
{{ define "pushover.default.url" }}{{ template "__alertmanagerURL" . }}{{ end }}

{{ define "amazon_sns.default.subject" }}{{ template "__subject" . }}{{ end }}
{{ define "amazon_sns.default.message" }}{{ .CommonAnnotations.SortedPairs.Values | join " " }}
{{ if gt (len .Alerts.Firing) 0 }}
Alerts Firing:
{{ template "__text_alert_list" .Alerts.Firing }}
{{ end }}
{{ if gt (len .Alerts.Resolved) 0 }}
Alerts Resolved:
{{ template "__text_alert_list" .Alerts.Resolved }}
{{ end }}
{{ end }}
4 changes: 2 additions & 2 deletions template/internal/deftmpl/bindata.go

Large diffs are not rendered by default.

Loading

0 comments on commit 9264491

Please sign in to comment.