switch to YAML config.

This commit is contained in:
Fabian Reinartz 2015-06-29 17:21:36 +02:00
parent cb9c85ea5a
commit 2e1a01b2fa
26 changed files with 597 additions and 2006 deletions

View File

@ -20,7 +20,3 @@ web: web/blob/files.go
web/blob/files.go: $(shell find web/templates/ web/static/ -type f)
./web/blob/embed-static.sh web/static web/templates | $(GOFMT) > $@
.PHONY: config
config:
$(MAKE) -C config

View File

@ -1,17 +0,0 @@
# Copyright 2013 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.
all: generated/config.pb.go
generated/config.pb.go: config.proto
protoc --go_out=generated/ config.proto

View File

@ -1,4 +1,4 @@
// Copyright 2013 Prometheus Team
// 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
@ -15,138 +15,473 @@ package config
import (
"fmt"
"io/ioutil"
"regexp"
"strings"
"time"
"github.com/golang/protobuf/proto"
pb "github.com/prometheus/alertmanager/config/generated"
"github.com/prometheus/alertmanager/manager"
"github.com/prometheus/common/model"
"gopkg.in/yaml.v2"
)
const minimumRepeatRate = 1 * time.Minute
var (
DefaultAggrRule = AggrRule{
RepeatRate: model.Duration(2 * time.Hour),
SendResolved: false,
}
// Config encapsulates the configuration of an Alert Manager instance. It wraps
// the raw configuration protocol buffer to be able to add custom methods to
// it.
DefaultHipchatConfig = HipchatConfig{
Color: "purple",
ColorResolved: "green",
MessageFormat: HipchatFormatHTML,
}
DefaultSlackConfig = SlackConfig{
Color: "warning",
ColorResolved: "good",
}
)
// Load parses the YAML input s into a Config.
func Load(s string) (*Config, error) {
cfg := &Config{}
err := yaml.Unmarshal([]byte(s), cfg)
if err != nil {
return nil, err
}
cfg.original = s
return cfg, nil
}
// LoadFile parses the given YAML file into a Config.
func LoadFile(filename string) (*Config, error) {
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return Load(string(content))
}
// Config is the top-level configuration for Alertmanager's config files.
type Config struct {
// The protobuf containing the actual configuration values.
pb.AlertManagerConfig
AggrRules []*AggrRule `yaml:"aggregation_rules,omitempty"`
InhibitRules []*InhibitRule `yaml:"inhibit_rules,omitempty"`
NotificationConfigs []*NotificationConfig `yaml:"notification_configs,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
// original is the input from which the config was parsed.
original string
}
// String returns an ASCII serialization of the loaded configuration protobuf.
func (c Config) String() string {
return proto.MarshalTextString(&c.AlertManagerConfig)
}
// Validate checks an entire parsed Config for the validity of its fields.
func (c Config) Validate() error {
ncNames := map[string]bool{}
for _, nc := range c.NotificationConfig {
if nc.Name == nil {
return fmt.Errorf("Missing name in notification config: %s", proto.MarshalTextString(nc))
func checkOverflow(m map[string]interface{}, ctx string) error {
if len(m) > 0 {
var keys []string
for k := range m {
keys = append(keys, k)
}
for _, pdc := range nc.PagerdutyConfig {
if pdc.ServiceKey == nil {
return fmt.Errorf("Missing service key in PagerDuty notification config: %s", proto.MarshalTextString(pdc))
}
}
for _, ec := range nc.EmailConfig {
if ec.Email == nil {
return fmt.Errorf("Missing email address in email notification config: %s", proto.MarshalTextString(ec))
}
}
for _, ec := range nc.PushoverConfig {
if ec.Token == nil {
return fmt.Errorf("Missing token in Pushover notification config: %s", proto.MarshalTextString(ec))
}
if ec.UserKey == nil {
return fmt.Errorf("Missing user key in Pushover notification config: %s", proto.MarshalTextString(ec))
}
}
for _, hcc := range nc.HipchatConfig {
if hcc.AuthToken == nil {
return fmt.Errorf("Missing token in HipChat config: %s", proto.MarshalTextString(hcc))
}
if hcc.RoomId == nil {
return fmt.Errorf("Missing room in HipChat config: %s", proto.MarshalTextString(hcc))
}
}
for _, sc := range nc.SlackConfig {
if sc.WebhookUrl == nil {
return fmt.Errorf("Missing webhook URL in Slack config: %s", proto.MarshalTextString(sc))
}
}
for _, fc := range nc.FlowdockConfig {
if fc.ApiToken == nil {
return fmt.Errorf("Missing API token in Flowdock config: %s", proto.MarshalTextString(fc))
}
if fc.FromAddress == nil {
return fmt.Errorf("Missing from_address Flowdock config: %s", proto.MarshalTextString(fc))
}
}
if _, ok := ncNames[nc.GetName()]; ok {
return fmt.Errorf("Notification config name not unique: %s", nc.GetName())
}
ncNames[nc.GetName()] = true
return fmt.Errorf("unknown fields in %s: %s", ctx, strings.Join(keys, ", "))
}
for _, a := range c.AggregationRule {
for _, f := range a.Filter {
if f.NameRe == nil {
return fmt.Errorf("Missing name pattern (name_re) in filter definition: %s", proto.MarshalTextString(f))
}
if f.ValueRe == nil {
return fmt.Errorf("Missing value pattern (value_re) in filter definition: %s", proto.MarshalTextString(f))
}
}
if _, ok := ncNames[a.GetNotificationConfigName()]; !ok {
return fmt.Errorf("No such notification config: %s", a.GetNotificationConfigName())
}
}
return nil
}
func filtersFromPb(filters []*pb.Filter) manager.Filters {
fs := make(manager.Filters, 0, len(filters))
for _, f := range filters {
fs = append(fs, manager.NewFilter(f.GetNameRe(), f.GetValueRe()))
func (c Config) String() string {
if c.original != "" {
return c.original
}
return fs
b, err := yaml.Marshal(c)
if err != nil {
return fmt.Sprintf("<error creating config string: %s>", err)
}
return string(b)
}
// AggregationRules returns all the AggregationRules in a Config object.
func (c Config) AggregationRules() manager.AggregationRules {
rules := make(manager.AggregationRules, 0, len(c.AggregationRule))
for _, r := range c.AggregationRule {
rate := time.Duration(r.GetRepeatRateSeconds()) * time.Second
if rate < minimumRepeatRate {
rate = minimumRepeatRate
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
// We want to set c to the defaults and then overwrite it with the input.
// To make unmarshal fill the plain data struct rather than calling UnmarshalYAML
// again, we have to hide it using a type indirection.
type plain Config
if err := unmarshal((*plain)(c)); err != nil {
return err
}
names := map[string]struct{}{}
for _, nc := range c.NotificationConfigs {
if _, ok := names[nc.Name]; ok {
return fmt.Errorf("notification config name %q is not unique", nc.Name)
}
rules = append(rules, &manager.AggregationRule{
Filters: filtersFromPb(r.Filter),
RepeatRate: rate,
NotificationConfigName: r.GetNotificationConfigName(),
})
names[nc.Name] = struct{}{}
}
return rules
return checkOverflow(c.XXX, "config")
}
// InhibitRules returns all the InhibitRules in a Config object.
func (c Config) InhibitRules() manager.InhibitRules {
rules := make(manager.InhibitRules, 0, len(c.InhibitRule))
for _, r := range c.InhibitRule {
sFilters := filtersFromPb(r.SourceFilter)
tFilters := filtersFromPb(r.TargetFilter)
rules = append(rules, &manager.InhibitRule{
SourceFilters: sFilters,
TargetFilters: tFilters,
MatchOn: r.MatchOn,
})
}
return rules
// An InhibitRule specifies that a class of (source) alerts should inhibit
// notifications for another class of (target) alerts if all specified matching
// labels are equal between the two alerts. This may be used to inhibit alerts
// from sending notifications if their meaning is logically a subset of a
// higher-level alert.
type InhibitRule struct {
// The set of Filters which define the group of source alerts (which inhibit
// the target alerts).
SourceFilters []*Filter `yaml:"source_filters,omitempty"`
// The set of Filters which define the group of target alerts (which are
// inhibited by the source alerts).
TargetFilters []*Filter `yaml:"target_filters,omitempty"`
// A set of label names whose label values need to be identical in source and
// target alerts in order for the inhibition to take effect.
MatchOn []string `yaml:"match_on,omitempty"`
// How many seconds to wait for a corresponding inhibit source alert to
// appear before sending any notifications for active target alerts.
// TODO(julius): Not supported yet. Implement this!
// optional int32 before_allowance = 4 [default = 0];
// How many seconds to wait after a corresponding inhibit source alert
// disappears before sending any notifications for active target alerts.
// TODO(julius): Not supported yet. Implement this!
// optional int32 after_allowance = 5 [default = 0];
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (r *InhibitRule) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain InhibitRule
if err := unmarshal((*plain)(r)); err != nil {
return err
}
return checkOverflow(r.XXX, "inhibit rule")
}
// Grouping and notification setting definitions for alerts.
type AggrRule struct {
// Filters that define which alerts are matched by this AggregationRule.
Filters []*Filter `yaml:"filters,omitempty"`
// How many seconds to wait before resending a notification for a specific alert.
RepeatRate model.Duration `yaml:"repeat_rate"`
// Notification configurations to use for this AggregationRule, referenced
// by their name.
NotificationConfigs []string `yaml:"notification_configs"`
// Notify when resolved.
SendResolved bool `yaml:"send_resolved"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (r *AggrRule) UnmarshalYAML(unmarshal func(interface{}) error) error {
*r = DefaultAggrRule
type plain AggrRule
if err := unmarshal((*plain)(r)); err != nil {
return err
}
if len(r.NotificationConfigs) == 0 {
return fmt.Errorf("aggregation rule needs at least one notification config")
}
return checkOverflow(r.XXX, "aggregation rule")
}
// A regex-based label filter used in aggregations.
type Filter struct {
Name string `yaml:"name"`
Regex *Regexp `yaml:"regex"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (f *Filter) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain Filter
if err := unmarshal((*plain)(f)); err != nil {
return err
}
return checkOverflow(f.XXX, "aggregation rule")
}
// Notification configuration definition.
type NotificationConfig struct {
// Name of this NotificationConfig. Referenced from AggregationRule.
Name string `yaml:"name"`
// Notify when resolved.
SendResolved bool `yaml:"send_resolved"`
// Zero or more PagerDuty notification configurations.
PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs"`
// Zero or more email notification configurations.
EmailConfigs []*EmailConfig `yaml:"email_configs"`
// Zero or more pushover notification configurations.
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs"`
// Zero or more hipchat notification configurations.
HipchatConfigs []*HipchatConfig `yaml:"hipchat_configs"`
// Zero or more slack notification configurations.
SlackConfigs []*SlackConfig `yaml:"slack_config"`
// Zero or more Flowdock notification configurations.
FlowdockConfigs []*FlowdockConfig `yaml:"flowdock_config"`
// Zero or more generic web hook notification configurations.
WebhookConfigs []*WebhookConfig `yaml:"webhook_config"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *NotificationConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain NotificationConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.Name == "" {
return fmt.Errorf("missing name in notification config")
}
return checkOverflow(c.XXX, "notification config")
}
// Configuration for notification via PagerDuty.
type PagerdutyConfig struct {
// PagerDuty service key, see:
// http://developer.pagerduty.com/documentation/integration/events
ServiceKey string `yaml:"service_key"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain PagerdutyConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.ServiceKey == "" {
return fmt.Errorf("missing service key in PagerDuty config")
}
return checkOverflow(c.XXX, "pagerduty config")
}
// Configuration for notification via mail.
type EmailConfig struct {
// Email address to notify.
Email string `yaml:"email"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *EmailConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain EmailConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.Email == "" {
return fmt.Errorf("missing email address in email config")
}
return checkOverflow(c.XXX, "email config")
}
// Configuration for notification via pushover.net.
type PushoverConfig struct {
// Pushover token.
Token string `yaml:"token"`
// Pushover user_key.
UserKey string `yaml:"user_key"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *PushoverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain PushoverConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.Token == "" {
return fmt.Errorf("missing token in Pushover config")
}
if c.UserKey == "" {
return fmt.Errorf("missing user key in Pushover config")
}
return checkOverflow(c.XXX, "pushover config")
}
type HipchatFormat string
const (
HipchatFormatHTML HipchatFormat = "html"
HipchatFormatText HipchatFormat = "text"
)
// Configuration for notification via HipChat.
// https://www.hipchat.com/docs/apiv2/method/send_room_notification
type HipchatConfig struct {
// HipChat auth token, (https://www.hipchat.com/docs/api/auth).
AuthToken string `yaml:"auth_token"`
// HipChat room id, (https://www.hipchat.com/rooms/ids).
RoomID int `yaml:"room_id"`
// Color of message when triggered.
Color string `yaml:"color"`
// Color of message when resolved.
ColorResolved string `yaml:"color_resolved"`
// Should this message notify or not.
Notify bool `yaml:"notify"`
// Prefix to be put in front of the message (useful for @mentions, etc.).
Prefix string `yaml:"prefix"`
// Format the message as "html" or "text".
MessageFormat HipchatFormat `yaml:"message_format"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *HipchatConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultHipchatConfig
type plain HipchatConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.AuthToken == "" {
return fmt.Errorf("missing auth token in HipChat config")
}
if c.MessageFormat != HipchatFormatHTML && c.MessageFormat != HipchatFormatText {
return fmt.Errorf("invalid message format %q", c.MessageFormat)
}
return checkOverflow(c.XXX, "hipchat config")
}
// Configuration for notification via Slack.
type SlackConfig struct {
// Slack webhook URL, (https://api.slack.com/incoming-webhooks).
WebhookURL string `yaml:"webhook_url"`
// Slack channel override, (like #other-channel or @username).
Channel string `yaml:"channel"`
// Color of message when triggered.
Color string `yaml:"color"`
// Color of message when resolved.
ColorResolved string `yaml:"color_resolved"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultSlackConfig
type plain SlackConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.WebhookURL == "" {
return fmt.Errorf("missing webhook URL in Slack config")
}
if c.Channel == "" {
return fmt.Errorf("missing channel in Slack config")
}
return checkOverflow(c.XXX, "slack config")
}
// Configuration for notification via Flowdock.
type FlowdockConfig struct {
// Flowdock flow API token.
APIToken string `yaml:"api_token"`
// Flowdock from_address.
FromAddress string `yaml:"from_address"`
// Flowdock flow tags.
Tags []string `yaml:"tags"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *FlowdockConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain FlowdockConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.APIToken == "" {
return fmt.Errorf("missing API token in Flowdock config")
}
if c.FromAddress == "" {
return fmt.Errorf("missing from address in Flowdock config")
}
return checkOverflow(c.XXX, "flowdock config")
}
// Configuration for notification via generic webhook.
type WebhookConfig struct {
// URL to send POST request to.
URL string `yaml:"url"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain WebhookConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.URL == "" {
return fmt.Errorf("missing URL in webhook config")
}
return checkOverflow(c.XXX, "slack config")
}
// Regexp encapsulates a regexp.Regexp and makes it YAML marshallable.
type Regexp struct {
regexp.Regexp
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (re *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
regex, err := regexp.Compile(s)
if err != nil {
return err
}
re.Regexp = *regex
return nil
}
// MarshalYAML implements the yaml.Marshaler interface.
func (re *Regexp) MarshalYAML() (interface{}, error) {
if re != nil {
return re.String(), nil
}
return nil, nil
}

View File

@ -1,221 +0,0 @@
// Copyright 2013 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 io.prometheus.alertmanager;
// Configuration for notification via PagerDuty.
message PagerDutyConfig {
// PagerDuty service key, see:
// http://developer.pagerduty.com/documentation/integration/events
optional string service_key = 1;
}
// Configuration for notification via mail.
message EmailConfig {
// Email address to notify.
optional string email = 1;
// Notify when resolved.
optional bool send_resolved = 2 [default = false];
}
// Configuration for notification via pushover.net.
message PushoverConfig {
// Pushover token.
optional string token = 1;
// Pushover user_key.
optional string user_key = 2;
// Notify when resolved.
optional bool send_resolved = 3 [default = false];
}
// Configuration for notification via HipChat.
message HipChatConfig {
// https://www.hipchat.com/docs/apiv2/method/send_room_notification
// HipChat auth token, (https://www.hipchat.com/docs/api/auth).
optional string auth_token = 1;
// HipChat room id, (https://www.hipchat.com/rooms/ids).
optional int32 room_id = 2;
// Color of message when triggered.
optional string color = 3 [default = "purple"];
// Color of message when resolved.
optional string color_resolved = 5 [default = "green"];
// Should this message notify or not.
optional bool notify = 4 [default = false];
// Notify when resolved.
optional bool send_resolved = 6 [default = false];
// Prefix to be put in front of the message (useful for @mentions, etc.).
optional string prefix = 7 [default = ""];
// Format the message as "html" or "text".
enum MessageFormat {
HTML = 0;
TEXT = 1;
}
optional MessageFormat message_format = 8 [default = HTML];
}
// Configuration for notification via Slack.
message SlackConfig {
// Slack webhook URL, (https://api.slack.com/incoming-webhooks).
optional string webhook_url = 1;
// Slack channel override, (like #other-channel or @username).
optional string channel = 2;
// Color of message when triggered.
optional string color = 3 [default = "warning"];
// Color of message when resolved.
optional string color_resolved = 4 [default = "good"];
// Notify when resolved.
optional bool send_resolved = 5 [default = false];
}
// Configuration for notification via Flowdock.
message FlowdockConfig {
// Flowdock flow API token.
optional string api_token = 1;
// Flowdock from_address.
optional string from_address = 2;
// Flowdock flow tags.
repeated string tag = 3;
// Notify when resolved.
optional bool send_resolved = 4 [default = false];
}
// Configuration for notification via generic webhook.
message WebhookConfig {
// URL to send POST request to.
optional string url = 1;
// Notify when resolved.
optional bool send_resolved = 2 [default = false];
}
// Notification configuration definition.
message NotificationConfig {
// Name of this NotificationConfig. Referenced from AggregationRule.
optional string name = 1;
// Zero or more PagerDuty notification configurations.
repeated PagerDutyConfig pagerduty_config = 2;
// Zero or more email notification configurations.
repeated EmailConfig email_config = 3;
// Zero or more pushover notification configurations.
repeated PushoverConfig pushover_config = 4;
// Zero or more hipchat notification configurations.
repeated HipChatConfig hipchat_config = 5;
// Zero or more slack notification configurations.
repeated SlackConfig slack_config = 6;
// Zero or more Flowdock notification configurations.
repeated FlowdockConfig flowdock_config = 7;
// Zero or more generic web hook notification configurations.
repeated WebhookConfig webhook_config = 8;
}
// A regex-based label filter used in aggregations.
message Filter {
// The regex matching the label name.
optional string name_re = 1;
// The regex matching the label value.
optional string value_re = 2;
}
// Grouping and notification setting definitions for alerts.
message AggregationRule {
// Filters that define which alerts are matched by this AggregationRule.
repeated Filter filter = 1;
// How many seconds to wait before resending a notification for a specific alert.
optional int32 repeat_rate_seconds = 2 [default = 7200];
// Notification configuration to use for this AggregationRule, referenced by
// their name.
optional string notification_config_name = 3;
}
// An InhibitRule specifies that a class of (source) alerts should inhibit
// notifications for another class of (target) alerts if all specified matching
// labels are equal between the two alerts. This may be used to inhibit alerts
// from sending notifications if their meaning is logically a subset of a
// higher-level alert.
//
// For example, if an entire job is down, there is little sense in sending a
// notification for every single instance of said job being down. This could be
// expressed as the following inhibit rule:
//
// inhibit_rule {
// # Select all source alerts that are candidates for being inhibitors. All
// # supplied source filters have to match in order to select a source alert.
// source_filter: {
// name_re: "alertname"
// value_re: "JobDown"
// }
// source_filter: {
// name_re: "service"
// value_re: "api"
// }
//
// # Select all target alerts that are candidates for being inhibited. All
// # supplied target filters have to match in order to select a target alert.
// target_filter: {
// name_re: "alertname"
// value_re: "InstanceDown"
// }
// target_filter: {
// name_re: "service"
// value_re: "api"
// }
//
// # A target alert only actually inhibits a source alert if they match on
// # these labels. I.e. the alerts needs to fire for the same job in the same
// # zone for the inhibit to take effect between them.
// match_on: "job"
// match_on: "zone"
// }
//
// In this example, when JobDown is firing for
//
// JobDown{zone="aa",job="test",service="api"}
//
// ...it would inhibit an InstanceDown alert for
//
// InstanceDown{zone="aa",job="test",instance="1",service="api"}
//
// However, an InstanceDown alert for another zone:
//
// {zone="ab",job="test",instance="1",service="api"}
//
// ...would still fire.
message InhibitRule {
// The set of Filters which define the group of source alerts (which inhibit
// the target alerts).
repeated Filter source_filter = 1;
// The set of Filters which define the group of target alerts (which are
// inhibited by the source alerts).
repeated Filter target_filter = 2;
// A set of label names whose label values need to be identical in source and
// target alerts in order for the inhibition to take effect.
repeated string match_on = 3;
// How many seconds to wait for a corresponding inhibit source alert to
// appear before sending any notifications for active target alerts.
// TODO(julius): Not supported yet. Implement this!
// optional int32 before_allowance = 4 [default = 0];
// How many seconds to wait after a corresponding inhibit source alert
// disappears before sending any notifications for active target alerts.
// TODO(julius): Not supported yet. Implement this!
// optional int32 after_allowance = 5 [default = 0];
}
// Global alert manager configuration.
message AlertManagerConfig {
// Aggregation rule definitions.
repeated AggregationRule aggregation_rule = 1;
// Notification configuration definitions.
repeated NotificationConfig notification_config = 2;
// List of alert inhibition rules.
repeated InhibitRule inhibit_rule = 3;
}

View File

@ -1,76 +0,0 @@
// Copyright 2013 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 config
import (
"path"
"strings"
"testing"
)
var fixturesPath = "fixtures"
type configTest struct {
inputFile string
shouldFail bool
errContains string
}
func (ct *configTest) test(i int, t *testing.T) {
_, err := LoadFromFile(path.Join(fixturesPath, ct.inputFile))
if err != nil {
if !ct.shouldFail {
t.Fatalf("%d. Error parsing config %v: %v", i, ct.inputFile, err)
} else {
if !strings.Contains(err.Error(), ct.errContains) {
t.Fatalf("%d. Expected error containing '%v', got: %v", i, ct.errContains, err)
}
}
}
}
func TestConfigs(t *testing.T) {
var configTests = []configTest{
{
inputFile: "empty.conf.input",
}, {
inputFile: "sample.conf.input",
}, {
inputFile: "missing_filter_name_re.conf.input",
shouldFail: true,
errContains: "Missing name pattern",
}, {
inputFile: "invalid_proto_format.conf.input",
shouldFail: true,
errContains: "unknown field name",
}, {
inputFile: "duplicate_nc_name.conf.input",
shouldFail: true,
errContains: "not unique",
}, {
inputFile: "nonexistent_nc_name.conf.input",
shouldFail: true,
errContains: "No such notification config",
}, {
inputFile: "missing_nc_name.conf.input",
shouldFail: true,
errContains: "Missing name",
},
}
for i, ct := range configTests {
ct.test(i, t)
}
}

View File

@ -1,28 +0,0 @@
notification_config {
name: "alertmanager_test"
pagerduty_config {
service_key: "supersecretapikey"
}
email_config {
email: "test@testservice.org"
}
}
notification_config {
name: "alertmanager_test"
pagerduty_config {
service_key: "supersecretapikey"
}
email_config {
email: "test@testservice.org"
}
}
aggregation_rule {
filter {
name_re: "service"
value_re: "test"
}
repeat_rate_seconds: 3600
notification_config_name: "alertmanager_test"
}

View File

@ -1,18 +0,0 @@
aggregation_rule {
filter {
label_re: "service" // Unknown protobuf field name.
value_re: "discovery"
}
filter {
name_re: "zone"
value_re: "aa"
}
repeat_rate: 3600
}
aggregation_rule {
filter {
name_re: "service"
value_re: "test"
}
}

View File

@ -1,6 +0,0 @@
aggregation_rule {
filter {
value_re: "test"
}
repeat_rate_seconds: 3600
}

View File

@ -1,27 +0,0 @@
notification_config {
pagerduty_config {
service_key: "supersecretapikey"
}
email_config {
email: "test@testservice.org"
}
}
notification_config {
name: "alertmanager_test"
pagerduty_config {
service_key: "supersecretapikey"
}
email_config {
email: "test@testservice.org"
}
}
aggregation_rule {
filter {
name_re: "service"
value_re: "test"
}
repeat_rate_seconds: 3600
notification_config_name: "alertmanager_test"
}

View File

@ -1,18 +0,0 @@
notification_config {
name: "alertmanager_test"
pagerduty_config {
service_key: "supersecretapikey"
}
email_config {
email: "test@testservice.org"
}
}
aggregation_rule {
filter {
name_re: "service"
value_re: "test"
}
repeat_rate_seconds: 3600
notification_config_name: "alertmanager_test2"
}

View File

@ -1,36 +0,0 @@
notification_config {
name: "alertmanager_test"
pagerduty_config {
service_key: "supersecretapikey"
}
email_config {
email: "test@testservice.org"
}
pushover_config {
token: "mypushovertoken"
user_key: "mypushoverkey"
}
hipchat_config {
auth_token: "hipchatauthtoken"
room_id: 123456
send_resolved: true
}
slack_config {
webhook_url: "webhookurl"
send_resolved: true
}
flowdock_config {
api_token: "4c7234902348234902384234234cdb59"
from_address: "aliaswithgravatar@somehwere.com"
tag: "monitoring"
}
}
aggregation_rule {
filter {
name_re: "service"
value_re: "test"
}
repeat_rate_seconds: 3600
notification_config_name: "alertmanager_test"
}

View File

@ -1,650 +0,0 @@
// Code generated by protoc-gen-go.
// source: config.proto
// DO NOT EDIT!
/*
Package io_prometheus_alertmanager is a generated protocol buffer package.
It is generated from these files:
config.proto
It has these top-level messages:
PagerDutyConfig
EmailConfig
PushoverConfig
HipChatConfig
SlackConfig
FlowdockConfig
WebhookConfig
NotificationConfig
Filter
AggregationRule
InhibitRule
AlertManagerConfig
*/
package io_prometheus_alertmanager
import proto "github.com/golang/protobuf/proto"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = math.Inf
// Format the message as "html" or "text".
type HipChatConfig_MessageFormat int32
const (
HipChatConfig_HTML HipChatConfig_MessageFormat = 0
HipChatConfig_TEXT HipChatConfig_MessageFormat = 1
)
var HipChatConfig_MessageFormat_name = map[int32]string{
0: "HTML",
1: "TEXT",
}
var HipChatConfig_MessageFormat_value = map[string]int32{
"HTML": 0,
"TEXT": 1,
}
func (x HipChatConfig_MessageFormat) Enum() *HipChatConfig_MessageFormat {
p := new(HipChatConfig_MessageFormat)
*p = x
return p
}
func (x HipChatConfig_MessageFormat) String() string {
return proto.EnumName(HipChatConfig_MessageFormat_name, int32(x))
}
func (x *HipChatConfig_MessageFormat) UnmarshalJSON(data []byte) error {
value, err := proto.UnmarshalJSONEnum(HipChatConfig_MessageFormat_value, data, "HipChatConfig_MessageFormat")
if err != nil {
return err
}
*x = HipChatConfig_MessageFormat(value)
return nil
}
// Configuration for notification via PagerDuty.
type PagerDutyConfig struct {
// PagerDuty service key, see:
// http://developer.pagerduty.com/documentation/integration/events
ServiceKey *string `protobuf:"bytes,1,opt,name=service_key" json:"service_key,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *PagerDutyConfig) Reset() { *m = PagerDutyConfig{} }
func (m *PagerDutyConfig) String() string { return proto.CompactTextString(m) }
func (*PagerDutyConfig) ProtoMessage() {}
func (m *PagerDutyConfig) GetServiceKey() string {
if m != nil && m.ServiceKey != nil {
return *m.ServiceKey
}
return ""
}
// Configuration for notification via mail.
type EmailConfig struct {
// Email address to notify.
Email *string `protobuf:"bytes,1,opt,name=email" json:"email,omitempty"`
// Notify when resolved.
SendResolved *bool `protobuf:"varint,2,opt,name=send_resolved,def=0" json:"send_resolved,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *EmailConfig) Reset() { *m = EmailConfig{} }
func (m *EmailConfig) String() string { return proto.CompactTextString(m) }
func (*EmailConfig) ProtoMessage() {}
const Default_EmailConfig_SendResolved bool = false
func (m *EmailConfig) GetEmail() string {
if m != nil && m.Email != nil {
return *m.Email
}
return ""
}
func (m *EmailConfig) GetSendResolved() bool {
if m != nil && m.SendResolved != nil {
return *m.SendResolved
}
return Default_EmailConfig_SendResolved
}
// Configuration for notification via pushover.net.
type PushoverConfig struct {
// Pushover token.
Token *string `protobuf:"bytes,1,opt,name=token" json:"token,omitempty"`
// Pushover user_key.
UserKey *string `protobuf:"bytes,2,opt,name=user_key" json:"user_key,omitempty"`
// Notify when resolved.
SendResolved *bool `protobuf:"varint,3,opt,name=send_resolved,def=0" json:"send_resolved,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *PushoverConfig) Reset() { *m = PushoverConfig{} }
func (m *PushoverConfig) String() string { return proto.CompactTextString(m) }
func (*PushoverConfig) ProtoMessage() {}
const Default_PushoverConfig_SendResolved bool = false
func (m *PushoverConfig) GetToken() string {
if m != nil && m.Token != nil {
return *m.Token
}
return ""
}
func (m *PushoverConfig) GetUserKey() string {
if m != nil && m.UserKey != nil {
return *m.UserKey
}
return ""
}
func (m *PushoverConfig) GetSendResolved() bool {
if m != nil && m.SendResolved != nil {
return *m.SendResolved
}
return Default_PushoverConfig_SendResolved
}
// Configuration for notification via HipChat.
type HipChatConfig struct {
// HipChat auth token, (https://www.hipchat.com/docs/api/auth).
AuthToken *string `protobuf:"bytes,1,opt,name=auth_token" json:"auth_token,omitempty"`
// HipChat room id, (https://www.hipchat.com/rooms/ids).
RoomId *int32 `protobuf:"varint,2,opt,name=room_id" json:"room_id,omitempty"`
// Color of message when triggered.
Color *string `protobuf:"bytes,3,opt,name=color,def=purple" json:"color,omitempty"`
// Color of message when resolved.
ColorResolved *string `protobuf:"bytes,5,opt,name=color_resolved,def=green" json:"color_resolved,omitempty"`
// Should this message notify or not.
Notify *bool `protobuf:"varint,4,opt,name=notify,def=0" json:"notify,omitempty"`
// Notify when resolved.
SendResolved *bool `protobuf:"varint,6,opt,name=send_resolved,def=0" json:"send_resolved,omitempty"`
// Prefix to be put in front of the message (useful for @mentions, etc.).
Prefix *string `protobuf:"bytes,7,opt,name=prefix,def=" json:"prefix,omitempty"`
MessageFormat *HipChatConfig_MessageFormat `protobuf:"varint,8,opt,name=message_format,enum=io.prometheus.alertmanager.HipChatConfig_MessageFormat,def=0" json:"message_format,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *HipChatConfig) Reset() { *m = HipChatConfig{} }
func (m *HipChatConfig) String() string { return proto.CompactTextString(m) }
func (*HipChatConfig) ProtoMessage() {}
const Default_HipChatConfig_Color string = "purple"
const Default_HipChatConfig_ColorResolved string = "green"
const Default_HipChatConfig_Notify bool = false
const Default_HipChatConfig_SendResolved bool = false
const Default_HipChatConfig_MessageFormat HipChatConfig_MessageFormat = HipChatConfig_HTML
func (m *HipChatConfig) GetAuthToken() string {
if m != nil && m.AuthToken != nil {
return *m.AuthToken
}
return ""
}
func (m *HipChatConfig) GetRoomId() int32 {
if m != nil && m.RoomId != nil {
return *m.RoomId
}
return 0
}
func (m *HipChatConfig) GetColor() string {
if m != nil && m.Color != nil {
return *m.Color
}
return Default_HipChatConfig_Color
}
func (m *HipChatConfig) GetColorResolved() string {
if m != nil && m.ColorResolved != nil {
return *m.ColorResolved
}
return Default_HipChatConfig_ColorResolved
}
func (m *HipChatConfig) GetNotify() bool {
if m != nil && m.Notify != nil {
return *m.Notify
}
return Default_HipChatConfig_Notify
}
func (m *HipChatConfig) GetSendResolved() bool {
if m != nil && m.SendResolved != nil {
return *m.SendResolved
}
return Default_HipChatConfig_SendResolved
}
func (m *HipChatConfig) GetPrefix() string {
if m != nil && m.Prefix != nil {
return *m.Prefix
}
return ""
}
func (m *HipChatConfig) GetMessageFormat() HipChatConfig_MessageFormat {
if m != nil && m.MessageFormat != nil {
return *m.MessageFormat
}
return Default_HipChatConfig_MessageFormat
}
// Configuration for notification via Slack.
type SlackConfig struct {
// Slack webhook URL, (https://api.slack.com/incoming-webhooks).
WebhookUrl *string `protobuf:"bytes,1,opt,name=webhook_url" json:"webhook_url,omitempty"`
// Slack channel override, (like #other-channel or @username).
Channel *string `protobuf:"bytes,2,opt,name=channel" json:"channel,omitempty"`
// Color of message when triggered.
Color *string `protobuf:"bytes,3,opt,name=color,def=warning" json:"color,omitempty"`
// Color of message when resolved.
ColorResolved *string `protobuf:"bytes,4,opt,name=color_resolved,def=good" json:"color_resolved,omitempty"`
// Notify when resolved.
SendResolved *bool `protobuf:"varint,5,opt,name=send_resolved,def=0" json:"send_resolved,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *SlackConfig) Reset() { *m = SlackConfig{} }
func (m *SlackConfig) String() string { return proto.CompactTextString(m) }
func (*SlackConfig) ProtoMessage() {}
const Default_SlackConfig_Color string = "warning"
const Default_SlackConfig_ColorResolved string = "good"
const Default_SlackConfig_SendResolved bool = false
func (m *SlackConfig) GetWebhookUrl() string {
if m != nil && m.WebhookUrl != nil {
return *m.WebhookUrl
}
return ""
}
func (m *SlackConfig) GetChannel() string {
if m != nil && m.Channel != nil {
return *m.Channel
}
return ""
}
func (m *SlackConfig) GetColor() string {
if m != nil && m.Color != nil {
return *m.Color
}
return Default_SlackConfig_Color
}
func (m *SlackConfig) GetColorResolved() string {
if m != nil && m.ColorResolved != nil {
return *m.ColorResolved
}
return Default_SlackConfig_ColorResolved
}
func (m *SlackConfig) GetSendResolved() bool {
if m != nil && m.SendResolved != nil {
return *m.SendResolved
}
return Default_SlackConfig_SendResolved
}
// Configuration for notification via Flowdock.
type FlowdockConfig struct {
// Flowdock flow API token.
ApiToken *string `protobuf:"bytes,1,opt,name=api_token" json:"api_token,omitempty"`
// Flowdock from_address.
FromAddress *string `protobuf:"bytes,2,opt,name=from_address" json:"from_address,omitempty"`
// Flowdock flow tags.
Tag []string `protobuf:"bytes,3,rep,name=tag" json:"tag,omitempty"`
// Notify when resolved.
SendResolved *bool `protobuf:"varint,4,opt,name=send_resolved,def=0" json:"send_resolved,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *FlowdockConfig) Reset() { *m = FlowdockConfig{} }
func (m *FlowdockConfig) String() string { return proto.CompactTextString(m) }
func (*FlowdockConfig) ProtoMessage() {}
const Default_FlowdockConfig_SendResolved bool = false
func (m *FlowdockConfig) GetApiToken() string {
if m != nil && m.ApiToken != nil {
return *m.ApiToken
}
return ""
}
func (m *FlowdockConfig) GetFromAddress() string {
if m != nil && m.FromAddress != nil {
return *m.FromAddress
}
return ""
}
func (m *FlowdockConfig) GetTag() []string {
if m != nil {
return m.Tag
}
return nil
}
func (m *FlowdockConfig) GetSendResolved() bool {
if m != nil && m.SendResolved != nil {
return *m.SendResolved
}
return Default_FlowdockConfig_SendResolved
}
// Configuration for notification via generic webhook.
type WebhookConfig struct {
// URL to send POST request to.
Url *string `protobuf:"bytes,1,opt,name=url" json:"url,omitempty"`
// Notify when resolved.
SendResolved *bool `protobuf:"varint,2,opt,name=send_resolved,def=0" json:"send_resolved,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *WebhookConfig) Reset() { *m = WebhookConfig{} }
func (m *WebhookConfig) String() string { return proto.CompactTextString(m) }
func (*WebhookConfig) ProtoMessage() {}
const Default_WebhookConfig_SendResolved bool = false
func (m *WebhookConfig) GetUrl() string {
if m != nil && m.Url != nil {
return *m.Url
}
return ""
}
func (m *WebhookConfig) GetSendResolved() bool {
if m != nil && m.SendResolved != nil {
return *m.SendResolved
}
return Default_WebhookConfig_SendResolved
}
// Notification configuration definition.
type NotificationConfig struct {
// Name of this NotificationConfig. Referenced from AggregationRule.
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// Zero or more PagerDuty notification configurations.
PagerdutyConfig []*PagerDutyConfig `protobuf:"bytes,2,rep,name=pagerduty_config" json:"pagerduty_config,omitempty"`
// Zero or more email notification configurations.
EmailConfig []*EmailConfig `protobuf:"bytes,3,rep,name=email_config" json:"email_config,omitempty"`
// Zero or more pushover notification configurations.
PushoverConfig []*PushoverConfig `protobuf:"bytes,4,rep,name=pushover_config" json:"pushover_config,omitempty"`
// Zero or more hipchat notification configurations.
HipchatConfig []*HipChatConfig `protobuf:"bytes,5,rep,name=hipchat_config" json:"hipchat_config,omitempty"`
// Zero or more slack notification configurations.
SlackConfig []*SlackConfig `protobuf:"bytes,6,rep,name=slack_config" json:"slack_config,omitempty"`
// Zero or more Flowdock notification configurations.
FlowdockConfig []*FlowdockConfig `protobuf:"bytes,7,rep,name=flowdock_config" json:"flowdock_config,omitempty"`
// Zero or more generic web hook notification configurations.
WebhookConfig []*WebhookConfig `protobuf:"bytes,8,rep,name=webhook_config" json:"webhook_config,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *NotificationConfig) Reset() { *m = NotificationConfig{} }
func (m *NotificationConfig) String() string { return proto.CompactTextString(m) }
func (*NotificationConfig) ProtoMessage() {}
func (m *NotificationConfig) GetName() string {
if m != nil && m.Name != nil {
return *m.Name
}
return ""
}
func (m *NotificationConfig) GetPagerdutyConfig() []*PagerDutyConfig {
if m != nil {
return m.PagerdutyConfig
}
return nil
}
func (m *NotificationConfig) GetEmailConfig() []*EmailConfig {
if m != nil {
return m.EmailConfig
}
return nil
}
func (m *NotificationConfig) GetPushoverConfig() []*PushoverConfig {
if m != nil {
return m.PushoverConfig
}
return nil
}
func (m *NotificationConfig) GetHipchatConfig() []*HipChatConfig {
if m != nil {
return m.HipchatConfig
}
return nil
}
func (m *NotificationConfig) GetSlackConfig() []*SlackConfig {
if m != nil {
return m.SlackConfig
}
return nil
}
func (m *NotificationConfig) GetFlowdockConfig() []*FlowdockConfig {
if m != nil {
return m.FlowdockConfig
}
return nil
}
func (m *NotificationConfig) GetWebhookConfig() []*WebhookConfig {
if m != nil {
return m.WebhookConfig
}
return nil
}
// A regex-based label filter used in aggregations.
type Filter struct {
// The regex matching the label name.
NameRe *string `protobuf:"bytes,1,opt,name=name_re" json:"name_re,omitempty"`
// The regex matching the label value.
ValueRe *string `protobuf:"bytes,2,opt,name=value_re" json:"value_re,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Filter) Reset() { *m = Filter{} }
func (m *Filter) String() string { return proto.CompactTextString(m) }
func (*Filter) ProtoMessage() {}
func (m *Filter) GetNameRe() string {
if m != nil && m.NameRe != nil {
return *m.NameRe
}
return ""
}
func (m *Filter) GetValueRe() string {
if m != nil && m.ValueRe != nil {
return *m.ValueRe
}
return ""
}
// Grouping and notification setting definitions for alerts.
type AggregationRule struct {
// Filters that define which alerts are matched by this AggregationRule.
Filter []*Filter `protobuf:"bytes,1,rep,name=filter" json:"filter,omitempty"`
// How many seconds to wait before resending a notification for a specific alert.
RepeatRateSeconds *int32 `protobuf:"varint,2,opt,name=repeat_rate_seconds,def=7200" json:"repeat_rate_seconds,omitempty"`
// Notification configuration to use for this AggregationRule, referenced by
// their name.
NotificationConfigName *string `protobuf:"bytes,3,opt,name=notification_config_name" json:"notification_config_name,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *AggregationRule) Reset() { *m = AggregationRule{} }
func (m *AggregationRule) String() string { return proto.CompactTextString(m) }
func (*AggregationRule) ProtoMessage() {}
const Default_AggregationRule_RepeatRateSeconds int32 = 7200
func (m *AggregationRule) GetFilter() []*Filter {
if m != nil {
return m.Filter
}
return nil
}
func (m *AggregationRule) GetRepeatRateSeconds() int32 {
if m != nil && m.RepeatRateSeconds != nil {
return *m.RepeatRateSeconds
}
return Default_AggregationRule_RepeatRateSeconds
}
func (m *AggregationRule) GetNotificationConfigName() string {
if m != nil && m.NotificationConfigName != nil {
return *m.NotificationConfigName
}
return ""
}
// An InhibitRule specifies that a class of (source) alerts should inhibit
// notifications for another class of (target) alerts if all specified matching
// labels are equal between the two alerts. This may be used to inhibit alerts
// from sending notifications if their meaning is logically a subset of a
// higher-level alert.
//
// For example, if an entire job is down, there is little sense in sending a
// notification for every single instance of said job being down. This could be
// expressed as the following inhibit rule:
//
// inhibit_rule {
// # Select all source alerts that are candidates for being inhibitors. All
// # supplied source filters have to match in order to select a source alert.
// source_filter: {
// name_re: "alertname"
// value_re: "JobDown"
// }
// source_filter: {
// name_re: "service"
// value_re: "api"
// }
//
// # Select all target alerts that are candidates for being inhibited. All
// # supplied target filters have to match in order to select a target alert.
// target_filter: {
// name_re: "alertname"
// value_re: "InstanceDown"
// }
// target_filter: {
// name_re: "service"
// value_re: "api"
// }
//
// # A target alert only actually inhibits a source alert if they match on
// # these labels. I.e. the alerts needs to fire for the same job in the same
// # zone for the inhibit to take effect between them.
// match_on: "job"
// match_on: "zone"
// }
//
// In this example, when JobDown is firing for
//
// JobDown{zone="aa",job="test",service="api"}
//
// ...it would inhibit an InstanceDown alert for
//
// InstanceDown{zone="aa",job="test",instance="1",service="api"}
//
// However, an InstanceDown alert for another zone:
//
// {zone="ab",job="test",instance="1",service="api"}
//
// ...would still fire.
type InhibitRule struct {
// The set of Filters which define the group of source alerts (which inhibit
// the target alerts).
SourceFilter []*Filter `protobuf:"bytes,1,rep,name=source_filter" json:"source_filter,omitempty"`
// The set of Filters which define the group of target alerts (which are
// inhibited by the source alerts).
TargetFilter []*Filter `protobuf:"bytes,2,rep,name=target_filter" json:"target_filter,omitempty"`
// A set of label names whose label values need to be identical in source and
// target alerts in order for the inhibition to take effect.
MatchOn []string `protobuf:"bytes,3,rep,name=match_on" json:"match_on,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *InhibitRule) Reset() { *m = InhibitRule{} }
func (m *InhibitRule) String() string { return proto.CompactTextString(m) }
func (*InhibitRule) ProtoMessage() {}
func (m *InhibitRule) GetSourceFilter() []*Filter {
if m != nil {
return m.SourceFilter
}
return nil
}
func (m *InhibitRule) GetTargetFilter() []*Filter {
if m != nil {
return m.TargetFilter
}
return nil
}
func (m *InhibitRule) GetMatchOn() []string {
if m != nil {
return m.MatchOn
}
return nil
}
// Global alert manager configuration.
type AlertManagerConfig struct {
// Aggregation rule definitions.
AggregationRule []*AggregationRule `protobuf:"bytes,1,rep,name=aggregation_rule" json:"aggregation_rule,omitempty"`
// Notification configuration definitions.
NotificationConfig []*NotificationConfig `protobuf:"bytes,2,rep,name=notification_config" json:"notification_config,omitempty"`
// List of alert inhibition rules.
InhibitRule []*InhibitRule `protobuf:"bytes,3,rep,name=inhibit_rule" json:"inhibit_rule,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *AlertManagerConfig) Reset() { *m = AlertManagerConfig{} }
func (m *AlertManagerConfig) String() string { return proto.CompactTextString(m) }
func (*AlertManagerConfig) ProtoMessage() {}
func (m *AlertManagerConfig) GetAggregationRule() []*AggregationRule {
if m != nil {
return m.AggregationRule
}
return nil
}
func (m *AlertManagerConfig) GetNotificationConfig() []*NotificationConfig {
if m != nil {
return m.NotificationConfig
}
return nil
}
func (m *AlertManagerConfig) GetInhibitRule() []*InhibitRule {
if m != nil {
return m.InhibitRule
}
return nil
}
func init() {
proto.RegisterEnum("io.prometheus.alertmanager.HipChatConfig_MessageFormat", HipChatConfig_MessageFormat_name, HipChatConfig_MessageFormat_value)
}

View File

@ -1,43 +0,0 @@
// Copyright 2013 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 config
import (
"github.com/prometheus/client_golang/prometheus"
)
const namespace = "alertmanager"
var configReloads = prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: namespace,
Subsystem: "config",
Name: "reloads_total",
Help: "The total number of configuration reloads.",
},
)
var failedConfigReloads = prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: namespace,
Subsystem: "config",
Name: "failed_reloads_total",
Help: "The number of failed configuration reloads.",
},
)
func init() {
prometheus.MustRegister(configReloads)
prometheus.MustRegister(failedConfigReloads)
}

View File

@ -1,52 +0,0 @@
// Copyright 2013 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 config
import (
"io/ioutil"
"github.com/golang/protobuf/proto"
"github.com/prometheus/log"
pb "github.com/prometheus/alertmanager/config/generated"
)
func LoadFromString(configStr string) (Config, error) {
configProto := pb.AlertManagerConfig{}
if err := proto.UnmarshalText(configStr, &configProto); err != nil {
return Config{}, err
}
config := Config{AlertManagerConfig: configProto}
err := config.Validate()
return config, err
}
func LoadFromFile(fileName string) (Config, error) {
configStr, err := ioutil.ReadFile(fileName)
if err != nil {
return Config{}, err
}
return LoadFromString(string(configStr))
}
func MustLoadFromFile(fileName string) Config {
conf, err := LoadFromFile(fileName)
if err != nil {
log.Fatalf("Error loading configuration from %s: %s", fileName, err)
}
return conf
}

View File

@ -1,25 +0,0 @@
// Copyright 2013 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 config
import (
"testing"
)
func TestLoadFromFile(t *testing.T) {
_, err := LoadFromFile("file-does-not-exist.conf")
if err == nil {
t.Error(err)
}
}

View File

@ -1,69 +0,0 @@
// Copyright 2013 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 config
import (
"github.com/prometheus/log"
"gopkg.in/fsnotify.v0"
)
type ReloadCallback func(*Config)
type Watcher interface {
Watch(c ReloadCallback)
}
type fileWatcher struct {
fileName string
}
func NewFileWatcher(fileName string) *fileWatcher {
return &fileWatcher{
fileName: fileName,
}
}
func (w *fileWatcher) Watch(cb ReloadCallback) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
err = watcher.WatchFlags(w.fileName, fsnotify.FSN_MODIFY)
if err != nil {
log.Fatal(err)
}
for {
select {
case ev := <-watcher.Event:
log.Infof("Config file changed (%s), attempting reload", ev)
conf, err := LoadFromFile(w.fileName)
if err != nil {
log.Error("Error loading new config: ", err)
failedConfigReloads.Inc()
} else {
cb(&conf)
log.Info("Config reloaded successfully")
configReloads.Inc()
}
// Re-add the file watcher since it can get lost on some changes. E.g.
// saving a file with vim results in a RENAME-MODIFY-DELETE event
// sequence, after which the newly written file is no longer watched.
err = watcher.WatchFlags(w.fileName, fsnotify.FSN_MODIFY)
case err := <-watcher.Error:
log.Error("Error watching config: ", err)
}
}
}

View File

@ -1,471 +0,0 @@
// 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 config
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"regexp"
"strings"
"time"
"gopkg.in/yaml.v2"
clientmodel "github.com/prometheus/client_golang/model"
"github.com/prometheus/prometheus/util/strutil"
)
var (
DefaultAggrRule = AggrRule{
RepeatRate: Duration(2 * time.Hour),
SendResolved: false,
}
DefaultHipchatConfig = HipchatConfig{
Color: "purple",
ColorResolved: "green",
MessageFormat: HipchatFormatHTML,
}
DefaultSlackConfig = SlackConfig{
Color: "warning",
ColorResolved: "good",
}
)
// Load parses the YAML input s into a Config.
func Load(s string) (*Config, error) {
cfg := &Config{}
err := yaml.Unmarshal([]byte(s), cfg)
if err != nil {
return nil, err
}
cfg.original = s
return cfg, nil
}
// LoadFromFile parses the given YAML file into a Config.
func LoadFromFile(filename string) (*Config, error) {
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return Load(string(content))
}
// Config is the top-level configuration for Alertmanager's config files.
type Config struct {
AggrRules []*AggrRule `yaml:"aggregation_rules,omitempty"`
InhibitRules []*InhibitRule `yaml:"inhibit_rules,omitempty"`
NotificationConfigs []*NotificationConfig `yaml:"notification_configs,omitempty"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
// original is the input from which the config was parsed.
original string
}
func checkOverflow(m map[string]interface{}, ctx string) error {
if len(m) > 0 {
var keys []string
for k := range m {
keys = append(keys, k)
}
return fmt.Errorf("unknown fields in %s: %s", ctx, strings.Join(keys, ", "))
}
return nil
}
func (c Config) String() string {
if c.original != "" {
return c.original
}
b, err := yaml.Marshal(c)
if err != nil {
return fmt.Sprintf("<error creating config string: %s>", err)
}
return string(b)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
// We want to set c to the defaults and then overwrite it with the input.
// To make unmarshal fill the plain data struct rather than calling UnmarshalYAML
// again, we have to hide it using a type indirection.
type plain Config
if err := unmarshal((*plain)(c)); err != nil {
return err
}
names := map[string]struct{}{}
for _, nc := range c.NotificationConfigs {
if _, ok := names[nc.Name]; ok {
return fmt.Errorf("notification config name %q is not unique", nc.Name)
}
names[nc.Name] = struct{}{}
}
return checkOverflow(c.XXX, "config")
}
// An InhibitRule specifies that a class of (source) alerts should inhibit
// notifications for another class of (target) alerts if all specified matching
// labels are equal between the two alerts. This may be used to inhibit alerts
// from sending notifications if their meaning is logically a subset of a
// higher-level alert.
type InhibitRule struct {
// The set of Filters which define the group of source alerts (which inhibit
// the target alerts).
SourceFilters []*Filter `yaml:"source_filters,omitempty"`
// The set of Filters which define the group of target alerts (which are
// inhibited by the source alerts).
TargetFilters []*Filter `yaml:"target_filters,omitempty"`
// A set of label names whose label values need to be identical in source and
// target alerts in order for the inhibition to take effect.
MatchOn []string `yaml:"match_on,omitempty"`
// How many seconds to wait for a corresponding inhibit source alert to
// appear before sending any notifications for active target alerts.
// TODO(julius): Not supported yet. Implement this!
// optional int32 before_allowance = 4 [default = 0];
// How many seconds to wait after a corresponding inhibit source alert
// disappears before sending any notifications for active target alerts.
// TODO(julius): Not supported yet. Implement this!
// optional int32 after_allowance = 5 [default = 0];
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (r *InhibitRule) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain InhibitRule
if err := unmarshal((*plain)(r)); err != nil {
return err
}
return checkOverflow(r.XXX, "inhibit rule")
}
// Grouping and notification setting definitions for alerts.
type AggrRule struct {
// Filters that define which alerts are matched by this AggregationRule.
Filters []*Filter `yaml:"filters,omitempty"`
// How many seconds to wait before resending a notification for a specific alert.
RepeatRate model.Duration `yaml:"repeat_rate"`
// Notification configurations to use for this AggregationRule, referenced
// by their name.
NotificationConfigs []string `yaml:"notification_configs"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (r *AggrRule) UnmarshalYAML(unmarshal func(interface{}) error) error {
*r = DefaultAggrRule
type plain AggrRule
if err := unmarshal((*plain)(r)); err != nil {
return err
}
if len(r.NotificationConfigs) == 0 {
return fmt.Errorf("aggregation rule needs at least one notification config")
}
return checkOverflow(r.XXX, "aggregation rule")
}
// A regex-based label filter used in aggregations.
type Filter struct {
Name string `yaml:"name"`
Regex *Regexp `yaml:"regex"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (f *Filter) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain Filter
if err := unmarshal((*plain)(f)); err != nil {
return err
}
return checkOverflow(r.XXX, "aggregation rule")
}
// Notification configuration definition.
type NotificationConfig struct {
// Name of this NotificationConfig. Referenced from AggregationRule.
Name string `yaml:"name"`
// Notify when resolved.
SendResolved bool `yaml:"send_resolved"`
// Zero or more PagerDuty notification configurations.
PagerdutyConfigs []*PagerDutyConfig `yaml:"pagerduty_configs"`
// Zero or more email notification configurations.
EmailConfigs []*EmailConfig `yaml:"email_configs"`
// Zero or more pushover notification configurations.
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs"`
// Zero or more hipchat notification configurations.
HipchatConfigs []*HipchatConfig `yaml:"hipchat_configs"`
// Zero or more slack notification configurations.
SlackConfigs []*SlackConfig `yaml:"slack_config"`
// Zero or more Flowdock notification configurations.
FlowdockConfigs []*FlowdockConfig `yaml:"flowdock_config"`
// Zero or more generic web hook notification configurations.
WebhookConfig []*WebhookConfig `yaml:"webhook_config"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *NotificationConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain NotificationConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.Name == "" {
return fmt.Errorf("missing name in notification config")
}
return checkOverflow(r.XXX, "notification config")
}
// Configuration for notification via PagerDuty.
type PagerdutyConfig struct {
// PagerDuty service key, see:
// http://developer.pagerduty.com/documentation/integration/events
ServiceKey string `yaml:"service_key"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain PagerdutyConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.ServiceKey == "" {
return fmt.Errorf("missing service key in PagerDuty config")
}
return checkOverflow(r.XXX, "pagerduty config")
}
// Configuration for notification via mail.
type EmailConfig struct {
// Email address to notify.
Email string `yaml:"email"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *EmailConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain EmailConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.Email == "" {
return fmt.Errorf("missing email address in email config")
}
return checkOverflow(r.XXX, "email config")
}
// Configuration for notification via pushover.net.
type PushoverConfig struct {
// Pushover token.
Token string `yaml:"token"`
// Pushover user_key.
UserKey string `yaml:"user_key"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *PushoverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain PushoverConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.Token == "" {
return fmt.Errorf("missing token in Pushover config")
}
if c.UserKey == "" {
return fmt.Errorf("missing user key in Pushover config")
}
return checkOverflow(r.XXX, "pushover config")
}
type HipchatFormat string
const (
HipchatFormatHTML HipchatFormat = "html"
HipchatFormatText HipchatFormat = "text"
)
// Configuration for notification via HipChat.
// https://www.hipchat.com/docs/apiv2/method/send_room_notification
type HipchatConfig struct {
// HipChat auth token, (https://www.hipchat.com/docs/api/auth).
AuthToken string `yaml:"auth_token"`
// HipChat room id, (https://www.hipchat.com/rooms/ids).
RoomID int `yaml:"room_id"`
// Color of message when triggered.
Color string `yaml:"color"`
// Color of message when resolved.
ColorResolved string `yaml:"color_resolved"`
// Should this message notify or not.
Notify bool `yaml:"notify"`
// Prefix to be put in front of the message (useful for @mentions, etc.).
Prefix string `yaml:"prefix"`
// Format the message as "html" or "text".
MessageFormat HipchatFormat `yaml:"message_format"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *HipchatConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultHipchatConfig
type plain HipchatConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.AuthToken == "" {
return fmt.Errorf("missing auth token in HipChat config")
}
if c.RoomID == "" {
return fmt.Errorf("missing room ID in HipChat config")
}
if c.MessageFormat != HipchatFormatHTML && c.MessageFormat != HipchatFormatText {
return fmt.Errorf("invalid message format %q", c.MessageFormat)
}
return checkOverflow(r.XXX, "hipchat config")
}
// Configuration for notification via Slack.
type SlackConfig struct {
// Slack webhook URL, (https://api.slack.com/incoming-webhooks).
WebhookURL string `yaml:"webhook_url"`
// Slack channel override, (like #other-channel or @username).
Channel string `yaml:"channel"`
// Color of message when triggered.
Color string `yaml:"color"`
// Color of message when resolved.
ColorResolved string `yaml:"color_resolved"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultSlackConfig
type plain SlackConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.WebhookURL == "" {
return fmt.Errorf("missing webhook URL in Slack config")
}
if c.Channel == "" {
return fmt.Errorf("missing channel in Slack config")
}
return checkOverflow(r.XXX, "slack config")
}
// Configuration for notification via Flowdock.
type FlowdockConfig struct {
// Flowdock flow API token.
APIToken string `yaml:"api_token"`
// Flowdock from_address.
FromAddress string `yaml:"from_address"`
// Flowdock flow tags.
Tags []string `yaml:"tags"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *FlowdockConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain FlowdockConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.APIToken == "" {
return fmt.Errorf("missing API token in Flowdock config")
}
if c.FromAddress == "" {
return fmt.Errorf("missing from address in Flowdock config")
}
return checkOverflow(r.XXX, "flowdock config")
}
// Configuration for notification via generic webhook.
type WebhookConfig struct {
// URL to send POST request to.
URL string `yaml:"url"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain WebhookConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.URL == "" {
return fmt.Errorf("missing URL in webhook config")
}
return checkOverflow(r.XXX, "slack config")
}
// Regexp encapsulates a regexp.Regexp and makes it YAML marshallable.
type Regexp struct {
regexp.Regexp
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (re *Regexp) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
regex, err := regexp.Compile(s)
if err != nil {
return err
}
re.Regexp = *regex
return nil
}
// MarshalYAML implements the yaml.Marshaler interface.
func (re *Regexp) MarshalYAML() (interface{}, error) {
if re != nil {
return re.String(), nil
}
return nil, nil
}

23
main.go
View File

@ -66,12 +66,16 @@ func main() {
versionInfoTmpl.Execute(os.Stdout, BuildInfo)
conf := config.MustLoadFromFile(*configFile)
conf, err := config.LoadFile(*configFile)
if err != nil {
log.Errorf("error loading config: %s", err)
os.Exit(1)
}
silencer := manager.NewSilencer()
defer silencer.Close()
err := silencer.LoadFromFile(*silencesFile)
err = silencer.LoadFromFile(*silencesFile)
if err != nil {
log.Warn("Couldn't load silences, starting up with empty silence list: ", err)
}
@ -89,11 +93,11 @@ func main() {
if err != nil {
log.Fatalln("Error building Alertmanager URL:", err)
}
notifier := manager.NewNotifier(conf.NotificationConfig, amURL)
notifier := manager.NewNotifier(conf.NotificationConfigs, amURL)
defer notifier.Close()
inhibitor := new(manager.Inhibitor)
inhibitor.SetInhibitRules(conf.InhibitRules())
inhibitor.SetInhibitRules(conf.InhibitRules)
options := &manager.MemoryAlertManagerOptions{
Inhibitor: inhibitor,
@ -102,7 +106,7 @@ func main() {
MinRefreshInterval: *minRefreshPeriod,
}
alertManager := manager.NewMemoryAlertManager(options)
alertManager.SetAggregationRules(conf.AggregationRules())
alertManager.SetAggregationRules(conf.AggrRules)
go alertManager.Run()
// Web initialization.
@ -139,15 +143,6 @@ func main() {
}
go webService.ServeForever(*listenAddress, *pathPrefix)
// React to configuration changes.
watcher := config.NewFileWatcher(*configFile)
go watcher.Watch(func(conf *config.Config) {
inhibitor.SetInhibitRules(conf.InhibitRules())
notifier.SetNotificationConfigs(conf.NotificationConfig)
alertManager.SetAggregationRules(conf.AggregationRules())
statusHandler.UpdateConfig(conf.String())
})
log.Info("Running notification dispatcher...")
notifier.Dispatch()
}

View File

@ -17,9 +17,9 @@ import (
"fmt"
"hash/fnv"
"sort"
)
const AlertNameLabel = "alertname"
"github.com/prometheus/common/model"
)
type AlertFingerprint uint64

View File

@ -16,36 +16,35 @@ package manager
import (
"fmt"
"hash/fnv"
"regexp"
"github.com/prometheus/alertmanager/config"
)
type Filters []*Filter
type Filter struct {
Name *regexp.Regexp
Value *regexp.Regexp
NamePattern string
Name string
Value *config.Regexp
ValuePattern string
fingerprint uint64
}
func NewFilter(namePattern string, valuePattern string) *Filter {
func NewFilter(name string, value *config.Regexp) *Filter {
summer := fnv.New64a()
fmt.Fprintf(summer, namePattern, valuePattern)
fmt.Fprintf(summer, name, value.String())
return &Filter{
Name: regexp.MustCompile("^" + namePattern + "$"),
Value: regexp.MustCompile("^" + valuePattern + "$"),
NamePattern: namePattern,
ValuePattern: valuePattern,
Name: name,
Value: value,
ValuePattern: value.String(),
fingerprint: summer.Sum64(),
}
}
func (f *Filter) Handles(l AlertLabelSet) bool {
for k, v := range l {
if f.Name.MatchString(k) && f.Value.MatchString(v) {
if f.Name == k && f.Value.MatchString(v) {
return true
}
}

View File

@ -16,7 +16,7 @@ package manager
import (
"sync"
_ "github.com/prometheus/alertmanager/config/generated"
"github.com/prometheus/alertmanager/config"
)
type InhibitRules []*InhibitRule
@ -53,16 +53,30 @@ func (i *InhibitRule) Filter(s AlertLabelSets, t AlertLabelSets) AlertLabelSets
// emits uninhibited alert labelsets.
type Inhibitor struct {
mu sync.Mutex
inhibitRules InhibitRules
inhibitRules []*InhibitRule
dirty bool
}
// Replaces the current InhibitRules with a new set.
func (i *Inhibitor) SetInhibitRules(r InhibitRules) {
func (i *Inhibitor) SetInhibitRules(r []*config.InhibitRule) {
i.mu.Lock()
defer i.mu.Unlock()
i.inhibitRules = r
i.inhibitRules = i.inhibitRules[:0]
for _, ih := range r {
ihr := &InhibitRule{
MatchOn: ih.MatchOn,
}
for _, f := range ih.SourceFilters {
ihr.SourceFilters = append(ihr.SourceFilters, NewFilter(f.Name, f.Regex))
}
for _, f := range ih.TargetFilters {
ihr.TargetFilters = append(ihr.TargetFilters, NewFilter(f.Name, f.Regex))
}
i.inhibitRules = append(i.inhibitRules, ihr)
}
i.dirty = true
}

View File

@ -19,6 +19,7 @@ import (
"sync"
"time"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/log"
)
@ -30,7 +31,7 @@ type AlertManager interface {
// Retrieves all alerts from the store that match the provided Filters.
GetAll(Filters) AlertAggregates
// Sets the AggregationRules to associate with alerts.
SetAggregationRules(AggregationRules)
SetAggregationRules([]*config.AggrRule)
// Runs the AlertManager dispatcher loop.
Run()
}
@ -39,9 +40,9 @@ type AggregationRules []*AggregationRule
// AggregationRule creates and manages the scope for received events.
type AggregationRule struct {
Filters Filters
RepeatRate time.Duration
NotificationConfigName string
Filters Filters
RepeatRate time.Duration
NotificationConfigs []string
}
// Returns whether a given AggregationRule matches an Alert.
@ -239,10 +240,23 @@ func (s memoryAlertManager) GetAll(f Filters) AlertAggregates {
}
// Replace the current set of loaded AggregationRules by another.
func (s *memoryAlertManager) SetAggregationRules(rules AggregationRules) {
func (s *memoryAlertManager) SetAggregationRules(crs []*config.AggrRule) {
s.mu.Lock()
defer s.mu.Unlock()
var rules AggregationRules
for _, ar := range crs {
var filters Filters
for _, f := range ar.Filters {
filters = append(filters, NewFilter(f.Name, f.Regex))
}
rules = append(rules, &AggregationRule{
Filters: filters,
RepeatRate: time.Duration(ar.RepeatRate),
NotificationConfigs: ar.NotificationConfigs,
})
}
log.Infof("Replacing aggregator rules (old: %d, new: %d)...", len(s.rules), len(rules))
s.rules = rules
@ -279,7 +293,7 @@ func (s *memoryAlertManager) removeExpiredAggregates() {
if time.Since(agg.LastRefreshed) > s.minRefreshInterval {
delete(s.aggregates, agg.Alert.Fingerprint())
s.notifier.QueueNotification(agg.Alert, notificationOpResolve, agg.Rule.NotificationConfigName)
s.notifier.QueueNotification(agg.Alert, notificationOpResolve, agg.Rule.NotificationConfigs)
s.needsNotificationRefresh = true
} else {
heap.Push(&s.aggregatesByLastRefreshed, agg)
@ -343,7 +357,7 @@ func (s *memoryAlertManager) refreshNotifications() {
continue
}
if agg.Rule != nil {
s.notifier.QueueNotification(agg.Alert, notificationOpTrigger, agg.Rule.NotificationConfigName)
s.notifier.QueueNotification(agg.Alert, notificationOpTrigger, agg.Rule.NotificationConfigs)
agg.LastNotification = time.Now()
agg.NextNotification = agg.LastNotification.Add(agg.Rule.RepeatRate)
numSent++

View File

@ -34,7 +34,7 @@ import (
"github.com/prometheus/log"
"github.com/thorduri/pushover"
pb "github.com/prometheus/alertmanager/config/generated"
"github.com/prometheus/alertmanager/config"
)
const (
@ -77,10 +77,10 @@ type notificationOp int
// a provided notification configuration.
type Notifier interface {
// Queue a notification for asynchronous dispatching.
QueueNotification(a *Alert, op notificationOp, configName string) error
QueueNotification(a *Alert, op notificationOp, configs []string) error
// Replace current notification configs. Already enqueued messages will remain
// unaffected.
SetNotificationConfigs([]*pb.NotificationConfig)
SetNotificationConfigs([]*config.NotificationConfig)
// Start alert notification dispatch loop.
Dispatch()
// Stop the alert notification dispatch loop.
@ -90,7 +90,7 @@ type Notifier interface {
// Request for sending a notification.
type notificationReq struct {
alert *Alert
notificationConfig *pb.NotificationConfig
notificationConfig *config.NotificationConfig
op notificationOp
}
@ -104,11 +104,11 @@ type notifier struct {
// Mutex to protect the fields below.
mu sync.Mutex
// Map of notification configs by name.
notificationConfigs map[string]*pb.NotificationConfig
notificationConfigs map[string]*config.NotificationConfig
}
// NewNotifier construct a new notifier.
func NewNotifier(configs []*pb.NotificationConfig, amURL string) *notifier {
func NewNotifier(configs []*config.NotificationConfig, amURL string) *notifier {
notifier := &notifier{
pendingNotifications: make(chan *notificationReq, *notificationBufferSize),
alertmanagerURL: amURL,
@ -117,32 +117,34 @@ func NewNotifier(configs []*pb.NotificationConfig, amURL string) *notifier {
return notifier
}
func (n *notifier) SetNotificationConfigs(configs []*pb.NotificationConfig) {
func (n *notifier) SetNotificationConfigs(configs []*config.NotificationConfig) {
n.mu.Lock()
defer n.mu.Unlock()
n.notificationConfigs = map[string]*pb.NotificationConfig{}
n.notificationConfigs = map[string]*config.NotificationConfig{}
for _, c := range configs {
n.notificationConfigs[c.GetName()] = c
n.notificationConfigs[c.Name] = c
}
}
func (n *notifier) QueueNotification(a *Alert, op notificationOp, configName string) error {
n.mu.Lock()
nc, ok := n.notificationConfigs[configName]
n.mu.Unlock()
func (n *notifier) QueueNotification(a *Alert, op notificationOp, configs []string) error {
for _, cname := range configs {
n.mu.Lock()
nc, ok := n.notificationConfigs[cname]
n.mu.Unlock()
if !ok {
return fmt.Errorf("No such notification configuration %s", configName)
}
if !ok {
return fmt.Errorf("No such notification configuration %s", cname)
}
// We need to save a reference to the notification config in the
// notificationReq since the config might be replaced or gone at the time the
// message gets dispatched.
n.pendingNotifications <- &notificationReq{
alert: a,
notificationConfig: nc,
op: op,
// We need to save a reference to the notification config in the
// notificationReq since the config might be replaced or gone at the time the
// message gets dispatched.
n.pendingNotifications <- &notificationReq{
alert: a,
notificationConfig: nc,
op: op,
}
}
return nil
}
@ -195,32 +197,34 @@ func (n *notifier) sendPagerDutyNotification(serviceKey string, op notificationO
return nil
}
func (n *notifier) sendHipChatNotification(op notificationOp, config *pb.HipChatConfig, a *Alert) error {
func (n *notifier) sendHipChatNotification(op notificationOp, conf *config.HipchatConfig, a *Alert) error {
// https://www.hipchat.com/docs/apiv2/method/send_room_notification
incidentKey := a.Fingerprint()
color := ""
status := ""
message := ""
messageFormat := ""
var (
color string
status string
message string
messageFormat config.HipchatFormat
)
switch op {
case notificationOpTrigger:
color = config.GetColor()
color = conf.Color
status = "firing"
case notificationOpResolve:
color = config.GetColorResolved()
color = conf.ColorResolved
status = "resolved"
}
if config.GetMessageFormat() == pb.HipChatConfig_TEXT {
message = fmt.Sprintf("%s%s %s: %s", config.GetPrefix(), a.Labels["alertname"], status, a.Summary)
if conf.MessageFormat == config.HipchatFormatText {
message = fmt.Sprintf("%s%s %s: %s", conf.Prefix, a.Labels["alertname"], status, a.Summary)
messageFormat = "text"
} else {
message = fmt.Sprintf("%s<b>%s %s</b>: %s (<a href='%s'>view</a>)", config.GetPrefix(), html.EscapeString(a.Labels["alertname"]), status, html.EscapeString(a.Summary), a.Payload["generatorURL"])
message = fmt.Sprintf("%s<b>%s %s</b>: %s (<a href='%s'>view</a>)", conf.Prefix, html.EscapeString(a.Labels["alertname"]), status, html.EscapeString(a.Summary), a.Payload["generatorURL"])
messageFormat = "html"
}
buf, err := json.Marshal(map[string]interface{}{
"color": color,
"message": message,
"notify": config.GetNotify(),
"notify": conf.Notify,
"message_format": messageFormat,
})
if err != nil {
@ -232,7 +236,7 @@ func (n *notifier) sendHipChatNotification(op notificationOp, config *pb.HipChat
Timeout: timeout,
}
resp, err := client.Post(
fmt.Sprintf("%s/room/%d/notification?auth_token=%s", *hipchatURL, config.GetRoomId(), config.GetAuthToken()),
fmt.Sprintf("%s/room/%d/notification?auth_token=%s", *hipchatURL, conf.RoomID, conf.AuthToken),
contentTypeJSON,
bytes.NewBuffer(buf),
)
@ -276,17 +280,17 @@ type slackAttachmentField struct {
Short bool `json:"short,omitempty"`
}
func (n *notifier) sendSlackNotification(op notificationOp, config *pb.SlackConfig, a *Alert) error {
func (n *notifier) sendSlackNotification(op notificationOp, conf *config.SlackConfig, a *Alert) error {
// https://api.slack.com/incoming-webhooks
incidentKey := a.Fingerprint()
color := ""
status := ""
switch op {
case notificationOpTrigger:
color = config.GetColor()
color = conf.Color
status = "firing"
case notificationOpResolve:
color = config.GetColorResolved()
color = conf.ColorResolved
status = "resolved"
}
@ -310,7 +314,7 @@ func (n *notifier) sendSlackNotification(op notificationOp, config *pb.SlackConf
}
req := &slackReq{
Channel: config.GetChannel(),
Channel: conf.Channel,
Attachments: []slackAttachment{
*attachment,
},
@ -325,11 +329,7 @@ func (n *notifier) sendSlackNotification(op notificationOp, config *pb.SlackConf
client := http.Client{
Timeout: timeout,
}
resp, err := client.Post(
config.GetWebhookUrl(),
contentTypeJSON,
bytes.NewBuffer(buf),
)
resp, err := client.Post(conf.WebhookURL, contentTypeJSON, bytes.NewBuffer(buf))
if err != nil {
return err
}
@ -355,9 +355,10 @@ type flowdockMessage struct {
Tags []string `json:"tags,omitempty"`
}
func (n *notifier) sendFlowdockNotification(op notificationOp, config *pb.FlowdockConfig, a *Alert) error {
flowdockMessage := newFlowdockMessage(op, config, a)
url := strings.TrimRight(*flowdockURL, "/") + "/" + config.GetApiToken()
func (n *notifier) sendFlowdockNotification(op notificationOp, conf *config.FlowdockConfig, a *Alert) error {
flowdockMessage := newFlowdockMessage(op, conf, a)
url := strings.TrimRight(*flowdockURL, "/") + "/" + conf.APIToken
jsonMessage, err := json.Marshal(flowdockMessage)
if err != nil {
return err
@ -372,7 +373,7 @@ func (n *notifier) sendFlowdockNotification(op notificationOp, config *pb.Flowdo
return nil
}
func newFlowdockMessage(op notificationOp, config *pb.FlowdockConfig, a *Alert) *flowdockMessage {
func newFlowdockMessage(op notificationOp, conf *config.FlowdockConfig, a *Alert) *flowdockMessage {
status := ""
switch op {
case notificationOpTrigger:
@ -383,12 +384,12 @@ func newFlowdockMessage(op notificationOp, config *pb.FlowdockConfig, a *Alert)
msg := &flowdockMessage{
Source: "Prometheus",
FromAddress: config.GetFromAddress(),
FromAddress: conf.FromAddress,
Subject: html.EscapeString(a.Summary),
Format: "html",
Content: fmt.Sprintf("*%s %s*: %s (<%s|view>)", html.EscapeString(a.Labels["alertname"]), status, html.EscapeString(a.Summary), a.Payload["generatorURL"]),
Link: a.Payload["generatorURL"],
Tags: append(config.GetTag(), status),
Tags: append(conf.Tags, status),
}
return msg
}
@ -399,7 +400,7 @@ type webhookMessage struct {
Alerts []Alert `json:"alert"`
}
func (n *notifier) sendWebhookNotification(op notificationOp, config *pb.WebhookConfig, a *Alert) error {
func (n *notifier) sendWebhookNotification(op notificationOp, conf *config.WebhookConfig, a *Alert) error {
status := ""
switch op {
case notificationOpTrigger:
@ -417,7 +418,7 @@ func (n *notifier) sendWebhookNotification(op notificationOp, config *pb.Webhook
if err != nil {
return err
}
httpResponse, err := postJSON(jsonMessage, config.GetUrl())
httpResponse, err := postJSON(jsonMessage, conf.URL)
if err != nil {
return err
}
@ -587,61 +588,47 @@ func processResponse(r *http.Response, targetName string, a *Alert) error {
return nil
}
func (n *notifier) handleNotification(a *Alert, op notificationOp, config *pb.NotificationConfig) {
for _, pdConfig := range config.PagerdutyConfig {
if err := n.sendPagerDutyNotification(pdConfig.GetServiceKey(), op, a); err != nil {
func (n *notifier) handleNotification(a *Alert, op notificationOp, conf *config.NotificationConfig) {
if op == notificationOpResolve && !conf.SendResolved {
return
}
for _, c := range conf.PagerdutyConfigs {
if err := n.sendPagerDutyNotification(c.ServiceKey, op, a); err != nil {
log.Errorln("Error sending PagerDuty notification:", err)
}
}
for _, emailConfig := range config.EmailConfig {
if op == notificationOpResolve && !emailConfig.GetSendResolved() {
continue
}
for _, c := range conf.EmailConfigs {
if *smtpSmartHost == "" {
log.Warn("No SMTP smarthost configured, not sending email notification.")
continue
}
if err := n.sendEmailNotification(emailConfig.GetEmail(), op, a); err != nil {
if err := n.sendEmailNotification(c.Email, op, a); err != nil {
log.Errorln("Error sending email notification:", err)
}
}
for _, poConfig := range config.PushoverConfig {
if op == notificationOpResolve && !poConfig.GetSendResolved() {
continue
}
if err := n.sendPushoverNotification(poConfig.GetToken(), op, poConfig.GetUserKey(), a); err != nil {
for _, c := range conf.PushoverConfigs {
if err := n.sendPushoverNotification(c.Token, op, c.UserKey, a); err != nil {
log.Errorln("Error sending Pushover notification:", err)
}
}
for _, hcConfig := range config.HipchatConfig {
if op == notificationOpResolve && !hcConfig.GetSendResolved() {
continue
}
if err := n.sendHipChatNotification(op, hcConfig, a); err != nil {
for _, c := range conf.HipchatConfigs {
if err := n.sendHipChatNotification(op, c, a); err != nil {
log.Errorln("Error sending HipChat notification:", err)
}
}
for _, scConfig := range config.SlackConfig {
if op == notificationOpResolve && !scConfig.GetSendResolved() {
continue
}
if err := n.sendSlackNotification(op, scConfig, a); err != nil {
for _, c := range conf.SlackConfigs {
if err := n.sendSlackNotification(op, c, a); err != nil {
log.Errorln("Error sending Slack notification:", err)
}
}
for _, fdConfig := range config.FlowdockConfig {
if op == notificationOpResolve && !fdConfig.GetSendResolved() {
continue
}
if err := n.sendFlowdockNotification(op, fdConfig, a); err != nil {
for _, c := range conf.FlowdockConfigs {
if err := n.sendFlowdockNotification(op, c, a); err != nil {
log.Errorln("Error sending Flowdock notification:", err)
}
}
for _, whConfig := range config.WebhookConfig {
if op == notificationOpResolve && !whConfig.GetSendResolved() {
continue
}
if err := n.sendWebhookNotification(op, whConfig, a); err != nil {
for _, c := range conf.WebhookConfigs {
if err := n.sendWebhookNotification(op, c, a); err != nil {
log.Errorln("Error sending Webhook notification:", err)
}
}

View File

@ -26,7 +26,7 @@ import (
"testing"
"time"
pb "github.com/prometheus/alertmanager/config/generated"
"github.com/prometheus/alertmanager/config"
)
func TestWriteEmailBody(t *testing.T) {
@ -192,8 +192,8 @@ func TestSendWebhookNotification(t *testing.T) {
}))
defer ts.Close()
config := &pb.WebhookConfig{
Url: &ts.URL,
conf := &config.WebhookConfig{
URL: ts.URL,
}
alert := &Alert{
Summary: "Testsummary",
@ -206,7 +206,7 @@ func TestSendWebhookNotification(t *testing.T) {
},
}
n := &notifier{}
err := n.sendWebhookNotification(notificationOpTrigger, config, alert)
err := n.sendWebhookNotification(notificationOpTrigger, conf, alert)
if err != nil {
t.Errorf("error sending webhook notification: %s", err)
}

View File

@ -17,10 +17,13 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"regexp"
"sync"
"time"
"github.com/prometheus/log"
"github.com/prometheus/alertmanager/config"
)
type SilenceID uint
@ -56,8 +59,8 @@ type ApiSilence struct {
func (s *Silence) MarshalJSON() ([]byte, error) {
filters := map[string]string{}
for _, f := range s.Filters {
name := f.Name.String()[1 : len(f.Name.String())-1]
value := f.Value.String()[1 : len(f.Value.String())-1]
name := f.Name
value := f.ValuePattern
filters[name] = value
}
@ -76,8 +79,13 @@ func (s *Silence) UnmarshalJSON(data []byte) error {
json.Unmarshal(data, sc)
filters := make(Filters, 0, len(sc.Filters))
for label, value := range sc.Filters {
filters = append(filters, NewFilter(label, value))
re, err := regexp.Compile(value)
if err != nil {
return err
}
filters = append(filters, NewFilter(label, &config.Regexp{*re}))
}
if sc.CreatedAtSeconds == 0 {