← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/snap-mail into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/snap-mail into lp:launchpad with lp:~cjwatson/launchpad/snap-webservice as a prerequisite.

Commit message:
Add snap build notifications.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1476405 in Launchpad itself: "Add support for building snaps"
  https://bugs.launchpad.net/launchpad/+bug/1476405

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/snap-mail/+merge/265701

Add snap build notifications.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/snap-mail into lp:launchpad.
=== added directory 'lib/lp/snappy/emailtemplates'
=== added file 'lib/lp/snappy/emailtemplates/snapbuild-notification.txt'
--- lib/lp/snappy/emailtemplates/snapbuild-notification.txt	1970-01-01 00:00:00 +0000
+++ lib/lp/snappy/emailtemplates/snapbuild-notification.txt	2015-07-23 16:59:18 +0000
@@ -0,0 +1,10 @@
+ * Snap Package: %(snap_name)s
+ * Archive: %(archive_tag)s
+ * Distroseries: %(distroseries)s
+ * Architecture: %(architecturetag)s
+ * Pocket: %(pocket)s
+ * State: %(build_state)s
+ * Duration: %(build_duration)s
+ * Build Log: %(log_url)s
+ * Upload Log: %(upload_log_url)s
+ * Builder: %(builder_url)s

=== added directory 'lib/lp/snappy/mail'
=== added file 'lib/lp/snappy/mail/__init__.py'
=== added file 'lib/lp/snappy/mail/snapbuild.py'
--- lib/lp/snappy/mail/snapbuild.py	1970-01-01 00:00:00 +0000
+++ lib/lp/snappy/mail/snapbuild.py	2015-07-23 16:59:18 +0000
@@ -0,0 +1,82 @@
+# Copyright 2015 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+__all__ = [
+    'SnapBuildMailer',
+    ]
+
+from lp.app.browser.tales import DurationFormatterAPI
+from lp.services.config import config
+from lp.services.mail.basemailer import (
+    BaseMailer,
+    RecipientReason,
+    )
+from lp.services.webapp import canonical_url
+
+
+class SnapBuildMailer(BaseMailer):
+
+    app = 'snappy'
+
+    @classmethod
+    def forStatus(cls, build):
+        """Create a mailer for notifying about snap package build status.
+
+        :param build: The relevant build.
+        """
+        requester = build.requester
+        recipients = {requester: RecipientReason.forBuildRequester(requester)}
+        return cls(
+            "[Snap build #%(build_id)d] %(build_title)s",
+            "snapbuild-notification.txt", recipients,
+            config.canonical.noreply_from_address, build)
+
+    def __init__(self, subject, template_name, recipients, from_address,
+                 build):
+        super(SnapBuildMailer, self).__init__(
+            subject, template_name, recipients, from_address,
+            notification_type="snap-build-status")
+        self.build = build
+
+    def _getHeaders(self, email):
+        """See `BaseMailer`."""
+        headers = super(SnapBuildMailer, self)._getHeaders(email)
+        headers["X-Launchpad-Build-State"] = self.build.status.name
+        return headers
+
+    def _getTemplateParams(self, email, recipient):
+        """See `BaseMailer`."""
+        build = self.build
+        params = super(SnapBuildMailer, self)._getTemplateParams(
+            email, recipient)
+        params.update({
+            "archive_tag": build.archive.reference,
+            "build_id": build.id,
+            "build_title": build.title,
+            "snap_name": build.snap.name,
+            "distroseries": build.snap.distro_series,
+            "architecturetag": build.distro_arch_series.architecturetag,
+            "pocket": build.pocket.name,
+            "build_state": build.status.title,
+            "build_duration": "",
+            "log_url": "",
+            "upload_log_url": "",
+            "builder_url": "",
+            "build_url": canonical_url(self.build),
+            })
+        if build.duration is not None:
+            duration_formatter = DurationFormatterAPI(build.duration)
+            params["build_duration"] = duration_formatter.approximateduration()
+        if build.log is not None:
+            params["log_url"] = build.log_url
+        if build.upload_log is not None:
+            params["upload_log_url"] = build.upload_log_url
+        if build.builder is not None:
+            params["builder_url"] = canonical_url(build.builder)
+        return params
+
+    def _getFooter(self, params):
+        """See `BaseMailer`."""
+        return ("%(build_url)s\n"
+                "%(reason)s\n" % params)

=== modified file 'lib/lp/snappy/model/snapbuild.py'
--- lib/lp/snappy/model/snapbuild.py	2015-07-23 16:59:18 +0000
+++ lib/lp/snappy/model/snapbuild.py	2015-07-23 16:59:18 +0000
@@ -34,6 +34,7 @@
 from lp.buildmaster.model.packagebuild import PackageBuildMixin
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.registry.model.person import Person
+from lp.services.config import config
 from lp.services.database.bulk import load_related
 from lp.services.database.constants import DEFAULT
 from lp.services.database.decoratedresultset import DecoratedResultSet
@@ -57,6 +58,7 @@
     ISnapBuildSet,
     ISnapFile,
     )
+from lp.snappy.mail.snapbuild import SnapBuildMailer
 from lp.soyuz.interfaces.component import IComponentSet
 from lp.soyuz.model.archive import Archive
 
@@ -289,6 +291,15 @@
         """See `IPackageBuild`."""
         return not self.getFiles().is_empty()
 
+    def notify(self, extra_info=None):
+        """See `IPackageBuild`."""
+        if not config.builddmaster.send_build_notification:
+            return
+        if self.status == BuildStatus.FULLYBUILT:
+            return
+        mailer = SnapBuildMailer.forStatus(self)
+        mailer.sendAll()
+
     def lfaUrl(self, lfa):
         """Return the URL for a LibraryFileAlias in this context."""
         if lfa is None:

=== modified file 'lib/lp/snappy/tests/test_snapbuild.py'
--- lib/lp/snappy/tests/test_snapbuild.py	2015-07-23 16:59:18 +0000
+++ lib/lp/snappy/tests/test_snapbuild.py	2015-07-23 16:59:18 +0000
@@ -71,6 +71,20 @@
             self.factory.makeDistroArchSeries(), None)
 
 
+expected_body = """\
+ * Snap Package: snap-1
+ * Archive: distro
+ * Distroseries: distro unstable
+ * Architecture: i386
+ * Pocket: RELEASE
+ * State: Failed to build
+ * Duration: 10 minutes
+ * Build Log: %s
+ * Upload Log: %s
+ * Builder: http://launchpad.dev/builders/bob
+"""
+
+
 class TestSnapBuild(TestCaseWithFactory):
 
     layer = LaunchpadZopelessLayer
@@ -205,6 +219,55 @@
         self.factory.makeSnapFile(snapbuild=self.build)
         self.assertTrue(self.build.verifySuccessfulUpload())
 
+    def test_notify_fullybuilt(self):
+        # notify does not send mail when a SnapBuild completes normally.
+        person = self.factory.makePerson(name="person")
+        build = self.factory.makeSnapBuild(
+            requester=person, status=BuildStatus.FULLYBUILT)
+        build.notify()
+        self.assertEqual(0, len(pop_notifications()))
+
+    def test_notify_packagefail(self):
+        # notify sends mail when a SnapBuild fails.
+        person = self.factory.makePerson(name="person")
+        distribution = self.factory.makeDistribution(name="distro")
+        distroseries = self.factory.makeDistroSeries(
+            distribution=distribution, name="unstable")
+        processor = getUtility(IProcessorSet).getByName("386")
+        distroarchseries = self.factory.makeDistroArchSeries(
+            distroseries=distroseries, architecturetag="i386",
+            processor=processor)
+        build = self.factory.makeSnapBuild(
+            name=u"snap-1", requester=person, owner=person,
+            distroarchseries=distroarchseries,
+            date_created=datetime(2014, 04, 25, 10, 38, 0, tzinfo=pytz.UTC),
+            status=BuildStatus.FAILEDTOBUILD,
+            builder=self.factory.makeBuilder(name="bob"),
+            duration=timedelta(minutes=10))
+        build.setLog(self.factory.makeLibraryFileAlias())
+        build.notify()
+        [notification] = pop_notifications()
+        self.assertEqual(
+            config.canonical.noreply_from_address, notification["From"])
+        self.assertEqual(
+            "Person <%s>" % person.preferredemail.email, notification["To"])
+        subject = notification["Subject"].replace("\n ", " ")
+        self.assertEqual(
+            "[Snap build #%d] i386 build of snap-1 snap package in distro "
+            "unstable" % build.id, subject)
+        self.assertEqual(
+            "Requester", notification["X-Launchpad-Message-Rationale"])
+        self.assertEqual(
+            "snap-build-status",
+            notification["X-Launchpad-Notification-Type"])
+        self.assertEqual(
+            "FAILEDTOBUILD", notification["X-Launchpad-Build-State"])
+        body, footer = notification.get_payload(decode=True).split("\n-- \n")
+        self.assertEqual(expected_body % (build.log_url, ""), body)
+        self.assertEqual(
+            "http://launchpad.dev/~person/+snap/snap-1/+build/%d\n";
+            "You are the requester of the build.\n" % build.id, footer)
+
     def addFakeBuildLog(self, build):
         build.setLog(self.factory.makeLibraryFileAlias("mybuildlog.txt"))
 


Follow ups