Merge pull request #1046 from spulec/feature/add-proper-errors-to-ecr-calls
Add proper errors to ECR calls
This commit is contained in:
commit
9ebcaf561e
22
moto/ecr/exceptions.py
Normal file
22
moto/ecr/exceptions.py
Normal 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))
|
@ -7,6 +7,11 @@ from moto.ec2 import ec2_backends
|
|||||||
from copy import copy
|
from copy import copy
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
|
from moto.ecr.exceptions import ImageNotFoundException, RepositoryNotFoundException
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_REGISTRY_ID = '012345678910'
|
||||||
|
|
||||||
|
|
||||||
class BaseObject(BaseModel):
|
class BaseObject(BaseModel):
|
||||||
|
|
||||||
@ -35,14 +40,13 @@ class BaseObject(BaseModel):
|
|||||||
class Repository(BaseObject):
|
class Repository(BaseObject):
|
||||||
|
|
||||||
def __init__(self, repository_name):
|
def __init__(self, repository_name):
|
||||||
self.arn = 'arn:aws:ecr:us-east-1:012345678910:repository/{0}'.format(
|
self.registry_id = DEFAULT_REGISTRY_ID
|
||||||
repository_name)
|
self.arn = 'arn:aws:ecr:us-east-1:{0}:repository/{1}'.format(
|
||||||
|
self.registry_id, repository_name)
|
||||||
self.name = repository_name
|
self.name = repository_name
|
||||||
# self.created = datetime.utcnow()
|
# self.created = datetime.utcnow()
|
||||||
self.uri = '012345678910.dkr.ecr.us-east-1.amazonaws.com/{0}'.format(
|
self.uri = '{0}.dkr.ecr.us-east-1.amazonaws.com/{1}'.format(
|
||||||
repository_name
|
self.registry_id, repository_name)
|
||||||
)
|
|
||||||
self.registry_id = '012345678910'
|
|
||||||
self.images = []
|
self.images = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -93,7 +97,7 @@ class Repository(BaseObject):
|
|||||||
|
|
||||||
class Image(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_tag = tag
|
||||||
self.image_manifest = manifest
|
self.image_manifest = manifest
|
||||||
self.image_size_in_bytes = 50 * 1024 * 1024
|
self.image_size_in_bytes = 50 * 1024 * 1024
|
||||||
@ -151,6 +155,11 @@ class ECRBackend(BaseBackend):
|
|||||||
"""
|
"""
|
||||||
maxResults and nextToken not implemented
|
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 = []
|
repositories = []
|
||||||
for repository in self.repositories.values():
|
for repository in self.repositories.values():
|
||||||
# If a registry_id was supplied, ensure this repository matches
|
# If a registry_id was supplied, ensure this repository matches
|
||||||
@ -170,11 +179,11 @@ class ECRBackend(BaseBackend):
|
|||||||
self.repositories[repository_name] = repository
|
self.repositories[repository_name] = repository
|
||||||
return repository
|
return repository
|
||||||
|
|
||||||
def delete_repository(self, respository_name, registry_id=None):
|
def delete_repository(self, repository_name, registry_id=None):
|
||||||
if respository_name in self.repositories:
|
if repository_name in self.repositories:
|
||||||
return self.repositories.pop(respository_name)
|
return self.repositories.pop(repository_name)
|
||||||
else:
|
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):
|
def list_images(self, repository_name, registry_id=None):
|
||||||
"""
|
"""
|
||||||
@ -198,17 +207,27 @@ class ECRBackend(BaseBackend):
|
|||||||
if repository_name in self.repositories:
|
if repository_name in self.repositories:
|
||||||
repository = self.repositories[repository_name]
|
repository = self.repositories[repository_name]
|
||||||
else:
|
else:
|
||||||
raise Exception("{0} is not a repository".format(repository_name))
|
raise RepositoryNotFoundException(repository_name, registry_id or DEFAULT_REGISTRY_ID)
|
||||||
|
|
||||||
if image_ids:
|
if image_ids:
|
||||||
response = set()
|
response = set()
|
||||||
for image_id in image_ids:
|
for image_id in image_ids:
|
||||||
if 'imageDigest' in image_id:
|
found = False
|
||||||
desired_digest = image_id['imageDigest']
|
for image in repository.images:
|
||||||
response.update([i for i in repository.images if i.get_image_digest() == desired_digest])
|
if (('imageDigest' in image_id and image.get_image_digest() == image_id['imageDigest']) or
|
||||||
if 'imageTag' in image_id:
|
('imageTag' in image_id and image.image_tag == image_id['imageTag'])):
|
||||||
desired_tag = image_id['imageTag']
|
found = True
|
||||||
response.update([i for i in repository.images if i.image_tag == desired_tag])
|
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:
|
else:
|
||||||
response = []
|
response = []
|
||||||
for image in repository.images:
|
for image in repository.images:
|
||||||
|
@ -45,7 +45,8 @@ class ECRResponse(BaseResponse):
|
|||||||
|
|
||||||
def delete_repository(self):
|
def delete_repository(self):
|
||||||
repository_str = self._get_param('repositoryName')
|
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({
|
return json.dumps({
|
||||||
'repository': repository.response_object
|
'repository': repository.response_object
|
||||||
})
|
})
|
||||||
|
@ -59,6 +59,7 @@ class Message(BaseModel):
|
|||||||
return str.encode('utf-8')
|
return str.encode('utf-8')
|
||||||
return str
|
return str
|
||||||
md5 = hashlib.md5()
|
md5 = hashlib.md5()
|
||||||
|
struct_format = "!I".encode('ascii') # ensure it's a bytestring
|
||||||
for name in sorted(self.message_attributes.keys()):
|
for name in sorted(self.message_attributes.keys()):
|
||||||
attr = self.message_attributes[name]
|
attr = self.message_attributes[name]
|
||||||
data_type = attr['data_type']
|
data_type = attr['data_type']
|
||||||
@ -67,10 +68,10 @@ class Message(BaseModel):
|
|||||||
# Each part of each attribute is encoded right after it's
|
# Each part of each attribute is encoded right after it's
|
||||||
# own length is packed into a 4-byte integer
|
# own length is packed into a 4-byte integer
|
||||||
# 'timestamp' -> b'\x00\x00\x00\t'
|
# '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
|
# The datatype is additionally given a final byte
|
||||||
# representing which type it is
|
# 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]
|
encoded += TRANSPORT_TYPE_ENCODINGS[data_type]
|
||||||
|
|
||||||
if data_type == 'String' or data_type == 'Number':
|
if data_type == 'String' or data_type == 'Number':
|
||||||
@ -86,7 +87,7 @@ class Message(BaseModel):
|
|||||||
# MD5 so as not to break client softwre
|
# MD5 so as not to break client softwre
|
||||||
return('deadbeefdeadbeefdeadbeefdeadbeef')
|
return('deadbeefdeadbeefdeadbeefdeadbeef')
|
||||||
|
|
||||||
encoded += struct.pack("!I", len(utf8(value))) + utf8(value)
|
encoded += struct.pack(struct_format, len(utf8(value))) + utf8(value)
|
||||||
|
|
||||||
md5.update(encoded)
|
md5.update(encoded)
|
||||||
return md5.hexdigest()
|
return md5.hexdigest()
|
||||||
|
@ -7,5 +7,5 @@ flake8
|
|||||||
freezegun
|
freezegun
|
||||||
flask
|
flask
|
||||||
boto3>=1.4.4
|
boto3>=1.4.4
|
||||||
botocore>=1.4.28
|
botocore>=1.5.77
|
||||||
six
|
six
|
||||||
|
@ -5,9 +5,11 @@ import json
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from random import random
|
from random import random
|
||||||
|
|
||||||
|
import re
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
from dateutil.tz import tzlocal
|
from dateutil.tz import tzlocal
|
||||||
|
|
||||||
from moto import mock_ecr
|
from moto import mock_ecr
|
||||||
@ -141,19 +143,6 @@ def test_describe_repositories_3():
|
|||||||
response['repositories'][0]['repositoryUri'].should.equal(respository_uri)
|
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
|
@mock_ecr
|
||||||
def test_describe_repositories_with_image():
|
def test_describe_repositories_with_image():
|
||||||
client = boto3.client('ecr', region_name='us-east-1')
|
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'])
|
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
|
@mock_ecr
|
||||||
def test_describe_images_by_digest():
|
def test_describe_images_by_digest():
|
||||||
client = boto3.client('ecr', region_name='us-east-1')
|
client = boto3.client('ecr', region_name='us-east-1')
|
||||||
|
Loading…
Reference in New Issue
Block a user