Repository: prometheus/prometheus (language: go)

Issue:
Relabeling: Using a labelname in a regex (to use the labelname's value)
### Proposal

In relabeling config, let's say I have saved a value in a label like `__tmp_port`. I then want to match some port in Kubernetes service discovery 

```yaml
  relabel_configs:
    - source_labels: [ __meta_kubernetes_pod_container_port_number ]
      action: keep
      regex: ${__tmp_port}
```

If the value of `__meta_kubernetes_pod_container_port_number` and `__tmp_port` are both `8080`, it will keep this target. It could possibly be used in `replacement` as well.

I would like to be able to use any existing `__` value in the regex, even the ones found from service discovery such as `__meta_kubernetes_pod_container_port_number`.

It solves e.g. https://github.com/prometheus/prometheus/issues/3756


Discussion / hints:

Fix patch:
diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md
index de495eaecb0..756e51d3f2a 100644
--- a/docs/configuration/configuration.md
+++ b/docs/configuration/configuration.md
@@ -2857,6 +2857,8 @@ anchored on both ends. To un-anchor the regex, use `.*<regex>.*`.
 * `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`.
+* `keepequal`: Drop targets for which the concatenated `source_labels` do not match `target_label`.
+* `dropequal`: Drop targets for which the concatenated `source_labels` do match `target_label`.
 * `hashmod`: Set `target_label` to the `modulus` of a hash of the concatenated `source_labels`.
 * `labelmap`: Match `regex` against all source label names, not just those specified in `source_labels`. Then
    copy the values of the matching labels  to label names given by `replacement` with match
diff --git a/model/relabel/relabel.go b/model/relabel/relabel.go
index e0d7f6ddf54..c731f6e0d3b 100644
--- a/model/relabel/relabel.go
+++ b/model/relabel/relabel.go
@@ -45,6 +45,10 @@ const (
 	Keep Action = "keep"
 	// Drop drops targets for which the input does match the regex.
 	Drop Action = "drop"
+	// KeepEqual drops targets for which the input does not match the target.
+	KeepEqual Action = "keepequal"
+	// Drop drops targets for which the input does match the target.
+	DropEqual Action = "dropequal"
 	// HashMod sets a label to the modulus of a hash of labels.
 	HashMod Action = "hashmod"
 	// LabelMap copies labels to other labelnames based on a regex.
@@ -66,7 +70,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, Lowercase, Uppercase:
+	case Replace, Keep, Drop, HashMod, LabelMap, LabelDrop, LabelKeep, Lowercase, Uppercase, KeepEqual, DropEqual:
 		*a = act
 		return nil
 	}
@@ -109,13 +113,13 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if c.Modulus == 0 && c.Action == HashMod {
 		return fmt.Errorf("relabel configuration for hashmod requires non-zero modulus")
 	}
-	if (c.Action == Replace || c.Action == HashMod || c.Action == Lowercase || c.Action == Uppercase) && c.TargetLabel == "" {
+	if (c.Action == Replace || c.Action == HashMod || c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.TargetLabel == "" {
 		return fmt.Errorf("relabel configuration for %s action requires 'target_label' value", c.Action)
 	}
-	if (c.Action == Replace || c.Action == Lowercase || c.Action == Uppercase) && !relabelTarget.MatchString(c.TargetLabel) {
+	if (c.Action == Replace || c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && !relabelTarget.MatchString(c.TargetLabel) {
 		return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
 	}
-	if (c.Action == Lowercase || c.Action == Uppercase) && c.Replacement != DefaultRelabelConfig.Replacement {
+	if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.Replacement != DefaultRelabelConfig.Replacement {
 		return fmt.Errorf("'replacement' can not be set for %s action", c.Action)
 	}
 	if c.Action == LabelMap && !relabelTarget.MatchString(c.Replacement) {
@@ -125,6 +129,15 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
 		return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
 	}
 
+	if c.Action == DropEqual || c.Action == KeepEqual {
+		if c.Regex != DefaultRelabelConfig.Regex ||
+			c.Modulus != DefaultRelabelConfig.Modulus ||
+			c.Separator != DefaultRelabelConfig.Separator ||
+			c.Replacement != DefaultRelabelConfig.Replacement {
+			return fmt.Errorf("%s action requires only 'source_labels' and `target_label`, and no other fields", c.Action)
+		}
+	}
+
 	if c.Action == LabelDrop || c.Action == LabelKeep {
 		if c.SourceLabels != nil ||
 			c.TargetLabel != DefaultRelabelConfig.TargetLabel ||
@@ -225,6 +238,14 @@ func relabel(lset labels.Labels, cfg *Config, lb *labels.Builder) labels.Labels
 		if !cfg.Regex.MatchString(val) {
 			return nil
 		}
+	case DropEqual:
+		if lset.Get(cfg.TargetLabel) == val {
+			return nil
+		}
+	case KeepEqual:
+		if lset.Get(cfg.TargetLabel) != val {
+			return nil
+		}
 	case Replace:
 		indexes := cfg.Regex.FindStringSubmatchIndex(val)
 		// If there is no match no replacement must take place.


Test patch:
diff --git a/config/config_test.go b/config/config_test.go
index 1e30ab2999d..ddf65a06824 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -217,26 +217,45 @@ var expectedConf = &Config{
 					Regex:        relabel.MustNewRegexp("(.*)some-[regex]"),
 					Replacement:  "foo-${1}",
 					Action:       relabel.Replace,
-				}, {
+				},
+				{
 					SourceLabels: model.LabelNames{"abc"},
 					TargetLabel:  "cde",
 					Separator:    ";",
 					Regex:        relabel.DefaultRelabelConfig.Regex,
 					Replacement:  relabel.DefaultRelabelConfig.Replacement,
 					Action:       relabel.Replace,
-				}, {
+				},
+				{
 					TargetLabel: "abc",
 					Separator:   ";",
 					Regex:       relabel.DefaultRelabelConfig.Regex,
 					Replacement: "static",
 					Action:      relabel.Replace,
-				}, {
+				},
+				{
 					TargetLabel: "abc",
 					Separator:   ";",
 					Regex:       relabel.MustNewRegexp(""),
 					Replacement: "static",
 					Action:      relabel.Replace,
 				},
+				{
+					SourceLabels: model.LabelNames{"foo"},
+					TargetLabel:  "abc",
+					Action:       relabel.KeepEqual,
+					Regex:        relabel.DefaultRelabelConfig.Regex,
+					Replacement:  relabel.DefaultRelabelConfig.Replacement,
+					Separator:    relabel.DefaultRelabelConfig.Separator,
+				},
+				{
+					SourceLabels: model.LabelNames{"foo"},
+					TargetLabel:  "abc",
+					Action:       relabel.DropEqual,
+					Regex:        relabel.DefaultRelabelConfig.Regex,
+					Replacement:  relabel.DefaultRelabelConfig.Replacement,
+					Separator:    relabel.DefaultRelabelConfig.Separator,
+				},
 			},
 		},
 		{
@@ -1316,6 +1335,22 @@ var expectedErrors = []struct {
 		filename: "labeldrop5.bad.yml",
 		errMsg:   "labeldrop action requires only 'regex', and no other fields",
 	},
+	{
+		filename: "dropequal.bad.yml",
+		errMsg:   "relabel configuration for dropequal action requires 'target_label' value",
+	},
+	{
+		filename: "dropequal1.bad.yml",
+		errMsg:   "dropequal action requires only 'source_labels' and `target_label`, and no other fields",
+	},
+	{
+		filename: "keepequal.bad.yml",
+		errMsg:   "relabel configuration for keepequal action requires 'target_label' value",
+	},
+	{
+		filename: "keepequal1.bad.yml",
+		errMsg:   "keepequal action requires only 'source_labels' and `target_label`, and no other fields",
+	},
 	{
 		filename: "labelmap.bad.yml",
 		errMsg:   "\"l-$1\" is invalid 'replacement' for labelmap action",
diff --git a/config/testdata/conf.good.yml b/config/testdata/conf.good.yml
index da0042b9415..764f1a342b0 100644
--- a/config/testdata/conf.good.yml
+++ b/config/testdata/conf.good.yml
@@ -87,6 +87,12 @@ scrape_configs:
       - regex:
         replacement: static
         target_label: abc
+      - source_labels: [foo]
+        target_label: abc
+        action: keepequal
+      - source_labels: [foo]
+        target_label: abc
+        action: dropequal
 
     authorization:
       credentials_file: valid_token_file
diff --git a/config/testdata/dropequal.bad.yml b/config/testdata/dropequal.bad.yml
new file mode 100644
index 00000000000..32bd1a9641e
--- /dev/null
+++ b/config/testdata/dropequal.bad.yml
@@ -0,0 +1,5 @@
+scrape_configs:
+  - job_name: prometheus
+    relabel_configs:
+      - source_labels: [abcdef]
+        action: dropequal
diff --git a/config/testdata/dropequal1.bad.yml b/config/testdata/dropequal1.bad.yml
new file mode 100644
index 00000000000..b648c0db268
--- /dev/null
+++ b/config/testdata/dropequal1.bad.yml
@@ -0,0 +1,7 @@
+scrape_configs:
+  - job_name: prometheus
+    relabel_configs:
+      - source_labels: [abcdef]
+        action: dropequal
+        regex: foo
+        target_label: bar
diff --git a/config/testdata/keepequal.bad.yml b/config/testdata/keepequal.bad.yml
new file mode 100644
index 00000000000..5f5662177a3
--- /dev/null
+++ b/config/testdata/keepequal.bad.yml
@@ -0,0 +1,5 @@
+scrape_configs:
+  - job_name: prometheus
+    relabel_configs:
+      - source_labels: [abcdef]
+        action: keepequal
diff --git a/config/testdata/keepequal1.bad.yml b/config/testdata/keepequal1.bad.yml
new file mode 100644
index 00000000000..c4628314c69
--- /dev/null
+++ b/config/testdata/keepequal1.bad.yml
@@ -0,0 +1,7 @@
+scrape_configs:
+  - job_name: prometheus
+    relabel_configs:
+      - source_labels: [abcdef]
+        action: keepequal
+        regex: foo
+        target_label: bar
diff --git a/model/relabel/relabel_test.go b/model/relabel/relabel_test.go
index c437e5c1c13..0b0dfd511f0 100644
--- a/model/relabel/relabel_test.go
+++ b/model/relabel/relabel_test.go
@@ -451,6 +451,74 @@ func TestRelabel(t *testing.T) {
 				"foo_uppercase": "BAR123FOO",
 			}),
 		},
+		{
+			input: labels.FromMap(map[string]string{
+				"__tmp_port": "1234",
+				"__port1":    "1234",
+				"__port2":    "5678",
+			}),
+			relabel: []*Config{
+				{
+					SourceLabels: model.LabelNames{"__tmp_port"},
+					Action:       KeepEqual,
+					TargetLabel:  "__port1",
+				},
+			},
+			output: labels.FromMap(map[string]string{
+				"__tmp_port": "1234",
+				"__port1":    "1234",
+				"__port2":    "5678",
+			}),
+		},
+		{
+			input: labels.FromMap(map[string]string{
+				"__tmp_port": "1234",
+				"__port1":    "1234",
+				"__port2":    "5678",
+			}),
+			relabel: []*Config{
+				{
+					SourceLabels: model.LabelNames{"__tmp_port"},
+					Action:       DropEqual,
+					TargetLabel:  "__port1",
+				},
+			},
+			output: nil,
+		},
+		{
+			input: labels.FromMap(map[string]string{
+				"__tmp_port": "1234",
+				"__port1":    "1234",
+				"__port2":    "5678",
+			}),
+			relabel: []*Config{
+				{
+					SourceLabels: model.LabelNames{"__tmp_port"},
+					Action:       DropEqual,
+					TargetLabel:  "__port2",
+				},
+			},
+			output: labels.FromMap(map[string]string{
+				"__tmp_port": "1234",
+				"__port1":    "1234",
+				"__port2":    "5678",
+			}),
+		},
+		{
+			input: labels.FromMap(map[string]string{
+				"__tmp_port": "1234",
+				"__port1":    "1234",
+				"__port2":    "5678",
+			}),
+			relabel: []*Config{
+				{
+					SourceLabels: model.LabelNames{"__tmp_port"},
+					Action:       KeepEqual,
+					TargetLabel:  "__port2",
+				},
+			},
+			output: nil,
+		},
 	}
 
 	for _, test := range tests {