mirror of
https://github.com/prymitive/karma
synced 2026-05-13 03:56:59 +00:00
Merge pull request #6 from cloudflare/viper
Generate flag for each environment key
This commit is contained in:
138
README.md
138
README.md
@@ -35,20 +35,10 @@ By default unsee will listen on port `8080` and Alertmanager mock data will be
|
||||
used, to override Alertmanager URI set `ALERTMANAGER_URI` and/or `PORT` make
|
||||
variables. Example:
|
||||
|
||||
make PORT=5000 ALERTMANAGER_URI=https://alertmanager.unicorn.corp run
|
||||
make PORT=5000 ALERTMANAGER_URI=https://alertmanager.example.com run
|
||||
|
||||
### Environment variables
|
||||
|
||||
#### ALERTMANAGER_URI
|
||||
|
||||
URI of the Alertmanager instance, unsee will use it to pull alert groups and
|
||||
silences. Endpoints in use:
|
||||
|
||||
* ${ALERTMANAGER_URI}/api/v1/alerts/groups
|
||||
* ${ALERTMANAGER_URI}/api/v1/silences
|
||||
|
||||
This variable is required and there is no default value.
|
||||
|
||||
#### ALERTMANAGER_TIMEOUT
|
||||
|
||||
Timeout for requests send to Alertmanager, accepts values in
|
||||
@@ -57,8 +47,45 @@ Timeout for requests send to Alertmanager, accepts values in
|
||||
ALERTMANAGER_TIMEOUT=10s
|
||||
ALERTMANAGER_TIMEOUT=2m
|
||||
|
||||
This option can also be set using `-alertmanager.timeout` flag. Example:
|
||||
|
||||
$ unsee -alertmanager.timeout 2m
|
||||
|
||||
Default is `40s`.
|
||||
|
||||
#### ALERTMANAGER_TTL
|
||||
|
||||
Interval for refreshing alerts and silences, tells unsee how often pull new
|
||||
data from Alertmanager, accepts values in
|
||||
[time.Duration](https://golang.org/pkg/time/#Duration) format. Examples:
|
||||
|
||||
ALERTMANAGER_TTL=30s
|
||||
ALERTMANAGER_TTL=5m
|
||||
|
||||
This option can also be set using `-alertmanager.ttl` flag. Example:
|
||||
|
||||
$ unsee -alertmanager.ttl 5m
|
||||
|
||||
Default is `1m`.
|
||||
|
||||
#### ALERTMANAGER_URI
|
||||
|
||||
URI of the Alertmanager instance, unsee will use it to pull alert groups and
|
||||
silences. Endpoints in use:
|
||||
|
||||
* ${ALERTMANAGER_URI}/api/v1/alerts/groups
|
||||
* ${ALERTMANAGER_URI}/api/v1/silences
|
||||
|
||||
Example:
|
||||
|
||||
ALERTMANAGER_URI=https://alertmanager.example.com
|
||||
|
||||
This option can also be set using `-alertmanager.uri` flag. Example:
|
||||
|
||||
$ unsee -alertmanager.uri https://alertmanager.example.com
|
||||
|
||||
This variable is required and there is no default value.
|
||||
|
||||
#### DEBUG
|
||||
|
||||
Will enable [gin](https://github.com/gin-gonic/gin) debug mode. Examples:
|
||||
@@ -66,29 +93,60 @@ Will enable [gin](https://github.com/gin-gonic/gin) debug mode. Examples:
|
||||
DEBUG=true
|
||||
DEBUG=false
|
||||
|
||||
This option can also be set using `-debug` flag. Example:
|
||||
|
||||
$ unsee -debug
|
||||
|
||||
Default is `false`.
|
||||
|
||||
#### COLOR_LABELS
|
||||
#### COLOR_LABELS_STATIC
|
||||
|
||||
List of label names that will all have the same color applied (different than
|
||||
the default label color). This allows to quickly spot a specific label that
|
||||
can have high range of values, but it's important when reading the dashboard.
|
||||
For example coloring the instance label allows to quickly learn which instance
|
||||
is affected by given alert. Accepts space separated list of label names.
|
||||
Examples:
|
||||
|
||||
COLOR_LABELS_STATIC=instance
|
||||
COLOR_LABELS_STATIC="instance cluster"
|
||||
|
||||
This option can also be set using `-color.labels.static` flag. Example:
|
||||
|
||||
$ unsee -color.labels.static "instance cluster"
|
||||
|
||||
This variable is optional and default is not set (no label will have static
|
||||
color).
|
||||
|
||||
#### COLOR_LABELS_UNIQUE
|
||||
|
||||
List of label names that should have unique colors generated in the UI. Colors
|
||||
can help visually identify alerts with shared labels, for example coloring
|
||||
hostname label will allow to quickly spot all alerts for the same host.
|
||||
Accepts space separated list of label names. Examples:
|
||||
|
||||
COLOR_LABELS=hostname
|
||||
COLOR_LABELS="cluster environment rack"
|
||||
COLOR_LABELS_UNIQUE=hostname
|
||||
COLOR_LABELS_UNIQUE="cluster environment rack"
|
||||
|
||||
This option can also be set using `-color.labels.unique` flag. Example:
|
||||
|
||||
$ unsee -color.labels.unique "cluster environment rack"
|
||||
|
||||
This variable is optional and default is not set (no label will have unique
|
||||
color).
|
||||
|
||||
#### DEFAULT_FILTER
|
||||
#### FILTER_DEFAULT
|
||||
|
||||
Default alert filter to apply when user loads unsee UI without any filter
|
||||
specified. Accepts comma separated list of filter expressions (visit /help page
|
||||
in unsee for details on filters). Examples:
|
||||
|
||||
DEFAULT_FILTER=level=critical
|
||||
DEFAULT_FILTER="cluster=prod,instance=~prod"
|
||||
FILTER_DEFAULT=level=critical
|
||||
FILTER_DEFAULT="cluster=prod,instance=~prod"
|
||||
|
||||
This option can also be set using `-filter.default` flag. Example:
|
||||
|
||||
$ unsee -filter.default "cluster=prod,instance=~prod"
|
||||
|
||||
Default is not set (no filter will be applied).
|
||||
|
||||
@@ -103,24 +161,17 @@ Rule syntax:
|
||||
|
||||
Accepts space separated list of rules. Examples:
|
||||
|
||||
JIRA_REGEX="DEVOPS-[0-9]+@https://jira.unicorn.corp
|
||||
JIRA_REGEX="DEVOPS-[0-9]+@https://jira.example.com"
|
||||
|
||||
The above will match DEVOPS-123 text in the silence comment string and convert
|
||||
it to `https://jira.unicorn.corp/browse/DEVOPS-123` link.
|
||||
it to `https://jira.example.com/browse/DEVOPS-123` link.
|
||||
|
||||
This option can also be set using `-jira.regex` flag. Example:
|
||||
|
||||
$ unsee -jira.regex "DEVOPS-[0-9]+@https://jira.example.com"
|
||||
|
||||
This variable is optional and default is not set (no rule will be applied).
|
||||
|
||||
#### UPDATE_INTERVAL
|
||||
|
||||
Interval for refreshing alerts and silences, tells unsee how often pull new
|
||||
data from Alertmanager, accepts values in
|
||||
[time.Duration](https://golang.org/pkg/time/#Duration) format. Examples:
|
||||
|
||||
UPDATE_INTERVAL=30s
|
||||
UPDATE_INTERVAL=5m
|
||||
|
||||
Default is `1m`.
|
||||
|
||||
#### SENTRY_DSN
|
||||
|
||||
DSN for [Sentry](https://sentry.io) integration in Go. See
|
||||
@@ -129,6 +180,10 @@ details. Example:
|
||||
|
||||
SENTRY_DSN=https://<key>:<secret>@sentry.io/<project>
|
||||
|
||||
This option can also be set using `-sentry.dsn` flag. Example:
|
||||
|
||||
$ unsee -sentry.dsn "https://<key>:<secret>@sentry.io/<project>"
|
||||
|
||||
This variable is optional and default is not set (Sentry support is disabled for
|
||||
Go errors).
|
||||
|
||||
@@ -140,24 +195,13 @@ Example:
|
||||
|
||||
SENTRY_PUBLIC_DSN=https://<key>@sentry.io/<project>
|
||||
|
||||
This option can also be set using `-sentry.public.dsn` flag. Example:
|
||||
|
||||
$ unsee -sentry.public.dsn "https://<key>@sentry.io/<project>"
|
||||
|
||||
This variable is optional and default is not set (Sentry support is disabled for
|
||||
javascript errors).
|
||||
|
||||
#### STATIC_COLOR_LABELS
|
||||
|
||||
List of label names that will all have the same color applied (different than
|
||||
the default label color). This allows to quickly spot a specific label that
|
||||
can have high range of values, but it's important when reading the dashboard.
|
||||
For example coloring the instance label allows to quickly learn which instance
|
||||
is affected by given alert. Accepts space separated list of label names.
|
||||
Examples:
|
||||
|
||||
STATIC_COLOR_LABELS=instance
|
||||
STATIC_COLOR_LABELS="instance cluster"
|
||||
|
||||
This variable is optional and default is not set (no label will have static
|
||||
color).
|
||||
|
||||
#### STRIP_LABELS
|
||||
|
||||
List of label names that should not be shown on the UI. This allows to hide some
|
||||
@@ -167,4 +211,8 @@ of label names. Examples:
|
||||
STRIP_LABELS=exporter_type
|
||||
STRIP_LABELS="prometheus_instance alert_type"
|
||||
|
||||
This option can also be set using `-strip.labels` flag. Example:
|
||||
|
||||
$ unsee -strip.labels "prometheus_instance alert_type"
|
||||
|
||||
This variable is optional and default is not set (all labels will be shown).
|
||||
|
||||
@@ -3,6 +3,7 @@ package alertmanager
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/unsee/config"
|
||||
"github.com/cloudflare/unsee/models"
|
||||
|
||||
@@ -12,21 +13,21 @@ import (
|
||||
// AlertGroupsAPIResponse is the schema of API response for /api/v1/alerts/groups
|
||||
type AlertGroupsAPIResponse struct {
|
||||
Status string `json:"status"`
|
||||
Groups []models.AlertManagerAlertGroup `json:"data"`
|
||||
Groups []models.AlertmanagerAlertGroup `json:"data"`
|
||||
ErrorType string `json:"errorType"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// Get response from AlertManager /api/v1/alerts/groups
|
||||
// Get response from Alertmanager /api/v1/alerts/groups
|
||||
func (response *AlertGroupsAPIResponse) Get() error {
|
||||
start := time.Now()
|
||||
|
||||
url, err := joinURL(config.Config.AlertManagerURL, "api/v1/alerts/groups")
|
||||
url, err := joinURL(config.Config.AlertmanagerURI, "api/v1/alerts/groups")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = getJSONFromURL(url, config.Config.AlertManagerTimeout, response)
|
||||
err = getJSONFromURL(url, config.Config.AlertmanagerTimeout, response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func getJSONFromURL(url string, timeout time.Duration, target interface{}) error
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Request to AlertManager failed with %s", resp.Status)
|
||||
return fmt.Errorf("Request to Alertmanager failed with %s", resp.Status)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/unsee/config"
|
||||
"github.com/cloudflare/unsee/models"
|
||||
|
||||
@@ -12,11 +13,11 @@ import (
|
||||
)
|
||||
|
||||
type silencesData struct {
|
||||
Silences []models.AlertManagerSilence `json:"silences"`
|
||||
Silences []models.AlertmanagerSilence `json:"silences"`
|
||||
TotalSilences int `json:"totalSilences"`
|
||||
}
|
||||
|
||||
// SilenceAPIResponse is what AlertManager API returns
|
||||
// SilenceAPIResponse is what Alertmanager API returns
|
||||
type SilenceAPIResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data silencesData `json:"data"`
|
||||
@@ -24,17 +25,17 @@ type SilenceAPIResponse struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// Get will return fresh data from AlertManager API
|
||||
// Get will return fresh data from Alertmanager API
|
||||
func (response *SilenceAPIResponse) Get() error {
|
||||
start := time.Now()
|
||||
|
||||
url, err := joinURL(config.Config.AlertManagerURL, "api/v1/silences")
|
||||
url, err := joinURL(config.Config.AlertmanagerURI, "api/v1/silences")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url = fmt.Sprintf("%s?limit=%d", url, math.MaxUint32)
|
||||
|
||||
err = getJSONFromURL(url, config.Config.AlertManagerTimeout, response)
|
||||
err = getJSONFromURL(url, config.Config.AlertmanagerTimeout, response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
106
config/config.go
106
config/config.go
@@ -1,9 +1,14 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
@@ -17,27 +22,102 @@ func (mvd *spaceSeparatedList) Decode(value string) error {
|
||||
}
|
||||
|
||||
type configEnvs struct {
|
||||
Debug bool `envconfig:"DEBUG" default:"false"`
|
||||
AlertManagerURL string `envconfig:"ALERTMANAGER_URI" required:"true"`
|
||||
AlertManagerTimeout time.Duration `envconfig:"ALERTMANAGER_TIMEOUT" default:"40s"`
|
||||
UpdateInterval time.Duration `envconfig:"UPDATE_INTERVAL" default:"1m"`
|
||||
SentryDSN string `envconfig:"SENTRY_DSN"`
|
||||
SentryPublicDSN string `envconfig:"SENTRY_PUBLIC_DSN"`
|
||||
DefaultFilter string `envconfig:"DEFAULT_FILTER"`
|
||||
ColorLabels spaceSeparatedList `envconfig:"COLOR_LABELS"`
|
||||
StaticColorLabels spaceSeparatedList `envconfig:"STATIC_COLOR_LABELS"`
|
||||
StripLabels spaceSeparatedList `envconfig:"STRIP_LABELS"`
|
||||
JIRARegexp spaceSeparatedList `envconfig:"JIRA_REGEX"`
|
||||
AlertmanagerTimeout time.Duration `envconfig:"ALERTMANAGER_TIMEOUT" default:"40s" help:"Timeout for all request send to Alertmanager"`
|
||||
AlertmanagerTTL time.Duration `envconfig:"ALERTMANAGER_TTL" default:"1m" help:"TTL for Alertmanager alerts and silences"`
|
||||
AlertmanagerURI string `envconfig:"ALERTMANAGER_URI" required:"true" help:"Alertmanager URI"`
|
||||
ColorLabelsStatic spaceSeparatedList `envconfig:"COLOR_LABELS_STATIC" help:"List of label names that should have the same (but distinct) color"`
|
||||
ColorLabelsUnique spaceSeparatedList `envconfig:"COLOR_LABELS_UNIQUE" help:"List of label names that should have unique color"`
|
||||
Debug bool `envconfig:"DEBUG" default:"false" help:"Enable debug mode"`
|
||||
FilterDefault string `envconfig:"FILTER_DEFAULT" help:"Default filter string"`
|
||||
JiraRegexp spaceSeparatedList `envconfig:"JIRA_REGEX" help:"List of JIRA regex rules"`
|
||||
SentryDSN string `envconfig:"SENTRY_DSN" help:"Sentry DSN for Go exceptions"`
|
||||
SentryPublicDSN string `envconfig:"SENTRY_PUBLIC_DSN" help:"Sentry DSN for javascript exceptions"`
|
||||
StripLabels spaceSeparatedList `envconfig:"STRIP_LABELS" help:"List of labels to ignore"`
|
||||
}
|
||||
|
||||
// Config exposes all options required to run
|
||||
var Config configEnvs
|
||||
|
||||
//
|
||||
// generate flag name from the option name, a dot will be injected between
|
||||
// <lower case char><upper case char>
|
||||
func makeFlagName(s string) string {
|
||||
var buffer bytes.Buffer
|
||||
prevUpper := true
|
||||
for _, rune := range s {
|
||||
if unicode.IsUpper(rune) && !prevUpper {
|
||||
buffer.WriteRune('.')
|
||||
}
|
||||
prevUpper = unicode.IsUpper(rune)
|
||||
buffer.WriteRune(unicode.ToLower(rune))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Iterate all defined envconfig variables and generate a flag for each key.
|
||||
// Next parse those flags and for each set flag inject env variable which will
|
||||
// be read by envconfig later on.
|
||||
type flagMapper struct {
|
||||
isBool bool
|
||||
stringVal *string
|
||||
boolVal *bool
|
||||
}
|
||||
|
||||
func mapEnvConfigToFlags() {
|
||||
flags := make(map[string]flagMapper)
|
||||
s := reflect.ValueOf(Config)
|
||||
typeOfSpec := s.Type()
|
||||
for i := 0; i < s.NumField(); i++ {
|
||||
f := typeOfSpec.Field(i)
|
||||
|
||||
flagName := makeFlagName(f.Name)
|
||||
// check if flag was already set, this usually happens only during testing
|
||||
if flag.Lookup(flagName) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
envName := f.Tag.Get("envconfig")
|
||||
defaultVal := f.Tag.Get("default")
|
||||
|
||||
helpMsg := fmt.Sprintf("%s. This flag can also be set via %s environment variable.", f.Tag.Get("help"), f.Tag.Get("envconfig"))
|
||||
if f.Tag.Get("required") == "true" {
|
||||
helpMsg = fmt.Sprintf("%s This option is required.", helpMsg)
|
||||
}
|
||||
|
||||
mapper := flagMapper{}
|
||||
if s.Field(i).Kind() == reflect.Bool {
|
||||
mapper.isBool = true
|
||||
mapper.boolVal = flag.Bool(flagName, false, helpMsg)
|
||||
} else {
|
||||
mapper.stringVal = flag.String(flagName, defaultVal, helpMsg)
|
||||
}
|
||||
flags[envName] = mapper
|
||||
}
|
||||
flag.Parse()
|
||||
for envName, mapper := range flags {
|
||||
if mapper.isBool {
|
||||
if *mapper.boolVal == true {
|
||||
err := os.Setenv(envName, "true")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if *mapper.stringVal != "" {
|
||||
err := os.Setenv(envName, *mapper.stringVal)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (config *configEnvs) Read() {
|
||||
mapEnvConfigToFlags()
|
||||
|
||||
err := envconfig.Process("", config)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s := reflect.ValueOf(config).Elem()
|
||||
|
||||
@@ -2,12 +2,12 @@ package filters_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
"github.com/cloudflare/unsee/filters"
|
||||
"github.com/cloudflare/unsee/models"
|
||||
"github.com/cloudflare/unsee/store"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type filterTest struct {
|
||||
@@ -34,13 +34,13 @@ var tests = []filterTest{
|
||||
filterTest{
|
||||
Expression: "@silenced=true",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silenced!=true",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
@@ -51,225 +51,225 @@ var tests = []filterTest{
|
||||
filterTest{
|
||||
Expression: "@silence_jira=1",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1}, JiraID: "1"},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1}, JiraID: "1"},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silence_jira=2",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silence_jira!=3",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1}, JiraID: "x"},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1}, JiraID: "x"},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silence_jira!=4",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1}, JiraID: "4"},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1}, JiraID: "4"},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silence_jira!=5",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silence_jira=~abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1}, JiraID: "xxabcxx"},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1}, JiraID: "xxabcxx"},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silence_jira=~abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1}, JiraID: "xxx"},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1}, JiraID: "xxx"},
|
||||
IsMatch: false,
|
||||
},
|
||||
|
||||
filterTest{
|
||||
Expression: "@silence_author=john",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1, CreatedBy: "john"}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1, CreatedBy: "john"}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silence_author=john",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1, CreatedBy: "bob"}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1, CreatedBy: "bob"}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silence_author!=john",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1, CreatedBy: "bob"}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1, CreatedBy: "bob"}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silence_author!=john",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1, CreatedBy: "john"}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1, CreatedBy: "john"}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@silence_author!=john",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1}},
|
||||
IsMatch: true,
|
||||
},
|
||||
|
||||
filterTest{
|
||||
Expression: "@age<1h",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{StartsAt: time.Now().Add(time.Minute * -55)}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{StartsAt: time.Now().Add(time.Minute * -55)}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@age>1h",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{StartsAt: time.Now().Add(time.Hour * -2)}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{StartsAt: time.Now().Add(time.Hour * -2)}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@age<-1h",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{StartsAt: time.Now().Add(time.Minute * -55)}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{StartsAt: time.Now().Add(time.Minute * -55)}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "@age>-1h",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{StartsAt: time.Now().Add(time.Hour * -2)}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{StartsAt: time.Now().Add(time.Hour * -2)}},
|
||||
IsMatch: true,
|
||||
},
|
||||
|
||||
filterTest{
|
||||
Expression: "node=vps1",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Labels: map[string]string{"node": "vps1"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Labels: map[string]string{"node": "vps1"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "node=vps1",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "node!=vps1",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Labels: map[string]string{"node": "vps1"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Labels: map[string]string{"node": "vps1"}}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "node!=vps1",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Labels: map[string]string{"node": "vps2"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Labels: map[string]string{"node": "vps2"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "node=~vps",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Labels: map[string]string{"node": "vps1"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Labels: map[string]string{"node": "vps1"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "node!~vps",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Labels: map[string]string{"node": "vps1"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Labels: map[string]string{"node": "vps1"}}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "node!~abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Labels: map[string]string{"node": "vps1"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Labels: map[string]string{"node": "vps1"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Labels: map[string]string{"key": "abc"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Labels: map[string]string{"key": "abc"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Labels: map[string]string{"key": "XXXabcx"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Labels: map[string]string{"key": "XXXabcx"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Labels: map[string]string{"abc": "xxxab"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Labels: map[string]string{"abc": "xxxab"}}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Annotations: map[string]string{"key": "abc"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Annotations: map[string]string{"key": "abc"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Annotations: map[string]string{"key": "ccc abc"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Annotations: map[string]string{"key": "ccc abc"}}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Annotations: map[string]string{"abc": "zzz"}}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Annotations: map[string]string{"abc": "zzz"}}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1, Comment: "abc"}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1, Comment: "abc"}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1, Comment: "abcxxx"}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1, Comment: "abcxxx"}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1, Comment: "ABCD"}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1, Comment: "ABCD"}},
|
||||
IsMatch: true,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertManagerSilence: models.AlertManagerSilence{ID: 1, Comment: "xzc"}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{Silenced: 1}},
|
||||
Silence: models.UnseeSilence{AlertmanagerSilence: models.AlertmanagerSilence{ID: 1, Comment: "xzc"}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
Expression: "abc",
|
||||
IsValid: true,
|
||||
Alert: models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{}},
|
||||
Alert: models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{}},
|
||||
IsMatch: false,
|
||||
},
|
||||
filterTest{
|
||||
@@ -365,7 +365,7 @@ func TestLimitFilter(t *testing.T) {
|
||||
t.Errorf("[%s] GetIsValid() returned %#v while %#v was expected", ft.Expression, f.GetIsValid(), ft.IsValid)
|
||||
}
|
||||
if f.GetIsValid() {
|
||||
alert := models.UnseeAlert{AlertManagerAlert: models.AlertManagerAlert{}}
|
||||
alert := models.UnseeAlert{AlertmanagerAlert: models.AlertmanagerAlert{}}
|
||||
var index int = 0
|
||||
for _, isMatch := range ft.IsMatch {
|
||||
m := f.Match(&alert, index)
|
||||
|
||||
33
main.go
33
main.go
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/unsee/config"
|
||||
"github.com/cloudflare/unsee/transform"
|
||||
|
||||
@@ -20,7 +21,7 @@ var (
|
||||
version = "dev"
|
||||
|
||||
// ticker is a timer used by background loop that will keep pulling
|
||||
// data from AlertManager
|
||||
// data from Alertmanager
|
||||
ticker *time.Ticker
|
||||
|
||||
// apiCache will be used to keep short lived copy of JSON reponses generated for the UI
|
||||
@@ -28,31 +29,31 @@ var (
|
||||
// rather than do all the filtering every time
|
||||
apiCache *cache.Cache
|
||||
|
||||
// errorLock holds a mutex used to synchronize updates to AlertManagerError
|
||||
// errorLock holds a mutex used to synchronize updates to AlertmanagerError
|
||||
// to avoid any race between readers and writers
|
||||
errorLock = sync.RWMutex{}
|
||||
// alertManagerError holds the description of last error raised when pulling data
|
||||
// from AlertManager, if there was any error
|
||||
// from Alertmanager, if there was any error
|
||||
// This error will be returned in UnseeAlertsResponse and presented by Ui
|
||||
alertManagerError string
|
||||
|
||||
metricAlerts = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "unsee_collected_alerts",
|
||||
Help: "Total number of alerts collected from AlertManager API",
|
||||
Help: "Total number of alerts collected from Alertmanager API",
|
||||
},
|
||||
[]string{"silenced"},
|
||||
)
|
||||
metricAlertGroups = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "unsee_collected_groups",
|
||||
Help: "Total number of alert groups collected from AlertManager API",
|
||||
Help: "Total number of alert groups collected from Alertmanager API",
|
||||
},
|
||||
)
|
||||
metricAlertManagerErrors = prometheus.NewGaugeVec(
|
||||
metricAlertmanagerErrors = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "unsee_alertmanager_errors_total",
|
||||
Help: "Total number of errors encounter when requesting data from AlertManager API",
|
||||
Help: "Total number of errors encounter when requesting data from Alertmanager API",
|
||||
},
|
||||
[]string{"endpoint"},
|
||||
)
|
||||
@@ -61,27 +62,27 @@ var (
|
||||
func init() {
|
||||
prometheus.MustRegister(metricAlerts)
|
||||
prometheus.MustRegister(metricAlertGroups)
|
||||
prometheus.MustRegister(metricAlertManagerErrors)
|
||||
prometheus.MustRegister(metricAlertmanagerErrors)
|
||||
|
||||
metricAlertManagerErrors.With(prometheus.Labels{"endpoint": "alerts"}).Set(0)
|
||||
metricAlertManagerErrors.With(prometheus.Labels{"endpoint": "silences"}).Set(0)
|
||||
metricAlertmanagerErrors.With(prometheus.Labels{"endpoint": "alerts"}).Set(0)
|
||||
metricAlertmanagerErrors.With(prometheus.Labels{"endpoint": "silences"}).Set(0)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.Infof("Version: %s", version)
|
||||
|
||||
config.Config.Read()
|
||||
transform.ParseRules(config.Config.JIRARegexp)
|
||||
transform.ParseRules(config.Config.JiraRegexp)
|
||||
|
||||
apiCache = cache.New(cache.NoExpiration, 10*time.Second)
|
||||
|
||||
// before we start try to fetch data from AlertManager
|
||||
log.Infof("Initial AlertManager query, this can delay startup up to %s", 2*config.Config.AlertManagerTimeout)
|
||||
PullFromAlertManager()
|
||||
// before we start try to fetch data from Alertmanager
|
||||
log.Infof("Initial Alertmanager query, this can delay startup up to %s", 2*config.Config.AlertmanagerTimeout)
|
||||
PullFromAlertmanager()
|
||||
log.Info("Done, starting HTTP server")
|
||||
|
||||
// background loop that will fetch updates from AlertManager
|
||||
ticker = time.NewTicker(config.Config.UpdateInterval)
|
||||
// background loop that will fetch updates from Alertmanager
|
||||
ticker = time.NewTicker(config.Config.AlertmanagerTTL)
|
||||
go Tick()
|
||||
|
||||
switch config.Config.Debug {
|
||||
|
||||
@@ -2,8 +2,8 @@ package models
|
||||
|
||||
import "time"
|
||||
|
||||
// AlertManagerAlert is vanilla alert object from AlertManager
|
||||
type AlertManagerAlert struct {
|
||||
// AlertmanagerAlert is vanilla alert object from Alertmanager
|
||||
type AlertmanagerAlert struct {
|
||||
Annotations map[string]string `json:"annotations"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
StartsAt time.Time `json:"startsAt"`
|
||||
@@ -13,16 +13,16 @@ type AlertManagerAlert struct {
|
||||
Silenced int `json:"silenced"`
|
||||
}
|
||||
|
||||
// AlertManagerAlertGroup is vanilla group object from AlertManager, exposed under api/v1/alerts/groups
|
||||
type AlertManagerAlertGroup struct {
|
||||
// AlertmanagerAlertGroup is vanilla group object from Alertmanager, exposed under api/v1/alerts/groups
|
||||
type AlertmanagerAlertGroup struct {
|
||||
Labels map[string]string `json:"labels"`
|
||||
Blocks []struct {
|
||||
Alerts []AlertManagerAlert `json:"alerts"`
|
||||
Alerts []AlertmanagerAlert `json:"alerts"`
|
||||
} `json:"blocks"`
|
||||
}
|
||||
|
||||
// AlertManagerSilence is vanilla silence object from AlertManager, exposed under api/v1/silences
|
||||
type AlertManagerSilence struct {
|
||||
// AlertmanagerSilence is vanilla silence object from Alertmanager, exposed under api/v1/silences
|
||||
type AlertmanagerSilence struct {
|
||||
ID int `json:"id"`
|
||||
Matchers []struct {
|
||||
Name string `json:"name"`
|
||||
@@ -41,7 +41,7 @@ type AlertManagerSilence struct {
|
||||
// extracted ID is used to generate link to JIRA issue
|
||||
// this means Unsee needs to store additional fields for each silence
|
||||
type UnseeSilence struct {
|
||||
AlertManagerSilence
|
||||
AlertmanagerSilence
|
||||
JiraID string `json:"jiraID"`
|
||||
JiraURL string `json:"jiraURL"`
|
||||
}
|
||||
@@ -52,7 +52,7 @@ type UnseeSilence struct {
|
||||
// and returned under links field, Unsee UI used this to show links differently
|
||||
// than other annotations
|
||||
type UnseeAlert struct {
|
||||
AlertManagerAlert
|
||||
AlertmanagerAlert
|
||||
Links map[string]string `json:"links"`
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (a UnseeAlertList) Less(i, j int) bool {
|
||||
return a[i].StartsAt.Round(2 * time.Second).After(a[j].StartsAt.Round(2 * time.Second))
|
||||
}
|
||||
|
||||
// UnseeAlertGroup is vanilla AlertManager group, but alerts are flattened
|
||||
// UnseeAlertGroup is vanilla Alertmanager group, but alerts are flattened
|
||||
// There is a hash computed from all alerts, it's used by UI to quickly tell
|
||||
// if there was any change in a group and it needs to refresh it
|
||||
type UnseeAlertGroup struct {
|
||||
|
||||
@@ -3,6 +3,7 @@ package store
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/unsee/models"
|
||||
)
|
||||
|
||||
@@ -31,10 +32,10 @@ var (
|
||||
// (alerts, silences, colors, ac)
|
||||
StoreLock = sync.RWMutex{}
|
||||
|
||||
// AlertStore holds all alerts retrieved from AlertManager
|
||||
// AlertStore holds all alerts retrieved from Alertmanager
|
||||
AlertStore = alertStoreType{}
|
||||
|
||||
// SilenceStore holds all silences retrieved from AlertManager
|
||||
// SilenceStore holds all silences retrieved from Alertmanager
|
||||
SilenceStore = silenceStoreType{}
|
||||
|
||||
// ColorStore holds all color maps generated from alerts
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
autocomplete="off"
|
||||
value="{{ .QFilter }}"
|
||||
data-default-used="{{ .DefaultUsed }}"
|
||||
data-default-filter="{{ .Config.DefaultFilter }}"
|
||||
data-default-filter="{{ .Config.FilterDefault }}"
|
||||
autofocus>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
21
timer.go
21
timer.go
@@ -8,6 +8,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/unsee/alertmanager"
|
||||
"github.com/cloudflare/unsee/config"
|
||||
"github.com/cloudflare/unsee/models"
|
||||
@@ -19,10 +20,10 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// PullFromAlertManager will try to fetch latest alerts and silences
|
||||
// from AlertManager API, it's called by Ticker timer
|
||||
func PullFromAlertManager() {
|
||||
log.Info("Pulling latest alerts and silences from AlertManager")
|
||||
// PullFromAlertmanager will try to fetch latest alerts and silences
|
||||
// from Alertmanager API, it's called by Ticker timer
|
||||
func PullFromAlertmanager() {
|
||||
log.Info("Pulling latest alerts and silences from Alertmanager")
|
||||
|
||||
silenceResponse := alertmanager.SilenceAPIResponse{}
|
||||
err := silenceResponse.Get()
|
||||
@@ -31,7 +32,7 @@ func PullFromAlertManager() {
|
||||
errorLock.Lock()
|
||||
alertManagerError = err.Error()
|
||||
errorLock.Unlock()
|
||||
metricAlertManagerErrors.With(prometheus.Labels{"endpoint": "silences"}).Inc()
|
||||
metricAlertmanagerErrors.With(prometheus.Labels{"endpoint": "silences"}).Inc()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ func PullFromAlertManager() {
|
||||
errorLock.Lock()
|
||||
alertManagerError = err.Error()
|
||||
errorLock.Unlock()
|
||||
metricAlertManagerErrors.With(prometheus.Labels{"endpoint": "alerts"}).Inc()
|
||||
metricAlertmanagerErrors.With(prometheus.Labels{"endpoint": "alerts"}).Inc()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,7 +51,7 @@ func PullFromAlertManager() {
|
||||
for _, silence := range silenceResponse.Data.Silences {
|
||||
jiraID, jiraLink := transform.DetectJIRAs(&silence)
|
||||
silenceStore[strconv.Itoa(silence.ID)] = models.UnseeSilence{
|
||||
AlertManagerSilence: silence,
|
||||
AlertmanagerSilence: silence,
|
||||
JiraID: jiraID,
|
||||
JiraURL: jiraLink,
|
||||
}
|
||||
@@ -93,7 +94,7 @@ func PullFromAlertManager() {
|
||||
|
||||
for _, alertBlock := range alertGroup.Blocks {
|
||||
for _, alert := range alertBlock.Alerts {
|
||||
apiAlert := models.UnseeAlert{AlertManagerAlert: alert}
|
||||
apiAlert := models.UnseeAlert{AlertmanagerAlert: alert}
|
||||
|
||||
apiAlert.Annotations, apiAlert.Links = transform.DetectLinks(apiAlert.Annotations)
|
||||
|
||||
@@ -163,12 +164,12 @@ func PullFromAlertManager() {
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
// Tick is the background timer used to call PullFromAlertManager
|
||||
// Tick is the background timer used to call PullFromAlertmanager
|
||||
func Tick() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
PullFromAlertManager()
|
||||
PullFromAlertmanager()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package transform
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"io"
|
||||
"math/rand"
|
||||
"github.com/cloudflare/unsee/config"
|
||||
"github.com/cloudflare/unsee/models"
|
||||
"io"
|
||||
"math/rand"
|
||||
|
||||
"github.com/hansrodtang/randomcolor"
|
||||
)
|
||||
@@ -25,7 +25,7 @@ func labelToSeed(key string, val string) int64 {
|
||||
// from label key and value passed here
|
||||
// It's used to generate unique colors for configured labels
|
||||
func ColorLabel(colorStore models.UnseeColorMap, key string, val string) {
|
||||
if stringInSlice(config.Config.ColorLabels, key) == true {
|
||||
if stringInSlice(config.Config.ColorLabelsUnique, key) == true {
|
||||
if _, found := colorStore[key]; !found {
|
||||
colorStore[key] = make(map[string]models.UnseeLabelColor)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudflare/unsee/models"
|
||||
)
|
||||
|
||||
@@ -33,10 +34,10 @@ func ParseRules(rules []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// DetectJIRAs will try to find JIRA links in AlertManager silence objects
|
||||
// DetectJIRAs will try to find JIRA links in Alertmanager silence objects
|
||||
// using regexp rules from configuration that were parsed and populated
|
||||
// by ParseRules call
|
||||
func DetectJIRAs(silence *models.AlertManagerSilence) (jiraID, jiraLink string) {
|
||||
func DetectJIRAs(silence *models.AlertmanagerSilence) (jiraID, jiraLink string) {
|
||||
for _, jdr := range jiraDetectRules {
|
||||
jiraID := jdr.Regexp.FindString(silence.Comment)
|
||||
if jiraID != "" {
|
||||
|
||||
@@ -12,7 +12,7 @@ func StripLables(ignoredLabels []string, sourceLabels map[string]string) map[str
|
||||
for label, value := range sourceLabels {
|
||||
if !stringInSlice(ignoredLabels, label) {
|
||||
// strip leading and trailung space in label value
|
||||
// this is to normalize values in case space is added by AlertManager rules
|
||||
// this is to normalize values in case space is added by Alertmanager rules
|
||||
labels[label] = strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
|
||||
8
views.go
8
views.go
@@ -4,15 +4,15 @@ import (
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cloudflare/unsee/config"
|
||||
"github.com/cloudflare/unsee/models"
|
||||
"github.com/cloudflare/unsee/store"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"github.com/cloudflare/unsee/config"
|
||||
"github.com/cloudflare/unsee/models"
|
||||
"github.com/cloudflare/unsee/store"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -54,7 +54,7 @@ func Index(c *gin.Context) {
|
||||
"Config": config.Config,
|
||||
"QFilter": q,
|
||||
"DefaultUsed": defaultUsed,
|
||||
"StaticColorLabels": strings.Join(config.Config.StaticColorLabels, " "),
|
||||
"StaticColorLabels": strings.Join(config.Config.ColorLabelsStatic, " "),
|
||||
})
|
||||
|
||||
log.Infof("[%s] %s %s took %s", c.ClientIP(), c.Request.Method, c.Request.RequestURI, time.Since(start))
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
func mockConfig() {
|
||||
log.SetLevel(log.FatalLevel)
|
||||
os.Setenv("ALERTMANAGER_URI", "http://localhost")
|
||||
os.Setenv("COLOR_LABELS", "alertname")
|
||||
os.Setenv("COLOR_LABELS_UNIQUE", "alertname")
|
||||
config.Config.Read()
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func mockAlerts() {
|
||||
}`
|
||||
httpmock.RegisterResponder("GET", "http://localhost/api/v1/alerts/groups", httpmock.NewStringResponder(200, alerts))
|
||||
|
||||
PullFromAlertManager()
|
||||
PullFromAlertmanager()
|
||||
}
|
||||
|
||||
func TestAlerts(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user