Repository: getmoto/moto
Pull request #6479 (2023-07-04 10:23:27)

ECR: add CreateRepository name validation
This is a small fix to add name validation for the `repository-name` within ECR's `create-repository`.
Currently there is no name validation during the creation - but the [documentation](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_CreateRepository.html#API_CreateRepository_RequestSyntax) states that it needs to follow a regex pattern. I found this through a [localstack issue](https://github.com/localstack/localstack/issues/8051).


Patch:
diff --git a/moto/core/exceptions.py b/moto/core/exceptions.py
index 5c0e306c2..51f219e1e 100644
--- a/moto/core/exceptions.py
+++ b/moto/core/exceptions.py
@@ -3,8 +3,6 @@ from jinja2 import DictLoader, Environment
 from typing import Any, List, Tuple, Optional
 import json
 
-# TODO: add "<Type>Sender</Type>" to error responses below?
-
 
 SINGLE_ERROR_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
 <Error>
@@ -20,6 +18,9 @@ WRAPPED_SINGLE_ERROR_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
     <Error>
         <Code>{{error_type}}</Code>
         <Message><![CDATA[{{message}}]]></Message>
+        {% if include_type_sender %}
+        <Type>Sender</Type>
+        {% endif %}
         {% block extra %}{% endblock %}
         <{{request_id_tag}}>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</{{request_id_tag}}>
     </Error>
@@ -44,6 +45,10 @@ class RESTError(HTTPException):
     # most APIs use <RequestId>, but some APIs (including EC2, S3) use <RequestID>
     request_id_tag_name = "RequestId"
 
+    # When this field is set, the `Type` field will be included in the response
+    # This indicates that the fault lies with the client
+    include_type_sender = True
+
     templates = {
         "single_error": SINGLE_ERROR_RESPONSE,
         "wrapped_single_error": WRAPPED_SINGLE_ERROR_RESPONSE,
@@ -63,6 +68,7 @@ class RESTError(HTTPException):
                 error_type=error_type,
                 message=message,
                 request_id_tag=self.request_id_tag_name,
+                include_type_sender=self.include_type_sender,
                 **kwargs,
             )
             self.content_type = "application/xml"
diff --git a/moto/ecr/models.py b/moto/ecr/models.py
index ced3be688..f2d2961bf 100644
--- a/moto/ecr/models.py
+++ b/moto/ecr/models.py
@@ -30,6 +30,9 @@ from moto.moto_api._internal import mock_random as random
 from moto.utilities.tagging_service import TaggingService
 
 ECR_REPOSITORY_ARN_PATTERN = "^arn:(?P<partition>[^:]+):ecr:(?P<region>[^:]+):(?P<account_id>[^:]+):repository/(?P<repo_name>.*)$"
+ECR_REPOSITORY_NAME_PATTERN = (
+    "(?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)*[a-z0-9]+(?:[._-][a-z0-9]+)*"
+)
 
 EcrRepositoryArn = namedtuple(
     "EcrRepositoryArn", ["partition", "region", "account_id", "repo_name"]
@@ -466,6 +469,12 @@ class ECRBackend(BaseBackend):
         if self.repositories.get(repository_name):
             raise RepositoryAlreadyExistsException(repository_name, self.account_id)
 
+        match = re.fullmatch(ECR_REPOSITORY_NAME_PATTERN, repository_name)
+        if not match:
+            raise InvalidParameterException(
+                f"Invalid parameter at 'repositoryName' failed to satisfy constraint: 'must satisfy regular expression '{ECR_REPOSITORY_NAME_PATTERN}'"
+            )
+
         repository = Repository(
             account_id=self.account_id,
             region_name=self.region_name,
diff --git a/moto/sns/exceptions.py b/moto/sns/exceptions.py
index 705d5539f..b1704e303 100644
--- a/moto/sns/exceptions.py
+++ b/moto/sns/exceptions.py
@@ -67,6 +67,7 @@ class TagLimitExceededError(SNSException):
 
 class InternalError(SNSException):
     code = 500
+    include_type_sender = False
 
     def __init__(self, message: str):
         super().__init__("InternalFailure", message)


Test patch:
diff --git a/tests/test_ecr/test_ecr_boto3.py b/tests/test_ecr/test_ecr_boto3.py
index ca1479ebe..f3856b455 100644
--- a/tests/test_ecr/test_ecr_boto3.py
+++ b/tests/test_ecr/test_ecr_boto3.py
@@ -156,6 +156,19 @@ def test_create_repository_error_already_exists():
     )
 
 
+@mock_ecr
+def test_create_repository_error_name_validation():
+    client = boto3.client("ecr", region_name="eu-central-1")
+    repo_name = "tesT"
+
+    with pytest.raises(ClientError) as e:
+        client.create_repository(repositoryName=repo_name)
+
+    ex = e.value
+    ex.operation_name.should.equal("CreateRepository")
+    ex.response["Error"]["Code"].should.contain("InvalidParameterException")
+
+
 @mock_ecr
 def test_describe_repositories():
     client = boto3.client("ecr", region_name="us-east-1")
diff --git a/tests/test_sns/test_topics_boto3.py b/tests/test_sns/test_topics_boto3.py
index 964b724fc..cb99afb4a 100644
--- a/tests/test_sns/test_topics_boto3.py
+++ b/tests/test_sns/test_topics_boto3.py
@@ -148,6 +148,7 @@ def test_create_topic_in_multiple_regions():
     err = exc.value.response["Error"]
     assert err["Code"] == "NotFound"
     assert err["Message"] == "Topic does not exist"
+    assert err["Type"] == "Sender"
 
 
 @mock_sns
@@ -538,6 +539,7 @@ def test_create_fifo_topic():
             "Fifo Topic names must end with .fifo and must be made up of only uppercase and lowercase ASCII letters, "
             "numbers, underscores, and hyphens, and must be between 1 and 256 characters long."
         )
+        err.response["Error"]["Type"].should.equal("Sender")
 
     try:
         conn.create_topic(Name="test_topic.fifo")