diff --git a/config/config_test.go b/config/config_test.go index e0f762194..ef198d6dd 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -858,6 +858,17 @@ var expectedConf = &Config{ Scheme: DefaultScrapeConfig.Scheme, HTTPClientConfig: config.DefaultHTTPClientConfig, + RelabelConfigs: []*relabel.Config{ + { + Action: relabel.Uppercase, + Regex: relabel.DefaultRelabelConfig.Regex, + Replacement: relabel.DefaultRelabelConfig.Replacement, + Separator: relabel.DefaultRelabelConfig.Separator, + SourceLabels: model.LabelNames{"instance"}, + TargetLabel: "instance", + }, + }, + ServiceDiscoveryConfigs: discovery.Configs{ &hetzner.SDConfig{ HTTPClientConfig: config.HTTPClientConfig{ @@ -1197,6 +1208,30 @@ var expectedErrors = []struct { filename: "labelmap.bad.yml", errMsg: "\"l-$1\" is invalid 'replacement' for labelmap action", }, + { + filename: "lowercase.bad.yml", + errMsg: "relabel configuration for lowercase action requires 'target_label' value", + }, + { + filename: "lowercase2.bad.yml", + errMsg: "\"42lab\" is invalid 'target_label' for lowercase action", + }, + { + filename: "lowercase3.bad.yml", + errMsg: "'replacement' can not be set for lowercase action", + }, + { + filename: "uppercase.bad.yml", + errMsg: "relabel configuration for uppercase action requires 'target_label' value", + }, + { + filename: "uppercase2.bad.yml", + errMsg: "\"42lab\" is invalid 'target_label' for uppercase action", + }, + { + filename: "uppercase3.bad.yml", + errMsg: "'replacement' can not be set for uppercase action", + }, { filename: "rules.bad.yml", errMsg: "invalid rule file path", diff --git a/config/testdata/conf.good.yml b/config/testdata/conf.good.yml index 58ef791af..9b5d4b039 100644 --- a/config/testdata/conf.good.yml +++ b/config/testdata/conf.good.yml @@ -328,6 +328,10 @@ scrape_configs: key_file: valid_key_file - job_name: hetzner + relabel_configs: + - action: uppercase + source_labels: [instance] + target_label: instance hetzner_sd_configs: - role: hcloud authorization: diff --git a/config/testdata/lowercase.bad.yml b/config/testdata/lowercase.bad.yml new file mode 100644 index 000000000..9bc958334 --- /dev/null +++ b/config/testdata/lowercase.bad.yml @@ -0,0 +1,5 @@ +scrape_configs: + - job_name: prometheus + relabel_configs: + - action: lowercase + source_labels: [__name__] diff --git a/config/testdata/lowercase2.bad.yml b/config/testdata/lowercase2.bad.yml new file mode 100644 index 000000000..bde8862c6 --- /dev/null +++ b/config/testdata/lowercase2.bad.yml @@ -0,0 +1,6 @@ +scrape_configs: + - job_name: prometheus + relabel_configs: + - action: lowercase + source_labels: [__name__] + target_label: 42lab diff --git a/config/testdata/lowercase3.bad.yml b/config/testdata/lowercase3.bad.yml new file mode 100644 index 000000000..6a19f31f3 --- /dev/null +++ b/config/testdata/lowercase3.bad.yml @@ -0,0 +1,7 @@ +scrape_configs: + - job_name: prometheus + relabel_configs: + - action: lowercase + source_labels: [__name__] + target_label: __name__ + replacement: bar diff --git a/config/testdata/uppercase.bad.yml b/config/testdata/uppercase.bad.yml new file mode 100644 index 000000000..582692b5c --- /dev/null +++ b/config/testdata/uppercase.bad.yml @@ -0,0 +1,5 @@ +scrape_configs: + - job_name: prometheus + relabel_configs: + - action: uppercase + source_labels: [__name__] diff --git a/config/testdata/uppercase2.bad.yml b/config/testdata/uppercase2.bad.yml new file mode 100644 index 000000000..330b9aceb --- /dev/null +++ b/config/testdata/uppercase2.bad.yml @@ -0,0 +1,6 @@ +scrape_configs: + - job_name: prometheus + relabel_configs: + - action: uppercase + source_labels: [__name__] + target_label: 42lab diff --git a/config/testdata/uppercase3.bad.yml b/config/testdata/uppercase3.bad.yml new file mode 100644 index 000000000..9a7b17f40 --- /dev/null +++ b/config/testdata/uppercase3.bad.yml @@ -0,0 +1,7 @@ +scrape_configs: + - job_name: prometheus + relabel_configs: + - action: uppercase + source_labels: [__name__] + target_label: __name__ + replacement: bar diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index cde1a03b9..621fff3dc 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -2536,6 +2536,8 @@ anchored on both ends. To un-anchor the regex, use `.*.*`. `target_label` to `replacement`, with match group references (`${1}`, `${2}`, ...) in `replacement` substituted by their value. If `regex` does not match, no replacement takes place. +* `lowercase`: Maps the concatenated `source_labels` to their lower case. +* `uppercase`: Maps the concatenated `source_labels` to their upper case. * `keep`: Drop targets for which `regex` does not match the concatenated `source_labels`. * `drop`: Drop targets for which `regex` matches the concatenated `source_labels`. * `hashmod`: Set `target_label` to the `modulus` of a hash of the concatenated `source_labels`. diff --git a/model/relabel/relabel.go b/model/relabel/relabel.go index da21ff918..98a245bba 100644 --- a/model/relabel/relabel.go +++ b/model/relabel/relabel.go @@ -54,6 +54,10 @@ const ( LabelDrop Action = "labeldrop" // LabelKeep drops any label not matching the regex. LabelKeep Action = "labelkeep" + // Lowercase maps input letters to their lower case. + Lowercase Action = "lowercase" + // Uppercase maps input letters to their upper case. + Uppercase Action = "uppercase" ) // UnmarshalYAML implements the yaml.Unmarshaler interface. @@ -63,7 +67,7 @@ func (a *Action) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } switch act := Action(strings.ToLower(s)); act { - case Replace, Keep, Drop, HashMod, LabelMap, LabelDrop, LabelKeep: + case Replace, Keep, Drop, HashMod, LabelMap, LabelDrop, LabelKeep, Lowercase, Uppercase: *a = act return nil } @@ -106,12 +110,15 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { if c.Modulus == 0 && c.Action == HashMod { return errors.Errorf("relabel configuration for hashmod requires non-zero modulus") } - if (c.Action == Replace || c.Action == HashMod) && c.TargetLabel == "" { + if (c.Action == Replace || c.Action == HashMod || c.Action == Lowercase || c.Action == Uppercase) && c.TargetLabel == "" { return errors.Errorf("relabel configuration for %s action requires 'target_label' value", c.Action) } - if c.Action == Replace && !relabelTarget.MatchString(c.TargetLabel) { + if (c.Action == Replace || c.Action == Lowercase || c.Action == Uppercase) && !relabelTarget.MatchString(c.TargetLabel) { return errors.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action) } + if (c.Action == Lowercase || c.Action == Uppercase) && c.Replacement != DefaultRelabelConfig.Replacement { + return errors.Errorf("'replacement' can not be set for %s action", c.Action) + } if c.Action == LabelMap && !relabelTarget.MatchString(c.Replacement) { return errors.Errorf("%q is invalid 'replacement' for %s action", c.Replacement, c.Action) } @@ -228,6 +235,10 @@ func relabel(lset labels.Labels, cfg *Config) labels.Labels { break } lb.Set(string(target), string(res)) + case Lowercase: + lb.Set(cfg.TargetLabel, strings.ToLower(val)) + case Uppercase: + lb.Set(cfg.TargetLabel, strings.ToUpper(val)) case HashMod: mod := sum64(md5.Sum([]byte(val))) % cfg.Modulus lb.Set(cfg.TargetLabel, fmt.Sprintf("%d", mod)) diff --git a/model/relabel/relabel_test.go b/model/relabel/relabel_test.go index 1e50344a0..4ae1f5344 100644 --- a/model/relabel/relabel_test.go +++ b/model/relabel/relabel_test.go @@ -428,6 +428,28 @@ func TestRelabel(t *testing.T) { "a": "foo", }), }, + { + input: labels.FromMap(map[string]string{ + "foo": "bAr123Foo", + }), + relabel: []*Config{ + { + SourceLabels: model.LabelNames{"foo"}, + Action: Uppercase, + TargetLabel: "foo_uppercase", + }, + { + SourceLabels: model.LabelNames{"foo"}, + Action: Lowercase, + TargetLabel: "foo_lowercase", + }, + }, + output: labels.FromMap(map[string]string{ + "foo": "bAr123Foo", + "foo_lowercase": "bar123foo", + "foo_uppercase": "BAR123FOO", + }), + }, } for _, test := range tests {