Skip to content

Commit

Permalink
Merge pull request #2615 from treid314/sns-reciever
Browse files Browse the repository at this point in the history
Add AWS SNS receiver
  • Loading branch information
roidelapluie committed Jul 26, 2021
2 parents 8491f81 + a1260af commit 010c683
Show file tree
Hide file tree
Showing 14 changed files with 701 additions and 21 deletions.
4 changes: 2 additions & 2 deletions asset/assets_vfsdata.go

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions cmd/alertmanager/main.go
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/alertmanager/notify/sns"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/model"
Expand Down Expand Up @@ -165,6 +166,9 @@ func buildReceiverIntegrations(nc *config.Receiver, tmpl *template.Template, log
for i, c := range nc.PushoverConfigs {
add("pushover", i, c, func(l log.Logger) (notify.Notifier, error) { return pushover.New(c, tmpl, l) })
}
for i, c := range nc.SNSConfigs {
add("sns", i, c, func(l log.Logger) (notify.Notifier, error) { return sns.New(c, tmpl, l) })
}
if errs.Len() > 0 {
return nil, &errs
}
Expand Down
9 changes: 9 additions & 0 deletions config/config.go
Expand Up @@ -242,6 +242,9 @@ func resolveFilepaths(baseDir string, cfg *Config) {
for _, cfg := range receiver.WechatConfigs {
cfg.HTTPConfig.SetDirectory(baseDir)
}
for _, cfg := range receiver.SNSConfigs {
cfg.HTTPConfig.SetDirectory(baseDir)
}
}
}

Expand Down Expand Up @@ -447,6 +450,11 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
voc.APIKey = c.Global.VictorOpsAPIKey
}
}
for _, sns := range rcv.SNSConfigs {
if sns.HTTPConfig == nil {
sns.HTTPConfig = c.Global.HTTPConfig
}
}
names[rcv.Name] = struct{}{}
}

Expand Down Expand Up @@ -784,6 +792,7 @@ type Receiver struct {
WechatConfigs []*WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"`
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver.
Expand Down
20 changes: 19 additions & 1 deletion config/config_test.go
Expand Up @@ -26,7 +26,7 @@ import (
commoncfg "github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"
)

func TestLoadEmptyString(t *testing.T) {
Expand Down Expand Up @@ -1020,6 +1020,24 @@ func TestSlackGlobalAPIURLFile(t *testing.T) {
}
}

func TestValidSNSConfig(t *testing.T) {
_, err := LoadFile("testdata/conf.sns-topic-arn.yml")
if err != nil {
t.Fatalf("Error parsing %s: %s", "testdata/conf.sns-topic-arn.yml\"", err)
}
}

func TestInvalidSNSConfig(t *testing.T) {
_, err := LoadFile("testdata/conf.sns-invalid.yml")
if err == nil {
t.Fatalf("expected error with missing fields on SNS config")
}
const expectedErr = `must provide either a Target ARN, Topic ARN, or Phone Number for SNS config`
if err.Error() != expectedErr {
t.Errorf("Expected: %s\nGot: %s", expectedErr, err.Error())
}
}

func TestUnmarshalHostPort(t *testing.T) {
for _, tc := range []struct {
in string
Expand Down
41 changes: 41 additions & 0 deletions config/notifiers.go
Expand Up @@ -20,6 +20,7 @@ import (
"time"

"github.com/pkg/errors"
"github.com/prometheus/common/sigv4"

commoncfg "github.com/prometheus/common/config"
)
Expand Down Expand Up @@ -127,6 +128,15 @@ var (
Expire: duration(1 * time.Hour),
HTML: false,
}

// DefaultSNSConfig defines default values for SNS configurations.
DefaultSNSConfig = SNSConfig{
NotifierConfig: NotifierConfig{
VSendResolved: true,
},
Subject: `{{ template "sns.default.subject" . }}`,
Message: `{{ template "sns.default.message" . }}`,
}
)

// NotifierConfig contains base options common across all notifier configurations.
Expand Down Expand Up @@ -579,3 +589,34 @@ func (c *PushoverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
}
return nil
}

type SNSConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`

HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`

APIUrl string `yaml:"api_url,omitempty" json:"api_url,omitempty"`
Sigv4 sigv4.SigV4Config `yaml:"sigv4" json:"sigv4"`
TopicARN string `yaml:"topic_arn,omitempty" json:"topic_arn,omitempty"`
PhoneNumber string `yaml:"phone_number,omitempty" json:"phone_number,omitempty"`
TargetARN string `yaml:"target_arn,omitempty" json:"target_arn,omitempty"`
Subject string `yaml:"subject,omitempty" json:"subject,omitempty"`
Message string `yaml:"message,omitempty" json:"message,omitempty"`
Attributes map[string]string `yaml:"attributes,omitempty" json:"attributes,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *SNSConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultSNSConfig
type plain SNSConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if (c.TargetARN == "") != (c.TopicARN == "") != (c.PhoneNumber == "") {
return fmt.Errorf("must provide either a Target ARN, Topic ARN, or Phone Number for SNS config")
}
if (c.Sigv4.AccessKey == "") != (c.Sigv4.SecretKey == "") {
return fmt.Errorf("must provide a AWS SigV4 Access key and Secret Key if credentials are specified in the SNS config")
}
return nil
}
14 changes: 14 additions & 0 deletions config/testdata/conf.sns-invalid.yml
@@ -0,0 +1,14 @@
route:
receiver: 'sns-api-notifications'
group_by: [alertname]

receivers:
- name: 'sns-api-notifications'
sns_configs:
- api_url: https://sns.us-east-2.amazonaws.com
sigv4:
region: us-east-2
access_key: access_key
secret_key: secret_ket
attributes:
severity: Sev2
15 changes: 15 additions & 0 deletions config/testdata/conf.sns-topic-arn.yml
@@ -0,0 +1,15 @@
route:
receiver: 'sns-api-notifications'
group_by: [alertname]

receivers:
- name: 'sns-api-notifications'
sns_configs:
- api_url: https://sns.us-east-2.amazonaws.com
topic_arn: arn:aws:sns:us-east-2:123456789012:My-Topic
sigv4:
region: us-east-2
access_key: access_key
secret_key: secret_ket
attributes:
severity: Sev2
58 changes: 58 additions & 0 deletions docs/configuration.md
Expand Up @@ -693,6 +693,64 @@ value: <tmpl_string>
[ short: <boolean> | default = slack_config.short_fields ]
```

## `<sns_configs>`
```yaml
# Whether or not to notify about resolved alerts.
[ send_resolved: <boolean> | default = false ]

# The SNS API URL i.e. https://sns.us-east-2.amazonaws.com.
# If not specified, the SNS API URL from the SNS SDK will be used.
[ api_url: <tmpl_string> ]

# Configures AWS's Signature Verification 4 signing process to sign requests.
sigv4:
[ <sigv4_config> ]

# SNS topic ARN, i.e. arn:aws:sns:us-east-2:698519295917:My-Topic
# If you don't specify this value, you must specify a value for the phone_number or target_arn.
# If you are using a FIFO SNS topic you should set a message group interval longer than 5 minutes
# to prevent messages with the same group key being deduplicated by the SNS default deduplication window
[ topic_arn: <tmpl_string> ]

# Subject line when the message is delivered to email endpoints.
[ subject: <tmpl_string> | default = '{{ template "sns.default.subject" .}}' ]

# Phone number if message is delivered via SMS in E.164 format.
# If you don't specify this value, you must specify a value for the topic_arn or target_arn.
[ phone_number: <tmpl_string> ]

# The mobile platform endpoint ARN if message is delivered via mobile notifications.
# If you don't specify this value, you must specify a value for the topic_arn or phone_number.
[ target_arn: <tmpl_string> ]

# The message content of the SNS notification.
[ message: <tmpl_string> | default = '{{ template "sns.default.message" .}}' ]

# SNS message attributes.
attributes:
[ <string>: <string> ... ]

# The HTTP client's configuration.
[ http_config: <http_config> | default = global.http_config ]
```

###`<sigv4_config>`
```yaml
# The AWS region. If blank, the region from the default credentials chain is used.
[ region: <string> ]

# The AWS API keys. Both access_key and secret_key must be supplied or both must be blank.
# If blank the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are used.
[ access_key: <string> ]
[ secret_key: <secret> ]

# Named AWS profile used to authenticate.
[ profile: <string> ]

# AWS Role ARN, an alternative to using AWS API keys.
[ role_arn: <string> ]
```

## `<matcher>`

A matcher is a string with a syntax inspired by PromQL and OpenMetrics. The syntax of a matcher consists of three tokens:
Expand Down
8 changes: 5 additions & 3 deletions go.mod
Expand Up @@ -2,6 +2,7 @@ module github.com/prometheus/alertmanager

require (
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15
github.com/aws/aws-sdk-go v1.38.35
github.com/cenkalti/backoff/v4 v4.1.0
github.com/cespare/xxhash v1.1.0
github.com/go-kit/kit v0.10.0
Expand All @@ -22,16 +23,17 @@ require (
github.com/oklog/run v1.1.0
github.com/oklog/ulid v1.3.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.10.0
github.com/prometheus/common v0.24.0
github.com/prometheus/client_golang v1.11.0
github.com/prometheus/common v0.29.0
github.com/prometheus/common/sigv4 v0.1.0
github.com/prometheus/exporter-toolkit v0.5.1
github.com/rs/cors v1.7.0
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546
github.com/stretchr/testify v1.7.0
github.com/xlab/treeprint v1.1.0
go.uber.org/atomic v1.5.0
golang.org/x/net v0.0.0-20210421230115-4e50805a0758
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
golang.org/x/tools v0.1.0
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/yaml.v2 v2.4.0
Expand Down

0 comments on commit 010c683

Please sign in to comment.