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

Proposed by Colin Watson
Status: Merged
Merged at revision: 17665
Proposed branch: lp:~cjwatson/launchpad/snap-mail
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/snap-webservice
Diff against target: 230 lines (+168/-0) (has conflicts)
4 files modified
lib/lp/snappy/emailtemplates/snapbuild-notification.txt (+10/-0)
lib/lp/snappy/mail/snapbuild.py (+82/-0)
lib/lp/snappy/model/snapbuild.py (+11/-0)
lib/lp/snappy/tests/test_snapbuild.py (+65/-0)
Text conflict in lib/lp/snappy/interfaces/snap.py
Text conflict in lib/lp/snappy/tests/test_snap.py
To merge this branch: bzr merge lp:~cjwatson/launchpad/snap-mail
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+265701@code.launchpad.net

Commit message

Add snap build notifications.

Description of the change

Add snap build notifications.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'lib/lp/snappy/emailtemplates'
2=== added file 'lib/lp/snappy/emailtemplates/snapbuild-notification.txt'
3--- lib/lp/snappy/emailtemplates/snapbuild-notification.txt 1970-01-01 00:00:00 +0000
4+++ lib/lp/snappy/emailtemplates/snapbuild-notification.txt 2015-08-03 13:04:19 +0000
5@@ -0,0 +1,10 @@
6+ * Snap Package: %(snap_name)s
7+ * Archive: %(archive_tag)s
8+ * Distroseries: %(distroseries)s
9+ * Architecture: %(architecturetag)s
10+ * Pocket: %(pocket)s
11+ * State: %(build_state)s
12+ * Duration: %(build_duration)s
13+ * Build Log: %(log_url)s
14+ * Upload Log: %(upload_log_url)s
15+ * Builder: %(builder_url)s
16
17=== added directory 'lib/lp/snappy/mail'
18=== added file 'lib/lp/snappy/mail/__init__.py'
19=== added file 'lib/lp/snappy/mail/snapbuild.py'
20--- lib/lp/snappy/mail/snapbuild.py 1970-01-01 00:00:00 +0000
21+++ lib/lp/snappy/mail/snapbuild.py 2015-08-03 13:04:19 +0000
22@@ -0,0 +1,82 @@
23+# Copyright 2015 Canonical Ltd. This software is licensed under the
24+# GNU Affero General Public License version 3 (see the file LICENSE).
25+
26+__metaclass__ = type
27+__all__ = [
28+ 'SnapBuildMailer',
29+ ]
30+
31+from lp.app.browser.tales import DurationFormatterAPI
32+from lp.services.config import config
33+from lp.services.mail.basemailer import (
34+ BaseMailer,
35+ RecipientReason,
36+ )
37+from lp.services.webapp import canonical_url
38+
39+
40+class SnapBuildMailer(BaseMailer):
41+
42+ app = 'snappy'
43+
44+ @classmethod
45+ def forStatus(cls, build):
46+ """Create a mailer for notifying about snap package build status.
47+
48+ :param build: The relevant build.
49+ """
50+ requester = build.requester
51+ recipients = {requester: RecipientReason.forBuildRequester(requester)}
52+ return cls(
53+ "[Snap build #%(build_id)d] %(build_title)s",
54+ "snapbuild-notification.txt", recipients,
55+ config.canonical.noreply_from_address, build)
56+
57+ def __init__(self, subject, template_name, recipients, from_address,
58+ build):
59+ super(SnapBuildMailer, self).__init__(
60+ subject, template_name, recipients, from_address,
61+ notification_type="snap-build-status")
62+ self.build = build
63+
64+ def _getHeaders(self, email):
65+ """See `BaseMailer`."""
66+ headers = super(SnapBuildMailer, self)._getHeaders(email)
67+ headers["X-Launchpad-Build-State"] = self.build.status.name
68+ return headers
69+
70+ def _getTemplateParams(self, email, recipient):
71+ """See `BaseMailer`."""
72+ build = self.build
73+ params = super(SnapBuildMailer, self)._getTemplateParams(
74+ email, recipient)
75+ params.update({
76+ "archive_tag": build.archive.reference,
77+ "build_id": build.id,
78+ "build_title": build.title,
79+ "snap_name": build.snap.name,
80+ "distroseries": build.snap.distro_series,
81+ "architecturetag": build.distro_arch_series.architecturetag,
82+ "pocket": build.pocket.name,
83+ "build_state": build.status.title,
84+ "build_duration": "",
85+ "log_url": "",
86+ "upload_log_url": "",
87+ "builder_url": "",
88+ "build_url": canonical_url(self.build),
89+ })
90+ if build.duration is not None:
91+ duration_formatter = DurationFormatterAPI(build.duration)
92+ params["build_duration"] = duration_formatter.approximateduration()
93+ if build.log is not None:
94+ params["log_url"] = build.log_url
95+ if build.upload_log is not None:
96+ params["upload_log_url"] = build.upload_log_url
97+ if build.builder is not None:
98+ params["builder_url"] = canonical_url(build.builder)
99+ return params
100+
101+ def _getFooter(self, params):
102+ """See `BaseMailer`."""
103+ return ("%(build_url)s\n"
104+ "%(reason)s\n" % params)
105
106=== modified file 'lib/lp/snappy/model/snapbuild.py'
107--- lib/lp/snappy/model/snapbuild.py 2015-08-03 09:54:47 +0000
108+++ lib/lp/snappy/model/snapbuild.py 2015-08-03 13:04:19 +0000
109@@ -34,6 +34,7 @@
110 from lp.buildmaster.model.packagebuild import PackageBuildMixin
111 from lp.registry.interfaces.pocket import PackagePublishingPocket
112 from lp.registry.model.person import Person
113+from lp.services.config import config
114 from lp.services.database.bulk import load_related
115 from lp.services.database.constants import DEFAULT
116 from lp.services.database.decoratedresultset import DecoratedResultSet
117@@ -57,6 +58,7 @@
118 ISnapBuildSet,
119 ISnapFile,
120 )
121+from lp.snappy.mail.snapbuild import SnapBuildMailer
122 from lp.soyuz.interfaces.component import IComponentSet
123 from lp.soyuz.model.archive import Archive
124
125@@ -290,6 +292,15 @@
126 """See `IPackageBuild`."""
127 return not self.getFiles().is_empty()
128
129+ def notify(self, extra_info=None):
130+ """See `IPackageBuild`."""
131+ if not config.builddmaster.send_build_notification:
132+ return
133+ if self.status == BuildStatus.FULLYBUILT:
134+ return
135+ mailer = SnapBuildMailer.forStatus(self)
136+ mailer.sendAll()
137+
138 def lfaUrl(self, lfa):
139 """Return the URL for a LibraryFileAlias in this context."""
140 if lfa is None:
141
142=== modified file 'lib/lp/snappy/tests/test_snapbuild.py'
143--- lib/lp/snappy/tests/test_snapbuild.py 2015-08-03 13:04:19 +0000
144+++ lib/lp/snappy/tests/test_snapbuild.py 2015-08-03 13:04:19 +0000
145@@ -23,7 +23,9 @@
146 from lp.buildmaster.enums import BuildStatus
147 from lp.buildmaster.interfaces.buildqueue import IBuildQueue
148 from lp.buildmaster.interfaces.packagebuild import IPackageBuild
149+from lp.buildmaster.interfaces.processor import IProcessorSet
150 from lp.registry.enums import PersonVisibility
151+from lp.services.config import config
152 from lp.services.features.testing import FeatureFixture
153 from lp.services.librarian.browser import ProxiedLibraryFileAlias
154 from lp.services.webapp.interfaces import OAuthPermission
155@@ -67,6 +69,20 @@
156 self.factory.makeDistroArchSeries(), None)
157
158
159+expected_body = """\
160+ * Snap Package: snap-1
161+ * Archive: distro
162+ * Distroseries: distro unstable
163+ * Architecture: i386
164+ * Pocket: RELEASE
165+ * State: Failed to build
166+ * Duration: 10 minutes
167+ * Build Log: %s
168+ * Upload Log: %s
169+ * Builder: http://launchpad.dev/builders/bob
170+"""
171+
172+
173 class TestSnapBuild(TestCaseWithFactory):
174
175 layer = LaunchpadZopelessLayer
176@@ -201,6 +217,55 @@
177 self.factory.makeSnapFile(snapbuild=self.build)
178 self.assertTrue(self.build.verifySuccessfulUpload())
179
180+ def test_notify_fullybuilt(self):
181+ # notify does not send mail when a SnapBuild completes normally.
182+ person = self.factory.makePerson(name="person")
183+ build = self.factory.makeSnapBuild(
184+ requester=person, status=BuildStatus.FULLYBUILT)
185+ build.notify()
186+ self.assertEqual(0, len(pop_notifications()))
187+
188+ def test_notify_packagefail(self):
189+ # notify sends mail when a SnapBuild fails.
190+ person = self.factory.makePerson(name="person")
191+ distribution = self.factory.makeDistribution(name="distro")
192+ distroseries = self.factory.makeDistroSeries(
193+ distribution=distribution, name="unstable")
194+ processor = getUtility(IProcessorSet).getByName("386")
195+ distroarchseries = self.factory.makeDistroArchSeries(
196+ distroseries=distroseries, architecturetag="i386",
197+ processor=processor)
198+ build = self.factory.makeSnapBuild(
199+ name=u"snap-1", requester=person, owner=person,
200+ distroarchseries=distroarchseries,
201+ date_created=datetime(2014, 04, 25, 10, 38, 0, tzinfo=pytz.UTC),
202+ status=BuildStatus.FAILEDTOBUILD,
203+ builder=self.factory.makeBuilder(name="bob"),
204+ duration=timedelta(minutes=10))
205+ build.setLog(self.factory.makeLibraryFileAlias())
206+ build.notify()
207+ [notification] = pop_notifications()
208+ self.assertEqual(
209+ config.canonical.noreply_from_address, notification["From"])
210+ self.assertEqual(
211+ "Person <%s>" % person.preferredemail.email, notification["To"])
212+ subject = notification["Subject"].replace("\n ", " ")
213+ self.assertEqual(
214+ "[Snap build #%d] i386 build of snap-1 snap package in distro "
215+ "unstable" % build.id, subject)
216+ self.assertEqual(
217+ "Requester", notification["X-Launchpad-Message-Rationale"])
218+ self.assertEqual(
219+ "snap-build-status",
220+ notification["X-Launchpad-Notification-Type"])
221+ self.assertEqual(
222+ "FAILEDTOBUILD", notification["X-Launchpad-Build-State"])
223+ body, footer = notification.get_payload(decode=True).split("\n-- \n")
224+ self.assertEqual(expected_body % (build.log_url, ""), body)
225+ self.assertEqual(
226+ "http://launchpad.dev/~person/+snap/snap-1/+build/%d\n"
227+ "You are the requester of the build.\n" % build.id, footer)
228+
229 def addFakeBuildLog(self, build):
230 build.setLog(self.factory.makeLibraryFileAlias("mybuildlog.txt"))
231