fix(config): change config schema to allow regex based matches

BREAKING CHANGE: this changes configuration schema for custom label values, it provides more flexibility when setting custom labels and also prevents '*' special wildcard value from conflicting with labels, since '*' is a valid label value.

Fixes #537
This commit is contained in:
Łukasz Mierzwa
2019-03-21 11:06:31 +00:00
parent ac8f042951
commit aa070a769c
5 changed files with 114 additions and 41 deletions

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
}
}
}