Jürgen Gmach has proposed merging ~jugmac00/launchpad:add-build-upload-processing-for-rock-recipes into launchpad:master with ~jugmac00/launchpad:add-build-behaviour-for-rock-recipes as a prerequisite.
Commit message: Add build upload processing for rock recipes Requested reviews: Launchpad code reviewers (launchpad-reviewers) For more details, see: https://code.launchpad.net/~jugmac00/launchpad/+git/launchpad/+merge/472981 similar to https://git.launchpad.net/launchpad/commit/?id=06441a35383386d9d15f682de64b0d7ad306d4fb -- Your team Launchpad code reviewers is requested to review the proposed merge of ~jugmac00/launchpad:add-build-upload-processing-for-rock-recipes into launchpad:master.
diff --git a/lib/lp/archiveuploader/rockrecipeupload.py b/lib/lp/archiveuploader/rockrecipeupload.py new file mode 100644 index 0000000..d7b9152 --- /dev/null +++ b/lib/lp/archiveuploader/rockrecipeupload.py @@ -0,0 +1,66 @@ +# Copyright 2024 Canonical Ltd. This software is licensed under the +# GNU Affero General Public License version 3 (see the file LICENSE). + +"""Process a rock recipe upload.""" + +__all__ = [ + "RockRecipeUpload", +] + +import os + +from zope.component import getUtility + +from lp.archiveuploader.utils import UploadError +from lp.buildmaster.enums import BuildStatus +from lp.services.helpers import filenameToContentType +from lp.services.librarian.interfaces import ILibraryFileAliasSet + + +class RockRecipeUpload: + """A rock recipe upload.""" + + def __init__(self, upload_path, logger): + """Create a `RockRecipeUpload`. + + :param upload_path: A directory containing files to upload. + :param logger: The logger to be used. + """ + self.upload_path = upload_path + self.logger = logger + + self.librarian = getUtility(ILibraryFileAliasSet) + + def process(self, build): + """Process this upload, loading it into the database.""" + self.logger.debug("Beginning processing.") + + found_rock = False + rock_paths = [] + for dirpath, _, filenames in os.walk(self.upload_path): + if dirpath == self.upload_path: + # All relevant files will be in a subdirectory. + continue + for rock_file in sorted(filenames): + if rock_file.endswith(".rock"): + found_rock = True + rock_paths.append(os.path.join(dirpath, rock_file)) + + if not found_rock: + raise UploadError("Build did not produce any rocks.") + + for rock_path in rock_paths: + libraryfile = self.librarian.create( + os.path.basename(rock_path), + os.stat(rock_path).st_size, + open(rock_path, "rb"), + filenameToContentType(rock_path), + restricted=build.is_private, + ) + build.addFile(libraryfile) + + # The master verifies the status to confirm successful upload. + self.logger.debug("Updating %s" % build.title) + build.updateStatus(BuildStatus.FULLYBUILT) + + self.logger.debug("Finished upload.") diff --git a/lib/lp/archiveuploader/tests/test_rockrecipeupload.py b/lib/lp/archiveuploader/tests/test_rockrecipeupload.py new file mode 100644 index 0000000..28a12cb --- /dev/null +++ b/lib/lp/archiveuploader/tests/test_rockrecipeupload.py @@ -0,0 +1,78 @@ +# Copyright 2024 Canonical Ltd. This software is licensed under the +# GNU Affero General Public License version 3 (see the file LICENSE). + +"""Tests for `RockRecipeUpload`.""" + +import os + +from storm.store import Store + +from lp.archiveuploader.tests.test_uploadprocessor import ( + TestUploadProcessorBase, +) +from lp.archiveuploader.uploadprocessor import UploadHandler, UploadStatusEnum +from lp.buildmaster.enums import BuildStatus +from lp.rocks.interfaces.rockrecipe import ROCK_RECIPE_ALLOW_CREATE +from lp.services.features.testing import FeatureFixture +from lp.services.osutils import write_file + + +class TestRockRecipeUploads(TestUploadProcessorBase): + """End-to-end tests of rock recipe uploads.""" + + def setUp(self): + super().setUp() + self.useFixture(FeatureFixture({ROCK_RECIPE_ALLOW_CREATE: "on"})) + + self.setupBreezy() + + self.switchToAdmin() + self.build = self.factory.makeRockRecipeBuild( + distro_arch_series=self.breezy["i386"] + ) + self.build.updateStatus(BuildStatus.UPLOADING) + Store.of(self.build).flush() + self.switchToUploader() + self.options.context = "buildd" + + self.uploadprocessor = self.getUploadProcessor( + self.layer.txn, builds=True + ) + + def test_sets_build_and_state_123(self): + # The upload processor uploads files and sets the correct status. + self.assertFalse(self.build.verifySuccessfulUpload()) + upload_dir = os.path.join( + self.incoming_folder, "test", str(self.build.id), "ubuntu" + ) + write_file(os.path.join(upload_dir, "foo_0_all.rock"), b"rock") + write_file(os.path.join(upload_dir, "foo_0_all.manifest"), b"manifest") + handler = UploadHandler.forProcessor( + self.uploadprocessor, self.incoming_folder, "test", self.build + ) + result = handler.processRockRecipe(self.log) + self.assertEqual( + UploadStatusEnum.ACCEPTED, + result, + "Rock upload failed\nGot: %s" % self.log.getLogBuffer(), + ) + self.assertEqual(BuildStatus.FULLYBUILT, self.build.status) + self.assertTrue(self.build.verifySuccessfulUpload()) + + def test_requires_rock(self): + # The upload processor fails if the upload does not contain any + # .rock files. + self.assertFalse(self.build.verifySuccessfulUpload()) + upload_dir = os.path.join( + self.incoming_folder, "test", str(self.build.id), "ubuntu" + ) + write_file(os.path.join(upload_dir, "foo_0_all.manifest"), b"manifest") + handler = UploadHandler.forProcessor( + self.uploadprocessor, self.incoming_folder, "test", self.build + ) + result = handler.processRockRecipe(self.log) + self.assertEqual(UploadStatusEnum.REJECTED, result) + self.assertIn( + "ERROR Build did not produce any rocks.", self.log.getLogBuffer() + ) + self.assertFalse(self.build.verifySuccessfulUpload()) diff --git a/lib/lp/archiveuploader/uploadprocessor.py b/lib/lp/archiveuploader/uploadprocessor.py index baa903b..1289241 100644 --- a/lib/lp/archiveuploader/uploadprocessor.py +++ b/lib/lp/archiveuploader/uploadprocessor.py @@ -44,7 +44,6 @@ worst of the results from the various changes files found (in the order above, failed being worst). """ - import os import shutil import sys @@ -61,6 +60,7 @@ from lp.archiveuploader.nascentupload import ( NascentUpload, ) from lp.archiveuploader.ocirecipeupload import OCIRecipeUpload +from lp.archiveuploader.rockrecipeupload import RockRecipeUpload from lp.archiveuploader.snapupload import SnapUpload from lp.archiveuploader.uploadpolicy import ( BuildDaemonUploadPolicy, @@ -77,6 +77,7 @@ from lp.code.interfaces.sourcepackagerecipebuild import ( from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuild from lp.registry.interfaces.distribution import IDistributionSet from lp.registry.interfaces.person import IPersonSet +from lp.rocks.interfaces.rockrecipebuild import IRockRecipeBuild from lp.services.log.logger import BufferLogger from lp.services.statsd.interfaces.statsd_client import IStatsdClient from lp.services.webapp.adapter import ( @@ -775,6 +776,32 @@ class BuildUploadHandler(UploadHandler): self.processor.ztm.abort() raise + def processRockRecipe(self, logger=None): + """Process a rock recipe upload.""" + assert IRockRecipeBuild.providedBy(self.build) + if logger is None: + logger = self.processor.log + try: + logger.info("Processing rock upload %s" % self.upload_path) + RockRecipeUpload(self.upload_path, logger).process(self.build) + + if self.processor.dry_run: + logger.info("Dry run, aborting transaction.") + self.processor.ztm.abort() + else: + logger.info( + "Committing the transaction and any mails associated " + "with this upload." + ) + self.processor.ztm.commit() + return UploadStatusEnum.ACCEPTED + except UploadError as e: + logger.error(str(e)) + return UploadStatusEnum.REJECTED + except BaseException: + self.processor.ztm.abort() + raise + def process(self): """Process an upload that is the result of a build. @@ -830,6 +857,8 @@ class BuildUploadHandler(UploadHandler): result = self.processOCIRecipe(logger) elif ICharmRecipeBuild.providedBy(self.build): result = self.processCharmRecipe(logger) + elif IRockRecipeBuild.providedBy(self.build): + result = self.processRockRecipe(logger) elif ICIBuild.providedBy(self.build): result = self.processCIResult(logger) else:
_______________________________________________ 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