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)
This commit is contained in:
Matias Bordese 2024-09-04 14:47:09 -03:00 committed by GitHub
parent de40bbbc6a
commit 0fa3522db7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 75 additions and 14 deletions

View file

@ -95,6 +95,8 @@ Grafana OnCall enhances Jinja with additional functions:
- `regex_replace`: Performs a regex find and replace
- `regex_match`: Performs a regex match, returns `True` or `False`
- Usage example: `{{ payload.ruleName | regex_match(".*") }}`
- `regex_search`: Performs a regex search, returns `True` or `False`
- Usage example: `{{ payload.message | regex_search("Severity: (High|Critical)") }}`
- `b64decode`: Performs a base64 string decode
- Usage example: `{{ payload.data | b64decode }}`
- `parse_json`:Parses a JSON string to an object

View file

@ -1,11 +1,13 @@
import base64
import json
import re
from datetime import datetime
import regex
from django.utils.dateparse import parse_datetime
from pytz import timezone
REGEX_TIMEOUT = 2
def datetimeparse(value, format="%H:%M / %d-%m-%Y"):
try:
@ -52,22 +54,22 @@ def json_dumps(value):
def regex_replace(value, find, replace):
try:
return re.sub(find, replace, value)
except (ValueError, AttributeError, TypeError):
return regex.sub(find, replace, value, timeout=REGEX_TIMEOUT)
except (ValueError, AttributeError, TypeError, TimeoutError):
return None
def regex_match(pattern, value):
try:
return bool(re.match(value, pattern))
except (ValueError, AttributeError, TypeError):
return bool(regex.match(value, pattern, timeout=REGEX_TIMEOUT))
except (ValueError, AttributeError, TypeError, TimeoutError):
return None
def regex_search(pattern, value):
try:
return bool(re.search(value, pattern))
except (ValueError, AttributeError, TypeError):
return bool(regex.search(value, pattern, timeout=REGEX_TIMEOUT))
except (ValueError, AttributeError, TypeError, TimeoutError):
return None

View file

@ -15,6 +15,38 @@ from common.jinja_templater.apply_jinja_template import (
templated_value_is_truthy,
)
EMAIL_SAMPLE_PAYLOAD = {
"subject": "[Reminder] Review GKE getServerConfig API permission changes",
"message": "Hello Google Kubernetes Customer,\r\n"
"\r\n"
"Were writing to remind you that starting October 22, 2024, "
"the \r\n"
"getServerConfig API for Google Kubernetes Engine (GKE) will "
"enforce \r\n"
"Identity and Access Management (IAM) container.clusters.list "
"checks. This \r\n"
"change follows a series of security improvements as IAM \r\n"
"container.clusters.list permissions are being enforced across "
"the \r\n"
"getServerConfig API.\r\n"
"\r\n"
"Weve provided additional information below to guide you through "
"this \r\n"
"change.\r\n"
"\r\n"
"What you need to know\r\n"
"\r\n"
"The current implementation doesnt apply a specific permissions "
"check via \r\n"
"getServerConfig API. After this change goes into effect for the "
"Google \r\n"
"Kubernetes Engine API getServerConfig, only authorized users with "
"the \r\n"
"container.clusters.list permissions will be able to call the \r\n"
"GetServerConfig.\r\n",
"sender": "someone@somewhere.dev",
}
def test_apply_jinja_template():
payload = {"name": "test"}
@ -127,25 +159,49 @@ def test_apply_jinja_template_json_dumps():
assert result == expected
@pytest.mark.filterwarnings("ignore:::jinja2.*") # ignore regex escape sequence warning
def test_apply_jinja_template_regex_match():
payload = {"name": "test"}
payload = {
"name": "test",
"message": json.dumps(EMAIL_SAMPLE_PAYLOAD),
}
assert apply_jinja_template("{{ payload.name | regex_match('.*') }}", payload) == "True"
assert apply_jinja_template("{{ payload.name | regex_match('tes') }}", payload) == "True"
assert apply_jinja_template("{{ payload.name | regex_match('test1') }}", payload) == "False"
# check for timeouts
with patch("common.jinja_templater.filters.REGEX_TIMEOUT", 1):
assert (
apply_jinja_template(
"{{ payload.message | regex_match('(.|\\s)+Severity(.|\\s){2}High(.|\\s)+') }}", payload
)
== "False"
)
# Check that exception is raised when regex is invalid
with pytest.raises(JinjaTemplateError):
apply_jinja_template("{{ payload.name | regex_match('*') }}", payload)
@pytest.mark.filterwarnings("ignore:::jinja2.*") # ignore regex escape sequence warning
def test_apply_jinja_template_regex_search():
payload = {"name": "test"}
payload = {
"name": "test",
"message": json.dumps(EMAIL_SAMPLE_PAYLOAD),
}
assert apply_jinja_template("{{ payload.name | regex_search('.*') }}", payload) == "True"
assert apply_jinja_template("{{ payload.name | regex_search('tes') }}", payload) == "True"
assert apply_jinja_template("{{ payload.name | regex_search('est') }}", payload) == "True"
assert apply_jinja_template("{{ payload.name | regex_search('test1') }}", payload) == "False"
# check for timeouts
with patch("common.jinja_templater.filters.REGEX_TIMEOUT", 1):
assert (
apply_jinja_template(
"{{ payload.message | regex_search('(.|\\s)+Severity(.|\\s){2}High(.|\\s)+') }}", payload
)
== "False"
)
# Check that exception is raised when regex is invalid
with pytest.raises(JinjaTemplateError):

View file

@ -71,6 +71,7 @@ module = [
"polymorphic.*",
"pyroscope.*",
"ratelimit.*",
"regex.*",
"recurring_ical_events.*",
"rest_polymorphic.*",
"slackclient.*",

View file

@ -52,7 +52,7 @@ PyMySQL==1.1.1
python-telegram-bot==13.13
recurring-ical-events==2.1.0
redis==5.0.1
regex==2021.11.2
regex==2024.7.24
requests==2.32.3
slack-export-viewer==1.1.4
slack_sdk==3.21.3

View file

@ -393,7 +393,7 @@ referencing==0.33.0
# via
# jsonschema
# jsonschema-specifications
regex==2021.11.2
regex==2024.7.24
# via -r engine/requirements.in
requests==2.32.3
# via

View file

@ -21,7 +21,7 @@ export const groupingTemplateCheatSheet: CheatSheetInterface = {
name: 'Additional variables and functions',
listItems: [
{ listItemName: 'time(), datetimeformat, iso8601_to_time' },
{ listItemName: 'regex_replace, regex_match' },
{ listItemName: 'regex_replace, regex_match, regex_search' },
],
},
{
@ -86,7 +86,7 @@ export const genericTemplateCheatSheet: CheatSheetInterface = {
{ listItemName: 'payload, grafana_oncall_link, grafana_oncall_incident_id, integration_name, source_link' },
{ listItemName: 'time(), datetimeformat, datetimeformat_as_timezone, datetimeparse, iso8601_to_time' },
{ listItemName: 'to_pretty_json' },
{ listItemName: 'regex_replace, regex_match' },
{ listItemName: 'regex_replace, regex_match, regex_search' },
{ listItemName: 'b64decode' },
],
},
@ -143,7 +143,7 @@ export const slackMessageTemplateCheatSheet: CheatSheetInterface = {
{ listItemName: 'payload, grafana_oncall_link, grafana_oncall_incident_id, integration_name, source_link' },
{ listItemName: 'time(), datetimeformat, iso8601_to_time' },
{ listItemName: 'to_pretty_json' },
{ listItemName: 'regex_replace, regex_match' },
{ listItemName: 'regex_replace, regex_match, regex_search' },
],
},
{