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 | ||||
| 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: | ||||
|  | ||||
| @ -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 | ||||
|         }) | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
| @ -7,5 +7,5 @@ flake8 | ||||
| freezegun | ||||
| flask | ||||
| boto3>=1.4.4 | ||||
| botocore>=1.4.28 | ||||
| botocore>=1.5.77 | ||||
| six | ||||
|  | ||||
| @ -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') | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user