diff --git a/endpoints/registry.py b/endpoints/registry.py index ded3f8915..f650398ec 100644 --- a/endpoints/registry.py +++ b/endpoints/registry.py @@ -88,6 +88,43 @@ def set_cache_headers(f): return wrapper +@registry.route('/images//layer', methods=['HEAD']) +@process_auth +@extract_namespace_repo_from_session +@require_completion +@set_cache_headers +def head_image_layer(namespace, repository, image_id, headers): + permission = ReadRepositoryPermission(namespace, repository) + + profile.debug('Checking repo permissions') + if permission.can() or model.repository_is_public(namespace, repository): + profile.debug('Looking up repo image') + repo_image = model.get_repo_image(namespace, repository, image_id) + + # Add the Accept-Ranges header if the storage engine supports resumeable + # downloads. + extra_headers = {} + + profile.debug('Looking up the layer path') + try: + path = store.image_layer_path(repo_image.storage.uuid) + + if store.get_supports_resumeable_downloads(repo_image.storage.locations, path): + profile.debug('Storage supports resumeable downloads') + extra_headers['Accept-Ranges'] = 'bytes'; + + resp = make_response('') + resp.headers.extend(extra_headers) + return resp + + except (IOError, AttributeError): + profile.debug('Image not found') + abort(404, 'Image %(image_id)s not found', issue='unknown-image', + image_id=image_id) + + abort(403) + + @registry.route('/images//layer', methods=['GET']) @process_auth @extract_namespace_repo_from_session @@ -101,13 +138,6 @@ def get_image_layer(namespace, repository, image_id, headers): profile.debug('Looking up repo image') repo_image = model.get_repo_image(namespace, repository, image_id) - # Add the Accept-Ranges header if the storage engine supports resumable - # downloads. - extra_headers = {} - - if store.get_supports_resumable_downloads(): - extra_headers['Accept-Ranges'] = 'bytes'; - profile.debug('Looking up the layer path') try: path = store.image_layer_path(repo_image.storage.uuid) @@ -118,7 +148,6 @@ def get_image_layer(namespace, repository, image_id, headers): if direct_download_url: profile.debug('Returning direct download URL') resp = redirect(direct_download_url) - resp.headers = dict(headers, **extra_headers) return resp profile.debug('Streaming layer data') diff --git a/storage/basestorage.py b/storage/basestorage.py index 94bdec615..a6b2c581c 100644 --- a/storage/basestorage.py +++ b/storage/basestorage.py @@ -57,7 +57,7 @@ class BaseStorage(StoragePaths): def get_direct_download_url(self, path, expires_in=60): return None - def get_supports_resumable_downloads(self): + def get_supports_resumeable_downloads(self, path): return False def get_content(self, path): diff --git a/storage/distributedstorage.py b/storage/distributedstorage.py index 796abdc2b..9941f0fa5 100644 --- a/storage/distributedstorage.py +++ b/storage/distributedstorage.py @@ -39,3 +39,4 @@ class DistributedStorage(StoragePaths): list_directory = _location_aware(BaseStorage.list_directory) exists = _location_aware(BaseStorage.exists) remove = _location_aware(BaseStorage.remove) + get_supports_resumeable_downloads = _location_aware(BaseStorage.get_supports_resumeable_downloads) diff --git a/storage/s3.py b/storage/s3.py index add4ed9dc..1f9c30211 100644 --- a/storage/s3.py +++ b/storage/s3.py @@ -83,7 +83,7 @@ class S3Storage(BaseStorage): key.set_contents_from_string(content, encrypt_key=True) return path - def get_supports_resumable_downloads(self): + def get_supports_resumeable_downloads(self, path): return True def get_direct_download_url(self, path, expires_in=60):