Get resumable downloads working in a way that the Docker CLI will actually understand. Also rename the method to conform the the name used in the Docker source code.
This commit is contained in:
parent
0d89fd7f26
commit
0ac6312c8a
4 changed files with 40 additions and 10 deletions
|
@ -88,6 +88,43 @@ def set_cache_headers(f):
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@registry.route('/images/<image_id>/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/<image_id>/layer', methods=['GET'])
|
@registry.route('/images/<image_id>/layer', methods=['GET'])
|
||||||
@process_auth
|
@process_auth
|
||||||
@extract_namespace_repo_from_session
|
@extract_namespace_repo_from_session
|
||||||
|
@ -101,13 +138,6 @@ def get_image_layer(namespace, repository, image_id, headers):
|
||||||
profile.debug('Looking up repo image')
|
profile.debug('Looking up repo image')
|
||||||
repo_image = model.get_repo_image(namespace, repository, image_id)
|
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')
|
profile.debug('Looking up the layer path')
|
||||||
try:
|
try:
|
||||||
path = store.image_layer_path(repo_image.storage.uuid)
|
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:
|
if direct_download_url:
|
||||||
profile.debug('Returning direct download URL')
|
profile.debug('Returning direct download URL')
|
||||||
resp = redirect(direct_download_url)
|
resp = redirect(direct_download_url)
|
||||||
resp.headers = dict(headers, **extra_headers)
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
profile.debug('Streaming layer data')
|
profile.debug('Streaming layer data')
|
||||||
|
|
|
@ -57,7 +57,7 @@ class BaseStorage(StoragePaths):
|
||||||
def get_direct_download_url(self, path, expires_in=60):
|
def get_direct_download_url(self, path, expires_in=60):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_supports_resumable_downloads(self):
|
def get_supports_resumeable_downloads(self, path):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_content(self, path):
|
def get_content(self, path):
|
||||||
|
|
|
@ -39,3 +39,4 @@ class DistributedStorage(StoragePaths):
|
||||||
list_directory = _location_aware(BaseStorage.list_directory)
|
list_directory = _location_aware(BaseStorage.list_directory)
|
||||||
exists = _location_aware(BaseStorage.exists)
|
exists = _location_aware(BaseStorage.exists)
|
||||||
remove = _location_aware(BaseStorage.remove)
|
remove = _location_aware(BaseStorage.remove)
|
||||||
|
get_supports_resumeable_downloads = _location_aware(BaseStorage.get_supports_resumeable_downloads)
|
||||||
|
|
|
@ -83,7 +83,7 @@ class S3Storage(BaseStorage):
|
||||||
key.set_contents_from_string(content, encrypt_key=True)
|
key.set_contents_from_string(content, encrypt_key=True)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def get_supports_resumable_downloads(self):
|
def get_supports_resumeable_downloads(self, path):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_direct_download_url(self, path, expires_in=60):
|
def get_direct_download_url(self, path, expires_in=60):
|
||||||
|
|
Reference in a new issue