Skip to content

Commit 7c188ba

Browse files
authored
Merge pull request #95 from GabrielSalla/generalize-plugin-attribute-retrieval
Generalize plugin attribute retrieval
2 parents 22bd185 + a1beb3e commit 7c188ba

File tree

14 files changed

+186
-202
lines changed

14 files changed

+186
-202
lines changed

configs/configs-scalable.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ application_database_settings:
1717
pool_size: 10
1818

1919
application_queue:
20-
type: plugin.aws.sqs
20+
type: plugin.aws.queues.sqs
2121
name: app
2222
url: http://motoserver:5000/123456789012/app
2323
region: us-east-1

docker/docker-compose-scalable.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ services:
3434
retries: 3
3535
start_period: 2s
3636
environment:
37-
CONFIGS_FILE: configs-scalable.yaml
37+
CONFIGS_FILE: configs/configs-scalable.yaml
3838
SAMPLE_SLACK_CHANNEL: C07NCL94SDT
3939
SAMPLE_SLACK_MENTION: U07NFGGMB98
4040
SLACK_WEBSOCKET_ENABLED: true
@@ -67,7 +67,7 @@ services:
6767
retries: 3
6868
start_period: 2s
6969
environment:
70-
CONFIGS_FILE: configs-scalable.yaml
70+
CONFIGS_FILE: configs/configs-scalable.yaml
7171
SAMPLE_SLACK_CHANNEL: C07NCL94SDT
7272
SAMPLE_SLACK_MENTION: U07NFGGMB98
7373
SLACK_WEBSOCKET_ENABLED: true

docs/plugins/aws.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ AWS_APPLICATION_SECRET_ACCESS_KEY=SOME_SECRET_ACCESS_KEY
2626
To use the AWS plugin queue, configure its parameters in the configuration file under the setting `application_queue`.
2727

2828
The queue settings are:
29-
- `type`: The plugin type, which should be `plugin.aws.sqs`.
29+
- `type`: The plugin type, which should be `plugin.aws.queues.sqs`.
3030
- `name`: The queue name.
3131
- `url`: The queue URL.
3232
- `region`: The region where the queue is located. If not configured, the region will be taken from the environment variables for the provided credentials.
@@ -37,7 +37,7 @@ The queue settings are:
3737
Suggested configuration for local development or testing:
3838
```yaml
3939
application_queue:
40-
type: plugin.aws.sqs
40+
type: plugin.aws.queues.sqs
4141
name: app
4242
url: http://motoserver:5000/123456789012/app
4343
region: us-east-1

resources/kubernetes_template/config_map.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ data:
2323
pool_size: 10
2424
2525
application_queue:
26-
type: plugin.aws.sqs
26+
type: plugin.aws.queues.sqs
2727
name: app
2828
url: http://motoserver:5000/123456789012/app
2929
region: us-east-1

src/components/executor/request_handler.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
import prometheus_client
88
from pydantic import ValidationError
99

10-
import plugins
1110
import registry as registry
1211
from base_exception import BaseSentinelaException
1312
from configs import configs
1413
from data_models.request_payload import RequestPayload
1514
from models import Alert, Issue
15+
from plugins.attribute_select import get_plugin_attribute
1616

1717
_logger = logging.getLogger("request_handler")
1818

@@ -88,21 +88,10 @@ async def issue_drop(message_payload: RequestPayload) -> None:
8888
def get_action(action_name: str) -> Callable[[RequestPayload], Coroutine[Any, Any, None]] | None:
8989
"""Get the action function by its name, checking if it is a plugin action"""
9090
if action_name.startswith("plugin."):
91-
plugin_name, action_name = action_name.split(".")[1:3]
92-
93-
plugin = plugins.loaded_plugins.get(plugin_name)
94-
if plugin is None:
95-
_logger.warning(f"Plugin '{plugin_name}' unknown")
96-
return None
97-
98-
plugin_actions = getattr(plugin, "actions", None)
99-
if plugin_actions is None:
100-
_logger.warning(f"Plugin '{plugin_name}' doesn't have actions")
101-
return None
102-
103-
action = getattr(plugin_actions, action_name, None)
104-
if action is None:
105-
_logger.warning(f"Action '{plugin_name}.{action_name}' unknown")
91+
try:
92+
action = get_plugin_attribute(action_name)
93+
except ValueError as e:
94+
_logger.warning(e.args[0])
10695
return None
10796

10897
return cast(Callable[[RequestPayload], Coroutine[Any, Any, None]], action)

src/message_queue/__init__.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Any
22

33
from configs import configs
4-
from plugins.queue_select import get_plugin_queue
4+
from plugins.attribute_select import get_plugin_attribute
55

66
from .internal_queue import InternalQueue
77
from .protocols import Message, Queue
@@ -18,7 +18,18 @@ async def init() -> None:
1818
if queue_type == "internal":
1919
queue = InternalQueue(config=configs.application_queue)
2020
elif queue_type.startswith("plugin."):
21-
queue_class = get_plugin_queue(queue_type)
21+
queue_module = get_plugin_attribute(queue_type)
22+
23+
try:
24+
queue_class: type[Queue] = queue_module.Queue
25+
except AttributeError:
26+
raise ValueError(f"'Queue' class not found for '{queue_type}'")
27+
28+
if not isinstance(queue_class, Queue):
29+
raise ValueError(
30+
f"'Queue' class in '{queue_type}' does not implement the Queue protocol"
31+
)
32+
2233
queue = queue_class(config=configs.application_queue)
2334
else:
2435
raise ValueError(f"Invalid queue type '{queue_type}'")

src/plugins/attribute_select.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import Any
2+
3+
import plugins
4+
5+
6+
def get_plugin_attribute(attribute_path: str) -> Any:
7+
"""Retrieves a plugin attribute based on the provided attribute path."""
8+
_, plugin_name, *parts = attribute_path.split(".")
9+
if len(parts) < 2:
10+
raise ValueError("Attribute path must specify a plugin and at least two attributes")
11+
12+
plugin = plugins.loaded_plugins.get(plugin_name)
13+
if plugin is None:
14+
raise ValueError(f"Plugin '{plugin_name}' not loaded")
15+
16+
target_path_parts = []
17+
target: Any = plugin
18+
19+
for part in parts:
20+
target_path_parts.append(part)
21+
target = getattr(target, part, None)
22+
23+
if target is None:
24+
target_path = ".".join(target_path_parts)
25+
raise ValueError(f"Plugin '{plugin_name}' has no attribute '{target_path}'")
26+
27+
return target

src/plugins/aws/queues/sqs/sqs_queue.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
@dataclass
1717
class SQSQueueConfig:
18-
type: Literal["plugin.aws.sqs"]
18+
type: Literal["plugin.aws.queues.sqs"]
1919
name: str
2020
url: str
2121
region: str | None = None

src/plugins/queue_select.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

tests/components/executor/test_request_handler.py

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
22
import time
3-
from unittest.mock import AsyncMock
3+
from unittest.mock import AsyncMock, MagicMock
44

55
import pytest
66

@@ -165,54 +165,27 @@ async def test_action(message_payload): ...
165165

166166
monkeypatch.setattr(plugins, "loaded_plugins", {"plugin1": Plugin}, raising=False)
167167

168-
assert request_handler.get_action("plugin.plugin1.test_action") == Plugin.actions.test_action
168+
expected_action = Plugin.actions.test_action
169+
assert request_handler.get_action("plugin.plugin1.actions.test_action") == expected_action
169170

170171

171172
async def test_get_action_unknown_plugin(caplog, monkeypatch):
172-
"""'get_action' should return 'None' when the plugin doesn't exists"""
173-
monkeypatch.setattr(plugins, "loaded_plugins", {"plugin1": "plugin1"}, raising=False)
173+
"""'get_action' should return 'None' when the attribute path could not be resolved"""
174+
monkeypatch.setattr(
175+
request_handler,
176+
"get_plugin_attribute",
177+
MagicMock(side_effect=ValueError("Some error from the function")),
178+
)
174179

175180
action = request_handler.get_action("plugin.plugin2.test_action")
176181

177182
assert action is None
178-
assert_message_in_log(caplog, "Plugin 'plugin2' unknown")
179-
180-
181-
async def test_get_action_plugin_no_actions(caplog, monkeypatch):
182-
"""'get_action' should return 'None' when the plugin doesn't have actions"""
183-
184-
class Plugin: ...
185-
186-
monkeypatch.setattr(plugins, "loaded_plugins", {"plugin1": Plugin}, raising=False)
187-
188-
action = request_handler.get_action("plugin.plugin1.test_action")
189-
190-
assert action is None
191-
assert_message_in_log(caplog, "Plugin 'plugin1' doesn't have actions")
192-
193-
194-
async def test_get_action_unknown_action(caplog, monkeypatch):
195-
"""'get_action' should return 'None' when the action doesn't exists"""
196-
197-
class Plugin:
198-
class actions: ...
199-
200-
monkeypatch.setattr(plugins, "loaded_plugins", {"plugin1": Plugin}, raising=False)
201-
202-
action = request_handler.get_action("plugin.plugin1.test_action")
203-
204-
assert action is None
205-
assert_message_in_log(caplog, "Action 'plugin1.test_action' unknown")
183+
assert_message_in_log(caplog, "Some error from the function")
206184

207185

208186
@pytest.mark.parametrize(
209187
"action_name",
210-
[
211-
"alert_acknowledge",
212-
"alert_lock",
213-
"alert_solve",
214-
"issue_drop",
215-
],
188+
["alert_acknowledge", "alert_lock", "alert_solve", "issue_drop"],
216189
)
217190
async def test_run_action(monkeypatch, action_name):
218191
"""'run' should executed the requested action"""

0 commit comments

Comments
 (0)