-
Notifications
You must be signed in to change notification settings - Fork 304
ROB-0000 add labels to robusta alerts using subject information #2065
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
RoiGlinik
wants to merge
5
commits into
master
Choose a base branch
from
ROB-0000-bettson-add-label-to-robusta-alerts
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
03e87b4
add a way to inject labels to robusta alert by resource info
RoiGlinik 46c2a3f
add labels enrich
RoiGlinik cdbc0c1
Merge branch 'master' into ROB-0000-bettson-add-label-to-robusta-alerts
RoiGlinik 097eea5
Merge branch 'master' into ROB-0000-bettson-add-label-to-robusta-alerts
RoiGlinik 81a7207
fix pr review
RoiGlinik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"sessionId":"0776f672-ce66-4052-b399-2ac0d76a9665","pid":91166,"procStart":"Thu May 7 08:09:44 2026","acquiredAt":1778142475272} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,216 @@ | ||
| from robusta.core.model.events import ExecutionBaseEvent | ||
| from robusta.core.reporting.base import Finding, FindingSeverity, FindingSubject | ||
| from robusta.core.reporting.consts import FindingSubjectType | ||
| from playbooks.robusta_playbooks.common_actions import FindingLabelRule, FindingOverrides, customise_finding | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Helpers | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| def _make_event(subject: FindingSubject) -> ExecutionBaseEvent: | ||
| finding = Finding( | ||
| title="Test Finding", | ||
| aggregation_key="TestKey", | ||
| severity=FindingSeverity.HIGH, | ||
| subject=subject, | ||
| ) | ||
| event = ExecutionBaseEvent(named_sinks=["default"]) | ||
| event.sink_findings["default"].append(finding) | ||
| event.get_subject = lambda: subject | ||
| return event | ||
|
|
||
|
|
||
| def _finding(event: ExecutionBaseEvent) -> Finding: | ||
| return event.sink_findings["default"][0] | ||
|
|
||
|
|
||
| def _run(subject: FindingSubject, rules: list) -> Finding: | ||
| event = _make_event(subject) | ||
| params = FindingOverrides(finding_label_rules=[FindingLabelRule(**r) for r in rules]) | ||
| customise_finding(event, params) | ||
| return _finding(event) | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Basic match / no-match | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| def test_namespace_match_sets_label(): | ||
| subject = FindingSubject(name="pod", namespace="infra-monitoring") | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "infra-.*", "target_label": "team", "replacement": "infra-team"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "infra-team" | ||
|
|
||
|
|
||
| def test_namespace_no_match_leaves_label_absent(): | ||
| subject = FindingSubject(name="pod", namespace="app-prod") | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "infra-.*", "target_label": "team", "replacement": "infra-team"}, | ||
| ]) | ||
| assert "team" not in finding.subject.labels | ||
|
|
||
|
|
||
| def test_alternation_regex_matches_second_branch(): | ||
| subject = FindingSubject(name="pod", namespace="kube-system") | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "infra-.*|kube-system", "target_label": "team", "replacement": "infra-team"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "infra-team" | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Capture-group replacement | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| def test_capture_group_replacement(): | ||
| subject = FindingSubject(name="pod", namespace="team-backend") | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "team-(.*)", "target_label": "team", "replacement": "$1"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "backend" | ||
|
|
||
|
|
||
| def test_multiple_capture_groups(): | ||
| subject = FindingSubject(name="pod", namespace="eu-backend-prod") | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "(\\w+)-(\\w+)-(\\w+)", "target_label": "env", "replacement": "$3-$1"}, | ||
| ]) | ||
| assert finding.subject.labels["env"] == "prod-eu" | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Source: name, kind, node | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| def test_source_from_name(): | ||
| subject = FindingSubject(name="payments-worker", namespace="default") | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["name"], "regex": "payments-.*", "target_label": "team", "replacement": "payments"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "payments" | ||
|
|
||
|
|
||
| def test_source_from_kind(): | ||
| subject = FindingSubject(name="pod", namespace="default", subject_type=FindingSubjectType.TYPE_POD) | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["kind"], "regex": "pod", "target_label": "resource_type", "replacement": "pod"}, | ||
| ]) | ||
| assert finding.subject.labels["resource_type"] == "pod" | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Source: pod labels, annotations, namespace_labels | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| def test_source_from_pod_label(): | ||
| subject = FindingSubject(name="pod", namespace="default", labels={"app": "payments"}) | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["labels.app"], "regex": "payments", "target_label": "team", "replacement": "payments-team"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "payments-team" | ||
|
|
||
|
|
||
| def test_source_from_annotation(): | ||
| subject = FindingSubject(name="pod", namespace="default", annotations={"owner": "platform"}) | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["annotations.owner"], "regex": "platform", "target_label": "team", "replacement": "platform-team"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "platform-team" | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Multiple source_fields (concatenation) | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| def test_multiple_source_fields_concatenated(): | ||
| subject = FindingSubject(name="pod", namespace="eu", labels={"env": "prod"}) | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace", "labels.env"], "regex": "eu;prod", "target_label": "region", "replacement": "eu-prod"}, | ||
| ]) | ||
| assert finding.subject.labels["region"] == "eu-prod" | ||
|
|
||
|
|
||
| def test_custom_separator(): | ||
| subject = FindingSubject(name="pod", namespace="eu", labels={"env": "prod"}) | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace", "labels.env"], "separator": "/", "regex": "eu/prod", "target_label": "region", "replacement": "eu-prod"}, | ||
| ]) | ||
| assert finding.subject.labels["region"] == "eu-prod" | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Multiple rules (ordering & overwrite) | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| def test_multiple_rules_both_match(): | ||
| subject = FindingSubject(name="pod", namespace="infra-db") | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "infra-.*", "target_label": "team", "replacement": "infra"}, | ||
| {"source_fields": ["namespace"], "regex": "infra-.*", "target_label": "cost_center", "replacement": "cc-infra"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "infra" | ||
| assert finding.subject.labels["cost_center"] == "cc-infra" | ||
|
|
||
|
|
||
| def test_later_rule_overwrites_earlier(): | ||
| subject = FindingSubject(name="pod", namespace="infra-db") | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "infra-.*", "target_label": "team", "replacement": "first"}, | ||
| {"source_fields": ["namespace"], "regex": "infra-db", "target_label": "team", "replacement": "second"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "second" | ||
|
|
||
|
|
||
| def test_only_matching_rules_apply(): | ||
| subject = FindingSubject(name="pod", namespace="app-prod") | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "infra-.*", "target_label": "team", "replacement": "infra"}, | ||
| {"source_fields": ["namespace"], "regex": "app-.*", "target_label": "team", "replacement": "app"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "app" | ||
|
|
||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Edge cases | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
| def test_no_namespace_does_not_crash(): | ||
| subject = FindingSubject(name="pod", namespace=None) | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "infra-.*", "target_label": "team", "replacement": "infra"}, | ||
| ]) | ||
| assert "team" not in finding.subject.labels | ||
|
|
||
|
|
||
| def test_missing_source_label_key_treated_as_empty(): | ||
| subject = FindingSubject(name="pod", namespace="default") | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["labels.nonexistent"], "regex": "", "target_label": "team", "replacement": "x"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "x" | ||
|
|
||
|
|
||
| def test_existing_pod_label_not_overwritten_when_no_match(): | ||
| subject = FindingSubject(name="pod", namespace="app-prod", labels={"team": "original"}) | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "infra-.*", "target_label": "team", "replacement": "infra"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "original" | ||
|
|
||
|
|
||
| def test_no_rules_is_noop(): | ||
| subject = FindingSubject(name="pod", namespace="infra-x", labels={"team": "original"}) | ||
| event = _make_event(subject) | ||
| customise_finding(event, FindingOverrides()) | ||
| assert _finding(event).subject.labels["team"] == "original" | ||
|
|
||
|
|
||
| def test_invalid_regex_is_skipped_and_does_not_crash(): | ||
| subject = FindingSubject(name="pod", namespace="infra-db", labels={"team": "original"}) | ||
| finding = _run(subject, [ | ||
| {"source_fields": ["namespace"], "regex": "infra-[", "target_label": "team", "replacement": "broken"}, | ||
| {"source_fields": ["namespace"], "regex": "infra-.*", "target_label": "team", "replacement": "infra"}, | ||
| ]) | ||
| assert finding.subject.labels["team"] == "infra" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.