Repository: VictoriaMetrics/VictoriaMetrics
Pull request #2665 (2022-05-31 11:52:53)

promrelabel: add support of `lowercase` and `uppercase` relabeling actions
https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2664
Signed-off-by: hagen1778 <roman@victoriametrics.com>


Patch:
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 149741746..f85640d30 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -15,6 +15,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
 
 ## tip
 
+* FEATURE: add support of `lowercase` and `uppercase` relabeling actions for compatibility reasons. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2664).
 * FEATURE: support query tracing, which allows determining bottlenecks during query processing. See [these docs](https://docs.victoriametrics.com/Single-server-VictoriaMetrics.html#query-tracing) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1403).
 * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): remove dependency on Internet access in `http://vmagent:8429/targets` page. Previously the page layout was broken without Internet access. See [shis issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2594).
 * FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): remove dependency on Internet access in [web API pages](https://docs.victoriametrics.com/vmalert.html#web). Previously the functionality and the layout of these pages was broken without Internet access. See [shis issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2594).
diff --git a/lib/promrelabel/config.go b/lib/promrelabel/config.go
index 094904aa8..aefa94df4 100644
--- a/lib/promrelabel/config.go
+++ b/lib/promrelabel/config.go
@@ -260,6 +260,13 @@ func parseRelabelConfig(rc *RelabelConfig) (*parsedRelabelConfig, error) {
 		}
 		sourceLabels = []string{"__name__"}
 		action = "drop"
+	case "uppercase", "lowercase":
+		if len(sourceLabels) == 0 {
+			return nil, fmt.Errorf("missing `source_labels` for `action=%s`", action)
+		}
+		if targetLabel == "" {
+			return nil, fmt.Errorf("missing `target_label` for `action=%s`", action)
+		}
 	case "labelmap":
 	case "labelmap_all":
 	case "labeldrop":
diff --git a/lib/promrelabel/relabel.go b/lib/promrelabel/relabel.go
index 7950a6e6f..ccf2efc18 100644
--- a/lib/promrelabel/relabel.go
+++ b/lib/promrelabel/relabel.go
@@ -184,7 +184,7 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
 		relabelBufPool.Put(bb)
 		return setLabelValue(labels, labelsOffset, nameStr, valueStr)
 	case "replace_all":
-		// Replace all the occurences of `regex` at `source_labels` joined with `separator` with the `replacement`
+		// Replace all the occurrences of `regex` at `source_labels` joined with `separator` with the `replacement`
 		// and store the result at `target_label`
 		bb := relabelBufPool.Get()
 		bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
@@ -300,6 +300,20 @@ func (prc *parsedRelabelConfig) apply(labels []prompbmarshal.Label, labelsOffset
 			}
 		}
 		return dst
+	case "uppercase":
+		bb := relabelBufPool.Get()
+		bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
+		value := strings.ToUpper(string(bb.B))
+		relabelBufPool.Put(bb)
+		labels = setLabelValue(labels, labelsOffset, prc.TargetLabel, value)
+		return labels
+	case "lowercase":
+		bb := relabelBufPool.Get()
+		bb.B = concatLabelValues(bb.B[:0], src, prc.SourceLabels, prc.Separator)
+		value := strings.ToLower(string(bb.B))
+		relabelBufPool.Put(bb)
+		labels = setLabelValue(labels, labelsOffset, prc.TargetLabel, value)
+		return labels
 	default:
 		logger.Panicf("BUG: unknown `action`: %q", prc.Action)
 		return labels


Test patch:
diff --git a/lib/promrelabel/config_test.go b/lib/promrelabel/config_test.go
index f460a2183..45dbf1e87 100644
--- a/lib/promrelabel/config_test.go
+++ b/lib/promrelabel/config_test.go
@@ -53,8 +53,8 @@ func TestLoadRelabelConfigsSuccess(t *testing.T) {
 	if err != nil {
 		t.Fatalf("cannot load relabel configs from %q: %s", path, err)
 	}
-	if n := pcs.Len(); n != 12 {
-		t.Fatalf("unexpected number of relabel configs loaded from %q; got %d; want %d", path, n, 12)
+	if n := pcs.Len(); n != 14 {
+		t.Fatalf("unexpected number of relabel configs loaded from %q; got %d; want %d", path, n, 14)
 	}
 }
 
diff --git a/lib/promrelabel/relabel_test.go b/lib/promrelabel/relabel_test.go
index bbc176681..abc4e12c8 100644
--- a/lib/promrelabel/relabel_test.go
+++ b/lib/promrelabel/relabel_test.go
@@ -1580,6 +1580,63 @@ func TestApplyRelabelConfigs(t *testing.T) {
 			},
 		})
 	})
+
+	t.Run("upper-lower-case", func(t *testing.T) {
+		f(`
+- action: uppercase
+  source_labels: ["foo"]
+  target_label: foo
+`, []prompbmarshal.Label{
+			{
+				Name:  "foo",
+				Value: "bar",
+			},
+		}, true, []prompbmarshal.Label{
+			{
+				Name:  "foo",
+				Value: "BAR",
+			},
+		})
+		f(`
+- action: lowercase
+  source_labels: ["foo", "bar"]
+  target_label: baz
+- action: labeldrop
+  regex: foo|bar
+`, []prompbmarshal.Label{
+			{
+				Name:  "foo",
+				Value: "BaR",
+			},
+			{
+				Name:  "bar",
+				Value: "fOO",
+			},
+		}, true, []prompbmarshal.Label{
+			{
+				Name:  "baz",
+				Value: "bar;foo",
+			},
+		})
+	})
+	f(`
+- action: lowercase
+  source_labels: ["foo"]
+  target_label: baz
+- action: uppercase
+  source_labels: ["bar"]
+  target_label: baz
+`, []prompbmarshal.Label{
+		{
+			Name:  "qux",
+			Value: "quux",
+		},
+	}, true, []prompbmarshal.Label{
+		{
+			Name:  "qux",
+			Value: "quux",
+		},
+	})
 }
 
 func TestFinalizeLabels(t *testing.T) {
diff --git a/lib/promrelabel/testdata/relabel_configs_valid.yml b/lib/promrelabel/testdata/relabel_configs_valid.yml
index 35bb6107f..5cd6b3102 100644
--- a/lib/promrelabel/testdata/relabel_configs_valid.yml
+++ b/lib/promrelabel/testdata/relabel_configs_valid.yml
@@ -32,3 +32,10 @@
   regex: [foo bar baz]
 - action: drop_metrics
   regex: "foo|bar|baz"
+- source_labels: [foo, bar]
+  separator: "-"
+  target_label: __tmp_uppercase
+  action: uppercase
+- source_labels: [__tmp_uppercase]
+  target_label: lower_aaa
+  action: lowercase
\ No newline at end of file