launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #18999
[Merge] lp:~cjwatson/launchpad/separate-build-notifications into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/separate-build-notifications into lp:launchpad.
Commit message:
Add a rationale to build mail notifications.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #410893 in Launchpad itself: "PPA Buildd emails are missing a rationale message"
https://bugs.launchpad.net/launchpad/+bug/410893
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/separate-build-notifications/+merge/264606
Add a rationale to build mail notifications.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/separate-build-notifications into lp:launchpad.
=== modified file 'lib/lp/soyuz/doc/build-failedtoupload-workflow.txt'
--- lib/lp/soyuz/doc/build-failedtoupload-workflow.txt 2015-02-17 07:37:36 +0000
+++ lib/lp/soyuz/doc/build-failedtoupload-workflow.txt 2015-07-13 16:09:27 +0000
@@ -67,7 +67,7 @@
Note that the generated notification contain the 'extra_info' content:
- >>> build_notification = notifications.pop(0)
+ >>> build_notification = notifications[0]
>>> build_notification['Subject']
'[Build #22] i386 build of cdrkit 1.0 in ubuntu breezy-autotest RELEASE'
@@ -103,3 +103,19 @@
i386 build of cdrkit 1.0 in ubuntu breezy-autotest RELEASE
http://launchpad.dev/ubuntu/+source/cdrkit/1.0/+build/22
<BLANKLINE>
+ You are receiving this email because you are a buildd administrator.
+ <BLANKLINE>
+
+The other notifications are similar except for the footer.
+
+ >>> print notifications[1].get_payload()
+ <BLANKLINE>
+ ...
+ You are receiving this email because you are a buildd administrator.
+ <BLANKLINE>
+ >>> print notifications[2].get_payload()
+ <BLANKLINE>
+ ...
+ You are receiving this email because you created this version of this
+ package.
+ <BLANKLINE>
=== modified file 'lib/lp/soyuz/emailtemplates/build-notification.txt'
--- lib/lp/soyuz/emailtemplates/build-notification.txt 2011-12-18 23:10:57 +0000
+++ lib/lp/soyuz/emailtemplates/build-notification.txt 2015-07-13 16:09:27 +0000
@@ -18,3 +18,5 @@
--
%(build_title)s
%(build_url)s
+
+%(reason)s
=== modified file 'lib/lp/soyuz/model/binarypackagebuild.py'
--- lib/lp/soyuz/model/binarypackagebuild.py 2015-07-08 16:05:11 +0000
+++ lib/lp/soyuz/model/binarypackagebuild.py 2015-07-13 16:09:27 +0000
@@ -17,6 +17,7 @@
attrgetter,
itemgetter,
)
+import textwrap
import apt_pkg
import pytz
@@ -702,7 +703,7 @@
def notify(self, extra_info=None):
"""See `IPackageBuild`.
- If config.buildmaster.build_notification is disable, simply
+ If config.buildmaster.send_build_notification is disabled, simply
return.
If config.builddmaster.notify_owner is enabled and SPR.creator
@@ -722,7 +723,7 @@
if self.status == BuildStatus.FULLYBUILT:
return
- recipients = set()
+ recipients = {}
fromaddress = format_address(
config.builddmaster.default_sender_name,
@@ -764,12 +765,13 @@
# notified if they are the PPA owner or in the PPA team.
# (see bug 375757)
# Non-PPA notifications inform the creator regardless.
- recipients = recipients.union(
- get_contact_email_addresses(creator))
+ for recipient in get_contact_email_addresses(creator):
+ recipients.setdefault(
+ recipient, "you created this version of this package")
dsc_key = self.source_package_release.dscsigningkey
if dsc_key:
- recipients = recipients.union(
- get_contact_email_addresses(dsc_key.owner))
+ for recipient in get_contact_email_addresses(dsc_key.owner):
+ recipients.setdefault(recipient, "you signed this package")
# Modify notification contents according to the targeted archive.
# 'Archive Tag', 'Subject' and 'Source URL' are customized for PPA.
@@ -780,12 +782,13 @@
subject = "[Build #%d] %s" % (self.id, self.title)
if not self.archive.is_ppa:
buildd_admins = getUtility(ILaunchpadCelebrities).buildd_admin
- recipients = recipients.union(
- get_contact_email_addresses(buildd_admins))
+ for recipient in get_contact_email_addresses(buildd_admins):
+ recipients.setdefault(
+ recipient, "you are a buildd administrator")
source_url = canonical_url(self.distributionsourcepackagerelease)
else:
- recipients = recipients.union(
- get_contact_email_addresses(self.archive.owner))
+ for recipient in get_contact_email_addresses(self.archive.owner):
+ recipients.setdefault(recipient, "you own this archive")
# For PPAs we run the risk of having no available contact_address,
# for instance, when both, SPR.creator and Archive.owner have
# not enabled it.
@@ -803,7 +806,7 @@
# pocket build. We don't build SECURITY yet :(
# XXX cprov 2006-08-02: find out a way to glue parameters reported
- # with the state in the build worflow, maybe by having an
+ # with the state in the build workflow, maybe by having an
# IBuild.statusReport property, which could also be used in the
# respective page template.
if self.status in [
@@ -852,9 +855,11 @@
'archive_tag': self.archive.reference,
'component_tag': self.current_component.name,
}
- message = template % replacements
- for toaddress in recipients:
+ for toaddress, reason in recipients.items():
+ replacements['reason'] = textwrap.fill(
+ 'You are receiving this email because %s.' % reason, width=72)
+ message = template % replacements
simple_sendmail(
fromaddress, toaddress, subject, message,
headers=extra_headers)
=== modified file 'lib/lp/soyuz/tests/test_build_notify.py'
--- lib/lp/soyuz/tests/test_build_notify.py 2015-04-20 15:59:52 +0000
+++ lib/lp/soyuz/tests/test_build_notify.py 2015-07-13 16:09:27 +0000
@@ -4,6 +4,7 @@
__metaclass__ = type
from datetime import timedelta
+from operator import itemgetter
from textwrap import dedent
from zope.component import getUtility
@@ -27,6 +28,18 @@
from lp.testing.sampledata import ADMIN_EMAIL
+REASONS = {
+ "creator": (
+ "You are receiving this email because you created this version of "
+ "this\npackage."),
+ "signer": "You are receiving this email because you signed this package.",
+ "buildd-admin": (
+ "You are receiving this email because you are a buildd "
+ "administrator."),
+ "owner": "You are receiving this email because you own this archive.",
+ }
+
+
class TestBuildNotify(TestCaseWithFactory):
layer = LaunchpadFunctionalLayer
@@ -51,6 +64,7 @@
'launchpad-buildd-admins')
self.buildd_admins_email = []
with person_logged_in(self.admin):
+ self.ppa_owner_email = self.ppa.owner.preferredemail.email
self.publisher = SoyuzTestPublisher()
self.publisher.prepareBreezyAutotest()
self.distroseries.nominatedarchindep = self.das
@@ -83,21 +97,22 @@
build.buildqueue_record.builder = self.builder
self.builds.append(build)
- def _assert_mail_is_correct(self, build, notification, ppa=False):
+ def _assert_mail_is_correct(self, build, notification, recipient, reason,
+ ppa=False):
# Assert that the mail sent (which is in notification), matches
# the data from the build
- self.assertEquals('test@xxxxxxxxxxx',
- notification['X-Creator-Recipient'])
- self.assertEquals(
+ self.assertEqual(recipient, notification['To'])
+ self.assertEqual(
+ 'test@xxxxxxxxxxx', notification['X-Creator-Recipient'])
+ self.assertEqual(
self.das.architecturetag, notification['X-Launchpad-Build-Arch'])
- self.assertEquals(
- 'main', notification['X-Launchpad-Build-Component'])
- self.assertEquals(
+ self.assertEqual('main', notification['X-Launchpad-Build-Component'])
+ self.assertEqual(
build.status.name, notification['X-Launchpad-Build-State'])
- self.assertEquals(
+ self.assertEqual(
build.archive.reference, notification['X-Launchpad-Archive'])
if ppa and build.archive.distribution.name == u'ubuntu':
- self.assertEquals(
+ self.assertEqual(
get_ppa_reference(self.ppa), notification['X-Launchpad-PPA'])
body = notification.get_payload(decode=True)
build_log = 'None'
@@ -105,10 +120,10 @@
source = 'not available'
else:
source = canonical_url(build.distributionsourcepackagerelease)
- builder = canonical_url(build.builder)
if build.status == BuildStatus.BUILDING:
duration = 'not finished'
build_log = 'see builder page'
+ builder = canonical_url(build.builder)
elif (
build.status == BuildStatus.SUPERSEDED or
build.status == BuildStatus.NEEDSBUILD):
@@ -122,6 +137,7 @@
else:
duration = DurationFormatterAPI(
build.duration).approximateduration()
+ builder = canonical_url(build.builder)
expected_body = dedent("""
* Source Package: %s
* Version: %s
@@ -147,59 +163,61 @@
build.source_package_release.version, self.das.architecturetag,
build.archive.reference, build.status.title, duration, build_log,
builder, source, build.title, canonical_url(build)))
- self.assertEquals(expected_body, body)
-
- def test_notify_buildd_admins(self):
- # A build will cause an e-mail to be sent out to the buildd-admins,
- # for primary archive builds.
- self.create_builds(self.archive)
- build = self.builds[BuildStatus.FAILEDTOBUILD.value]
- build.notify()
- expected_emails = self.buildd_admins_email + ['test@xxxxxxxxxxx']
- notifications = pop_notifications()
- actual_emails = [n['To'] for n in notifications]
- self.assertEquals(expected_emails, actual_emails)
-
- def test_ppa_does_not_notify_buildd_admins(self):
- # A build for a PPA does not notify the buildd admins.
- self.create_builds(self.ppa)
- build = self.builds[BuildStatus.FAILEDTOBUILD.value]
- build.notify()
- notifications = pop_notifications()
- # An e-mail is sent to the archive owner, as well as the creator
- self.assertEquals(2, len(notifications))
+ expected_body += "\n" + REASONS[reason] + "\n"
+ self.assertEqual(expected_body, body)
+
+ def _assert_mails_are_correct(self, build, reasons, ppa=False):
+ notifications = pop_notifications()
+ reasons = sorted(reasons, key=itemgetter(0))
+ for notification, (recipient, reason) in zip(notifications, reasons):
+ self._assert_mail_is_correct(
+ build, notification, recipient, reason, ppa=ppa)
def test_notify_failed_to_build(self):
- # An e-mail is sent to the source package creator on build failures.
+ # For primary archive builds, a build failure notifies the buildd
+ # admins and the source package creator.
self.create_builds(self.archive)
build = self.builds[BuildStatus.FAILEDTOBUILD.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification)
+ expected_reasons = [
+ (email, "buildd-admin") for email in self.buildd_admins_email]
+ expected_reasons.append(("test@xxxxxxxxxxx", "creator"))
+ self._assert_mails_are_correct(build, expected_reasons)
def test_notify_failed_to_build_ppa(self):
- # An e-mail is sent to the source package creator on build failures.
- self.create_builds(archive=self.ppa)
+ # For PPA builds, a build failure notifies the source package signer
+ # and the archive owner, but not the buildd admins.
+ self.create_builds(self.ppa)
build = self.builds[BuildStatus.FAILEDTOBUILD.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification, ppa=True)
+ expected_reasons = [
+ ("test@xxxxxxxxxxx", "signer"),
+ (self.ppa_owner_email, "owner"),
+ ]
+ self._assert_mails_are_correct(build, expected_reasons, ppa=True)
def test_notify_needs_building(self):
- # We can notify the creator when the build is needing to be built.
+ # We can notify the creator and buildd admins when a build needs to
+ # be built.
self.create_builds(self.archive)
build = self.builds[BuildStatus.NEEDSBUILD.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification)
+ expected_reasons = [
+ (email, "buildd-admin") for email in self.buildd_admins_email]
+ expected_reasons.append(("test@xxxxxxxxxxx", "creator"))
+ self._assert_mails_are_correct(build, expected_reasons)
def test_notify_needs_building_ppa(self):
- # We can notify the creator when the build is needing to be built.
+ # We can notify the signer and the archive owner when a build needs
+ # to be built.
self.create_builds(self.ppa)
build = self.builds[BuildStatus.NEEDSBUILD.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification, ppa=True)
+ expected_reasons = [
+ ("test@xxxxxxxxxxx", "signer"),
+ (self.ppa_owner_email, "owner"),
+ ]
+ self._assert_mails_are_correct(build, expected_reasons, ppa=True)
def test_notify_successfully_built(self):
# Successful builds don't notify anyone.
@@ -209,90 +227,121 @@
self.assertEqual([], pop_notifications())
def test_notify_dependency_wait(self):
- # We can notify the creator when the build can't find a dependency.
+ # We can notify the creator and buildd admins when a build can't
+ # find a dependency.
self.create_builds(self.archive)
build = self.builds[BuildStatus.MANUALDEPWAIT.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification)
+ expected_reasons = [
+ (email, "buildd-admin") for email in self.buildd_admins_email]
+ expected_reasons.append(("test@xxxxxxxxxxx", "creator"))
+ self._assert_mails_are_correct(build, expected_reasons)
def test_notify_dependency_wait_ppa(self):
- # We can notify the creator when the build can't find a dependency.
+ # We can notify the signer and the archive owner when the build
+ # can't find a dependency.
self.create_builds(self.ppa)
build = self.builds[BuildStatus.MANUALDEPWAIT.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification, ppa=True)
+ expected_reasons = [
+ ("test@xxxxxxxxxxx", "signer"),
+ (self.ppa_owner_email, "owner"),
+ ]
+ self._assert_mails_are_correct(build, expected_reasons, ppa=True)
def test_notify_chroot_problem(self):
- # We can notify the creator when the builder the build attempted to
- # be built on has an internal problem.
+ # We can notify the creator and buildd admins when the builder a
+ # build attempted to be built on has an internal problem.
self.create_builds(self.archive)
build = self.builds[BuildStatus.CHROOTWAIT.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification)
+ expected_reasons = [
+ (email, "buildd-admin") for email in self.buildd_admins_email]
+ expected_reasons.append(("test@xxxxxxxxxxx", "creator"))
+ self._assert_mails_are_correct(build, expected_reasons)
def test_notify_chroot_problem_ppa(self):
- # We can notify the creator when the builder the build attempted to
- # be built on has an internal problem.
+ # We can notify the signer and the archive owner when the builder a
+ # build attempted to be built on has an internal problem.
self.create_builds(self.ppa)
build = self.builds[BuildStatus.CHROOTWAIT.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification, ppa=True)
+ expected_reasons = [
+ ("test@xxxxxxxxxxx", "signer"),
+ (self.ppa_owner_email, "owner"),
+ ]
+ self._assert_mails_are_correct(build, expected_reasons, ppa=True)
def test_notify_build_for_superseded_source(self):
- # We can notify the creator when the source package had a newer
- # version uploaded before this build had a chance to be dispatched.
+ # We can notify the creator and buildd admins when the source
+ # package had a newer version uploaded before this build had a
+ # chance to be dispatched.
self.create_builds(self.archive)
build = self.builds[BuildStatus.SUPERSEDED.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification)
+ expected_reasons = [
+ (email, "buildd-admin") for email in self.buildd_admins_email]
+ expected_reasons.append(("test@xxxxxxxxxxx", "creator"))
+ self._assert_mails_are_correct(build, expected_reasons)
def test_notify_build_for_superseded_source_ppa(self):
- # We can notify the creator when the source package had a newer
- # version uploaded before this build had a chance to be dispatched.
+ # We can notify the signer and the archive owner when the source
+ # package had a newer version uploaded before this build had a
+ # chance to be dispatched.
self.create_builds(self.ppa)
build = self.builds[BuildStatus.SUPERSEDED.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification, ppa=True)
+ expected_reasons = [
+ ("test@xxxxxxxxxxx", "signer"),
+ (self.ppa_owner_email, "owner"),
+ ]
+ self._assert_mails_are_correct(build, expected_reasons, ppa=True)
def test_notify_currently_building(self):
- # We can notify the creator when the build is currently building.
+ # We can notify the creator and buildd admins when the build is
+ # currently building.
self.create_builds(self.archive)
build = self.builds[BuildStatus.BUILDING.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification)
+ expected_reasons = [
+ (email, "buildd-admin") for email in self.buildd_admins_email]
+ expected_reasons.append(("test@xxxxxxxxxxx", "creator"))
+ self._assert_mails_are_correct(build, expected_reasons)
def test_notify_currently_building_ppa(self):
- # We can notify the creator when the build is currently building.
+ # We can notify the signer and the archive owner when the build is
+ # currently building.
self.create_builds(self.ppa)
build = self.builds[BuildStatus.BUILDING.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification, ppa=True)
+ expected_reasons = [
+ ("test@xxxxxxxxxxx", "signer"),
+ (self.ppa_owner_email, "owner"),
+ ]
+ self._assert_mails_are_correct(build, expected_reasons, ppa=True)
def test_notify_uploading_build(self):
- # We can notify the creator when the build has completed, and binary
- # packages are being uploaded by the builder.
+ # We can notify the creator and buildd admins when the build has
+ # completed, and binary packages are being uploaded by the builder.
self.create_builds(self.archive)
build = self.builds[BuildStatus.UPLOADING.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification)
+ expected_reasons = [
+ (email, "buildd-admin") for email in self.buildd_admins_email]
+ expected_reasons.append(("test@xxxxxxxxxxx", "creator"))
+ self._assert_mails_are_correct(build, expected_reasons)
def test_notify_uploading_build_ppa(self):
- # We can notify the creator when the build has completed, and binary
- # packages are being uploaded by the builder.
+ # We can notify the signer and the archive owner when the build has
+ # completed, and binary packages are being uploaded by the builder.
self.create_builds(self.ppa)
build = self.builds[BuildStatus.UPLOADING.value]
build.notify()
- notification = pop_notifications()[1]
- self._assert_mail_is_correct(build, notification, ppa=True)
+ expected_reasons = [
+ ("test@xxxxxxxxxxx", "signer"),
+ (self.ppa_owner_email, "owner"),
+ ]
+ self._assert_mails_are_correct(build, expected_reasons, ppa=True)
def test_copied_into_ppa_does_not_spam(self):
# When a package is copied into a PPA, we don't send mail to the
@@ -304,10 +353,10 @@
self.distroseries, PackagePublishingPocket.RELEASE, self.ppa)
[ppa_build] = ppa_spph.createMissingBuilds()
ppa_build.notify()
- notifications = pop_notifications()
- self.assertEquals(1, len(notifications))
+ self._assert_mails_are_correct(
+ ppa_build, [(self.ppa_owner_email, "owner")], ppa=True)
- def test_notify_owner_supresses_mail(self):
+ def test_notify_owner_suppresses_mail(self):
# When the 'notify_owner' config option is False, we don't send mail
# to the owner of the SPR.
self.create_builds(self.archive)
@@ -319,13 +368,13 @@
""")
config.push('notify_owner', notify_owner)
build.notify()
- notifications = pop_notifications()
- actual_emails = [n['To'] for n in notifications]
- self.assertEquals(self.buildd_admins_email, actual_emails)
+ self._assert_mails_are_correct(
+ build,
+ [(email, "buildd-admin") for email in self.buildd_admins_email])
# And undo what we just did.
config.pop('notify_owner')
- def test_build_notification_supresses_mail(self):
+ def test_build_notification_suppresses_mail(self):
# When the 'build_notification' config option is False, we don't
# send any mail at all.
self.create_builds(self.archive)
@@ -337,12 +386,12 @@
config.push('send_build_notification', send_build_notification)
build.notify()
notifications = pop_notifications()
- self.assertEquals(0, len(notifications))
+ self.assertEqual(0, len(notifications))
# And undo what we just did.
config.pop('send_build_notification')
def test_sponsored_upload_notification(self):
- # If the signing key is different to the creator, they are both
+ # If the signing key is different from the creator, they are both
# notified.
sponsor = self.factory.makePerson('sponsor@xxxxxxxxxxx')
key = self.factory.makeGPGKey(owner=sponsor)
@@ -352,8 +401,8 @@
# Push past the security proxy
removeSecurityProxy(spr).dscsigningkey = key
build.notify()
- notifications = pop_notifications()
- expected_emails = self.buildd_admins_email + [
- 'sponsor@xxxxxxxxxxx', 'test@xxxxxxxxxxx']
- actual_emails = [n['To'] for n in notifications]
- self.assertEquals(expected_emails, actual_emails)
+ expected_reasons = [
+ (email, "buildd-admin") for email in self.buildd_admins_email]
+ expected_reasons.append(("test@xxxxxxxxxxx", "creator"))
+ expected_reasons.append(("sponsor@xxxxxxxxxxx", "signer"))
+ self._assert_mails_are_correct(build, expected_reasons)
Follow ups