Skip to content
This repository was archived by the owner on Mar 24, 2026. It is now read-only.

Commit 0fa3522

Browse files
authored
Add doc references to regex_search jinja filter (#4973)
This filter wasn't listed in cheatsheet or docs, but it is usually more flexible/simpler than `regex_match` if trying to search for a substring in payload. Also setup a [timeout](https://github.com/mrabarnett/mrab-regex?tab=readme-ov-file#timeout) when regex searching/matching/replacing. Related to [this issue](https://docs.google.com/document/d/1gESMLdbJSnLnSnK7Nhp7DvJ7f10qZXsEm5GrgBc5RqQ/edit)
1 parent de40bbb commit 0fa3522

7 files changed

Lines changed: 75 additions & 14 deletions

File tree

docs/sources/configure/jinja2-templating/advanced-templates/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ Grafana OnCall enhances Jinja with additional functions:
9595
- `regex_replace`: Performs a regex find and replace
9696
- `regex_match`: Performs a regex match, returns `True` or `False`
9797
- Usage example: `{{ payload.ruleName | regex_match(".*") }}`
98+
- `regex_search`: Performs a regex search, returns `True` or `False`
99+
- Usage example: `{{ payload.message | regex_search("Severity: (High|Critical)") }}`
98100
- `b64decode`: Performs a base64 string decode
99101
- Usage example: `{{ payload.data | b64decode }}`
100102
- `parse_json`:Parses a JSON string to an object

engine/common/jinja_templater/filters.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import base64
22
import json
3-
import re
43
from datetime import datetime
54

5+
import regex
66
from django.utils.dateparse import parse_datetime
77
from pytz import timezone
88

9+
REGEX_TIMEOUT = 2
10+
911

1012
def datetimeparse(value, format="%H:%M / %d-%m-%Y"):
1113
try:
@@ -52,22 +54,22 @@ def json_dumps(value):
5254

5355
def regex_replace(value, find, replace):
5456
try:
55-
return re.sub(find, replace, value)
56-
except (ValueError, AttributeError, TypeError):
57+
return regex.sub(find, replace, value, timeout=REGEX_TIMEOUT)
58+
except (ValueError, AttributeError, TypeError, TimeoutError):
5759
return None
5860

5961

6062
def regex_match(pattern, value):
6163
try:
62-
return bool(re.match(value, pattern))
63-
except (ValueError, AttributeError, TypeError):
64+
return bool(regex.match(value, pattern, timeout=REGEX_TIMEOUT))
65+
except (ValueError, AttributeError, TypeError, TimeoutError):
6466
return None
6567

6668

6769
def regex_search(pattern, value):
6870
try:
69-
return bool(re.search(value, pattern))
70-
except (ValueError, AttributeError, TypeError):
71+
return bool(regex.search(value, pattern, timeout=REGEX_TIMEOUT))
72+
except (ValueError, AttributeError, TypeError, TimeoutError):
7173
return None
7274

7375

engine/common/tests/test_apply_jinja_template.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,38 @@
1515
templated_value_is_truthy,
1616
)
1717

18+
EMAIL_SAMPLE_PAYLOAD = {
19+
"subject": "[Reminder] Review GKE getServerConfig API permission changes",
20+
"message": "Hello Google Kubernetes Customer,\r\n"
21+
"\r\n"
22+
"We’re writing to remind you that starting October 22, 2024, "
23+
"the \r\n"
24+
"getServerConfig API for Google Kubernetes Engine (GKE) will "
25+
"enforce \r\n"
26+
"Identity and Access Management (IAM) container.clusters.list "
27+
"checks. This \r\n"
28+
"change follows a series of security improvements as IAM \r\n"
29+
"container.clusters.list permissions are being enforced across "
30+
"the \r\n"
31+
"getServerConfig API.\r\n"
32+
"\r\n"
33+
"We’ve provided additional information below to guide you through "
34+
"this \r\n"
35+
"change.\r\n"
36+
"\r\n"
37+
"What you need to know\r\n"
38+
"\r\n"
39+
"The current implementation doesn’t apply a specific permissions "
40+
"check via \r\n"
41+
"getServerConfig API. After this change goes into effect for the "
42+
"Google \r\n"
43+
"Kubernetes Engine API getServerConfig, only authorized users with "
44+
"the \r\n"
45+
"container.clusters.list permissions will be able to call the \r\n"
46+
"GetServerConfig.\r\n",
47+
"sender": "someone@somewhere.dev",
48+
}
49+
1850

1951
def test_apply_jinja_template():
2052
payload = {"name": "test"}
@@ -127,25 +159,49 @@ def test_apply_jinja_template_json_dumps():
127159
assert result == expected
128160

129161

162+
@pytest.mark.filterwarnings("ignore:::jinja2.*") # ignore regex escape sequence warning
130163
def test_apply_jinja_template_regex_match():
131-
payload = {"name": "test"}
164+
payload = {
165+
"name": "test",
166+
"message": json.dumps(EMAIL_SAMPLE_PAYLOAD),
167+
}
132168

133169
assert apply_jinja_template("{{ payload.name | regex_match('.*') }}", payload) == "True"
134170
assert apply_jinja_template("{{ payload.name | regex_match('tes') }}", payload) == "True"
135171
assert apply_jinja_template("{{ payload.name | regex_match('test1') }}", payload) == "False"
172+
# check for timeouts
173+
with patch("common.jinja_templater.filters.REGEX_TIMEOUT", 1):
174+
assert (
175+
apply_jinja_template(
176+
"{{ payload.message | regex_match('(.|\\s)+Severity(.|\\s){2}High(.|\\s)+') }}", payload
177+
)
178+
== "False"
179+
)
136180

137181
# Check that exception is raised when regex is invalid
138182
with pytest.raises(JinjaTemplateError):
139183
apply_jinja_template("{{ payload.name | regex_match('*') }}", payload)
140184

141185

186+
@pytest.mark.filterwarnings("ignore:::jinja2.*") # ignore regex escape sequence warning
142187
def test_apply_jinja_template_regex_search():
143-
payload = {"name": "test"}
188+
payload = {
189+
"name": "test",
190+
"message": json.dumps(EMAIL_SAMPLE_PAYLOAD),
191+
}
144192

145193
assert apply_jinja_template("{{ payload.name | regex_search('.*') }}", payload) == "True"
146194
assert apply_jinja_template("{{ payload.name | regex_search('tes') }}", payload) == "True"
147195
assert apply_jinja_template("{{ payload.name | regex_search('est') }}", payload) == "True"
148196
assert apply_jinja_template("{{ payload.name | regex_search('test1') }}", payload) == "False"
197+
# check for timeouts
198+
with patch("common.jinja_templater.filters.REGEX_TIMEOUT", 1):
199+
assert (
200+
apply_jinja_template(
201+
"{{ payload.message | regex_search('(.|\\s)+Severity(.|\\s){2}High(.|\\s)+') }}", payload
202+
)
203+
== "False"
204+
)
149205

150206
# Check that exception is raised when regex is invalid
151207
with pytest.raises(JinjaTemplateError):

engine/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ module = [
7171
"polymorphic.*",
7272
"pyroscope.*",
7373
"ratelimit.*",
74+
"regex.*",
7475
"recurring_ical_events.*",
7576
"rest_polymorphic.*",
7677
"slackclient.*",

engine/requirements.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ PyMySQL==1.1.1
5252
python-telegram-bot==13.13
5353
recurring-ical-events==2.1.0
5454
redis==5.0.1
55-
regex==2021.11.2
55+
regex==2024.7.24
5656
requests==2.32.3
5757
slack-export-viewer==1.1.4
5858
slack_sdk==3.21.3

engine/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ referencing==0.33.0
393393
# via
394394
# jsonschema
395395
# jsonschema-specifications
396-
regex==2021.11.2
396+
regex==2024.7.24
397397
# via -r engine/requirements.in
398398
requests==2.32.3
399399
# via

grafana-plugin/src/components/CheatSheet/CheatSheet.config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const groupingTemplateCheatSheet: CheatSheetInterface = {
2121
name: 'Additional variables and functions',
2222
listItems: [
2323
{ listItemName: 'time(), datetimeformat, iso8601_to_time' },
24-
{ listItemName: 'regex_replace, regex_match' },
24+
{ listItemName: 'regex_replace, regex_match, regex_search' },
2525
],
2626
},
2727
{
@@ -86,7 +86,7 @@ export const genericTemplateCheatSheet: CheatSheetInterface = {
8686
{ listItemName: 'payload, grafana_oncall_link, grafana_oncall_incident_id, integration_name, source_link' },
8787
{ listItemName: 'time(), datetimeformat, datetimeformat_as_timezone, datetimeparse, iso8601_to_time' },
8888
{ listItemName: 'to_pretty_json' },
89-
{ listItemName: 'regex_replace, regex_match' },
89+
{ listItemName: 'regex_replace, regex_match, regex_search' },
9090
{ listItemName: 'b64decode' },
9191
],
9292
},
@@ -143,7 +143,7 @@ export const slackMessageTemplateCheatSheet: CheatSheetInterface = {
143143
{ listItemName: 'payload, grafana_oncall_link, grafana_oncall_incident_id, integration_name, source_link' },
144144
{ listItemName: 'time(), datetimeformat, iso8601_to_time' },
145145
{ listItemName: 'to_pretty_json' },
146-
{ listItemName: 'regex_replace, regex_match' },
146+
{ listItemName: 'regex_replace, regex_match, regex_search' },
147147
],
148148
},
149149
{

0 commit comments

Comments
 (0)