diff --git a/config/config.go b/config/config.go index 221bdc7a46..61775f6e7a 100644 --- a/config/config.go +++ b/config/config.go @@ -31,6 +31,7 @@ var ( patFileSDName = regexp.MustCompile(`^[^*]*(\*[^/]*)?\.(json|yml|yaml|JSON|YML|YAML)$`) patRulePath = regexp.MustCompile(`^[^*]*(\*[^/]*)?$`) patAuthLine = regexp.MustCompile(`((?:password|bearer_token|secret_key|client_secret):\s+)(".+"|'.+'|[^\s]+)`) + relabelTarget = regexp.MustCompile(`^(?:(?:[a-zA-Z_]|\$(?:\{\w+\}|\w+))+\w*)+$`) ) // Load parses the YAML input s into a Config. @@ -355,7 +356,6 @@ func (c *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { gc.EvaluationInterval = DefaultGlobalConfig.EvaluationInterval } *c = *gc - return nil } @@ -987,8 +987,9 @@ type RelabelConfig struct { Regex Regexp `yaml:"regex,omitempty"` // Modulus to take of the hash of concatenated values from the source labels. Modulus uint64 `yaml:"modulus,omitempty"` - // The label to which the resulting string is written in a replacement. - TargetLabel model.LabelName `yaml:"target_label,omitempty"` + // TargetLabel is the label to which the resulting string is written in a replacement. + // Regexp interpolation is allowed for the replace action. + TargetLabel string `yaml:"target_label,omitempty"` // Replacement is the regex replacement pattern to be used. Replacement string `yaml:"replacement,omitempty"` // Action is the action to be performed for the relabeling. @@ -1014,6 +1015,12 @@ func (c *RelabelConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if (c.Action == RelabelReplace || c.Action == RelabelHashMod) && c.TargetLabel == "" { return fmt.Errorf("relabel configuration for %s action requires 'target_label' value", c.Action) } + if c.Action == RelabelReplace && !relabelTarget.MatchString(c.TargetLabel) { + return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action) + } + if c.Action == RelabelHashMod && !model.LabelName(c.TargetLabel).IsValid() { + return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action) + } return nil } diff --git a/config/config_test.go b/config/config_test.go index 38d885f335..4bbb3e9b43 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -207,6 +207,17 @@ var expectedConf = &Config{ Scheme: DefaultConsulSDConfig.Scheme, }, }, + + RelabelConfigs: []*RelabelConfig{ + { + SourceLabels: model.LabelNames{"__meta_sd_consul_tags"}, + Regex: MustNewRegexp("label:([^=]+)=([^,]+)"), + Separator: ",", + TargetLabel: "${1}", + Replacement: "${2}", + Action: RelabelReplace, + }, + }, }, { JobName: "service-z", @@ -501,6 +512,34 @@ func TestEmptyGlobalBlock(t *testing.T) { } } +func TestTargetLabelValidity(t *testing.T) { + tests := []struct { + str string + valid bool + }{ + {"-label", false}, + {"label", true}, + {"label${1}", true}, + {"${1}label", true}, + {"${1}", true}, + {"${1}label", true}, + {"${", false}, + {"$", false}, + {"${}", false}, + {"foo${", false}, + {"$1", true}, + {"asd$2asd", true}, + {"-foo${1}bar-", false}, + {"_${1}_", true}, + {"foo${bar}foo", true}, + } + for _, test := range tests { + if relabelTarget.Match([]byte(test.str)) != test.valid { + t.Fatalf("Expected %q to be %v", test.str, test.valid) + } + } +} + func kubernetesSDHostURL() URL { tURL, _ := url.Parse("https://localhost:1234") return URL{URL: tURL} diff --git a/config/testdata/conf.good.yml b/config/testdata/conf.good.yml index a18deeddbb..a075a3dfe9 100644 --- a/config/testdata/conf.good.yml +++ b/config/testdata/conf.good.yml @@ -104,6 +104,13 @@ scrape_configs: - server: 'localhost:1234' services: ['nginx', 'cache', 'mysql'] + relabel_configs: + - source_labels: [__meta_sd_consul_tags] + separator: ',' + regex: label:([^=]+)=([^,]+) + target_label: ${1} + replacement: ${2} + - job_name: service-z tls_config: diff --git a/relabel/relabel.go b/relabel/relabel.go index 6ce37296c3..ff59045965 100644 --- a/relabel/relabel.go +++ b/relabel/relabel.go @@ -61,20 +61,20 @@ func relabel(labels model.LabelSet, cfg *config.RelabelConfig) model.LabelSet { if indexes == nil { break } - target := model.LabelName(cfg.Regex.ExpandString([]byte{}, string(cfg.TargetLabel), val, indexes)) + target := model.LabelName(cfg.Regex.ExpandString([]byte{}, cfg.TargetLabel, val, indexes)) if !target.IsValid() { - delete(labels, cfg.TargetLabel) + delete(labels, model.LabelName(cfg.TargetLabel)) break } res := cfg.Regex.ExpandString([]byte{}, cfg.Replacement, val, indexes) if len(res) == 0 { - delete(labels, cfg.TargetLabel) + delete(labels, model.LabelName(cfg.TargetLabel)) break } labels[target] = model.LabelValue(res) case config.RelabelHashMod: mod := sum64(md5.Sum([]byte(val))) % cfg.Modulus - labels[cfg.TargetLabel] = model.LabelValue(fmt.Sprintf("%d", mod)) + labels[model.LabelName(cfg.TargetLabel)] = model.LabelValue(fmt.Sprintf("%d", mod)) case config.RelabelLabelMap: out := make(model.LabelSet, len(labels)) // Take a copy to avoid infinite loops. diff --git a/relabel/relabel_test.go b/relabel/relabel_test.go index 4004fa96ff..28fa6e901b 100644 --- a/relabel/relabel_test.go +++ b/relabel/relabel_test.go @@ -38,7 +38,7 @@ func TestRelabel(t *testing.T) { { SourceLabels: model.LabelNames{"a"}, Regex: config.MustNewRegexp("f(.*)"), - TargetLabel: model.LabelName("d"), + TargetLabel: "d", Separator: ";", Replacement: "ch${1}-ch${1}", Action: config.RelabelReplace, @@ -61,7 +61,7 @@ func TestRelabel(t *testing.T) { { SourceLabels: model.LabelNames{"a", "b"}, Regex: config.MustNewRegexp("f(.*);(.*)r"), - TargetLabel: model.LabelName("a"), + TargetLabel: "a", Separator: ";", Replacement: "b${1}${2}m", // boobam Action: config.RelabelReplace, @@ -69,7 +69,7 @@ func TestRelabel(t *testing.T) { { SourceLabels: model.LabelNames{"c", "a"}, Regex: config.MustNewRegexp("(b).*b(.*)ba(.*)"), - TargetLabel: model.LabelName("d"), + TargetLabel: "d", Separator: ";", Replacement: "$1$2$2$3", Action: config.RelabelReplace, @@ -94,7 +94,7 @@ func TestRelabel(t *testing.T) { }, { SourceLabels: model.LabelNames{"a"}, Regex: config.MustNewRegexp("f(.*)"), - TargetLabel: model.LabelName("d"), + TargetLabel: "d", Separator: ";", Replacement: "ch$1-ch$1", Action: config.RelabelReplace, @@ -124,7 +124,7 @@ func TestRelabel(t *testing.T) { { SourceLabels: model.LabelNames{"a"}, Regex: config.MustNewRegexp(".*(b).*"), - TargetLabel: model.LabelName("d"), + TargetLabel: "d", Separator: ";", Replacement: "$1", Action: config.RelabelReplace, @@ -202,7 +202,7 @@ func TestRelabel(t *testing.T) { { SourceLabels: model.LabelNames{"a"}, Regex: config.MustNewRegexp("f"), - TargetLabel: model.LabelName("b"), + TargetLabel: "b", Replacement: "bar", Action: config.RelabelReplace, }, @@ -220,7 +220,7 @@ func TestRelabel(t *testing.T) { relabel: []*config.RelabelConfig{ { SourceLabels: model.LabelNames{"c"}, - TargetLabel: model.LabelName("d"), + TargetLabel: "d", Separator: ";", Action: config.RelabelHashMod, Modulus: 1000, @@ -287,7 +287,7 @@ func TestRelabel(t *testing.T) { Regex: config.MustNewRegexp("some-([^-]+)-([^,]+)"), Action: config.RelabelReplace, Replacement: "${2}", - TargetLabel: model.LabelName("${1}"), + TargetLabel: "${1}", }, }, output: model.LabelSet{ @@ -305,7 +305,7 @@ func TestRelabel(t *testing.T) { Regex: config.MustNewRegexp("some-([^-]+)-([^,]+)"), Action: config.RelabelReplace, Replacement: "${3}", - TargetLabel: model.LabelName("${1}"), + TargetLabel: "${1}", }, }, output: model.LabelSet{ @@ -322,21 +322,21 @@ func TestRelabel(t *testing.T) { Regex: config.MustNewRegexp("some-([^-]+)-([^,]+)"), Action: config.RelabelReplace, Replacement: "${1}", - TargetLabel: model.LabelName("${3}"), + TargetLabel: "${3}", }, { SourceLabels: model.LabelNames{"a"}, Regex: config.MustNewRegexp("some-([^-]+)-([^,]+)"), Action: config.RelabelReplace, Replacement: "${1}", - TargetLabel: model.LabelName("0${3}"), + TargetLabel: "0${3}", }, { SourceLabels: model.LabelNames{"a"}, Regex: config.MustNewRegexp("some-([^-]+)-([^,]+)"), Action: config.RelabelReplace, Replacement: "${1}", - TargetLabel: model.LabelName("-${3}"), + TargetLabel: "-${3}", }, }, output: model.LabelSet{ @@ -353,21 +353,21 @@ func TestRelabel(t *testing.T) { Regex: config.MustNewRegexp("(?:.+,|^)path:(/[^,]+).*"), Action: config.RelabelReplace, Replacement: "${1}", - TargetLabel: model.LabelName("__metrics_path__"), + TargetLabel: "__metrics_path__", }, { SourceLabels: model.LabelNames{"__meta_sd_tags"}, Regex: config.MustNewRegexp("(?:.+,|^)job:([^,]+).*"), Action: config.RelabelReplace, Replacement: "${1}", - TargetLabel: model.LabelName("job"), + TargetLabel: "job", }, { SourceLabels: model.LabelNames{"__meta_sd_tags"}, Regex: config.MustNewRegexp("(?:.+,|^)label:([^=]+)=([^,]+).*"), Action: config.RelabelReplace, Replacement: "${2}", - TargetLabel: model.LabelName("${1}"), + TargetLabel: "${1}", }, }, output: model.LabelSet{