diff --git a/data/model.py b/data/model.py index 31a432b88..cc3646f03 100644 --- a/data/model.py +++ b/data/model.py @@ -348,3 +348,8 @@ def delete_user_permission(username, namespace_name, repository_name): raise DataModelException('User does not have permission for repo.') fetched[0].delete_instance() + +def purge_repository(namespace_name, repository_name): + fetched = Repository.get(Repository.name == repository_name, + Repository.namespace == namespace_name) + fetched.delete_instance(recursive=True, delete_nullable=True) diff --git a/endpoints/api.py b/endpoints/api.py index 2299ba648..1f09d8b27 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -11,6 +11,7 @@ from util.gravatar import compute_hash from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission, AdministerRepositoryPermission) +from endpoints.registry import delete_registry_storage logger = logging.getLogger(__name__) @@ -138,6 +139,19 @@ def change_repo_visibility_api(namespace, repository): abort(404) +@app.route('/api/repository/', methods=['DELETE']) +@api_login_required +@parse_repository_name +def delete_repository(namespace, repository): + permission = AdministerRepositoryPermission(namespace, repository) + if permission.can(): + model.purge_repository(namespace, repository) + delete_registry_storage(namespace, repository) + return make_response('Deleted', 204) + + abort(404) + + def image_view(image): return { 'id': image.image_id, diff --git a/endpoints/registry.py b/endpoints/registry.py index 42aebf19e..830575d11 100644 --- a/endpoints/registry.py +++ b/endpoints/registry.py @@ -302,3 +302,11 @@ def put_image_json(namespace, repository, image_id): store.put_content(json_path, request.data) generate_ancestry(namespace, repository, image_id, parent_id) return make_response('true', 200) + + +def delete_registry_storage(namespace, repository): + """ Caller should have already verified proper permissions. """ + repository_path = store.repository_namespace_path(namespace, repository) + + logger.debug('Recursively deleting path: %s' % repository_path) + store.remove(repository_path) \ No newline at end of file diff --git a/endpoints/tags.py b/endpoints/tags.py index b9b331c7f..8b29d02b9 100644 --- a/endpoints/tags.py +++ b/endpoints/tags.py @@ -82,7 +82,7 @@ def delete_tag(namespace, repository, tag): methods=['DELETE']) @process_auth @parse_repository_name -def delete_repository(namespace, repository): +def delete_repository_tags(namespace, repository): permission = ModifyRepositoryPermission(namespace, repository) if permission.can(): diff --git a/storage/__init__.py b/storage/__init__.py index ddcc5948c..2a26b5883 100644 --- a/storage/__init__.py +++ b/storage/__init__.py @@ -28,43 +28,31 @@ class Storage(object): #FIXME(samalba): Move all path resolver in each module (out of the base) def images_list_path(self, namespace, repository): return '{0}/{1}/{2}/_images_list'.format(self.repositories, - namespace, - repository) + namespace, + repository) def image_json_path(self, namespace, repository, image_id): return '{0}/{1}/{2}/{3}/json'.format(self.images, namespace, - repository, image_id) + repository, image_id) def image_mark_path(self, namespace, repository, image_id): return '{0}/{1}/{2}/{3}/_inprogress'.format(self.images, namespace, - repository, image_id) + repository, image_id) def image_checksum_path(self, namespace, repository, image_id): return '{0}/{1}/{2}/{3}/_checksum'.format(self.images, namespace, - repository, image_id) + repository, image_id) def image_layer_path(self, namespace, repository, image_id): return '{0}/{1}/{2}/{3}/layer'.format(self.images, namespace, - repository, image_id) + repository, image_id) def image_ancestry_path(self, namespace, repository, image_id): return '{0}/{1}/{2}/{3}/ancestry'.format(self.images, namespace, - repository, image_id) + repository, image_id) - def tag_path(self, namespace, repository, tagname=None): - if not tagname: - return '{0}/{1}/{2}'.format(self.repositories, - namespace, - repository) - return '{0}/{1}/{2}/tag_{3}'.format(self.repositories, - namespace, - repository, - tagname) - - def index_images_path(self, namespace, repository): - return '{0}/{1}/{2}/_index_images'.format(self.repositories, - namespace, - repository) + def repository_namespace_path(self, namespace, repository): + return '{0}/{1}/{2}/'.format(self.images, namespace, repository) def get_content(self, path): raise NotImplementedError