2015-07-16 19:49:06 +00:00
|
|
|
# XXX This code is not yet ready to be run in production, and should remain disabled until such
|
|
|
|
# XXX time as this notice is removed.
|
|
|
|
|
2015-07-06 19:00:07 +00:00
|
|
|
import logging
|
|
|
|
|
|
|
|
from flask import make_response, url_for, request
|
|
|
|
|
2015-07-15 21:25:41 +00:00
|
|
|
from app import storage, app
|
|
|
|
from data import model
|
2015-07-06 19:00:07 +00:00
|
|
|
from digest import digest_tools
|
|
|
|
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write, get_input_stream
|
|
|
|
from auth.jwt_auth import process_jwt_auth
|
|
|
|
from endpoints.decorators import anon_protect
|
|
|
|
from util.http import abort
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2015-07-16 19:49:06 +00:00
|
|
|
BASE_BLOB_ROUTE = '/<namespace>/<repo_name>/blobs/<regex("{0}"):digest>'
|
|
|
|
BLOB_DIGEST_ROUTE = BASE_BLOB_ROUTE.format(digest_tools.DIGEST_PATTERN)
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route(BLOB_DIGEST_ROUTE, methods=['HEAD'])
|
2015-07-06 19:00:07 +00:00
|
|
|
@process_jwt_auth
|
|
|
|
@require_repo_read
|
|
|
|
@anon_protect
|
|
|
|
def check_blob_existence(namespace, repo_name, digest):
|
|
|
|
try:
|
2015-07-15 21:25:41 +00:00
|
|
|
found = model.blob.get_repo_blob_by_digest(namespace, repo_name, digest)
|
2015-07-06 19:00:07 +00:00
|
|
|
|
|
|
|
# The response body must be empty for a successful HEAD request
|
|
|
|
return make_response('')
|
2015-07-15 21:25:41 +00:00
|
|
|
except model.BlobDoesNotExist:
|
2015-07-06 19:00:07 +00:00
|
|
|
abort(404)
|
|
|
|
|
|
|
|
|
2015-07-16 19:49:06 +00:00
|
|
|
@v2_bp.route(BLOB_DIGEST_ROUTE, methods=['GET'])
|
2015-07-06 19:00:07 +00:00
|
|
|
@process_jwt_auth
|
|
|
|
@require_repo_read
|
|
|
|
@anon_protect
|
|
|
|
def download_blob(namespace, repo_name, digest):
|
2015-07-16 19:49:06 +00:00
|
|
|
# TODO Implement this
|
2015-07-06 19:00:07 +00:00
|
|
|
return make_response('')
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/<namespace>/<repo_name>/blobs/uploads/', methods=['POST'])
|
|
|
|
@process_jwt_auth
|
|
|
|
@require_repo_write
|
|
|
|
@anon_protect
|
|
|
|
def start_blob_upload(namespace, repo_name):
|
|
|
|
new_upload_uuid = storage.initiate_chunked_upload(storage.preferred_locations[0])
|
|
|
|
accepted = make_response('', 202)
|
|
|
|
accepted.headers['Location'] = url_for('v2.upload_chunk', namespace=namespace,
|
|
|
|
repo_name=repo_name, upload_uuid=new_upload_uuid)
|
|
|
|
accepted.headers['Range'] = 'bytes=0-0'
|
|
|
|
accepted.headers['Docker-Upload-UUID'] = new_upload_uuid
|
|
|
|
return accepted
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/<namespace>/<repo_name>/blobs/uploads/<upload_uuid>', methods=['PUT'])
|
|
|
|
@process_jwt_auth
|
|
|
|
@require_repo_write
|
|
|
|
@anon_protect
|
|
|
|
def upload_chunk(namespace, repo_name, upload_uuid):
|
|
|
|
digest = request.args.get('digest', None)
|
|
|
|
upload_location = storage.preferred_locations[0]
|
|
|
|
bytes_written = storage.stream_upload_chunk(upload_location, upload_uuid, 0, -1,
|
|
|
|
get_input_stream(request))
|
|
|
|
|
|
|
|
if digest is not None:
|
|
|
|
final_blob_location = digest_tools.content_path(digest)
|
|
|
|
storage.complete_chunked_upload(upload_location, upload_uuid, final_blob_location, digest)
|
2015-07-15 21:25:41 +00:00
|
|
|
model.blob.store_blob_record_and_temp_link(namespace, repo_name, digest, upload_location,
|
|
|
|
app.config['PUSH_TEMP_TAG_EXPIRATION_SEC'])
|
2015-07-06 19:00:07 +00:00
|
|
|
|
|
|
|
response = make_response('', 201)
|
|
|
|
response.headers['Docker-Content-Digest'] = digest
|
|
|
|
response.headers['Location'] = url_for('v2.download_blob', namespace=namespace,
|
|
|
|
repo_name=repo_name, digest=digest)
|
|
|
|
return response
|
|
|
|
|
|
|
|
return make_response('', 202)
|