mirror of
https://github.com/prymitive/karma
synced 2026-05-23 04:42:58 +00:00
Generate flag for each environment key
This allows to set config keys via flags, in additions to current env variable only configuration. Flags are autogenerated from supported env keys.
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