← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~vaishnavi-asawale/launchpad:binary-package-upload-webhooks into launchpad:master

 

Vaishnavi Asawale has proposed merging ~vaishnavi-asawale/launchpad:binary-package-upload-webhooks into launchpad:master.

Commit message:
Add webhooks for binary package uploads

In this MP:
1) Add subscriber and handler functions to trigger webhooks when the
binary package upload status changes to APPROVED/UNAPPROVED/REJECTED.
2)) Add tests to ensure webhooks are triggered/ not triggered based on whether
the package upload status changes and the feature flag to allow webhooks 
to be configured on archives is set.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~vaishnavi-asawale/launchpad/+git/launchpad/+merge/494979
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~vaishnavi-asawale/launchpad:binary-package-upload-webhooks into launchpad:master.
diff --git a/lib/lp/services/webhooks/interfaces.py b/lib/lp/services/webhooks/interfaces.py
index e922075..e3dca6a 100644
--- a/lib/lp/services/webhooks/interfaces.py
+++ b/lib/lp/services/webhooks/interfaces.py
@@ -74,6 +74,13 @@ WEBHOOK_EVENT_TYPES = {
     "upload rejected",
     "archive:source-package-upload:0.1::unapproved": "Source package"
     "upload unapproved",
+    "archive:binary-package-upload:0.1": "Binary package upload",
+    "archive:binary-package-upload:0.1::accepted": "Binary package"
+    " upload accepted",
+    "archive:binary-package-upload:0.1::rejected": "Binary package"
+    " upload rejected",
+    "archive:binary-package-upload:0.1::unapproved": "Binary package"
+    " upload unapproved",
     "archive:binary-build:0.1": "Binary build",
     "archive:binary-build:0.1::fullybuilt": "Binary build fully built",
     "archive:binary-build:0.1::failedtobuild": "Binary build failed to build",
diff --git a/lib/lp/soyuz/model/archive.py b/lib/lp/soyuz/model/archive.py
index e5d8392..a5f74bb 100644
--- a/lib/lp/soyuz/model/archive.py
+++ b/lib/lp/soyuz/model/archive.py
@@ -237,6 +237,10 @@ class Archive(StormBase, WebhookTargetMixin):
             "archive:source-package-upload:0.1::accepted",
             "archive:source-package-upload:0.1::rejected",
             "archive:source-package-upload:0.1::unapproved",
+            "archive:binary-package-upload:0.1",
+            "archive:binary-package-upload:0.1::accepted",
+            "archive:binary-package-upload:0.1::rejected",
+            "archive:binary-package-upload:0.1::unapproved",
             "archive:binary-build:0.1",
             "archive:binary-build:0.1::fullybuilt",
             "archive:binary-build:0.1::failedtobuild",
diff --git a/lib/lp/soyuz/subscribers/archive.py b/lib/lp/soyuz/subscribers/archive.py
index a4202de..2c652c7 100644
--- a/lib/lp/soyuz/subscribers/archive.py
+++ b/lib/lp/soyuz/subscribers/archive.py
@@ -34,6 +34,20 @@ def _trigger_source_package_status_change_webhook(upload, event_type):
         getUtility(IWebhookSet).trigger(upload.archive, event_type, payload)
 
 
+def _trigger_binary_package_status_change_webhook(upload, event_type):
+    if getFeatureFlag(ARCHIVE_WEBHOOKS_FEATURE_FLAG):
+        payload = {
+            "package_upload": canonical_url(upload, force_local_path=True),
+            "action": "status-changed",
+            "status": upload.status.name,
+            "archive": canonical_url(upload.archive, force_local_path=True),
+            "source_package_name": str(
+                upload.builds[0].build.source_package_release.sourcepackagename
+            ),
+        }
+        getUtility(IWebhookSet).trigger(upload.archive, event_type, payload)
+
+
 def _trigger_build_status_change_webhook(build, event_type):
     if getFeatureFlag(ARCHIVE_WEBHOOKS_FEATURE_FLAG):
         payload = {
@@ -77,6 +91,23 @@ def package_status_change_webhook(upload, event):
                 f"{upload.status.name.lower()}",
             )
 
+    # For binary packages
+    if upload.builds:
+        if (
+            event.edited_fields
+            and "status" in event.edited_fields
+            and (
+                upload.status == PackageUploadStatus.ACCEPTED
+                or upload.status == PackageUploadStatus.REJECTED
+                or upload.status == PackageUploadStatus.UNAPPROVED
+            )
+        ):
+            _trigger_binary_package_status_change_webhook(
+                upload,
+                f"archive:binary-package-upload:0.1::"
+                f"{upload.status.name.lower()}",
+            )
+
 
 def build_status_change_webhook(build, event):
     """Webhook for binary builds"""
diff --git a/lib/lp/soyuz/tests/test_archive.py b/lib/lp/soyuz/tests/test_archive.py
index 3f8551f..c0327f0 100644
--- a/lib/lp/soyuz/tests/test_archive.py
+++ b/lib/lp/soyuz/tests/test_archive.py
@@ -7511,6 +7511,158 @@ class TestSourcePackageUploadWebhooks(TestCaseWithFactory):
         self.assertEqual(hook.deliveries.count(), 0)
 
 
+class TestBinaryackageUploadWebhooks(TestCaseWithFactory):
+    """Tests that binary package upload webhooks trigger correctly."""
+
+    layer = LaunchpadZopelessLayer
+
+    def test_binary_package_upload_status_change_triggers_webhook(self):
+        self.useFixture(
+            FeatureFixture(
+                {
+                    ARCHIVE_WEBHOOKS_FEATURE_FLAG: "on",
+                }
+            )
+        )
+        archive = self.factory.makeArchive(name="test-archive")
+        hook = self.factory.makeWebhook(
+            target=archive,
+            delivery_url="http://localhost/test-webhook";,
+            event_types=["archive:binary-package-upload:0.1"],
+        )
+
+        upload = self.factory.makePackageUpload(
+            archive=archive,
+            status=PackageUploadStatus.NEW,
+        )
+
+        upload.addBuild(
+            self.factory.makeBinaryPackageBuild(
+                archive=archive, status=BuildStatus.FULLYBUILT
+            )
+        )
+
+        upload_unwrapped = removeSecurityProxy(upload)
+        upload_unwrapped.setAccepted()
+
+        job = hook.deliveries.one()
+        self.assertEqual(job.event_type, "archive:binary-package-upload:0.1")
+
+        payload = job.payload
+        self.assertEqual(
+            payload["package_upload"],
+            canonical_url(upload, force_local_path=True),
+        )
+        self.assertEqual(payload["action"], "status-changed")
+        self.assertEqual(payload["status"], "ACCEPTED")
+        self.assertEqual(
+            payload["archive"],
+            canonical_url(archive, force_local_path=True),
+        )
+        self.assertIsNotNone(
+            upload.builds[0].build.source_package_release.sourcepackagename
+        )
+
+    def test_binary_package_upload_no_status_change_does_not_trigger_webhook(
+        self,
+    ):
+        self.useFixture(
+            FeatureFixture(
+                {
+                    ARCHIVE_WEBHOOKS_FEATURE_FLAG: "on",
+                }
+            )
+        )
+        archive = self.factory.makeArchive(name="test-archive")
+        hook = self.factory.makeWebhook(
+            target=archive,
+            delivery_url="http://localhost/test-webhook";,
+            event_types=["archive:binary-package-upload:0.1"],
+        )
+
+        upload = self.factory.makePackageUpload(
+            archive=archive,
+            status=PackageUploadStatus.NEW,
+        )
+
+        upload.addBuild(
+            self.factory.makeBinaryPackageBuild(
+                archive=archive, status=BuildStatus.FULLYBUILT
+            )
+        )
+
+        self.assertEqual(hook.deliveries.count(), 0)
+
+    def test_only_webhook_for_chosen_subscope_is_triggered(self):
+        """If the webhook is configured only for the subscope 'accepted',
+        then rejection should not trigger a webhook, but acceptance should."""
+        self.useFixture(
+            FeatureFixture(
+                {
+                    ARCHIVE_WEBHOOKS_FEATURE_FLAG: "on",
+                }
+            )
+        )
+        archive = self.factory.makeArchive(name="test-archive")
+        hook = self.factory.makeWebhook(
+            target=archive,
+            delivery_url="http://localhost/test-webhook";,
+            event_types=["archive:binary-package-upload:0.1::accepted"],
+        )
+
+        upload = self.factory.makePackageUpload(
+            archive=archive,
+            status=PackageUploadStatus.NEW,
+        )
+
+        upload.addBuild(
+            self.factory.makeBinaryPackageBuild(
+                archive=archive, status=BuildStatus.FULLYBUILT
+            )
+        )
+
+        upload_unwrapped = removeSecurityProxy(upload)
+        upload_unwrapped.setRejected()
+
+        self.assertEqual(hook.deliveries.count(), 0)
+
+        upload_unwrapped.setAccepted()
+
+        job = hook.deliveries.one()
+        self.assertEqual(job.event_type, "archive:binary-package-upload:0.1")
+
+    def test_no_webhook_triggered_when_feature_flag_is_not_on(self):
+        self.useFixture(
+            FeatureFixture(
+                {
+                    ARCHIVE_WEBHOOKS_FEATURE_FLAG: "",
+                }
+            )
+        )
+        archive = self.factory.makeArchive(name="test-archive")
+        hook = self.factory.makeWebhook(
+            target=archive,
+            delivery_url="http://localhost/test-webhook";,
+            event_types=["archive:binary-package-upload:0.1"],
+        )
+
+        upload = self.factory.makePackageUpload(
+            archive=archive,
+            status=PackageUploadStatus.NEW,
+        )
+
+        upload.addBuild(
+            self.factory.makeBinaryPackageBuild(
+                archive=archive, status=BuildStatus.FULLYBUILT
+            )
+        )
+
+        upload_unwrapped = removeSecurityProxy(upload)
+        upload_unwrapped.setAccepted()
+
+        self.assertEqual(hook.deliveries.count(), 0)
+
+
 class TestBinaryBuildFinishWebhooks(TestCaseWithFactory):
     """Tests that webhooks trigger correctly on binary package
     status change."""