Merge pull request #1046 from spulec/feature/add-proper-errors-to-ecr-calls

Add proper errors to ECR calls
This commit is contained in:
Steve Pulec 2017-08-10 21:40:55 -04:00 committed by GitHub
commit 9ebcaf561e
6 changed files with 116 additions and 36 deletions

22
moto/ecr/exceptions.py Normal file
View File

@ -0,0 +1,22 @@
from __future__ import unicode_literals
from moto.core.exceptions import RESTError
class RepositoryNotFoundException(RESTError):
code = 400
def __init__(self, repository_name, registry_id):
super(RepositoryNotFoundException, self).__init__(
error_type="RepositoryNotFoundException",
message="The repository with name '{0}' does not exist in the registry "
"with id '{1}'".format(repository_name, registry_id))
class ImageNotFoundException(RESTError):
code = 400
def __init__(self, image_id, repository_name, registry_id):
super(ImageNotFoundException, self).__init__(
error_type="ImageNotFoundException",
message="The image with imageId {0} does not exist within the repository with name '{1}' "
"in the registry with id '{2}'".format(image_id, repository_name, registry_id))

View File

@ -7,6 +7,11 @@ from moto.ec2 import ec2_backends
from copy import copy
import hashlib
from moto.ecr.exceptions import ImageNotFoundException, RepositoryNotFoundException
DEFAULT_REGISTRY_ID = '012345678910'
class BaseObject(BaseModel):
@ -35,14 +40,13 @@ class BaseObject(BaseModel):
class Repository(BaseObject):
def __init__(self, repository_name):
self.arn = 'arn:aws:ecr:us-east-1:012345678910:repository/{0}'.format(
repository_name)
self.registry_id = DEFAULT_REGISTRY_ID
self.arn = 'arn:aws:ecr:us-east-1:{0}:repository/{1}'.format(
self.registry_id, repository_name)
self.name = repository_name
# self.created = datetime.utcnow()
self.uri = '012345678910.dkr.ecr.us-east-1.amazonaws.com/{0}'.format(
repository_name
)
self.registry_id = '012345678910'
self.uri = '{0}.dkr.ecr.us-east-1.amazonaws.com/{1}'.format(
self.registry_id, repository_name)
self.images = []
@property
@ -93,7 +97,7 @@ class Repository(BaseObject):
class Image(BaseObject):
def __init__(self, tag, manifest, repository, registry_id="012345678910"):
def __init__(self, tag, manifest, repository, registry_id=DEFAULT_REGISTRY_ID):
self.image_tag = tag
self.image_manifest = manifest
self.image_size_in_bytes = 50 * 1024 * 1024
@ -151,6 +155,11 @@ class ECRBackend(BaseBackend):
"""
maxResults and nextToken not implemented
"""
if repository_names:
for repository_name in repository_names:
if repository_name not in self.repositories:
raise RepositoryNotFoundException(repository_name, registry_id or DEFAULT_REGISTRY_ID)
repositories = []
for repository in self.repositories.values():
# If a registry_id was supplied, ensure this repository matches
@ -170,11 +179,11 @@ class ECRBackend(BaseBackend):
self.repositories[repository_name] = repository
return repository
def delete_repository(self, respository_name, registry_id=None):
if respository_name in self.repositories:
return self.repositories.pop(respository_name)
def delete_repository(self, repository_name, registry_id=None):
if repository_name in self.repositories:
return self.repositories.pop(repository_name)
else:
raise Exception("{0} is not a repository".format(respository_name))
raise RepositoryNotFoundException(repository_name, registry_id or DEFAULT_REGISTRY_ID)
def list_images(self, repository_name, registry_id=None):
"""
@ -198,17 +207,27 @@ class ECRBackend(BaseBackend):
if repository_name in self.repositories:
repository = self.repositories[repository_name]
else:
raise Exception("{0} is not a repository".format(repository_name))
raise RepositoryNotFoundException(repository_name, registry_id or DEFAULT_REGISTRY_ID)
if image_ids:
response = set()
for image_id in image_ids:
if 'imageDigest' in image_id:
desired_digest = image_id['imageDigest']
response.update([i for i in repository.images if i.get_image_digest() == desired_digest])
if 'imageTag' in image_id:
desired_tag = image_id['imageTag']
response.update([i for i in repository.images if i.image_tag == desired_tag])
found = False
for image in repository.images:
if (('imageDigest' in image_id and image.get_image_digest() == image_id['imageDigest']) or
('imageTag' in image_id and image.image_tag == image_id['imageTag'])):
found = True
response.add(image)
if not found:
image_id_representation = "{imageDigest:'%s', imageTag:'%s'}" % (
image_id.get('imageDigest', 'null'),
image_id.get('imageTag', 'null'),
)
raise ImageNotFoundException(
image_id=image_id_representation,
repository_name=repository_name,
registry_id=registry_id or DEFAULT_REGISTRY_ID)
else:
response = []
for image in repository.images:

View File

@ -45,7 +45,8 @@ class ECRResponse(BaseResponse):
def delete_repository(self):
repository_str = self._get_param('repositoryName')
repository = self.ecr_backend.delete_repository(repository_str)
registry_id = self._get_param('registryId')
repository = self.ecr_backend.delete_repository(repository_str, registry_id)
return json.dumps({
'repository': repository.response_object
})

View File

@ -59,6 +59,7 @@ class Message(BaseModel):
return str.encode('utf-8')
return str
md5 = hashlib.md5()
struct_format = "!I".encode('ascii') # ensure it's a bytestring
for name in sorted(self.message_attributes.keys()):
attr = self.message_attributes[name]
data_type = attr['data_type']
@ -67,10 +68,10 @@ class Message(BaseModel):
# Each part of each attribute is encoded right after it's
# own length is packed into a 4-byte integer
# 'timestamp' -> b'\x00\x00\x00\t'
encoded += struct.pack("!I", len(utf8(name))) + utf8(name)
encoded += struct.pack(struct_format, len(utf8(name))) + utf8(name)
# The datatype is additionally given a final byte
# representing which type it is
encoded += struct.pack("!I", len(data_type)) + utf8(data_type)
encoded += struct.pack(struct_format, len(data_type)) + utf8(data_type)
encoded += TRANSPORT_TYPE_ENCODINGS[data_type]
if data_type == 'String' or data_type == 'Number':
@ -86,7 +87,7 @@ class Message(BaseModel):
# MD5 so as not to break client softwre
return('deadbeefdeadbeefdeadbeefdeadbeef')
encoded += struct.pack("!I", len(utf8(value))) + utf8(value)
encoded += struct.pack(struct_format, len(utf8(value))) + utf8(value)
md5.update(encoded)
return md5.hexdigest()

View File

@ -7,5 +7,5 @@ flake8
freezegun
flask
boto3>=1.4.4
botocore>=1.4.28
botocore>=1.5.77
six

View File

@ -5,9 +5,11 @@ import json
from datetime import datetime
from random import random
import re
import sure # noqa
import boto3
from botocore.exceptions import ClientError
from dateutil.tz import tzlocal
from moto import mock_ecr
@ -141,19 +143,6 @@ def test_describe_repositories_3():
response['repositories'][0]['repositoryUri'].should.equal(respository_uri)
@mock_ecr
def test_describe_repositories_4():
client = boto3.client('ecr', region_name='us-east-1')
_ = client.create_repository(
repositoryName='test_repository1'
)
_ = client.create_repository(
repositoryName='test_repository0'
)
response = client.describe_repositories(repositoryNames=['not_a_valid_name'])
len(response['repositories']).should.equal(0)
@mock_ecr
def test_describe_repositories_with_image():
client = boto3.client('ecr', region_name='us-east-1')
@ -344,6 +333,54 @@ def test_describe_images_by_tag():
image_detail['imageDigest'].should.equal(put_response['imageId']['imageDigest'])
@mock_ecr
def test_describe_repository_that_doesnt_exist():
client = boto3.client('ecr', region_name='us-east-1')
error_msg = re.compile(
r".*The repository with name 'repo-that-doesnt-exist' does not exist in the registry with id '123'.*",
re.MULTILINE)
client.describe_repositories.when.called_with(
repositoryNames=['repo-that-doesnt-exist'],
registryId='123',
).should.throw(ClientError, error_msg)
@mock_ecr
def test_describe_image_that_doesnt_exist():
client = boto3.client('ecr', region_name='us-east-1')
client.create_repository(repositoryName='test_repository')
error_msg1 = re.compile(
r".*The image with imageId {imageDigest:'null', imageTag:'testtag'} does not exist within "
r"the repository with name 'test_repository' in the registry with id '123'.*",
re.MULTILINE)
client.describe_images.when.called_with(
repositoryName='test_repository', imageIds=[{'imageTag': 'testtag'}], registryId='123',
).should.throw(ClientError, error_msg1)
error_msg2 = re.compile(
r".*The repository with name 'repo-that-doesnt-exist' does not exist in the registry with id '123'.*",
re.MULTILINE)
client.describe_images.when.called_with(
repositoryName='repo-that-doesnt-exist', imageIds=[{'imageTag': 'testtag'}], registryId='123',
).should.throw(ClientError, error_msg2)
@mock_ecr
def test_delete_repository_that_doesnt_exist():
client = boto3.client('ecr', region_name='us-east-1')
error_msg = re.compile(
r".*The repository with name 'repo-that-doesnt-exist' does not exist in the registry with id '123'.*",
re.MULTILINE)
client.delete_repository.when.called_with(
repositoryName='repo-that-doesnt-exist',
registryId='123').should.throw(
ClientError, error_msg)
@mock_ecr
def test_describe_images_by_digest():
client = boto3.client('ecr', region_name='us-east-1')