From a6005ebd790ceb6cbd92c188cabc73ac86cae4a7 Mon Sep 17 00:00:00 2001 From: Zois Pagoulatos Date: Wed, 10 Jun 2026 10:30:20 +0200 Subject: [PATCH 1/2] Support '/' and '-' in braced template placeholders Kubernetes label and annotation keys routinely contain '/' (prefix separator) and '-', e.g. app.kubernetes.io/name or com.example/job-count. ExtendedTemplate's idpattern only allows [_a-z0-9.], so a placeholder like ${annotations.com.example/job-count} never matches and is left verbatim in the output of format_event_templated_string. Extend only the braced form via braceidpattern so unbraced placeholders (e.g. $name-suffix) keep their existing behavior. Co-Authored-By: Claude Fable 5 --- src/robusta/utils/parsing.py | 5 +++++ tests/test_parsing.py | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/test_parsing.py diff --git a/src/robusta/utils/parsing.py b/src/robusta/utils/parsing.py index e5c6006d2..aa576017b 100644 --- a/src/robusta/utils/parsing.py +++ b/src/robusta/utils/parsing.py @@ -15,6 +15,11 @@ class ExtendedTemplate(Template): # annotations and labels. Note the pattern is case-insensitive (see the documentation # of string.Template). idpattern = r'(?a:[_a-z][_a-z0-9.]*)' + # Braced placeholders (${...}) additionally allow "-" and "/" so that label and + # annotation keys like "com.example/job-count" can be referenced. This is restricted + # to the braced form to keep unbraced placeholders (e.g. "$name-suffix") behaving + # as before. + braceidpattern = r"(?a:[_a-z][_a-z0-9./-]*)" def format_event_templated_string(subject: Union[FindingSubject, ObjectReference], string_to_substitute) -> str: diff --git a/tests/test_parsing.py b/tests/test_parsing.py new file mode 100644 index 000000000..a12dc847f --- /dev/null +++ b/tests/test_parsing.py @@ -0,0 +1,37 @@ +import pytest + +from robusta.core.reporting import FindingSubject, FindingSubjectType +from robusta.utils.parsing import format_event_templated_string + +SUBJECT = FindingSubject( + name="example-pod-1", + subject_type=FindingSubjectType.TYPE_POD, + namespace="default", + node="node-1", + labels={"app": "example", "app.kubernetes.io/name": "example"}, + annotations={"com.example/job-count": "5", "team": "backend"}, +) + + +@pytest.mark.parametrize( + "template,expected_output", + [ + # basic subject fields, braced and unbraced + ("$name in $namespace", "example-pod-1 in default"), + ("${name} on ${node} (${kind})", "example-pod-1 on node-1 (pod)"), + # unbraced placeholders stop at characters outside the pattern + ("$name-suffix", "example-pod-1-suffix"), + # labels and annotations with plain keys + ("app=${labels.app}", "app=example"), + ("team=${annotations.team}", "team=backend"), + # keys containing "/" and "-" work in braced placeholders + ("count=${annotations.com.example/job-count}", "count=5"), + ("name=${labels.app.kubernetes.io/name}", "name=example"), + # unknown variables resolve to + ("${labels.missing} / ${annotations.com.example/missing}", " / "), + # escaped dollar signs are preserved + ("$$name", "$name"), + ], +) +def test_format_event_templated_string(template: str, expected_output: str): + assert format_event_templated_string(SUBJECT, template) == expected_output From 5d15b916e586ba7ffcf82fc95f7601aeaa0ac5c8 Mon Sep 17 00:00:00 2001 From: Zois Pagoulatos Date: Wed, 10 Jun 2026 10:52:02 +0200 Subject: [PATCH 2/2] Add docstrings to test module and test function --- tests/test_parsing.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index a12dc847f..0e858454a 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -1,3 +1,5 @@ +"""Tests for template placeholder substitution in robusta.utils.parsing.""" + import pytest from robusta.core.reporting import FindingSubject, FindingSubjectType @@ -34,4 +36,5 @@ ], ) def test_format_event_templated_string(template: str, expected_output: str): + """Resolve subject fields, labels and annotations in braced and unbraced placeholders.""" assert format_event_templated_string(SUBJECT, template) == expected_output