diff --git a/moto/events/models.py b/moto/events/models.py index 767d51864..fbc5ba4a6 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -4,7 +4,6 @@ import re import json import sys import warnings -from collections import namedtuple from datetime import datetime from enum import Enum, unique from json import JSONDecodeError @@ -27,6 +26,7 @@ from moto.events.exceptions import ( IllegalStatusException, ) from moto.moto_api._internal import mock_random as random +from moto.utilities.arns import parse_arn from moto.utilities.paginator import paginate from moto.utilities.tagging_service import TaggingService @@ -37,10 +37,6 @@ UNDEFINED = object() class Rule(CloudFormationModel): - Arn = namedtuple( - "Arn", ["account", "region", "service", "resource_type", "resource_id"] - ) - def __init__( self, name: str, @@ -123,13 +119,13 @@ class Rule(CloudFormationModel): # - SQS Queue + FIFO Queue # - Cross-region/account EventBus for target in self.targets: - arn = self._parse_arn(target["Arn"]) + arn = parse_arn(target["Arn"]) if arn.service == "logs" and arn.resource_type == "log-group": self._send_to_cw_log_group(arn.resource_id, event) elif arn.service == "events" and not arn.resource_type: input_template = json.loads(target["InputTransformer"]["InputTemplate"]) - archive_arn = self._parse_arn(input_template["archive-arn"]) + archive_arn = parse_arn(input_template["archive-arn"]) self._send_to_events_archive(archive_arn.resource_id, event) elif arn.service == "sqs": @@ -149,33 +145,6 @@ class Rule(CloudFormationModel): else: raise NotImplementedError(f"Expr not defined for {type(self)}") - def _parse_arn(self, arn: str) -> Arn: - # http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html - # this method needs probably some more fine tuning, - # when also other targets are supported - _, _, service, region, account, resource = arn.split(":", 5) - - if ":" in resource and "/" in resource: - if resource.index(":") < resource.index("/"): - resource_type, resource_id = resource.split(":", 1) - else: - resource_type, resource_id = resource.split("/", 1) - elif ":" in resource: - resource_type, resource_id = resource.split(":", 1) - elif "/" in resource: - resource_type, resource_id = resource.split("/", 1) - else: - resource_type = None - resource_id = resource - - return self.Arn( - account=account, - region=region, - service=service, - resource_type=resource_type, - resource_id=resource_id, - ) - def _send_to_cw_log_group(self, name: str, event: Dict[str, Any]) -> None: from moto.logs import logs_backends diff --git a/moto/sns/models.py b/moto/sns/models.py index 545d72227..4b87649aa 100644 --- a/moto/sns/models.py +++ b/moto/sns/models.py @@ -34,7 +34,7 @@ from .utils import ( is_e164, FilterPolicyMatcher, ) - +from moto.utilities.arns import parse_arn DEFAULT_PAGE_SIZE = 100 MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB @@ -486,13 +486,15 @@ class SNSBackend(BaseBackend): try: topic = self.get_topic(arn) self.delete_topic_subscriptions(topic) - self.topics.pop(arn) + parsed_arn = parse_arn(arn) + sns_backends[parsed_arn.account][parsed_arn.region].topics.pop(arn, None) except KeyError: raise SNSNotFoundError(f"Topic with arn {arn} not found") def get_topic(self, arn: str) -> Topic: + parsed_arn = parse_arn(arn) try: - return self.topics[arn] + return sns_backends[parsed_arn.account][parsed_arn.region].topics[arn] except KeyError: raise SNSNotFoundError(f"Topic with arn {arn} not found") @@ -932,10 +934,8 @@ class SNSBackend(BaseBackend): aws_account_ids: List[str], action_names: List[str], ) -> None: - if topic_arn not in self.topics: - raise SNSNotFoundError("Topic does not exist") - - policy = self.topics[topic_arn]._policy_json + topic = self.get_topic(topic_arn) + policy = topic._policy_json statement = next( ( statement @@ -964,18 +964,16 @@ class SNSBackend(BaseBackend): "Resource": topic_arn, } - self.topics[topic_arn]._policy_json["Statement"].append(statement) + topic._policy_json["Statement"].append(statement) def remove_permission(self, topic_arn: str, label: str) -> None: - if topic_arn not in self.topics: - raise SNSNotFoundError("Topic does not exist") - - statements = self.topics[topic_arn]._policy_json["Statement"] + topic = self.get_topic(topic_arn) + statements = topic._policy_json["Statement"] statements = [ statement for statement in statements if statement["Sid"] != label ] - self.topics[topic_arn]._policy_json["Statement"] = statements + topic._policy_json["Statement"] = statements def list_tags_for_resource(self, resource_arn: str) -> Dict[str, str]: if resource_arn not in self.topics: diff --git a/moto/utilities/arns.py b/moto/utilities/arns.py new file mode 100644 index 000000000..51a6753ac --- /dev/null +++ b/moto/utilities/arns.py @@ -0,0 +1,33 @@ +from collections import namedtuple + +Arn = namedtuple( + "Arn", ["account", "region", "service", "resource_type", "resource_id"] +) + + +def parse_arn(arn: str) -> Arn: + # http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html + # this method needs probably some more fine tuning, + # when also other targets are supported + _, _, service, region, account, resource = arn.split(":", 5) + + if ":" in resource and "/" in resource: + if resource.index(":") < resource.index("/"): + resource_type, resource_id = resource.split(":", 1) + else: + resource_type, resource_id = resource.split("/", 1) + elif ":" in resource: + resource_type, resource_id = resource.split(":", 1) + elif "/" in resource: + resource_type, resource_id = resource.split("/", 1) + else: + resource_type = None + resource_id = resource + + return Arn( + account=account, + region=region, + service=service, + resource_type=resource_type, + resource_id=resource_id, + ) diff --git a/tests/test_sns/test_topics_boto3.py b/tests/test_sns/test_topics_boto3.py index 76844c8aa..c0bcade9b 100644 --- a/tests/test_sns/test_topics_boto3.py +++ b/tests/test_sns/test_topics_boto3.py @@ -96,9 +96,9 @@ def test_create_topic_should_be_indempodent(): @mock_sns def test_get_missing_topic(): conn = boto3.client("sns", region_name="us-east-1") - conn.get_topic_attributes.when.called_with(TopicArn="a-fake-arn").should.throw( - ClientError - ) + conn.get_topic_attributes.when.called_with( + TopicArn="arn:aws:sns:us-east-1:424242424242:a-fake-arn" + ).should.throw(ClientError) @mock_sns @@ -360,7 +360,10 @@ def test_add_permission_errors(): Label="test-2", AWSAccountId=["999999999999"], ActionName=["AddPermission"], - ).should.throw(ClientError, "Topic does not exist") + ).should.throw( + ClientError, + f"An error occurred (NotFound) when calling the AddPermission operation: Topic with arn {topic_arn + '-not-existing'} not found", + ) client.add_permission.when.called_with( TopicArn=topic_arn, @@ -383,7 +386,10 @@ def test_remove_permission_errors(): client.remove_permission.when.called_with( TopicArn=topic_arn + "-not-existing", Label="test" - ).should.throw(ClientError, "Topic does not exist") + ).should.throw( + ClientError, + f"An error occurred (NotFound) when calling the RemovePermission operation: Topic with arn {topic_arn + '-not-existing'} not found", + ) @mock_sns