Skip to content

Commit 499e241

Browse files
authored
Feat: wire first modernized feature to platforms providers (#1078)
* feat: implement platform providers activation and initialization * feat: reorder import statements * feat: remove geolocate helper and refactor SRE command handling * feat: enhance geolocate response formatting with country code, postal code, and no data message * feat: remove deprecated geolocate helper tests and rename conflicting tests
1 parent d8dea5a commit 499e241

File tree

11 files changed

+158
-159
lines changed

11 files changed

+158
-159
lines changed

app/infrastructure/platforms/providers/slack.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
Provides integration with Slack using the Bolt SDK for Socket Mode.
44
"""
55

6-
import structlog
76
from typing import Any, Callable, Dict, Optional
7+
8+
import structlog
89
from slack_bolt import App
910
from slack_bolt.adapter.socket_mode import SocketModeHandler
1011

app/modules/sre/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""SRE module - Platform command registration."""
2+
3+
from infrastructure.services import hookimpl
4+
from modules.sre.platforms import slack, teams, discord
5+
6+
7+
@hookimpl
8+
def register_slack_commands(provider):
9+
"""Register SRE module Slack commands."""
10+
slack.register_commands(provider)
11+
12+
13+
@hookimpl
14+
def register_teams_commands(provider):
15+
"""Register SRE module Teams commands."""
16+
teams.register_commands(provider)
17+
18+
19+
@hookimpl
20+
def register_discord_commands(provider):
21+
"""Register SRE module Discord commands."""
22+
discord.register_commands(provider)

app/modules/sre/geolocate_helper.py

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

app/modules/sre/sre.py

Lines changed: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33
This module contains the main command for the SRE bot. It is responsible for handling the `/sre` command and its subcommands.
44
"""
55

6+
from typing import Any, Dict
7+
68
import structlog
7-
from core.config import settings
8-
from infrastructure.commands.router import CommandRouter
9+
from slack_bolt import Ack, App, Respond
10+
from slack_sdk import WebClient
11+
912
from infrastructure.commands.providers.slack import SlackCommandProvider
13+
from infrastructure.commands.router import CommandRouter
14+
from infrastructure.platforms.models import CommandPayload
15+
from infrastructure.services import get_platform_service
16+
from core.config import settings
17+
from modules.dev.core import dev_router
1018
from modules.groups import create_slack_provider
1119
from modules.incident import incident_helper
12-
from modules.sre import geolocate_helper, webhook_helper
13-
from modules.dev.core import dev_router
14-
from slack_bolt import Ack, App, Respond
15-
from slack_sdk import WebClient
20+
from modules.sre import webhook_helper
1621

1722
PREFIX = settings.PREFIX
1823
GIT_SHA = settings.GIT_SHA
@@ -92,30 +97,6 @@ def handle(self, platform_payload):
9297
)
9398

9499

95-
class GeolocateProvider(SlackCommandProvider):
96-
"""Adapter for geolocate helper."""
97-
98-
def __init__(self):
99-
super().__init__(settings=settings, config={"enabled": True})
100-
self.registry = None
101-
102-
def handle(self, platform_payload):
103-
"""Handle geolocate command."""
104-
self.acknowledge(platform_payload)
105-
106-
command = platform_payload["command"]
107-
respond = platform_payload["respond"]
108-
109-
text = command.get("text", "")
110-
args = text.split() if text else []
111-
112-
if not args:
113-
respond("Please provide an IP address.\n" "SVP fournir une adresse IP")
114-
return
115-
116-
geolocate_helper.geolocate(args, respond)
117-
118-
119100
class VersionProvider(SlackCommandProvider):
120101
"""Adapter for version command."""
121102

@@ -150,13 +131,6 @@ def handle(self, platform_payload):
150131
description_key="sre.subcommands.webhooks.description",
151132
)
152133

153-
sre_router.register_subcommand(
154-
name="geolocate",
155-
provider=GeolocateProvider(),
156-
platform="slack",
157-
description="Geolocate an IP address",
158-
description_key="sre.subcommands.geolocate.description",
159-
)
160134

161135
sre_router.register_subcommand(
162136
name="version",
@@ -175,16 +149,74 @@ def handle(self, platform_payload):
175149
)
176150

177151

178-
def register(bot: App):
152+
def register(bot: App) -> None:
179153
bot.command(f"/{PREFIX}sre")(sre_command)
180154

181155

156+
def _dispatch_platform_command(
157+
command: Dict[str, Any],
158+
respond: Respond,
159+
) -> bool:
160+
text = (command.get("text") or "").strip()
161+
if text:
162+
parts = text.split(maxsplit=1)
163+
command_name = f"sre.{parts[0]}"
164+
remaining_text = parts[1] if len(parts) > 1 else ""
165+
else:
166+
command_name = "sre"
167+
remaining_text = ""
168+
169+
try:
170+
platform_service = get_platform_service()
171+
slack_provider = platform_service.get_provider("slack")
172+
except Exception as exc: # pylint: disable=broad-except
173+
logger.debug("platform_provider_unavailable", error=str(exc))
174+
return False
175+
176+
payload = CommandPayload(
177+
text=remaining_text,
178+
user_id=command.get("user_id", ""),
179+
user_email=command.get("user_email"),
180+
channel_id=command.get("channel_id"),
181+
user_locale=command.get("locale", "en-US"),
182+
response_url=command.get("response_url"),
183+
platform_metadata=dict(command),
184+
)
185+
186+
response = slack_provider.dispatch_command(command_name, payload)
187+
unknown_command_message = f"Unknown command: {command_name}"
188+
if response.message == unknown_command_message:
189+
return False
190+
191+
response_type = "ephemeral" if response.ephemeral else "in_channel"
192+
if response.blocks:
193+
respond(
194+
text=response.message or "",
195+
blocks=response.blocks,
196+
response_type=response_type,
197+
)
198+
return True
199+
if response.attachments:
200+
respond(
201+
text=response.message or "",
202+
attachments=response.attachments,
203+
response_type=response_type,
204+
)
205+
return True
206+
if response.message:
207+
respond(text=response.message, response_type=response_type)
208+
return True
209+
210+
respond(text="", response_type=response_type)
211+
return True
212+
213+
182214
def sre_command(
183215
ack: Ack,
184-
command,
216+
command: Dict[str, Any],
185217
respond: Respond,
186218
client: WebClient,
187-
):
219+
) -> None:
188220
"""Main /sre command handler - delegates all subcommands to router."""
189221
ack()
190222
logger.info(
@@ -204,6 +236,9 @@ def sre_command(
204236
"ack": ack,
205237
}
206238

239+
if _dispatch_platform_command(command, respond):
240+
return
241+
207242
# Router handles ALL subcommands (new + legacy)
208243
# Router automatically generates help for empty commands or `/sre help`
209244
sre_router.handle(payload)

app/packages/geolocate/platforms/slack.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ def _format_success_blocks(data: GeolocateResponse) -> list[Dict[str, Any]]:
102102
fields.append({"type": "mrkdwn", "text": f"*City:*\n{data.city}"})
103103
if data.country:
104104
fields.append({"type": "mrkdwn", "text": f"*Country:*\n{data.country}"})
105+
if data.country_code:
106+
fields.append(
107+
{"type": "mrkdwn", "text": f"*Country Code:*\n{data.country_code}"}
108+
)
109+
if data.postal_code:
110+
fields.append({"type": "mrkdwn", "text": f"*Postal Code:*\n{data.postal_code}"})
105111
if data.latitude and data.longitude:
106112
fields.append(
107113
{
@@ -112,6 +118,14 @@ def _format_success_blocks(data: GeolocateResponse) -> list[Dict[str, Any]]:
112118
if data.time_zone:
113119
fields.append({"type": "mrkdwn", "text": f"*Time Zone:*\n{data.time_zone}"})
114120

121+
if not fields:
122+
fields.append(
123+
{
124+
"type": "mrkdwn",
125+
"text": "*Details:*\nNo location data available for this IP.",
126+
}
127+
)
128+
115129
return [
116130
{
117131
"type": "header",

app/server/lifespan.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
register_infrastructure_handlers,
1616
)
1717
from infrastructure.logging.setup import configure_logging
18-
from infrastructure.services import get_settings
18+
from infrastructure.services import (
19+
discover_and_register_platforms,
20+
get_platform_service,
21+
get_settings,
22+
)
1923
from jobs import scheduled_tasks
2024
from modules import (
2125
atip,
@@ -137,6 +141,46 @@ def _activate_providers(
137141
logger.error("group_providers_activation_failed", error=str(exc))
138142
raise
139143

144+
try:
145+
platform_service = get_platform_service()
146+
platform_providers = platform_service.load_providers()
147+
app.state.platform_service = platform_service
148+
app.state.platform_providers = platform_providers
149+
150+
# Initialize all enabled providers (establishes connections)
151+
init_results = platform_service.initialize_all_providers()
152+
initialized = [
153+
name for name, result in init_results.items() if result.is_success
154+
]
155+
failed = [
156+
name for name, result in init_results.items() if not result.is_success
157+
]
158+
159+
if failed:
160+
logger.warning(
161+
"platform_providers_initialization_partial_failure",
162+
initialized=initialized,
163+
failed=failed,
164+
)
165+
166+
# Discover and register platform commands for ALL enabled providers
167+
# The discover function handles None providers gracefully
168+
discover_and_register_platforms(
169+
slack_provider=platform_providers.get("slack"), # type: ignore
170+
teams_provider=platform_providers.get("teams"), # type: ignore
171+
discord_provider=platform_providers.get("discord"), # type: ignore
172+
)
173+
174+
logger.info(
175+
"platform_providers_activated",
176+
count=len(platform_providers),
177+
providers=list(platform_providers.keys()),
178+
initialized=initialized,
179+
)
180+
except Exception as exc:
181+
logger.error("platform_providers_activation_failed", error=str(exc))
182+
raise
183+
140184
try:
141185
command_providers = load_command_providers(settings=settings)
142186
app.state.command_providers = command_providers

app/tests/integration/modules/sre/test_sre_providers.py

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from unittest.mock import MagicMock, patch
88
from modules.sre.sre import (
99
VersionProvider,
10-
GeolocateProvider,
1110
LegacyWebhooksProvider,
1211
LegacyIncidentProvider,
1312
)
@@ -41,53 +40,6 @@ def test_version_provider_responds_to_version_command(self, mock_ack, mock_respo
4140
assert "SRE Bot version:" in call_args
4241

4342

44-
class TestGeolocateProvider:
45-
"""Tests for geolocate command provider integration."""
46-
47-
def test_geolocate_provider_initialization(self):
48-
"""Geolocate provider should initialize without errors."""
49-
provider = GeolocateProvider()
50-
assert provider is not None
51-
assert provider.registry is None
52-
53-
@patch("modules.sre.geolocate_helper.geolocate")
54-
def test_geolocate_provider_delegates_with_ip(
55-
self, mock_geolocate, mock_ack, mock_respond
56-
):
57-
"""Geolocate provider should delegate to helper with IP."""
58-
provider = GeolocateProvider()
59-
60-
payload = {
61-
"command": {"text": "192.168.1.1"},
62-
"respond": mock_respond,
63-
"ack": mock_ack,
64-
"client": MagicMock(),
65-
}
66-
67-
provider.handle(payload)
68-
69-
mock_ack.assert_called_once()
70-
mock_geolocate.assert_called_once_with(["192.168.1.1"], mock_respond)
71-
72-
def test_geolocate_provider_handles_missing_ip(self, mock_ack, mock_respond):
73-
"""Geolocate provider should handle missing IP gracefully."""
74-
provider = GeolocateProvider()
75-
76-
payload = {
77-
"command": {"text": ""},
78-
"respond": mock_respond,
79-
"ack": mock_ack,
80-
"client": MagicMock(),
81-
}
82-
83-
provider.handle(payload)
84-
85-
mock_ack.assert_called_once()
86-
mock_respond.assert_called_once_with(
87-
"Please provide an IP address.\nSVP fournir une adresse IP"
88-
)
89-
90-
9143
class TestLegacyWebhooksProvider:
9244
"""Tests for legacy webhooks command provider integration."""
9345

app/tests/unit/infrastructure/clients/aws/test_identity_store.py renamed to app/tests/unit/infrastructure/clients/aws/test_identity_store_unit.py

File renamed without changes.

app/tests/unit/infrastructure/clients/aws/test_organizations.py renamed to app/tests/unit/infrastructure/clients/aws/test_organizations_unit.py

File renamed without changes.

app/tests/unit/infrastructure/clients/aws/test_sso_admin.py renamed to app/tests/unit/infrastructure/clients/aws/test_sso_admin_unit.py

File renamed without changes.

0 commit comments

Comments
 (0)