diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 8f18b4f6d..2aa0e802b 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -329,7 +329,11 @@ labels: color: static: list of strings unique: list of strings - custom: dict + custom: + foo: + - value: string + value_re: string + color: string keep: list of strings strip: list of strings ``` @@ -343,14 +347,20 @@ labels: in the UI. - `color:custom` - nested map of label names and value with colors - this allows to configure a set of labels with custom predefined colors applied to them - rather than generated. Colors can be defined as RGB or HEX values. - Value is a mapping with `label name` -> `label value` -> `color`, see examples - below. Wildcard values (`*`) are allowed to provide a fallback custom color, - which can also be used instead of `color:static` option. Wildcard options - are evaluated after exact matches, so it's possible to mix explicit and - wildcard values. + rather than generated. Value is a mapping with `label name` -> + `list of dicts`, each dict object allows setting: + + - `value` - the exact value of the label to match against + - `value_re` - Go compatible + [regular expression](https://golang.org/pkg/regexp/) to match against + - `color`: color to apply if either `value` or `value_re` matches + + Either `value` or `value_re` is required, both can be set in which case + `value` with be tested first. Entries are evaluated in the order they appear + in the config file. Note: this option is not available via environment variables, you can only set it via the config file. + - `keep` - list of allowed labels, if empty all labels are allowed. - `strip` - list of ignored labels. @@ -394,27 +404,39 @@ labels: color: custom: "@alertmanager": - prod: "#e6e" + - value: prod + color: "#e6e" severity: - info: "#87c4e0" - warning: "#ffae42" - critical: "#ff220c" + - value: info + color: "#87c4e0" + - value: warning + color: "#ffae42" + - value: critical + color: "#ff220c" ``` -Example with a wildcard value, `info`, `warning` and `critical` will get colors -as below, but any value not matching those 3 values will use the color from `*`: +Example with a regex value, `info`, `warning` and `critical` will get colors +as below, but any value not matching those 3 values will use the color from +`.*`: ```YAML labels: color: custom: severity: - "*": "#736598" - info: "#87c4e0" - warning: "#ffae42" - critical: "#ff220c" + - value: info + color: "#87c4e0" + - value: warning + color: "#ffae42" + - value: critical + color: "#ff220c" + - value_re: ".*" + color: "#736598" ``` +Note: be sure to set fallback values at the end of the list, so they're only +evaluated if there's no exact value match + Defaults: ```YAML diff --git a/internal/config/config.go b/internal/config/config.go index 91454aa4e..e2c433437 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,7 @@ import ( "bytes" "io/ioutil" "os" + "regexp" "strings" "time" @@ -146,7 +147,7 @@ func (config *configSchema) Read() { config.Grid.Sorting.Order = v.GetString("grid.sorting.order") config.Grid.Sorting.Reverse = v.GetBool("grid.sorting.reverse") config.Grid.Sorting.Label = v.GetString("grid.sorting.label") - config.Labels.Color.Custom = map[string]map[string]string{} + config.Labels.Color.Custom = CustomLabelColors{} config.Labels.Color.Static = v.GetStringSlice("labels.color.static") config.Labels.Color.Unique = v.GetStringSlice("labels.color.unique") config.Labels.Keep = v.GetStringSlice("labels.keep") @@ -175,6 +176,19 @@ func (config *configSchema) Read() { if err != nil { log.Fatal(err) } + for labelName, customColors := range config.Labels.Color.Custom { + for i, customColor := range customColors { + if customColor.Value == "" && customColor.ValueRegex == "" { + log.Fatalf("Custom label color for '%s' is missing 'value' or 'value_re'", labelName) + } + if customColor.ValueRegex != "" { + config.Labels.Color.Custom[labelName][i].CompiledRegex, err = regexp.Compile(customColor.ValueRegex) + if err != nil { + log.Fatalf("Failed to parse custom color regex rule '%s' for '%s' label: %s", customColor.ValueRegex, labelName, err) + } + } + } + } err = v.UnmarshalKey("grid.sorting.customValues.labels", &config.Grid.Sorting.CustomValues.Labels) if err != nil { @@ -203,7 +217,6 @@ func (config *configSchema) Read() { log.Fatal(err) } - config.Labels.Color.Custom = raw.Labels.Color.Custom config.Grid.Sorting.CustomValues.Labels = raw.Grid.Sorting.CustomValues.Labels } diff --git a/internal/config/models.go b/internal/config/models.go index ed8a04f26..ac53968dd 100644 --- a/internal/config/models.go +++ b/internal/config/models.go @@ -1,6 +1,9 @@ package config -import "time" +import ( + "regexp" + "time" +) type alertmanagerConfig struct { Name string @@ -11,7 +14,7 @@ type alertmanagerConfig struct { CA string Cert string Key string - InsecureSkipVerify bool `yaml:"insecureSkipVerify"` + InsecureSkipVerify bool `yaml:"insecureSkipVerify" mapstructure:"insecureSkipVerify"` } Headers map[string]string } @@ -21,6 +24,15 @@ type jiraRule struct { URI string } +type CustomLabelColor struct { + Value string `yaml:"value" mapstructure:"value"` + ValueRegex string `yaml:"value_re" mapstructure:"value_re"` + CompiledRegex *regexp.Regexp `yaml:"-" mapstructure:"-"` + Color string `yaml:"color" mapstructure:"color"` +} + +type CustomLabelColors map[string][]CustomLabelColor + type configSchema struct { Alertmanager struct { Interval time.Duration @@ -50,14 +62,14 @@ type configSchema struct { Label string CustomValues struct { Labels map[string]map[string]int - } `yaml:"customValues"` + } `yaml:"customValues" mapstructure:"customValues"` } } Labels struct { Keep []string Strip []string Color struct { - Custom map[string]map[string]string + Custom CustomLabelColors Static []string Unique []string } diff --git a/internal/transform/color_test.go b/internal/transform/color_test.go index 85b17a4c7..796eff535 100644 --- a/internal/transform/color_test.go +++ b/internal/transform/color_test.go @@ -1,6 +1,7 @@ package transform_test import ( + "regexp" "testing" "github.com/prymitive/karma/internal/config" @@ -10,7 +11,7 @@ import ( type colorTest struct { uniqueLabels []string - customLabels map[string]map[string]string + customLabels config.CustomLabelColors labels map[string]string colors map[string]string } @@ -56,8 +57,10 @@ var colorTests = []colorTest{ }, }, { - customLabels: map[string]map[string]string{ - "node": map[string]string{"localhost": "#fff"}, + customLabels: config.CustomLabelColors{ + "node": []config.CustomLabelColor{ + config.CustomLabelColor{Value: "localhost", Color: "#fff"}, + }, }, labels: map[string]string{ "node": "localhost", @@ -67,8 +70,10 @@ var colorTests = []colorTest{ }, }, { - customLabels: map[string]map[string]string{ - "node": map[string]string{"localhost": "not a color"}, + customLabels: config.CustomLabelColors{ + "node": []config.CustomLabelColor{ + config.CustomLabelColor{Value: "localhost", Color: "not a valid color"}, + }, }, labels: map[string]string{ "node": "localhost", @@ -76,8 +81,21 @@ var colorTests = []colorTest{ colors: map[string]string{}, }, { - customLabels: map[string]map[string]string{ - "node": map[string]string{"*": "#123"}, + customLabels: config.CustomLabelColors{ + "node": []config.CustomLabelColor{ + config.CustomLabelColor{ValueRegex: ".*", Color: "#123"}, + }, + }, + labels: map[string]string{ + "node": "localhost", + }, + colors: map[string]string{"node": "localhost"}, + }, + { + customLabels: config.CustomLabelColors{ + "node": []config.CustomLabelColor{ + config.CustomLabelColor{Value: "foo", ValueRegex: ".*", Color: "#123"}, + }, }, labels: map[string]string{ "node": "localhost", @@ -91,9 +109,19 @@ func TestColorLabel(t *testing.T) { config.Config.Labels.Color.Unique = testCase.uniqueLabels config.Config.Labels.Color.Custom = testCase.customLabels colorStore := models.LabelsColorMap{} + + for key, rules := range testCase.customLabels { + for i, rule := range rules { + if rule.ValueRegex != "" { + testCase.customLabels[key][i].CompiledRegex = regexp.MustCompile(rule.ValueRegex) + } + } + } + for key, value := range testCase.labels { transform.ColorLabel(colorStore, key, value) } + for key, value := range testCase.colors { if label, found := colorStore[key]; found { if _, found := label[value]; !found { diff --git a/internal/transform/colors.go b/internal/transform/colors.go index 32c48718e..f7667383d 100644 --- a/internal/transform/colors.go +++ b/internal/transform/colors.go @@ -69,18 +69,16 @@ func ColorLabel(colorStore models.LabelsColorMap, key string, val string) { // first handle custom colors _, ok := config.Config.Labels.Color.Custom[key] if ok { - // try matching both key and value - customColor, found := config.Config.Labels.Color.Custom[key][val] - if found { - parseCustomColor(colorStore, key, val, customColor) - return - } + for _, colorRule := range config.Config.Labels.Color.Custom[key] { + if colorRule.Value == val { + parseCustomColor(colorStore, key, val, colorRule.Color) + return + } - // if not found try matching key and wildcard (*) - customColor, found = config.Config.Labels.Color.Custom[key]["*"] - if found { - parseCustomColor(colorStore, key, val, customColor) - return + if colorRule.CompiledRegex != nil && colorRule.CompiledRegex.MatchString(val) { + parseCustomColor(colorStore, key, val, colorRule.Color) + return + } } }