async def dispatch_converted_schema(tag, accepted_media_types, path): """ Convert a manifest from the format schema 2 to the format schema 1. The format is converted on-the-go and created resources are not stored for further uses. The conversion is made after each request which does not accept the format for schema 2. Args: tag: A tag object which contains reference to tagged manifests and config blobs. accepted_media_types: Accepted media types declared in the accept header. path: A path of a repository. Raises: PathNotResolved: There was not found a valid conversion for the specified tag. Returns: :class:`aiohttp.web.StreamResponse` or :class:`aiohttp.web.Response`: The response streamed back to the client. """ schema1_converter = Schema2toSchema1ConverterWrapper( tag, accepted_media_types, path) try: schema, converted, digest = schema1_converter.convert() except RuntimeError: raise PathNotResolved(tag.name) response_headers = { "Docker-Content-Digest": digest, "Content-Type": MEDIA_TYPE.MANIFEST_V1_SIGNED, "Docker-Distribution-API-Version": "registry/2.0", } if not converted: raise PathNotResolved(tag.name) return web.Response(text=schema, headers=response_headers)
async def handle_universe(self, request): """ Serve the '__universe__' metadata object at '/universe'. Since the '/universe' endpoint contains absolute URLs, translate the content of '__universe__' on the fly. """ path = request.match_info["path"] + "/universe" distribution = Handler._match_distribution(path) Handler._permit(request, distribution) publication = distribution.publication if not publication: raise PathNotResolved(path) pa = publication.published_artifact.get(relative_path="__universe__") ca = pa.content_artifact try: with ca.artifact.file.open("rb") as u: content = u.read().decode("utf-8") except FileNotFoundError: return PathNotResolved(path) except PermissionError: return aiohttp.web_exceptions.HTTPForbidden content = replace_all_paths( content, self._get_content_base_url(request) + distribution.base_path) return aiohttp.web.Response(body=content, content_type="application/json", charset="utf-8")
async def get_tag(self, request): """ Match the path and stream either Manifest or ManifestList. Args: request(:class:`~aiohttp.web.Request`): The request to prepare a response for. Raises: PathNotResolved: The path could not be matched to a published file. PermissionError: When not permitted. Returns: :class:`aiohttp.web.StreamResponse` or :class:`aiohttp.web.FileResponse`: The response streamed back to the client. """ path = request.match_info["path"] tag_name = request.match_info["tag_name"] distribution = self._match_distribution(path) self._permit(request, distribution) repository_version = distribution.get_repository_version() accepted_media_types = get_accepted_media_types(request.headers) try: tag = Tag.objects.get(pk__in=repository_version.content, name=tag_name) except ObjectDoesNotExist: raise PathNotResolved(tag_name) # we do not convert OCI to docker oci_mediatypes = [MEDIA_TYPE.MANIFEST_OCI, MEDIA_TYPE.INDEX_OCI] if (tag.tagged_manifest.media_type in oci_mediatypes and tag.tagged_manifest.media_type not in accepted_media_types): log.warn( "OCI format found, but the client only accepts {accepted_media_types}." .format(accepted_media_types=accepted_media_types)) raise PathNotResolved(tag_name) # return schema1 (even in case only oci is requested) if tag.tagged_manifest.media_type == MEDIA_TYPE.MANIFEST_V1: return_media_type = MEDIA_TYPE.MANIFEST_V1_SIGNED response_headers = { "Content-Type": return_media_type, "Docker-Content-Digest": tag.tagged_manifest.digest, } return await Registry.dispatch_tag(tag, response_headers) # return what was found in case media_type is accepted header (docker, oci) if tag.tagged_manifest.media_type in accepted_media_types: return_media_type = tag.tagged_manifest.media_type response_headers = { "Content-Type": return_media_type, "Docker-Content-Digest": tag.tagged_manifest.digest, } return await Registry.dispatch_tag(tag, response_headers) # convert if necessary return await Registry.dispatch_converted_schema( tag, accepted_media_types, path)
async def get_tag(self, request): """ Match the path and stream either Manifest or ManifestList. Args: request(:class:`~aiohttp.web.Request`): The request to prepare a response for. Raises: PathNotResolved: The path could not be matched to a published file. PermissionError: When not permitted. Returns: :class:`aiohttp.web.StreamResponse` or :class:`aiohttp.web.FileResponse`: The response streamed back to the client. """ path = request.match_info['path'] tag_name = request.match_info['tag_name'] distribution = self._match_distribution(path) repository_version = distribution.get_repository_version() accepted_media_types = await Registry.get_accepted_media_types(request) try: tag = Tag.objects.get( pk__in=repository_version.content, name=tag_name, ) except ObjectDoesNotExist: raise PathNotResolved(tag_name) if tag.tagged_manifest.media_type == MEDIA_TYPE.MANIFEST_V1: return_media_type = MEDIA_TYPE.MANIFEST_V1_SIGNED elif tag.tagged_manifest.media_type in accepted_media_types: return_media_type = tag.tagged_manifest.media_type else: # This is where we could eventually support on-the-fly conversion to schema 1. log.warn( "The requested tag `{name}` is of type {media_type}, but the client only accepts " "{accepted_media_types}.".format( name=tag.name, media_type=tag.tagged_manifest.media_type, accepted_media_types=accepted_media_types)) raise PathNotResolved(tag_name) response_headers = { 'Content-Type': return_media_type, 'Docker-Content-Digest': tag.tagged_manifest.digest } return await Registry.dispatch_tag(tag, response_headers)
async def get_by_digest(self, request): """ Return a response to the "GET" action. """ path = request.match_info['path'] digest = "sha256:{digest}".format(digest=request.match_info['digest']) distribution = self._match_distribution(path) repository_version = distribution.get_repository_version() log.info(digest) try: ca = ContentArtifact.objects.get( content__in=repository_version.content, relative_path=digest) headers = { 'Content-Type': ca.content.cast().media_type, 'Docker-Content-Digest': ca.content.cast().digest } except ObjectDoesNotExist: raise PathNotResolved(path) else: artifact = ca.artifact if artifact: return await Registry._dispatch( os.path.join(settings.MEDIA_ROOT, artifact.file.name), headers) else: return await self._stream_content_artifact( request, web.StreamResponse(), ca)
async def get_by_digest(self, request): """ Return a response to the "GET" action. """ path = request.match_info["path"] digest = "sha256:{digest}".format(digest=request.match_info["digest"]) distribution = self._match_distribution(path) self._permit(request, distribution) repository_version = distribution.get_repository_version() try: ca = ContentArtifact.objects.get( content__in=repository_version.content, relative_path=digest) headers = { "Content-Type": ca.content.cast().media_type, "Docker-Content-Digest": ca.content.cast().digest, } except ObjectDoesNotExist: raise PathNotResolved(path) else: artifact = ca.artifact if artifact: return await Registry._dispatch(artifact.file, headers) else: return await self._stream_content_artifact( request, web.StreamResponse(), ca)
async def get_by_digest(self, request): """ Return a response to the "GET" action. """ path = request.match_info["path"] digest = "sha256:{digest}".format(digest=request.match_info["digest"]) distribution = await sync_to_async(self._match_distribution)(path) await sync_to_async(self._permit)(request, distribution) repository_version = await sync_to_async( distribution.get_repository_version)() if not repository_version: raise PathNotResolved(path) if digest == EMPTY_BLOB: return await Registry._empty_blob() try: ca = await sync_to_async( ContentArtifact.objects.select_related("artifact", "content").get )( content__in=await sync_to_async(repository_version.get_content) (), relative_path=digest, ) ca_content = await sync_to_async(ca.content.cast)() if isinstance(ca_content, Blob): media_type = BLOB_CONTENT_TYPE else: media_type = ca_content.media_type headers = { "Content-Type": media_type, "Docker-Content-Digest": ca_content.digest, } except ObjectDoesNotExist: raise PathNotResolved(path) else: artifact = ca.artifact if artifact: return await Registry._dispatch(artifact.file, headers) else: return await self._stream_content_artifact( request, web.StreamResponse(), ca)
async def get_tag(self, request): """ Match the path and stream either Manifest or ManifestList. Args: request(:class:`~aiohttp.web.Request`): The request to prepare a response for. Raises: PathNotResolved: The path could not be matched to a published file. PermissionError: When not permitted. Returns: :class:`aiohttp.web.StreamResponse` or :class:`aiohttp.web.FileResponse`: The response streamed back to the client. """ Registry.verify_token(request, 'pull') path = request.match_info['path'] tag_name = request.match_info['tag_name'] distribution = self._match_distribution(path) repository_version = distribution.get_repository_version() accepted_media_types = await Registry.get_accepted_media_types(request) try: tag = Tag.objects.get( pk__in=repository_version.content, name=tag_name, ) except ObjectDoesNotExist: raise PathNotResolved(tag_name) if tag.tagged_manifest.media_type == MEDIA_TYPE.MANIFEST_V1: return_media_type = MEDIA_TYPE.MANIFEST_V1_SIGNED response_headers = { 'Content-Type': return_media_type, 'Docker-Content-Digest': tag.tagged_manifest.digest } return await Registry.dispatch_tag(tag, response_headers) if tag.tagged_manifest.media_type in accepted_media_types: return_media_type = tag.tagged_manifest.media_type response_headers = { 'Content-Type': return_media_type, 'Docker-Content-Digest': tag.tagged_manifest.digest } return await Registry.dispatch_tag(tag, response_headers) return await Registry.dispatch_converted_schema( tag, accepted_media_types, path)