mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user