def create(self, request): """ Create a Content Artifact """ serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) artifacts = serializer.validated_data.pop('artifacts') content = serializer.save() for relative_path, artifact in artifacts.items(): ca = ContentArtifact(artifact=artifact, content=content, relative_path=relative_path) ca.save() headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
async def _match_and_stream(self, path, request): """ Match the path and stream results either from the filesystem or by downloading new data. After deciding the client can access the distribution at ``path``, this function calls :meth:`BaseDistribution.content_handler`. If that function returns a not-None result, it is returned to the client. Then the publication linked to the Distribution is used to determine what content should be served. If ``path`` is a directory entry (i.e. not a file), the directory contents are served to the client. This method calls :meth:`BaseDistribution.content_handler_list_directory` to acquire any additional entries the Distribution's content_handler might serve in that directory. If there is an actifact to be served, it is served to the client. If there's no publication, the above paragraph is applied to the lastest repository linked to the matched Distribution. Finally, when nothing is served to client yet, we check if there is a remote for the Distribution. If so, the artifact is pulled from the remote and streamed to the client. Args: path (str): The path component of the URL. 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. """ distro = self._match_distribution(path) self._permit(request, distro) rel_path = path.lstrip("/") rel_path = rel_path[len(distro.base_path) :] rel_path = rel_path.lstrip("/") content_handler_result = distro.content_handler(rel_path) if content_handler_result is not None: return content_handler_result headers = self.response_headers(rel_path) publication = getattr(distro, "publication", None) if publication: if rel_path == "" or rel_path[-1] == "/": try: index_path = "{}index.html".format(rel_path) publication.published_artifact.get(relative_path=index_path) rel_path = index_path headers = self.response_headers(rel_path) except ObjectDoesNotExist: dir_list = await self.list_directory(None, publication, rel_path) dir_list.update(distro.content_handler_list_directory(rel_path)) return HTTPOk( headers={"Content-Type": "text/html"}, body=self.render_html(dir_list) ) # published artifact try: pa = publication.published_artifact.get(relative_path=rel_path) ca = pa.content_artifact except ObjectDoesNotExist: pass else: if ca.artifact: return self._serve_content_artifact(ca, headers) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca ) # pass-through if publication.pass_through: try: ca = ContentArtifact.objects.get( content__in=publication.repository_version.content, relative_path=rel_path ) except MultipleObjectsReturned: log.error( _("Multiple (pass-through) matches for {b}/{p}"), {"b": distro.base_path, "p": rel_path}, ) raise except ObjectDoesNotExist: pass else: if ca.artifact: return self._serve_content_artifact(ca, headers) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca ) repo_version = getattr(distro, "repository_version", None) repository = getattr(distro, "repository", None) if repository or repo_version: if repository: repo_version = distro.repository.latest_version() if rel_path == "" or rel_path[-1] == "/": try: index_path = "{}index.html".format(rel_path) ContentArtifact.objects.get( content__in=repo_version.content, relative_path=index_path ) rel_path = index_path except ObjectDoesNotExist: dir_list = await self.list_directory(repo_version, None, rel_path) dir_list.update(distro.content_handler_list_directory(rel_path)) return HTTPOk( headers={"Content-Type": "text/html"}, body=self.render_html(dir_list) ) try: ca = ContentArtifact.objects.get( content__in=repo_version.content, relative_path=rel_path ) except MultipleObjectsReturned: log.error( _("Multiple (pass-through) matches for {b}/{p}"), {"b": distro.base_path, "p": rel_path}, ) raise except ObjectDoesNotExist: pass else: if ca.artifact: return self._serve_content_artifact(ca, headers) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca ) if distro.remote: remote = distro.remote.cast() try: url = remote.get_remote_artifact_url(rel_path) ra = RemoteArtifact.objects.get(remote=remote, url=url) ca = ra.content_artifact if ca.artifact: return self._serve_content_artifact(ca, headers) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca ) except ObjectDoesNotExist: ca = ContentArtifact(relative_path=rel_path) ra = RemoteArtifact(remote=remote, url=url, content_artifact=ca) return await self._stream_remote_artifact( request, StreamResponse(headers=headers), ra ) raise PathNotResolved(path)
async def _match_and_stream(self, path, request): """ Match the path and stream results either from the filesystem or by downloading new data. After deciding the client can access the distribution at ``path``, this function calls :meth:`Distribution.content_handler`. If that function returns a not-None result, it is returned to the client. Then the publication linked to the Distribution is used to determine what content should be served. If ``path`` is a directory entry (i.e. not a file), the directory contents are served to the client. This method calls :meth:`Distribution.content_handler_list_directory` to acquire any additional entries the Distribution's content_handler might serve in that directory. If there is an Artifact to be served, it is served to the client. If there's no publication, the above paragraph is applied to the latest repository linked to the matched Distribution. Finally, when nothing is served to client yet, we check if there is a remote for the Distribution. If so, the Artifact is pulled from the remote and streamed to the client. Args: path (str): The path component of the URL. 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. """ distro = await sync_to_async(self._match_distribution)(path) await sync_to_async(self._permit)(request, distro) rel_path = path.lstrip("/") rel_path = rel_path[len(distro.base_path):] rel_path = rel_path.lstrip("/") content_handler_result = await sync_to_async(distro.content_handler )(rel_path) if content_handler_result is not None: return content_handler_result headers = self.response_headers(rel_path) repository = distro.repository publication = distro.publication repo_version = distro.repository_version if repository: def get_latest_publication_or_version_blocking(): nonlocal repo_version nonlocal publication # Search for publication serving the closest latest version if not publication: try: versions = repository.versions.all() publications = Publication.objects.filter( repository_version__in=versions, complete=True) publication = publications.select_related( "repository_version").latest( "repository_version", "pulp_created") repo_version = publication.repository_version except ObjectDoesNotExist: pass if not repo_version: repo_version = repository.latest_version() await sync_to_async(get_latest_publication_or_version_blocking)() if publication: if rel_path == "" or rel_path[-1] == "/": try: index_path = "{}index.html".format(rel_path) await sync_to_async(publication.published_artifact.get )(relative_path=index_path) rel_path = index_path headers = self.response_headers(rel_path) except ObjectDoesNotExist: dir_list = await self.list_directory( None, publication, rel_path) dir_list.update(await sync_to_async( distro.content_handler_list_directory)(rel_path)) return HTTPOk(headers={"Content-Type": "text/html"}, body=self.render_html(dir_list)) # published artifact try: def get_contentartifact_blocking(): return (publication.published_artifact.select_related( "content_artifact", "content_artifact__artifact", ).get(relative_path=rel_path).content_artifact) ca = await sync_to_async(get_contentartifact_blocking)() except ObjectDoesNotExist: pass else: if ca.artifact: return await self._serve_content_artifact( ca, headers, request) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca) # pass-through if publication.pass_through: try: def get_contentartifact_blocking(): ca = ContentArtifact.objects.select_related( "artifact").get( content__in=publication.repository_version. content, relative_path=rel_path, ) return ca ca = await sync_to_async(get_contentartifact_blocking)() except MultipleObjectsReturned: log.error( _("Multiple (pass-through) matches for {b}/{p}"), { "b": distro.base_path, "p": rel_path }, ) raise except ObjectDoesNotExist: pass else: if ca.artifact: return await self._serve_content_artifact( ca, headers, request) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca) if repo_version and not publication and not distro.SERVE_FROM_PUBLICATION: if rel_path == "" or rel_path[-1] == "/": index_path = "{}index.html".format(rel_path) def contentartifact_exists_blocking(): return ContentArtifact.objects.filter( content__in=repo_version.content, relative_path=index_path).exists() contentartifact_exists = await sync_to_async( contentartifact_exists_blocking)() if contentartifact_exists: rel_path = index_path else: dir_list = await self.list_directory( repo_version, None, rel_path) dir_list.update(await sync_to_async( distro.content_handler_list_directory)(rel_path)) return HTTPOk(headers={"Content-Type": "text/html"}, body=self.render_html(dir_list)) try: def get_contentartifact_blocking(): ca = ContentArtifact.objects.select_related( "artifact").get(content__in=repo_version.content, relative_path=rel_path) return ca ca = await sync_to_async(get_contentartifact_blocking)() except MultipleObjectsReturned: log.error( _("Multiple (pass-through) matches for {b}/{p}"), { "b": distro.base_path, "p": rel_path }, ) raise except ObjectDoesNotExist: pass else: if ca.artifact: return await self._serve_content_artifact( ca, headers, request) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca) if distro.remote: def cast_remote_blocking(): return distro.remote.cast() remote = await sync_to_async(cast_remote_blocking)() try: url = remote.get_remote_artifact_url(rel_path, request=request) def get_remote_artifact_blocking(): ra = RemoteArtifact.objects.select_related( "content_artifact", "content_artifact__artifact", "remote", ).get(remote=remote, url=url) return ra ra = await sync_to_async(get_remote_artifact_blocking)() ca = ra.content_artifact if ca.artifact: return await self._serve_content_artifact( ca, headers, request) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca) except ObjectDoesNotExist: ca = ContentArtifact(relative_path=rel_path) ra = RemoteArtifact(remote=remote, url=url, content_artifact=ca) return await self._stream_remote_artifact( request, StreamResponse(headers=headers), ra) raise PathNotResolved(path)
async def _match_and_stream(self, path, request): """ Match the path and stream results either from the filesystem or by downloading new data. Args: path (str): The path component of the URL. 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. """ distro = self._match_distribution(path) self._permit(request, distro) rel_path = path.lstrip('/') rel_path = rel_path[len(distro.base_path):] rel_path = rel_path.lstrip('/') headers = self.response_headers(rel_path) publication = getattr(distro, 'publication', None) if publication: if rel_path == '' or rel_path[-1] == '/': try: index_path = '{}index.html'.format(rel_path) publication.published_artifact.get( relative_path=index_path) rel_path = index_path except ObjectDoesNotExist: dir_list = await self.list_directory( None, publication, rel_path) return HTTPOk(headers={"Content-Type": "text/html"}, body=dir_list) # published artifact try: pa = publication.published_artifact.get(relative_path=rel_path) ca = pa.content_artifact except ObjectDoesNotExist: pass else: if ca.artifact: return self._handle_file_response(ca.artifact.file, headers) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca) # pass-through if publication.pass_through: try: ca = ContentArtifact.objects.get( content__in=publication.repository_version.content, relative_path=rel_path) except MultipleObjectsReturned: log.error(_('Multiple (pass-through) matches for {b}/{p}'), { 'b': distro.base_path, 'p': rel_path, }) raise except ObjectDoesNotExist: pass else: if ca.artifact: return self._handle_file_response( ca.artifact.file, headers) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca) repo_version = getattr(distro, 'repository_version', None) repository = getattr(distro, 'repository', None) if repository or repo_version: if repository: repo_version = distro.repository.latest_version() if rel_path == '' or rel_path[-1] == '/': try: index_path = '{}index.html'.format(rel_path) ContentArtifact.objects.get( content__in=repo_version.content, relative_path=index_path) rel_path = index_path except ObjectDoesNotExist: dir_list = await self.list_directory( repo_version, None, rel_path) return HTTPOk(headers={"Content-Type": "text/html"}, body=dir_list) try: ca = ContentArtifact.objects.get( content__in=repo_version.content, relative_path=rel_path) except MultipleObjectsReturned: log.error(_('Multiple (pass-through) matches for {b}/{p}'), { 'b': distro.base_path, 'p': rel_path, }) raise except ObjectDoesNotExist: pass else: return self._handle_file_response(ca.artifact.file, headers) if distro.remote: remote = distro.remote.cast() try: url = remote.get_remote_artifact_url(rel_path) ra = RemoteArtifact.objects.get(remote=remote, url=url) ca = ra.content_artifact if ca.artifact: return self._handle_file_response(ca.artifact.file, headers) else: return await self._stream_content_artifact( request, StreamResponse(headers=headers), ca) except ObjectDoesNotExist: ca = ContentArtifact(relative_path=rel_path) ra = RemoteArtifact(remote=remote, url=url, content_artifact=ca) return await self._stream_remote_artifact( request, StreamResponse(headers=headers), ra) raise PathNotResolved(path)
async def _match_and_stream(self, path, request): """ Match the path and stream results either from the filesystem or by downloading new data. Args: path (str): The path component of the URL. 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. """ distribution = Handler._match_distribution(path) self._permit(request, distribution) publication = distribution.publication remote = distribution.remote if not publication and not remote: raise PathNotResolved(path) rel_path = path.lstrip('/') rel_path = rel_path[len(distribution.base_path):] rel_path = rel_path.lstrip('/') if publication: # published artifact try: pa = publication.published_artifact.get(relative_path=rel_path) ca = pa.content_artifact except ObjectDoesNotExist: pass else: if ca.artifact: return self._handle_file_response(ca.artifact.file) else: return await self._stream_content_artifact(request, StreamResponse(), ca) # published metadata try: pm = publication.published_metadata.get(relative_path=rel_path) except ObjectDoesNotExist: pass else: return self._handle_file_response(pm.file) # pass-through if publication.pass_through: try: ca = ContentArtifact.objects.get( content__in=publication.repository_version.content, relative_path=rel_path) except MultipleObjectsReturned: log.error( _('Multiple (pass-through) matches for {b}/{p}'), { 'b': distribution.base_path, 'p': rel_path, } ) raise except ObjectDoesNotExist: pass else: if ca.artifact: return self._handle_file_response(ca.artifact.file) else: return await self._stream_content_artifact(request, StreamResponse(), ca) if remote: remote = remote.cast() try: url = remote.get_remote_artifact_url(rel_path) ra = RemoteArtifact.objects.get(remote=remote, url=url) ca = ra.content_artifact if ca.artifact: return self._handle_file_response(ca.artifact.file) else: return await self._stream_content_artifact(request, StreamResponse(), ca) except ObjectDoesNotExist: ca = ContentArtifact(relative_path=rel_path) ra = RemoteArtifact(remote=remote, url=url, content_artifact=ca) return await self._stream_remote_artifact(request, StreamResponse(), ra) raise PathNotResolved(path)