Ioana Lasc has proposed merging ~ilasc/launchpad:add-get-status-artifacts-api into launchpad:master.
Commit message: Add a get status artifacts endpoint Requested reviews: Launchpad code reviewers (launchpad-reviewers) For more details, see: https://code.launchpad.net/~ilasc/launchpad/+git/launchpad/+merge/414392 -- Your team Launchpad code reviewers is requested to review the proposed merge of ~ilasc/launchpad:add-get-status-artifacts-api into launchpad:master.
diff --git a/lib/lp/code/interfaces/revisionstatus.py b/lib/lp/code/interfaces/revisionstatus.py index 20c6a5a..852571c 100644 --- a/lib/lp/code/interfaces/revisionstatus.py +++ b/lib/lp/code/interfaces/revisionstatus.py @@ -16,6 +16,7 @@ import http.client from lazr.restful.declarations import ( error_status, + export_read_operation, export_write_operation, exported, exported_as_webservice_entry, @@ -78,6 +79,18 @@ class IRevisionStatusReportView(Interface): date_finished = exported(Datetime( title=_("When the report has finished.")), readonly=False) + @operation_parameters( + artifact_type=Choice(vocabulary=RevisionStatusArtifactType, + required=False)) + @scoped(AccessTokenScope.REPOSITORY_BUILD_STATUS.title) + @export_read_operation() + @operation_for_version("devel") + def getArtifactsURLs(artifact_type): + """Retrieves the list of URLs for artifacts that exist for this report. + + :param artifact_type: The type of artifact for the report. + """ + class IRevisionStatusReportEditableAttributes(Interface): """`IRevisionStatusReport` attributes that can be edited. @@ -235,6 +248,9 @@ class IRevisionStatusArtifactSet(Interface): def findByReport(report): """Returns the set of artifacts for a given report.""" + def getArtifactDownloadUrls(report, clauses): + """Returns download URLs for all artifacts under a given report.""" + class IRevisionStatusArtifact(Interface): id = Int(title=_("ID"), required=True, readonly=True) @@ -249,3 +265,9 @@ class IRevisionStatusArtifact(Interface): artifact_type = Choice( title=_('The type of artifact, only log for now.'), vocabulary=RevisionStatusArtifactType) + + def downloadUrl(): + """URL for downloading the artifact. + + :return: A URL for downloading this artifact. + """ diff --git a/lib/lp/code/model/revisionstatus.py b/lib/lp/code/model/revisionstatus.py index de9bfff..a4c3061 100644 --- a/lib/lp/code/model/revisionstatus.py +++ b/lib/lp/code/model/revisionstatus.py @@ -33,6 +33,7 @@ from lp.services.database.enumcol import DBEnum from lp.services.database.interfaces import IStore from lp.services.database.sqlbase import convert_storm_clause_to_string from lp.services.database.stormbase import StormBase +from lp.services.librarian.browser import ProxiedLibraryFileAlias from lp.services.librarian.interfaces import ILibraryFileAliasSet @@ -117,6 +118,20 @@ class RevisionStatusReport(StormBase): if result is not None: self.transitionToNewResult(result) + def getArtifactsURLs(self, artifact_type): + clauses = [ + RevisionStatusArtifact.report == self, + ] + if artifact_type == RevisionStatusArtifactType.LOG: + clauses.extend([RevisionStatusArtifact.artifact_type == + RevisionStatusArtifactType.LOG, ]) + elif artifact_type == RevisionStatusArtifactType.BINARY: + clauses.extend([RevisionStatusArtifact.artifact_type == + RevisionStatusArtifactType.BINARY, ]) + + return getUtility( + IRevisionStatusArtifactSet).getArtifactDownloadUrls(clauses) + @implementer(IRevisionStatusReportSet) class RevisionStatusReportSet: @@ -184,6 +199,14 @@ class RevisionStatusArtifact(StormBase): self.report = report self.artifact_type = artifact_type + def downloadUrl(self): + if self.library_file.restricted: + url = ProxiedLibraryFileAlias( + self.library_file, self.report).http_url + else: + url = self.library_file.http_url + return url + @implementer(IRevisionStatusArtifactSet) class RevisionStatusArtifactSet: @@ -204,3 +227,11 @@ class RevisionStatusArtifactSet: return IStore(RevisionStatusArtifact).find( RevisionStatusArtifact, RevisionStatusArtifact.report == report) + + def getArtifactDownloadUrls(self, clauses): + artifacts = IStore(RevisionStatusArtifact).find( + RevisionStatusArtifact, *clauses) + urls = [] + for artifact in list(artifacts): + urls.append(artifact.downloadUrl()) + return urls diff --git a/lib/lp/code/model/tests/test_revisionstatus.py b/lib/lp/code/model/tests/test_revisionstatus.py index 6ce84c5..05367de 100644 --- a/lib/lp/code/model/tests/test_revisionstatus.py +++ b/lib/lp/code/model/tests/test_revisionstatus.py @@ -6,6 +6,7 @@ import hashlib import io +import requests from testtools.matchers import ( AnyMatch, Equals, @@ -40,10 +41,11 @@ from lp.testing.pages import webservice_for_person class TestRevisionStatusReport(TestCaseWithFactory): layer = DatabaseFunctionalLayer - def makeRevisionStatusArtifact(self, report): + def makeRevisionStatusArtifact(self, report, artifact_type=None): # We don't need to upload files to the librarian in this test suite. lfa = self.factory.makeLibraryFileAlias(db_only=True) - return self.factory.makeRevisionStatusArtifact(lfa=lfa, report=report) + return self.factory.makeRevisionStatusArtifact( + lfa=lfa, report=report, artifact_type=artifact_type) def test_owner_public(self): # The owner of a public repository can view and edit its reports and @@ -239,3 +241,64 @@ class TestRevisionStatusReportWebservice(TestCaseWithFactory): result_summary=Equals(initial_result_summary), result=Equals(RevisionStatusResult.SUCCEEDED), date_finished=GreaterThan(date_finished_before_update))) + + def test_getArtifactsURLs(self): + report = self.factory.makeRevisionStatusReport() + artifact_log = self.factory.makeRevisionStatusArtifact( + report=report, artifact_type=RevisionStatusArtifactType.LOG, + content=b'log_data') + log_url = artifact_log.library_file.http_url + artifact_binary = self.factory.makeRevisionStatusArtifact( + report=report, artifact_type=RevisionStatusArtifactType.BINARY, + content=b'binary_data') + binary_url = artifact_binary.library_file.http_url + requester = report.creator + repository = report.git_repository + report_url = api_url(report) + webservice = self.getWebservice(requester, repository) + response = webservice.named_get( + report_url, "getArtifactsURLs", artifact_type="Log") + self.assertEqual(200, response.status) + with person_logged_in(requester): + self.assertIn(log_url, response.jsonBody()) + self.assertNotIn(binary_url, response.jsonBody()) + file = requests.get(response.jsonBody()[0]) + self.assertEqual(b'log_data', file.content) + response = webservice.named_get( + report_url, "getArtifactsURLs", artifact_type="Binary") + self.assertEqual(200, response.status) + with person_logged_in(requester): + self.assertNotIn(log_url, response.jsonBody()) + self.assertIn(binary_url, response.jsonBody()) + file = requests.get(response.jsonBody()[0]) + self.assertEqual(b'binary_data', file.content) + response = webservice.named_get( + report_url, "getArtifactsURLs") + self.assertEqual(200, response.status) + with person_logged_in(requester): + self.assertIn(log_url, response.jsonBody()) + self.assertIn(binary_url, response.jsonBody()) + + def test_getArtifactsURLs_restricted(self): + requester = self.factory.makePerson() + with person_logged_in(requester): + kwargs = {"owner": requester} + kwargs["information_type"] = InformationType.USERDATA + repository = self.factory.makeGitRepository(**kwargs) + report = self.factory.makeRevisionStatusReport( + git_repository=repository) + report_url = api_url(report) + artifact = self.factory.makeRevisionStatusArtifact( + report=report, artifact_type=RevisionStatusArtifactType.LOG, + content=b'log_data', restricted=True) + log_url = 'http://code.launchpad.test/%s/+status/%s/+files/%s' % ( + repository.unique_name, report.id, + artifact.library_file.filename) + + webservice = self.getWebservice(requester, repository) + response = webservice.named_get( + report_url, "getArtifactsURLs", artifact_type="Log") + self.assertEqual(200, response.status) + + with person_logged_in(requester): + self.assertIn(log_url, response.jsonBody()) diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py index 1588aad..d2a9a4e 100644 --- a/lib/lp/testing/factory.py +++ b/lib/lp/testing/factory.py @@ -1871,13 +1871,16 @@ class BareLaunchpadObjectFactory(ObjectFactory): result_summary, result) def makeRevisionStatusArtifact( - self, lfa=None, report=None, - artifact_type=RevisionStatusArtifactType.LOG): + self, lfa=None, content=None, report=None, + artifact_type=None, restricted=False): """Create a new RevisionStatusArtifact.""" if lfa is None: - lfa = self.makeLibraryFileAlias() + lfa = self.makeLibraryFileAlias( + content=content, restricted=restricted) if report is None: report = self.makeRevisionStatusReport() + if artifact_type is None: + artifact_type = RevisionStatusArtifactType.LOG return getUtility(IRevisionStatusArtifactSet).new( lfa, report, artifact_type)
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp