diff --git a/app/celery/provider_tasks.py b/app/celery/provider_tasks.py index 1836dda1b..3619298d9 100644 --- a/app/celery/provider_tasks.py +++ b/app/celery/provider_tasks.py @@ -1,3 +1,4 @@ +import os from datetime import datetime, timedelta from time import time @@ -39,9 +40,15 @@ def check_sms_delivery_receipt(self, message_id, notification_id, sent_at): failure appears in the cloudwatch logs, so this should keep retrying until the log appears, or until we run out of retries. """ - status, provider_response = aws_cloudwatch_client.check_sms( - message_id, notification_id, sent_at - ) + # TODO the localstack cloudwatch doesn't currently have our log groups. Possibly create them with awslocal? + if aws_cloudwatch_client.is_localstack(): + status = "success" + provider_response = "this is a fake successful localstack sms message" + else: + status, provider_response = aws_cloudwatch_client.check_sms( + message_id, notification_id, sent_at + ) + if status == "success": status = NOTIFICATION_DELIVERED elif status == "failure": @@ -73,8 +80,19 @@ def deliver_sms(self, notification_id): "Start sending SMS for notification id: {}".format(notification_id) ) notification = notifications_dao.get_notification_by_id(notification_id) + ansi_green = "\033[32m" + ansi_reset = "\033[0m" + if not notification: raise NoResultFound() + if ( + os.getenv("NOTIFY_ENVIRONMENT") == "development" + and "authentication code" in notification.content + ): + current_app.logger.warning( + ansi_green + f"AUTHENTICATION CODE: {notification.content}" + ansi_reset + ) + message_id = send_to_providers.send_sms_to_provider(notification) # We have to put it in UTC. For other timezones, the delay # will be ignored and it will fire immediately (although this probably only affects developer testing) diff --git a/app/clients/cloudwatch/aws_cloudwatch.py b/app/clients/cloudwatch/aws_cloudwatch.py index 9a3bc69e4..26f513111 100644 --- a/app/clients/cloudwatch/aws_cloudwatch.py +++ b/app/clients/cloudwatch/aws_cloudwatch.py @@ -1,4 +1,5 @@ import json +import os import re import time @@ -14,13 +15,26 @@ class AwsCloudwatchClient(Client): """ def init_app(self, current_app, *args, **kwargs): - self._client = client( - "logs", - region_name=cloud_config.sns_region, - aws_access_key_id=cloud_config.sns_access_key, - aws_secret_access_key=cloud_config.sns_secret_key, - config=AWS_CLIENT_CONFIG, - ) + if os.getenv("LOCALSTACK_ENDPOINT_URL"): + self._client = client( + "logs", + region_name=cloud_config.sns_region, + aws_access_key_id=cloud_config.sns_access_key, + aws_secret_access_key=cloud_config.sns_secret_key, + config=AWS_CLIENT_CONFIG, + endpoint_url=os.getenv("LOCALSTACK_ENDPOINT_URL"), + ) + self._is_localstack = True + else: + self._client = client( + "logs", + region_name=cloud_config.sns_region, + aws_access_key_id=cloud_config.sns_access_key, + aws_secret_access_key=cloud_config.sns_secret_key, + config=AWS_CLIENT_CONFIG, + ) + self._is_localstack = False + super(Client, self).__init__(*args, **kwargs) self.current_app = current_app self._valid_sender_regex = re.compile(r"^\+?\d{5,14}$") @@ -29,6 +43,9 @@ def init_app(self, current_app, *args, **kwargs): def name(self): return "cloudwatch" + def is_localstack(self): + return self._is_localstack + def _get_log(self, my_filter, log_group_name, sent_at): # Check all cloudwatch logs from the time the notification was sent (currently 5 minutes previously) until now now = round(time.time() * 1000) diff --git a/app/clients/sms/aws_sns.py b/app/clients/sms/aws_sns.py index 8d224d57c..1061ec9ce 100644 --- a/app/clients/sms/aws_sns.py +++ b/app/clients/sms/aws_sns.py @@ -1,3 +1,4 @@ +import os import re from time import monotonic @@ -16,13 +17,24 @@ class AwsSnsClient(SmsClient): """ def init_app(self, current_app, *args, **kwargs): - self._client = client( - "sns", - region_name=cloud_config.sns_region, - aws_access_key_id=cloud_config.sns_access_key, - aws_secret_access_key=cloud_config.sns_secret_key, - config=AWS_CLIENT_CONFIG, - ) + if os.getenv("LOCALSTACK_ENDPOINT_URL"): + self._client = client( + "sns", + region_name=cloud_config.sns_region, + aws_access_key_id=cloud_config.sns_access_key, + aws_secret_access_key=cloud_config.sns_secret_key, + config=AWS_CLIENT_CONFIG, + endpoint_url=os.getenv("LOCALSTACK_ENDPOINT_URL"), + ) + else: + self._client = client( + "sns", + region_name=cloud_config.sns_region, + aws_access_key_id=cloud_config.sns_access_key, + aws_secret_access_key=cloud_config.sns_secret_key, + config=AWS_CLIENT_CONFIG, + ) + super(SmsClient, self).__init__(*args, **kwargs) self.current_app = current_app self._valid_sender_regex = re.compile(r"^\+?\d{5,14}$") diff --git a/docs/localstack.md b/docs/localstack.md new file mode 100644 index 000000000..fc6e7fa75 --- /dev/null +++ b/docs/localstack.md @@ -0,0 +1,44 @@ +How to Use Localstack in Your Development Work +================================== + + + +### Install Docker Desktop (One-Time) + +* https://docs.docker.com/desktop/install/mac-install/ + + +### Install Localstack (One-Time) + +* >pip install --upgrade localstack +* >localstack --version # should be 2.2.0 or later + + +### Add LOCALSTACK_ENDPOINT_URL to Your .env File (One-Time) + +* Find the value in the sample.env file (# LOCALSTACK_ENDPOINT_URL=http://localhost:4566). +* Copy and uncomment it into your .env file + +### Run with Localstack (Recurring) + +#### Start Docker Desktop and localstack image + +* Open Docker Desktop from Finder +* Images->Local->localstack/localstack click on the start button on the right hand side to get the localstack + docker image going + + +#### Start Localstack + +* From your project directory in a separate terminal window, either: +* >localstack start +* >pipenv run localstack start + +#### Proceed With Your Usual Development Activities + +Assuming you followed all these steps and nothing went wrong, you should be running with localstack for SNS now. +You should be able to send an SMS message in the UI and observe it in the dashboard moving from Pending to Delivered +over a period of five minutes. And you should not receive a text message. + +NOTE: You will still be prompted for a 2FA code when you log in. To get the code, look in the notification-api +logs for "AUTHENTICATION_CODE:". \ No newline at end of file diff --git a/sample.env b/sample.env index 643c25c63..9cd957e64 100644 --- a/sample.env +++ b/sample.env @@ -16,6 +16,10 @@ AWS_US_TOLL_FREE_NUMBER=+18556438890 # DATABASE_URL=postgresql://postgres:chummy@db:5432/notification_api # SQLALCHEMY_DATABASE_TEST_URI=postgresql://postgres:chummy@db:5432/test_notification_api + +# If you want to do local development with localstack copy this to your .env file and uncomment it +# LOCALSTACK_ENDPOINT_URL=http://localhost:4566 + # Local direct setup, all overwritten in cloud.gov ADMIN_BASE_URL=http://localhost:6012 API_HOST_NAME=http://localhost:6011