Skip to content
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

Fix chalice deployment bug introduced in #270 #316

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 14 additions & 30 deletions slack_bolt/adapter/aws_lambda/chalice_handler.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import logging
import json
from os import getenv
from typing import Optional

from botocore.client import BaseClient

from chalice.app import Request, Response, Chalice
from chalice.config import Config
from chalice.test import BaseClient, LambdaContext, InvokeResponse

from slack_bolt.adapter.aws_lambda.chalice_lazy_listener_runner import (
ChaliceLazyListenerRunner,
Expand All @@ -17,38 +17,22 @@
from slack_bolt.response import BoltResponse


class LocalLambdaClient(BaseClient):
"""Lambda client implementing `invoke` for use when running with Chalice CLI"""

def __init__(self, app: Chalice, config: Config) -> None:
self._app = app
self._config = config

def invoke(
self,
FunctionName: str = None,
InvocationType: str = "Event",
Payload: str = "{}",
) -> InvokeResponse:
scoped = self._config.scope(self._config.chalice_stage, FunctionName)
lambda_context = LambdaContext(
FunctionName, memory_size=scoped.lambda_memory_size
)

with self._patched_env_vars(scoped.environment_variables):
response = self._app(json.loads(Payload), lambda_context)
return InvokeResponse(payload=response)


class ChaliceSlackRequestHandler:
def __init__(self, app: App, chalice: Chalice): # type: ignore
def __init__(self, app: App, chalice: Chalice, lambda_client: Optional[BaseClient] = None): # type: ignore
self.app = app
self.chalice = chalice
self.logger = get_bolt_app_logger(app.name, ChaliceSlackRequestHandler)

lambda_client = None
if getenv("AWS_CHALICE_CLI_MODE") == "true":
lambda_client = LocalLambdaClient(self.chalice, Config())
if getenv("AWS_CHALICE_CLI_MODE") == "true" and lambda_client is None:
try:
from slack_bolt.adapter.aws_lambda.local_lambda_client import (
LocalLambdaClient,
)

lambda_client = LocalLambdaClient(self.chalice, None)
except ImportError:
logging.info("Failed to load LocalLambdaClient for CLI mode.")
pass

self.app.listener_runner.lazy_listener_runner = ChaliceLazyListenerRunner(
logger=self.logger, lambda_client=lambda_client
Expand Down
6 changes: 3 additions & 3 deletions slack_bolt/adapter/aws_lambda/chalice_lazy_listener_runner.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import json
from logging import Logger
from typing import Callable, Optional, Any
from typing import Callable, Optional

import boto3
from botocore.client import BaseClient

from slack_bolt import BoltRequest
from slack_bolt.lazy_listener import LazyListenerRunner


class ChaliceLazyListenerRunner(LazyListenerRunner):
def __init__(self, logger: Logger, lambda_client: Optional[Any] = None):
def __init__(self, logger: Logger, lambda_client: Optional[BaseClient] = None):
self.lambda_client = lambda_client
self.logger = logger

Expand Down Expand Up @@ -38,4 +39,3 @@ def start(self, function: Callable[..., None], request: BoltRequest) -> None:
InvocationType="Event",
Payload=json.dumps(payload),
)
self.logger.info(invocation)
28 changes: 28 additions & 0 deletions slack_bolt/adapter/aws_lambda/local_lambda_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import json

from chalice.app import Chalice
from chalice.config import Config
from chalice.test import BaseClient, LambdaContext, InvokeResponse


class LocalLambdaClient(BaseClient):
"""Lambda client implementing `invoke` for use when running with Chalice CLI."""

def __init__(self, app: Chalice, config: Config) -> None:
self._app = app
self._config = config if config else Config()

def invoke(
self,
FunctionName: str = None,
InvocationType: str = "Event",
Payload: str = "{}",
) -> InvokeResponse:
scoped = self._config.scope(self._config.chalice_stage, FunctionName)
lambda_context = LambdaContext(
FunctionName, memory_size=scoped.lambda_memory_size
)

with self._patched_env_vars(scoped.environment_variables):
response = self._app(json.loads(Payload), lambda_context)
return InvokeResponse(payload=response)
63 changes: 61 additions & 2 deletions tests/adapter_tests/aws/test_aws_chalice.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from typing import Dict, Any
from urllib.parse import quote
from unittest import mock
import logging

from chalice import Chalice, Response
from chalice.app import Request
Expand All @@ -18,6 +17,7 @@
ChaliceSlackRequestHandler,
not_found,
)

from slack_bolt.app import App
from slack_bolt.oauth.oauth_settings import OAuthSettings
from tests.mock_web_api_server import (
Expand Down Expand Up @@ -315,13 +315,72 @@ def events() -> Response:
return slack_handler.handle(chalice_app.current_request)

headers = self.build_headers(timestamp, body)
client = Client(chalice_app, Config())
client = Client(chalice_app)
response = client.http.post("/slack/events", headers=headers, body=body)

assert response.status_code == 200, f"Failed request: {response.body}"
assert_auth_test_count(self, 1)
assert self.mock_received_requests["/chat.postMessage"] == 1

@mock.patch(
"slack_bolt.adapter.aws_lambda.chalice_lazy_listener_runner.boto3",
autospec=True,
)
def test_lazy_listeners_non_cli(self, mock_boto3):
with mock.patch.dict(os.environ, {"AWS_CHALICE_CLI_MODE": "false"}):
assert os.environ.get("AWS_CHALICE_CLI_MODE") == "false"

mock_lambda = mock.MagicMock() # mock of boto3.client('lambda')
mock_boto3.client.return_value = mock_lambda
app = App(
client=self.web_client,
signing_secret=self.signing_secret,
process_before_response=True,
)

def command_handler(ack):
ack()

def say_it(say):
say("Done!")

app.command("/hello-world")(ack=command_handler, lazy=[say_it])

input = (
"token=verification_token"
"&team_id=T111"
"&team_domain=test-domain"
"&channel_id=C111"
"&channel_name=random"
"&user_id=W111"
"&user_name=primary-owner"
"&command=%2Fhello-world"
"&text=Hi"
"&enterprise_id=E111"
"&enterprise_name=Org+Name"
"&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx"
"&trigger_id=111.111.xxx"
)
timestamp, body = str(int(time())), input

chalice_app = Chalice(app_name="bolt-python-chalice")

slack_handler = ChaliceSlackRequestHandler(app=app, chalice=chalice_app)

@chalice_app.route(
"/slack/events",
methods=["POST"],
content_types=["application/x-www-form-urlencoded", "application/json"],
)
def events() -> Response:
return slack_handler.handle(chalice_app.current_request)

headers = self.build_headers(timestamp, body)
client = Client(chalice_app)
response = client.http.post("/slack/events", headers=headers, body=body)
assert response
assert mock_lambda.invoke.called

def test_oauth(self):
app = App(
client=self.web_client,
Expand Down