bea8b9ac53
Implement the minimal changes to the local filesystem storage driver and feed them through the distributed storage driver. Create a digest package which contains digest_tools and checksums. Fix the tests to use the new v1 endpoint locations. Fix repository.delete_instance to properly filter the generated queries to avoid most subquery deletes, but still generate them when not explicitly filtered.
107 lines
3.2 KiB
Python
107 lines
3.2 KiB
Python
import logging
|
|
import re
|
|
import jwt.utils
|
|
import yaml
|
|
|
|
from flask import make_response, request
|
|
|
|
from app import storage
|
|
from auth.jwt_auth import process_jwt_auth
|
|
from endpoints.decorators import anon_protect
|
|
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write, get_input_stream
|
|
from digest import digest_tools
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
VALID_TAG_PATTERN = r'[\w][\w.-]{0,127}'
|
|
VALID_TAG_REGEX = re.compile(VALID_TAG_PATTERN)
|
|
|
|
|
|
class SignedManifest(object):
|
|
SIGNATURES_KEY = 'signatures'
|
|
PROTECTED_KEY = 'protected'
|
|
FORMAT_LENGTH_KEY = 'formatLength'
|
|
FORMAT_TAIL_KEY = 'formatTail'
|
|
REPO_NAME_KEY = 'name'
|
|
REPO_TAG_KEY = 'tag'
|
|
|
|
def __init__(self, manifest_bytes):
|
|
self._bytes = manifest_bytes
|
|
parsed = yaml.safe_load(manifest_bytes)
|
|
|
|
self._signatures = parsed[self.SIGNATURES_KEY]
|
|
self._namespace, self._repo_name = parsed[self.REPO_NAME_KEY].split('/')
|
|
self._tag = parsed[self.REPO_TAG_KEY]
|
|
|
|
self._validate()
|
|
|
|
def _validate(self):
|
|
pass
|
|
|
|
@property
|
|
def signatures(self):
|
|
return self._signatures
|
|
|
|
@property
|
|
def namespace(self):
|
|
return self._namespace
|
|
|
|
@property
|
|
def repo_name(self):
|
|
return self._repo_name
|
|
|
|
@property
|
|
def tag(self):
|
|
return self._tag
|
|
|
|
@property
|
|
def payload(self):
|
|
protected = self._signatures[0][self.PROTECTED_KEY]
|
|
parsed_protected = yaml.safe_load(jwt.utils.base64url_decode(protected))
|
|
logger.debug('parsed_protected: %s', parsed_protected)
|
|
signed_content_head = self._bytes[:parsed_protected[self.FORMAT_LENGTH_KEY]]
|
|
logger.debug('signed content head: %s', signed_content_head)
|
|
signed_content_tail = jwt.utils.base64url_decode(parsed_protected[self.FORMAT_TAIL_KEY])
|
|
logger.debug('signed content tail: %s', signed_content_tail)
|
|
return signed_content_head + signed_content_tail
|
|
|
|
|
|
@v2_bp.route('/<namespace>/<repo_name>/manifests/<regex("' + VALID_TAG_PATTERN + '"):tag_name>',
|
|
methods=['GET'])
|
|
@process_jwt_auth
|
|
@require_repo_read
|
|
@anon_protect
|
|
def fetch_manifest_by_tagname(namespace, repo_name, tag_name):
|
|
logger.debug('Fetching tag manifest with name: %s', tag_name)
|
|
return make_response('Manifest {0}'.format(tag_name))
|
|
|
|
|
|
@v2_bp.route('/<namespace>/<repo_name>/manifests/<regex("' + VALID_TAG_PATTERN + '"):tag_name>',
|
|
methods=['PUT'])
|
|
@process_jwt_auth
|
|
@require_repo_write
|
|
@anon_protect
|
|
def write_manifest_by_tagname(namespace, repo_name, tag_name):
|
|
manifest = SignedManifest(request.data)
|
|
manifest_digest = digest_tools.sha256_digest(manifest.payload)
|
|
|
|
response = make_response('OK', 202)
|
|
response.headers['Docker-Content-Digest'] = manifest_digest
|
|
response.headers['Location'] = 'https://fun.com'
|
|
return response
|
|
|
|
|
|
# @v2_bp.route('/<namespace>/<repo_name>/manifests/<regex("' + digest_tools.DIGEST_PATTERN + '"):tag_digest>',
|
|
# methods=['PUT'])
|
|
# @process_jwt_auth
|
|
# @require_repo_write
|
|
# @anon_protect
|
|
# def write_manifest(namespace, repo_name, tag_digest):
|
|
# logger.debug('Writing tag manifest with name: %s', tag_digest)
|
|
|
|
# manifest_path = digest_tools.content_path(tag_digest)
|
|
# storage.stream_write('local_us', manifest_path, get_input_stream(request))
|
|
|
|
# return make_response('Manifest {0}'.format(tag_digest))
|