← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~ruinedyourlife/launchpad:craft-build-webhook into launchpad:master

 

Quentin Debhi has proposed merging ~ruinedyourlife/launchpad:craft-build-webhook into launchpad:master.

Commit message:
Add webhook integration to sourcecraft builds

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~ruinedyourlife/launchpad/+git/launchpad/+merge/485534
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~ruinedyourlife/launchpad:craft-build-webhook into launchpad:master.
diff --git a/lib/lp/crafts/configure.zcml b/lib/lp/crafts/configure.zcml
index 59d8621..a0fca60 100644
--- a/lib/lp/crafts/configure.zcml
+++ b/lib/lp/crafts/configure.zcml
@@ -65,7 +65,7 @@
 
     <subscriber
         for="lp.crafts.interfaces.craftrecipebuild.ICraftRecipeBuild
-            lp.crafts.interfaces.craftrecipebuild.ICraftRecipeBuildStatusChangedEvent"
+             lazr.lifecycle.interfaces.IObjectModifiedEvent"
         handler="lp.crafts.subscribers.craftrecipebuild.craft_build_status_changed" />
 
     <!-- CraftRecipeBuildSet -->
diff --git a/lib/lp/crafts/interfaces/craftrecipebuild.py b/lib/lp/crafts/interfaces/craftrecipebuild.py
index 60e2a54..7b57a27 100644
--- a/lib/lp/crafts/interfaces/craftrecipebuild.py
+++ b/lib/lp/crafts/interfaces/craftrecipebuild.py
@@ -7,7 +7,6 @@ __all__ = [
     "ICraftFile",
     "ICraftRecipeBuild",
     "ICraftRecipeBuildSet",
-    "ICraftRecipeBuildStatusChangedEvent",
 ]
 
 from lazr.restful.declarations import (
@@ -18,7 +17,6 @@ from lazr.restful.declarations import (
 )
 from lazr.restful.fields import Reference
 from zope.interface import Attribute, Interface
-from zope.interface.interfaces import IObjectEvent
 from zope.schema import Bool, Datetime, Dict, Int, TextLine
 
 from lp import _
@@ -40,10 +38,6 @@ from lp.services.librarian.interfaces import ILibraryFileAlias
 from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
 
 
-class ICraftRecipeBuildStatusChangedEvent(IObjectEvent):
-    """The status of a craft recipe build changed."""
-
-
 class ICraftRecipeBuildView(IPackageBuildView):
     """ICraftRecipeBuild attributes that require launchpad.View."""
 
diff --git a/lib/lp/crafts/model/craftrecipe.py b/lib/lp/crafts/model/craftrecipe.py
index 1701ffd..9a5b41e 100644
--- a/lib/lp/crafts/model/craftrecipe.py
+++ b/lib/lp/crafts/model/craftrecipe.py
@@ -108,6 +108,8 @@ from lp.services.job.interfaces.job import JobStatus
 from lp.services.job.model.job import Job
 from lp.services.librarian.model import LibraryFileAlias
 from lp.services.propertycache import cachedproperty, get_property_cache
+from lp.services.webhooks.interfaces import IWebhookSet
+from lp.services.webhooks.model import WebhookTargetMixin
 from lp.soyuz.model.distroarchseries import DistroArchSeries, PocketChroot
 
 
@@ -121,7 +123,7 @@ def craft_recipe_modified(recipe, event):
 
 
 @implementer(ICraftRecipe)
-class CraftRecipe(StormBase):
+class CraftRecipe(StormBase, WebhookTargetMixin):
     """See `ICraftRecipe`."""
 
     __storm_table__ = "CraftRecipe"
@@ -323,6 +325,10 @@ class CraftRecipe(StormBase):
         """See `ICraftRecipe`."""
         self._store_channels = value or None
 
+    @property
+    def valid_webhook_event_types(self):
+        return ["craft-recipe:build:0.1"]
+
     def getAllowedInformationTypes(self, user):
         """See `ICraftRecipe`."""
         # Allow both public and private information types
@@ -399,8 +405,9 @@ class CraftRecipe(StormBase):
 
     @property
     def can_upload_to_store(self):
-        # no store upload planned for the initial implementation, as artifacts
-        # get pulled from Launchpad for now only.
+        # XXX ruinedyourlife 2025-05-02: remove this property all together as
+        # we do not use it anywhere. we do thorough checks for this in the
+        # publishing job itself `CraftPublishingJob`.
         return False
 
     def destroySelf(self):
@@ -439,8 +446,7 @@ class CraftRecipe(StormBase):
             And(CraftRecipeJob.job == Job.id, CraftRecipeJob.recipe == self),
         )
         store.find(Job, Job.id.is_in(affected_jobs)).remove()
-        # XXX ruinedyourlife 2024-10-02: we need to remove webhooks once
-        # implemented getUtility(IWebhookSet).delete(self.webhooks)
+        getUtility(IWebhookSet).delete(self.webhooks)
         store.remove(self)
         store.find(
             BuildFarmJob, BuildFarmJob.id.is_in(build_farm_job_ids)
diff --git a/lib/lp/crafts/model/craftrecipebuild.py b/lib/lp/crafts/model/craftrecipebuild.py
index 1ecd39c..8fa4baa 100644
--- a/lib/lp/crafts/model/craftrecipebuild.py
+++ b/lib/lp/crafts/model/craftrecipebuild.py
@@ -6,7 +6,6 @@
 __all__ = [
     "CraftFile",
     "CraftRecipeBuild",
-    "ICraftRecipeBuildStatusChangedEvent",
 ]
 
 from datetime import timedelta, timezone
@@ -17,7 +16,6 @@ from storm.locals import Bool, DateTime, Desc, Int, Reference, Store, Unicode
 from storm.store import EmptyResultSet
 from zope.component import getUtility
 from zope.interface import implementer
-from zope.interface.interfaces import ObjectEvent
 from zope.security.proxy import removeSecurityProxy
 
 from lp.app.errors import NotFoundError
@@ -35,7 +33,6 @@ from lp.crafts.interfaces.craftrecipebuild import (
     ICraftFile,
     ICraftRecipeBuild,
     ICraftRecipeBuildSet,
-    ICraftRecipeBuildStatusChangedEvent,
 )
 from lp.crafts.mail.craftrecipebuild import CraftRecipeBuildMailer
 from lp.registry.interfaces.pocket import PackagePublishingPocket
@@ -63,11 +60,6 @@ from lp.services.webapp.snapshot import notify_modified
 from lp.soyuz.model.distroarchseries import DistroArchSeries
 
 
-@implementer(ICraftRecipeBuildStatusChangedEvent)
-class CraftRecipeBuildStatusChangedEvent(ObjectEvent):
-    """See `ICraftRecipeBuildStatusChangedEvent`."""
-
-
 @implementer(ICraftRecipeBuild)
 class CraftRecipeBuild(PackageBuildMixin, StormBase):
     """See `ICraftRecipeBuild`."""
diff --git a/lib/lp/services/webhooks/model.py b/lib/lp/services/webhooks/model.py
index 77f96aa..c8c8619 100644
--- a/lib/lp/services/webhooks/model.py
+++ b/lib/lp/services/webhooks/model.py
@@ -103,6 +103,9 @@ class Webhook(StormBase):
     charm_recipe_id = Int(name="charm_recipe")
     charm_recipe = Reference(charm_recipe_id, "CharmRecipe.id")
 
+    craft_recipe_id = Int(name="craft_recipe")
+    craft_recipe = Reference(craft_recipe_id, "CraftRecipe.id")
+
     project_id = Int(name="project")
     project = Reference(project_id, "Product.id")
 
@@ -217,6 +220,7 @@ class WebhookSet:
         from lp.charms.interfaces.charmrecipe import ICharmRecipe
         from lp.code.interfaces.branch import IBranch
         from lp.code.interfaces.gitrepository import IGitRepository
+        from lp.crafts.interfaces.craftrecipe import ICraftRecipe
         from lp.oci.interfaces.ocirecipe import IOCIRecipe
         from lp.registry.interfaces.distribution import IDistribution
         from lp.registry.interfaces.distributionsourcepackage import (
@@ -246,7 +250,8 @@ class WebhookSet:
         elif IDistributionSourcePackage.providedBy(target):
             hook.distribution = target.distribution
             hook.source_package_name = target.sourcepackagename
-
+        elif ICraftRecipe.providedBy(target):
+            hook.craft_recipe = target
         else:
             raise AssertionError("Unsupported target: %r" % (target,))
         hook.registrant = registrant
@@ -273,6 +278,7 @@ class WebhookSet:
         from lp.charms.interfaces.charmrecipe import ICharmRecipe
         from lp.code.interfaces.branch import IBranch
         from lp.code.interfaces.gitrepository import IGitRepository
+        from lp.crafts.interfaces.craftrecipe import ICraftRecipe
         from lp.oci.interfaces.ocirecipe import IOCIRecipe
         from lp.registry.interfaces.distribution import IDistribution
         from lp.registry.interfaces.distributionsourcepackage import (
@@ -306,6 +312,8 @@ class WebhookSet:
                 Webhook.distribution == target.distribution,
                 Webhook.source_package_name == target.sourcepackagename,
             )
+        elif ICraftRecipe.providedBy(target):
+            target_filter = Webhook.craft_recipe == target
         else:
             raise AssertionError("Unsupported target: %r" % (target,))
         return (

Follow ups