Add support for direct pushing of schema 2 manifests without tags

This is required for manifest lists
This commit is contained in:
Joseph Schorr 2018-11-19 13:23:29 +02:00
parent 8a3427e55a
commit e6c2ddfa93
6 changed files with 158 additions and 47 deletions

View file

@ -62,7 +62,8 @@ def fetch_manifest_by_tagname(namespace_name, repo_name, manifest_ref):
logger.exception('Got exception when trying to parse manifest `%s`', manifest_ref)
raise ManifestInvalid()
manifest = _rewrite_to_schema1_if_necessary(namespace_name, repo_name, manifest_ref, parsed)
manifest = _rewrite_to_schema1_if_necessary(namespace_name, repo_name, manifest_ref, manifest,
parsed)
if manifest is None:
raise ManifestUnknown()
@ -100,7 +101,8 @@ def fetch_manifest_by_digest(namespace_name, repo_name, manifest_ref):
logger.exception('Got exception when trying to parse manifest `%s`', manifest_ref)
raise ManifestInvalid()
manifest = _rewrite_to_schema1_if_necessary(namespace_name, repo_name, '$digest', parsed)
manifest = _rewrite_to_schema1_if_necessary(namespace_name, repo_name, '$digest', manifest,
parsed)
if manifest is None:
raise ManifestUnknown()
@ -113,24 +115,17 @@ def fetch_manifest_by_digest(namespace_name, repo_name, manifest_ref):
})
def _rewrite_to_schema1_if_necessary(namespace_name, repo_name, tag_name, manifest):
def _rewrite_to_schema1_if_necessary(namespace_name, repo_name, tag_name, manifest, parsed):
# As per the Docker protocol, if the manifest is not schema version 1 and the manifest's
# media type is not in the Accept header, we return a schema 1 version of the manifest for
# the amd64+linux platform, if any, or None if none.
# See: https://docs.docker.com/registry/spec/manifest-v2-2
mimetypes = [mimetype for mimetype, _ in request.accept_mimetypes]
if manifest.media_type in mimetypes:
return manifest
if parsed.media_type in mimetypes:
return parsed
def lookup_fn(config_or_manifest_digest):
blob = registry_model.get_cached_repo_blob(model_cache, namespace_name, repo_name,
config_or_manifest_digest)
if blob is None:
return None
return storage.get_content(blob.placements, blob.storage_path)
return manifest.get_v1_compatible_manifest(namespace_name, repo_name, tag_name, lookup_fn)
return registry_model.get_schema1_parsed_manifest(manifest, namespace_name, repo_name, tag_name,
storage)
def _reject_manifest2_schema2(func):
@ -162,19 +157,8 @@ def _doesnt_accept_schema_v1():
@require_repo_write
@anon_protect
def write_manifest_by_tagname(namespace_name, repo_name, manifest_ref):
content_type = request.content_type or DOCKER_SCHEMA1_MANIFEST_CONTENT_TYPE
if content_type == 'application/json':
# For back-compat.
content_type = DOCKER_SCHEMA1_MANIFEST_CONTENT_TYPE
try:
manifest = parse_manifest_from_bytes(request.data, content_type)
except ManifestException as me:
logger.exception("failed to parse manifest when writing by tagname")
raise ManifestInvalid(detail={'message': 'failed to parse manifest: %s' % me.message})
return _write_manifest_and_log(namespace_name, repo_name, manifest_ref, manifest)
parsed = _parse_manifest()
return _write_manifest_and_log(namespace_name, repo_name, manifest_ref, parsed)
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['PUT'])
@ -184,16 +168,50 @@ def write_manifest_by_tagname(namespace_name, repo_name, manifest_ref):
@require_repo_write
@anon_protect
def write_manifest_by_digest(namespace_name, repo_name, manifest_ref):
try:
manifest = DockerSchema1Manifest(request.data)
except ManifestException as me:
logger.exception("failed to parse manifest when writing by digest")
raise ManifestInvalid(detail={'message': 'failed to parse manifest: %s' % me.message})
if manifest.digest != manifest_ref:
parsed = _parse_manifest()
if parsed.digest != manifest_ref:
raise ManifestInvalid(detail={'message': 'manifest digest mismatch'})
return _write_manifest_and_log(namespace_name, repo_name, manifest.tag, manifest)
if parsed.schema_version != 2:
return _write_manifest_and_log(namespace_name, repo_name, parsed.tag, parsed)
# If the manifest is schema version 2, then this cannot be a normal tag-based push, as the
# manifest does not contain the tag and this call was not given a tag name. Instead, we write the
# manifest with a temporary tag, as it is being pushed as part of a call for a manifest list.
repository_ref = registry_model.lookup_repository(namespace_name, repo_name)
if repository_ref is None:
raise NameUnknown()
expiration_sec = app.config['PUSH_TEMP_TAG_EXPIRATION_SEC']
manifest = registry_model.create_manifest_with_temp_tag(repository_ref, parsed, expiration_sec,
storage)
if manifest is None:
raise ManifestInvalid()
return Response(
'OK',
status=202,
headers={
'Docker-Content-Digest': manifest.digest,
'Location':
url_for('v2.fetch_manifest_by_digest',
repository='%s/%s' % (namespace_name, repo_name),
manifest_ref=manifest.digest),
},
)
def _parse_manifest():
content_type = request.content_type or DOCKER_SCHEMA1_MANIFEST_CONTENT_TYPE
if content_type == 'application/json':
# For back-compat.
content_type = DOCKER_SCHEMA1_MANIFEST_CONTENT_TYPE
try:
return parse_manifest_from_bytes(request.data, content_type)
except ManifestException as me:
logger.exception("failed to parse manifest when writing by tagname")
raise ManifestInvalid(detail={'message': 'failed to parse manifest: %s' % me.message})
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['DELETE'])