Example #1
0
    async def test_boto_session_singleton(self):
        session1 = BotoSession.get_session()

        assert session1 is not None

        session2 = BotoSession.get_session()

        assert session1 is session2
Example #2
0
    async def confirm_permission(self) -> None:
        """
        Confirms correct permissions are in place.

        :raises ImproperlyConfigured: If the permissions were not found.
        :raises AssertionError: If the permissions are not correctly configured on AWS.
        """
        session = BotoSession.get_session()
        async with session.create_client(
            "sqs",
            region_name=self.region_name or BotoSession.aws_default_region,
            endpoint_url=self.endpoint_url,
            aws_access_key_id=BotoSession.aws_access_key_id,
            aws_secret_access_key=BotoSession.aws_secret_access_key,
        ) as client:
            policy_attributes = await client.get_queue_attributes(
                QueueUrl=self.queue_url, AttributeNames=["Policy"]
            )

        try:
            policies = json.loads(policy_attributes["Attributes"]["Policy"])
            statement = policies["Statement"][0]
        except KeyError:
            raise ImproperlyConfigured("Permissions not found.")

        # need "Effect": "Allow", "Action": "SQS:SendMessage"
        assert statement["Effect"] == "Allow"
        assert "SQS:SendMessage" in statement["Action"]
Example #3
0
    async def publish(self) -> dict:
        """
        Serializes this message and publishes this message to SNS.

        :return: The response of the publish request to SNS.
        """

        session = BotoSession.get_session()
        try:
            async with session.create_client(
                    "sns",
                    region_name=BotoSession.aws_default_region,
                    endpoint_url=self.client.endpoint_url,
                    aws_access_key_id=BotoSession.aws_access_key_id,
                    aws_secret_access_key=BotoSession.aws_secret_access_key,
            ) as client:
                message = await client.publish(TopicArn=self.client.topic_arn,
                                               **self)
                logger.debug(f"[INIESTA] Published ({self.event}) with "
                             f"the following attributes: {self}")
                return message
        except botocore.exceptions.ClientError as e:
            error_logger.critical(
                f"[{e.response['Error']['Code']}]: {e.response['Error']['Message']}"
            )
            raise
        except Exception:
            error_logger.exception("Publishing SNS Message Failed!")
            raise
Example #4
0
    async def initialize(
        cls,
        *,
        queue_name: Optional[str] = None,
        endpoint_url: Optional[str] = None,
        region_name: Optional[str] = None,
    ):
        """
        The initialization classmethod that should be first run before any subsequent SQSClient initializations.

        :param queue_name: queue_name if want to initialize client with a different queue
        :rtype: :code:`SQSClient`
        """
        session = BotoSession.get_session()

        endpoint_url = endpoint_url or getattr(
            settings, "INIESTA_SQS_ENDPOINT_URL", None
        )

        if queue_name is None:
            queue_name = cls.default_queue_name()

        # check if queue exists
        if queue_name not in cls.queue_urls:
            try:
                async with session.create_client(
                    "sqs",
                    region_name=region_name or BotoSession.aws_default_region,
                    endpoint_url=endpoint_url,
                    aws_access_key_id=BotoSession.aws_access_key_id,
                    aws_secret_access_key=BotoSession.aws_secret_access_key,
                ) as client:
                    response = await client.get_queue_url(QueueName=queue_name)
            except botocore.exceptions.ClientError as e:
                error_message = f"[{e.response['Error']['Code']}]: {e.response['Error']['Message']} {queue_name}"
                error_logger.critical(error_message)
                raise
            else:
                queue_url = response["QueueUrl"]
                cls.queue_urls.update({queue_name: queue_url})

        sqs_client = cls(queue_name=queue_name)

        # check if subscription exists
        # await cls._confirm_subscription(sqs_client, topic_arn, endpoint_url)

        return sqs_client
Example #5
0
    async def get_subscription_attributes(self, subscription_arn: str) -> dict:
        """
        Retrieves the attributes of the subscription from AWS.

        Refer to https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sns.html#SNS.Client.get_subscription_attributes
        for more information.
        """

        async with BotoSession.get_session().create_client(
                "sns",
                region_name=BotoSession.aws_default_region,
                endpoint_url=self.endpoint_url,
                aws_access_key_id=BotoSession.aws_access_key_id,
                aws_secret_access_key=BotoSession.aws_secret_access_key,
        ) as client:
            return await client.get_subscription_attributes(
                SubscriptionArn=subscription_arn)
Example #6
0
    async def _confirm_topic(
        cls,
        topic_arn: str,
        *,
        region_name: Optional[str] = None,
        endpoint_url: Optional[str] = None,
    ) -> None:
        """
        Confirm that the topic exists by request :code:`get_topic_attributes` to AWS.
        """
        session = BotoSession.get_session()

        async with session.create_client(
                "sns",
                region_name=region_name or BotoSession.aws_default_region,
                endpoint_url=endpoint_url or settings.INIESTA_SNS_ENDPOINT_URL,
                aws_access_key_id=BotoSession.aws_access_key_id,
                aws_secret_access_key=BotoSession.aws_secret_access_key,
        ) as client:
            await client.get_topic_attributes(TopicArn=topic_arn)
Example #7
0
    async def _list_subscriptions_by_topic(self, next_token=None):
        session = BotoSession.get_session()

        query_args = {"TopicArn": self.topic_arn}

        if next_token is not None:
            query_args.update({"NextToken": next_token})

        try:
            async with session.create_client(
                    "sns",
                    region_name=BotoSession.aws_default_region,
                    endpoint_url=self.endpoint_url,
                    aws_access_key_id=BotoSession.aws_access_key_id,
                    aws_secret_access_key=BotoSession.aws_secret_access_key,
            ) as client:
                return await client.list_subscriptions_by_topic(**query_args)
        except botocore.exceptions.ClientError as e:
            error_message = f"[{e.response['Error']['Code']}]: {e.response['Error']['Message']} {self.topic_arn}"
            error_logger.critical(error_message)
            raise
Example #8
0
    def reset_session(self):
        yield

        BotoSession.reset_aws_credentials()
Example #9
0
    async def _poll(self) -> str:
        """
        The long running method that consistently polls the SQS queue for
        messages.
        :return:
        """
        session = BotoSession.get_session()

        async with session.create_client(
            "sqs",
            region_name=self.region_name,
            endpoint_url=self.endpoint_url,
            aws_access_key_id=BotoSession.aws_access_key_id,
            aws_secret_access_key=BotoSession.aws_secret_access_key,
        ) as client:
            try:
                while self._loop.is_running() and self._receive_messages:
                    try:
                        response = await client.receive_message(
                            QueueUrl=self.queue_url,
                            MaxNumberOfMessages=settings.INIESTA_SQS_RECEIVE_MESSAGE_MAX_NUMBER_OF_MESSAGES,
                            WaitTimeSeconds=settings.INIESTA_SQS_RECEIVE_MESSAGE_WAIT_TIME_SECONDS,
                            AttributeNames=["All"],
                            MessageAttributeNames=["All"],
                        )
                    except botocore.exceptions.ClientError as e:
                        error_logger.critical(
                            f"[INIESTA] [{e.response['Error']['Code']}]: {e.response['Error']['Message']}"
                        )
                    else:
                        event_tasks = [
                            asyncio.ensure_future(
                                self.handle_message(
                                    SQSMessage.from_sqs(client, message)
                                )
                            )
                            for message in response.get("Messages", [])
                        ]

                        for fut in asyncio.as_completed(event_tasks):
                            # NOTE: must catch CancelledError and raise
                            try:
                                message_obj, result = await fut
                            except asyncio.CancelledError:
                                raise
                            except Exception as e:
                                # if error log failure and pass so sqs message persists and message becomes visible again
                                self.handle_error(e)
                            else:
                                await self.handle_success(client, message_obj)

                        await self.hook_post_receive_message_handler()
            except asyncio.CancelledError:
                logger.info("[INIESTA] POLLING TASK CANCELLED")
                return "Cancelled"
            except StopPolling:
                # mainly used for tests
                logger.info("[INIESTA] STOP POLLING")
                return "Stopped"
            except Exception:
                if self._receive_messages and self._loop.is_running():
                    error_logger.critical("[INIESTA] POLLING TASK RESTARTING")
                    self._polling_task = asyncio.ensure_future(self._poll())
                error_logger.exception("[INIESTA] POLLING EXCEPTION CAUGHT")
            finally:
                await client.close()

        return "Shutdown"  # pragma: no cover
Example #10
0
def reset_boto_session():
    BotoSession.session = None
    yield
    BotoSession.session = None
    BotoSession.reset_aws_credentials()