-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add slack_app Django app with Slack Bolt and /inc help command #110
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
spalmurray
wants to merge
14
commits into
main
Choose a base branch
from
spalmurray/RELENG-463
base: main
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
14 commits
Select commit
Hold shift + click to select a range
b0e56c0
Add slack-bolt dependency
spalmurray ca5e90d
Add signing_secret to Slack config
spalmurray 10773d2
Add slack_app Django app with Bolt wiring and /inc help command
spalmurray 1936090
Add Datadog metrics instrumentation for slash commands
spalmurray d35354d
Add signing_secret to CI config and fix ruff lint
spalmurray bac3050
Add test values for slack bot
spalmurray 7bd4189
Fix TZ warnings
spalmurray e3d7400
tweaks
spalmurray d6f469a
Rename slack app in help command
spalmurray 1bf93a9
Remove HTTP-based slack event handling in favor of Socket Mode
spalmurray 0ab61df
Add app_token config for Slack Socket Mode
spalmurray cfe0808
Add run_slack_bot management command and Docker entrypoint
spalmurray 3101d49
Add slack bot deploy step to GitHub Actions
spalmurray 97ffeaf
Disable token verification in Bolt for Socket Mode
spalmurray 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
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
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 |
|---|---|---|
|
|
@@ -38,6 +38,8 @@ class SlackConfig: | |
| bot_token: str | ||
| team_id: str | ||
| participant_sync_throttle_seconds: int | ||
| signing_secret: str | ||
| app_token: str | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Existing configs may fail to deserializeHigh Severity
|
||
|
|
||
|
|
||
| @deserialize | ||
|
|
@@ -113,6 +115,8 @@ def __init__(self) -> None: | |
| bot_token="", | ||
| team_id="", | ||
| participant_sync_throttle_seconds=0, | ||
| signing_secret="", | ||
| app_token="", | ||
| ) | ||
| self.auth = AuthConfig( | ||
| iap_enabled=False, | ||
|
|
||
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
Empty file.
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,6 @@ | ||
| from django.apps import AppConfig | ||
|
|
||
|
|
||
| class SlackAppConfig(AppConfig): | ||
| default_auto_field = "django.db.models.BigAutoField" | ||
| name = "firetower.slack_app" |
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,39 @@ | ||
| import logging | ||
| from typing import Any | ||
|
|
||
| from datadog import statsd | ||
| from django.conf import settings | ||
| from slack_bolt import App | ||
|
|
||
| from firetower.slack_app.handlers.help import handle_help_command | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| METRICS_PREFIX = "slack_app.commands" | ||
|
|
||
| slack_config = settings.SLACK | ||
|
|
||
| bolt_app = App(token=slack_config["BOT_TOKEN"], token_verification_enabled=False) | ||
|
|
||
|
|
||
| @bolt_app.command("/inc") | ||
| @bolt_app.command("/testinc") | ||
| def handle_inc(ack: Any, body: dict, command: dict, respond: Any) -> None: | ||
| subcommand = (body.get("text") or "").strip().lower() | ||
| tags = [f"subcommand:{subcommand or 'help'}"] | ||
| statsd.increment(f"{METRICS_PREFIX}.submitted", tags=tags) | ||
|
|
||
| try: | ||
| if subcommand in ("help", ""): | ||
| handle_help_command(ack, command, respond) | ||
| else: | ||
| ack() | ||
| cmd = command.get("command", "/inc") | ||
| respond(f"Unknown command: `{cmd} {subcommand}`. Try `{cmd} help`.") | ||
| statsd.increment(f"{METRICS_PREFIX}.completed", tags=tags) | ||
| except Exception: | ||
| logger.exception( | ||
| "Slash command failed: %s %s", command.get("command", "/inc"), subcommand | ||
| ) | ||
| statsd.increment(f"{METRICS_PREFIX}.failed", tags=tags) | ||
| raise |
Empty file.
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,12 @@ | ||
| from typing import Any | ||
|
|
||
|
|
||
| def handle_help_command(ack: Any, command: dict, respond: Any) -> None: | ||
| ack() | ||
| cmd = command.get("command", "/inc") | ||
| respond( | ||
| f"*Firetower Slack App*\n" | ||
| f"Usage: `{cmd} <command>`\n\n" | ||
| f"Available commands:\n" | ||
| f" `{cmd} help` - Show this help message\n" | ||
| ) |
Empty file.
Empty file.
20 changes: 20 additions & 0 deletions
20
src/firetower/slack_app/management/commands/run_slack_bot.py
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,20 @@ | ||
| import logging | ||
| from typing import Any | ||
|
|
||
| from django.conf import settings | ||
| from django.core.management.base import BaseCommand | ||
| from slack_bolt.adapter.socket_mode import SocketModeHandler | ||
|
|
||
| from firetower.slack_app.bolt import bolt_app | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class Command(BaseCommand): | ||
| help = "Start the Slack bot in Socket Mode" | ||
|
|
||
| def handle(self, *args: Any, **options: Any) -> None: | ||
| app_token = settings.SLACK["APP_TOKEN"] | ||
| handler = SocketModeHandler(app=bolt_app, app_token=app_token) | ||
| logger.info("Starting Slack bot in Socket Mode") | ||
| handler.start() |
Empty file.
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,103 @@ | ||
| from unittest.mock import MagicMock, call, patch | ||
|
|
||
| import pytest | ||
|
|
||
| from firetower.slack_app.bolt import handle_inc | ||
|
|
||
|
|
||
| class TestHandleInc: | ||
| def _make_body(self, text="", command="/inc"): | ||
| return {"text": text, "command": command} | ||
|
|
||
| def _make_command(self, command="/inc", text=""): | ||
| return {"command": command, "text": text} | ||
|
|
||
| @patch("firetower.slack_app.bolt.statsd") | ||
| def test_help_returns_help_text(self, mock_statsd): | ||
| ack = MagicMock() | ||
| respond = MagicMock() | ||
| body = self._make_body(text="help") | ||
| command = self._make_command() | ||
|
|
||
| handle_inc(ack=ack, body=body, command=command, respond=respond) | ||
|
|
||
| ack.assert_called_once() | ||
| respond.assert_called_once() | ||
| response_text = respond.call_args[0][0] | ||
| assert "Firetower Slack App" in response_text | ||
| assert "/inc help" in response_text | ||
|
|
||
| @patch("firetower.slack_app.bolt.statsd") | ||
| def test_empty_text_returns_help(self, mock_statsd): | ||
| ack = MagicMock() | ||
| respond = MagicMock() | ||
| body = self._make_body(text="") | ||
| command = self._make_command() | ||
|
|
||
| handle_inc(ack=ack, body=body, command=command, respond=respond) | ||
|
|
||
| ack.assert_called_once() | ||
| respond.assert_called_once() | ||
| response_text = respond.call_args[0][0] | ||
| assert "Firetower Slack App" in response_text | ||
|
|
||
| @patch("firetower.slack_app.bolt.statsd") | ||
| def test_unknown_subcommand_returns_error(self, mock_statsd): | ||
| ack = MagicMock() | ||
| respond = MagicMock() | ||
| body = self._make_body(text="unknown") | ||
| command = self._make_command() | ||
|
|
||
| handle_inc(ack=ack, body=body, command=command, respond=respond) | ||
|
|
||
| ack.assert_called_once() | ||
| respond.assert_called_once() | ||
| response_text = respond.call_args[0][0] | ||
| assert "Unknown command" in response_text | ||
| assert "/inc unknown" in response_text | ||
|
|
||
| @patch("firetower.slack_app.bolt.statsd") | ||
| def test_help_uses_testinc_command(self, mock_statsd): | ||
| ack = MagicMock() | ||
| respond = MagicMock() | ||
| body = self._make_body(text="help", command="/testinc") | ||
| command = self._make_command(command="/testinc") | ||
|
|
||
| handle_inc(ack=ack, body=body, command=command, respond=respond) | ||
|
|
||
| ack.assert_called_once() | ||
| response_text = respond.call_args[0][0] | ||
| assert "/testinc help" in response_text | ||
|
|
||
| @patch("firetower.slack_app.bolt.statsd") | ||
| def test_emits_submitted_and_completed_metrics(self, mock_statsd): | ||
| ack = MagicMock() | ||
| respond = MagicMock() | ||
| body = self._make_body(text="help") | ||
| command = self._make_command() | ||
|
|
||
| handle_inc(ack=ack, body=body, command=command, respond=respond) | ||
|
|
||
| mock_statsd.increment.assert_has_calls( | ||
| [ | ||
| call("slack_app.commands.submitted", tags=["subcommand:help"]), | ||
| call("slack_app.commands.completed", tags=["subcommand:help"]), | ||
| ] | ||
| ) | ||
|
|
||
| @patch("firetower.slack_app.bolt.statsd") | ||
| def test_emits_failed_metric_on_error(self, mock_statsd): | ||
| ack = MagicMock() | ||
| respond = MagicMock(side_effect=RuntimeError("boom")) | ||
| body = self._make_body(text="help") | ||
| command = self._make_command() | ||
|
|
||
| with pytest.raises(RuntimeError): | ||
| handle_inc(ack=ack, body=body, command=command, respond=respond) | ||
|
|
||
| mock_statsd.increment.assert_any_call( | ||
| "slack_app.commands.submitted", tags=["subcommand:help"] | ||
| ) | ||
| mock_statsd.increment.assert_any_call( | ||
| "slack_app.commands.failed", tags=["subcommand:help"] | ||
| ) |
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.


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slack bot Cloud Run runs web server
High Severity
deploy-test-slack-botdeploysfiretower-slack-app-testwith onlyimage:set, so the new service likely runs the container’s defaultCMD(/app/entrypoint.sh server) instead of the Slack bot process. This can make the Slack bot service come up “healthy” while never startingrun_slack_bot.