From f1eebc0a99ff94acbbeaddbe1e456c0f53d3defb Mon Sep 17 00:00:00 2001
From: Aliaksandr Valialkin <valyala@victoriametrics.com>
Date: Fri, 30 Sep 2022 08:13:56 +0300
Subject: [PATCH] lib/promrelabel: properly parse regex with escaped $ at the
 end

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3131

Thanks to @dmitryk-dk for the initial fix at https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3179
---
 docs/CHANGELOG.md               |  1 +
 lib/promrelabel/relabel_test.go |  9 +++++++++
 lib/regexutil/regexutil.go      |  2 +-
 lib/regexutil/regexutil_test.go | 19 +++++++++++++++++++
 4 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 8a11cdf62b..c27f3d3fdb 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -36,6 +36,7 @@ The following tip changes can be tested by building VictoriaMetrics components f
 * FEATURE: [vmbackup](https://docs.victoriametrics.com/vmbackup.html) and [vmrestore](https://docs.victoriametrics.com/vmrestore.html): retry GCS operations for up to 3 minutes on temporary failures. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3147).
 
 * BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly encode query params for aws signed requests, use `%20` instead of `+` as api requires. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3171).
+* BUGFIX: [vmagent](https://docs.victoriametrics.com/vmagent.html): properly parse relabel config when regex ending with escaped `$`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3131).
 * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly calculate `rate_over_sum(m[d])` as `sum_over_time(m[d])/d`. Previously the `sum_over_time(m[d])` could be improperly divided by smaller than `d` time range. See [rate_over_sum() docs](https://docs.victoriametrics.com/MetricsQL.html#rate_over_sum) and [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3045).
 * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): properly calculate `increase(m[d])` over slow-changing counters with values smaller than 100. Previously [increase](https://docs.victoriametrics.com/MetricsQL.html#increase) could return unexpectedly big results in this case. See [the related issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/962) and [this pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/3163).
 * BUGFIX: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): properly calculate query results at `vmselect`. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3067). The issue has been introduced in [v1.81.0](https://docs.victoriametrics.com/CHANGELOG.html#v1810).
diff --git a/lib/promrelabel/relabel_test.go b/lib/promrelabel/relabel_test.go
index bc4bb7c3f3..f799e67770 100644
--- a/lib/promrelabel/relabel_test.go
+++ b/lib/promrelabel/relabel_test.go
@@ -682,6 +682,15 @@ func TestApplyRelabelConfigs(t *testing.T) {
   regex: "a(.+)"
 `, `qwe{foo="bar",baz="aaa"}`, true, `qwe{abc="qwe.bar.aa",baz="aaa",foo="bar"}`)
 	})
+	// Check $ at the end of regex - see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3131
+	t.Run("replacement-with-$-at-the-end-of-regex", func(t *testing.T) {
+		f(`
+- target_label: xyz
+  regex: "foo\\$$"
+  replacement: bar
+  source_labels: [xyz]
+`, `metric{xyz="foo$",a="b"}`, true, `metric{a="b",xyz="bar"}`)
+	})
 }
 
 func TestFinalizeLabels(t *testing.T) {
diff --git a/lib/regexutil/regexutil.go b/lib/regexutil/regexutil.go
index ca218098f9..2a28be255d 100644
--- a/lib/regexutil/regexutil.go
+++ b/lib/regexutil/regexutil.go
@@ -12,7 +12,7 @@ func RemoveStartEndAnchors(expr string) string {
 	for strings.HasPrefix(expr, "^") {
 		expr = expr[1:]
 	}
-	for strings.HasSuffix(expr, "$") {
+	for strings.HasSuffix(expr, "$") && !strings.HasSuffix(expr, "\\$") {
 		expr = expr[:len(expr)-1]
 	}
 	return expr
diff --git a/lib/regexutil/regexutil_test.go b/lib/regexutil/regexutil_test.go
index 8755ccc7fe..e45781d25e 100644
--- a/lib/regexutil/regexutil_test.go
+++ b/lib/regexutil/regexutil_test.go
@@ -110,3 +110,22 @@ func TestSimplify(t *testing.T) {
 	// The transformed regexp mustn't match barx
 	f("(foo|bar$)x*", "", "(?:foo|bar$)x*")
 }
+
+func TestRemoveStartEndAnchors(t *testing.T) {
+	f := func(s, resultExpected string) {
+		t.Helper()
+		result := RemoveStartEndAnchors(s)
+		if result != resultExpected {
+			t.Fatalf("unexpected result for RemoveStartEndAnchors(%q); got %q; want %q", s, result, resultExpected)
+		}
+	}
+	f("", "")
+	f("a", "a")
+	f("^^abc", "abc")
+	f("a^b$c", "a^b$c")
+	f("$$abc^", "$$abc^")
+	f("^abc|de$", "abc|de")
+	f("abc\\$", "abc\\$")
+	f("^abc\\$$$", "abc\\$")
+	f("^a\\$b\\$$", "a\\$b\\$")
+}