launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #19068
[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