launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #31695
[Merge] ~ruinedyourlife/launchpad:fix-missing-craft-api-endpoints into launchpad:master
Quentin Debhi has proposed merging ~ruinedyourlife/launchpad:fix-missing-craft-api-endpoints into launchpad:master.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~ruinedyourlife/launchpad/+git/launchpad/+merge/474922
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~ruinedyourlife/launchpad:fix-missing-craft-api-endpoints into launchpad:master.
diff --git a/lib/lp/crafts/interfaces/craftrecipe.py b/lib/lp/crafts/interfaces/craftrecipe.py
index c4ed465..d45bc71 100644
--- a/lib/lp/crafts/interfaces/craftrecipe.py
+++ b/lib/lp/crafts/interfaces/craftrecipe.py
@@ -784,6 +784,9 @@ class ICraftRecipeSet(Interface):
"project",
"name",
"description",
+ "git_repository",
+ "git_repository_url",
+ "git_path",
"git_ref",
"build_path",
"auto_build",
diff --git a/lib/lp/crafts/interfaces/craftrecipebuild.py b/lib/lp/crafts/interfaces/craftrecipebuild.py
index 4c77eb8..7b57a27 100644
--- a/lib/lp/crafts/interfaces/craftrecipebuild.py
+++ b/lib/lp/crafts/interfaces/craftrecipebuild.py
@@ -9,7 +9,12 @@ __all__ = [
"ICraftRecipeBuildSet",
]
-from lazr.restful.declarations import exported, exported_as_webservice_entry
+from lazr.restful.declarations import (
+ export_read_operation,
+ exported,
+ exported_as_webservice_entry,
+ operation_for_version,
+)
from lazr.restful.fields import Reference
from zope.interface import Attribute, Interface
from zope.schema import Bool, Datetime, Dict, Int, TextLine
@@ -130,6 +135,18 @@ class ICraftRecipeBuildView(IPackageBuildView):
_("A dict of data about store upload progress.")
)
+ build_metadata_url = exported(
+ TextLine(
+ title=_("URL of the build metadata file"),
+ description=_(
+ "URL of the metadata file generated by the fetch service, if "
+ "it exists."
+ ),
+ required=False,
+ readonly=True,
+ )
+ )
+
def getFiles():
"""Retrieve the build's ICraftFile records.
@@ -153,6 +170,13 @@ class ICraftRecipeBuildView(IPackageBuildView):
:return: The corresponding ILibraryFileAlias.
"""
+ @export_read_operation()
+ @operation_for_version("devel")
+ def getFileUrls():
+ """URLs for all the files produced by this build.
+
+ :return: A collection of URLs for this build."""
+
class ICraftRecipeBuildEdit(IBuildFarmJobEdit):
"""ICraftRecipeBuild methods that require launchpad.Edit."""
diff --git a/lib/lp/crafts/model/craftrecipebuild.py b/lib/lp/crafts/model/craftrecipebuild.py
index 2999b45..ef32f9f 100644
--- a/lib/lp/crafts/model/craftrecipebuild.py
+++ b/lib/lp/crafts/model/craftrecipebuild.py
@@ -18,6 +18,7 @@ from zope.component import getUtility
from zope.interface import implementer
from lp.app.errors import NotFoundError
+from lp.buildmaster.builderproxy import BUILD_METADATA_FILENAME_FORMAT
from lp.buildmaster.enums import (
BuildFarmJobType,
BuildQueueStatus,
@@ -44,6 +45,7 @@ from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.enumcol import DBEnum
from lp.services.database.interfaces import IPrimaryStore, IStore
from lp.services.database.stormbase import StormBase
+from lp.services.librarian.browser import ProxiedLibraryFileAlias
from lp.services.librarian.model import LibraryFileAlias, LibraryFileContent
from lp.services.propertycache import cachedproperty, get_property_cache
from lp.services.webapp.snapshot import notify_modified
@@ -358,6 +360,23 @@ class CraftRecipeBuild(PackageBuildMixin, StormBase):
mailer = CraftRecipeBuildMailer.forStatus(self)
mailer.sendAll()
+ def getFileUrls(self):
+ """See `ICraftRecipeBuild`."""
+ return [
+ ProxiedLibraryFileAlias(lfa, self).http_url
+ for _, lfa, _ in self.getFiles()
+ ]
+
+ @property
+ def build_metadata_url(self):
+ metadata_filename = BUILD_METADATA_FILENAME_FORMAT.format(
+ build_id=self.build_cookie
+ )
+ for url in self.getFileUrls():
+ if url.endswith(metadata_filename):
+ return url
+ return None
+
@implementer(ICraftRecipeBuildSet)
class CraftRecipeBuildSet(SpecificBuildFarmJobSourceMixin):
diff --git a/lib/lp/crafts/tests/test_craftrecipebuild.py b/lib/lp/crafts/tests/test_craftrecipebuild.py
index 5821ae3..7ffdcab 100644
--- a/lib/lp/crafts/tests/test_craftrecipebuild.py
+++ b/lib/lp/crafts/tests/test_craftrecipebuild.py
@@ -30,6 +30,7 @@ from lp.registry.enums import PersonVisibility, TeamMembershipPolicy
from lp.registry.interfaces.series import SeriesStatus
from lp.services.config import config
from lp.services.features.testing import FeatureFixture
+from lp.services.librarian.browser import ProxiedLibraryFileAlias
from lp.services.propertycache import clear_property_cache
from lp.services.webapp.interfaces import OAuthPermission
from lp.testing import (
@@ -596,3 +597,70 @@ class TestCraftRecipeBuildWebservice(TestCaseWithFactory):
self.assertCanOpenRedirectedUrl(browser, build["build_log_url"])
self.assertIsNotNone(build["upload_log_url"])
self.assertCanOpenRedirectedUrl(browser, build["upload_log_url"])
+
+ def test_getFileUrls(self):
+ # API clients can fetch files attached to builds.
+ db_build = self.factory.makeCraftRecipeBuild(requester=self.person)
+ db_files = [
+ self.factory.makeCraftFile(build=db_build) for i in range(2)
+ ]
+ build_url = api_url(db_build)
+ file_urls = [
+ ProxiedLibraryFileAlias(file.library_file, db_build).http_url
+ for file in db_files
+ ]
+ logout()
+ response = self.webservice.named_get(build_url, "getFileUrls")
+ self.assertEqual(200, response.status)
+ self.assertContentEqual(file_urls, response.jsonBody())
+ browser = self.getNonRedirectingBrowser(user=self.person)
+ browser.raiseHttpErrors = False
+ for file_url in file_urls:
+ self.assertCanOpenRedirectedUrl(browser, file_url)
+
+ def test_build_metadata_url(self):
+ # API clients can fetch the metadata from the build, generated by the
+ # fetch service
+ db_build = self.factory.makeCraftRecipeBuild(requester=self.person)
+ metadata_filename = f"{db_build.build_cookie}_metadata.json"
+ with person_logged_in(self.person):
+ file_1 = self.factory.makeLibraryFileAlias(
+ content="some_json",
+ filename="test_file.json",
+ )
+ db_build.addFile(file_1)
+ metadata_file = self.factory.makeLibraryFileAlias(
+ content="some_json",
+ filename=metadata_filename,
+ )
+ db_build.addFile(metadata_file)
+ file_2 = self.factory.makeLibraryFileAlias(
+ content="some_json",
+ filename="another_test_file.tar",
+ )
+ db_build.addFile(file_2)
+ build_url = api_url(db_build)
+ logout()
+ build = self.webservice.get(build_url).jsonBody()
+ self.assertIsNotNone(build["build_metadata_url"])
+ self.assertEndsWith(build["build_metadata_url"], metadata_filename)
+
+ def test_build_metadata_url_no_metadata_file(self):
+ # The attribute `build_metadata_url` returns None when metadata file
+ # does not exist.
+ db_build = self.factory.makeCraftRecipeBuild(requester=self.person)
+ with person_logged_in(self.person):
+ file_1 = self.factory.makeLibraryFileAlias(
+ content="some_json",
+ filename="test_file.json",
+ )
+ db_build.addFile(file_1)
+ file_2 = self.factory.makeLibraryFileAlias(
+ content="some_json",
+ filename="another_test_file.tar",
+ )
+ db_build.addFile(file_2)
+ build_url = api_url(db_build)
+ logout()
+ build = self.webservice.get(build_url).jsonBody()
+ self.assertIsNone(build["build_metadata_url"])