launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #03523
[Merge] lp:~abentley/launchpad/daily-build-permissions into lp:launchpad
Aaron Bentley has proposed merging lp:~abentley/launchpad/daily-build-permissions into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #740567 in Launchpad itself: "CannotUploadToPPA: Signer has no upload rights to this PPA."
https://bugs.launchpad.net/launchpad/+bug/740567
For more details, see:
https://code.launchpad.net/~abentley/launchpad/daily-build-permissions/+merge/60207
= Summary =
Fix bug #740567: CannotUploadToPPA: Signer has no upload rights to this PPA.
== Proposed fix ==
If CannotUploadToPPA is raised, no SourcePackageRecipeBuild is created.
A message is logged.
Daily builds on that recipe are disabled.
An email is sent to the recipe owner, explaining the situation.
== Pre-implementation notes ==
None
== Implementation details ==
Some driveby lint fixes, like unused variables.
== Tests ==
bin/test -t test_performDailyBuild_with_wrong_archive
== Demo and Q/A ==
Create a team PPA, with a team of which you are a member. Create a daily build
recipe for that PPA. Remove yourself from the team. Run cronscripts/request_daily_builds.py
Daily builds of the recipe should be disabled, and you should receive an email explaining this.
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/canonical/launchpad/emailtemplates/wrong-ppa.txt
lib/lp/code/model/tests/test_sourcepackagerecipe.py
lib/lp/testing/__init__.py
lib/lp/soyuz/model/archive.py
lib/lp/code/model/sourcepackagerecipe.py
lib/lp/code/interfaces/sourcepackagerecipe.py
./lib/canonical/launchpad/emailtemplates/wrong-ppa.txt
1: Line exceeds 78 characters.
--
https://code.launchpad.net/~abentley/launchpad/daily-build-permissions/+merge/60207
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/daily-build-permissions into lp:launchpad.
=== added file 'lib/canonical/launchpad/emailtemplates/wrong-ppa.txt'
--- lib/canonical/launchpad/emailtemplates/wrong-ppa.txt 1970-01-01 00:00:00 +0000
+++ lib/canonical/launchpad/emailtemplates/wrong-ppa.txt 2011-05-06 15:39:00 +0000
@@ -0,0 +1,1 @@
+Daily builds of your recipe %(recipe_name)s have been disabled, because you do not have permission to upload to the archive %(archive_name)s.
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
--- lib/lp/code/interfaces/sourcepackagerecipe.py 2011-04-11 01:30:37 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2011-05-06 15:39:00 +0000
@@ -162,7 +162,13 @@
@export_write_operation()
def performDailyBuild():
- """Perform a build into the daily build archive."""
+ """Perform a build into the daily build archive.
+
+ If `CannotUploadToPPA` is raised, disables daily builds for that
+ recipe and sends an email to the recipe owner.
+
+ :return: A list of each new `SourcePackageRecipeBuild`.
+ """
@export_read_operation()
@operation_for_version("devel")
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2011-03-23 16:28:51 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2011-05-06 15:39:00 +0000
@@ -15,6 +15,7 @@
datetime,
timedelta,
)
+import logging
from bzrlib.plugins.builder.recipe import RecipeParseError
from lazr.delegates import delegates
@@ -73,7 +74,11 @@
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.model.distroseries import DistroSeries
from lp.services.database.stormexpr import Greatest
-from lp.soyuz.interfaces.archive import IArchiveSet
+from lp.services.mail.basemailer import BaseMailer, RecipientReason
+from lp.soyuz.interfaces.archive import (
+ CannotUploadToPPA,
+ IArchiveSet,
+ )
from lp.soyuz.model.archive import Archive
# "Slam" a 400 response code onto RecipeParseError so that it will behave
@@ -121,6 +126,33 @@
distroseries = Reference(distroseries_id, 'DistroSeries.id')
+class WrongArchiveMailer(BaseMailer):
+ """Mailer to inform recipe owner that daily build is disabled."""
+
+ def __init__(self, recipe):
+ """Constructor.
+
+ :param recipe: The recipe that is disabled.
+ """
+ header = RecipientReason.makeRationale('Owner', recipe.owner)
+ reason = '%(entity_is)s the owner of the recipe.'
+ r_reason = RecipientReason(recipe.owner, recipe.owner, header, reason)
+ super(WrongArchiveMailer, self).__init__(
+ 'Daily builds disabled', 'wrong-ppa.txt',
+ {recipe.owner: r_reason}, 'noreply')
+ self.recipe = recipe
+
+ def _getTemplateParams(self, email, recipient):
+ """See `BaseMailer`"""
+ params = super(WrongArchiveMailer, self)._getTemplateParams(
+ email, recipient)
+ params.update({
+ 'recipe_name': str(self.recipe),
+ 'archive_name': self.recipe.daily_build_archive.unique_name,
+ })
+ return params
+
+
class SourcePackageRecipe(Storm):
"""See `ISourcePackageRecipe` and `ISourcePackageRecipeSource`."""
@@ -303,15 +335,23 @@
"""See `ISourcePackageRecipe`."""
builds = []
self.is_stale = False
- for distroseries in self.distroseries:
- try:
- build = self.requestBuild(
- self.daily_build_archive, self.owner,
- distroseries, PackagePublishingPocket.RELEASE)
- builds.append(build)
- except BuildAlreadyPending:
- continue
- return builds
+ try:
+ for distroseries in self.distroseries:
+ try:
+ build = self.requestBuild(
+ self.daily_build_archive, self.owner,
+ distroseries, PackagePublishingPocket.RELEASE)
+ builds.append(build)
+ except BuildAlreadyPending:
+ continue
+ return builds
+ except CannotUploadToPPA:
+ self.build_daily = False
+ logging.getLogger().error(
+ 'Owner of %s cannot upload to PPA %s. Daily builds disabled.'
+ % (str(self), self.daily_build_archive.unique_name))
+ WrongArchiveMailer(self).sendAll()
+ return []
@property
def builds(self):
@@ -363,8 +403,7 @@
for build in builds:
result.append(
{"distroseries": build.distroseries.displayname,
- "archive": '%s/%s' %
- (build.archive.owner.name, build.archive.name)})
+ "archive": build.archive.unique_name})
return result
@property
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-03-23 16:28:51 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-05-06 15:39:00 +0000
@@ -76,6 +76,7 @@
TestCaseWithFactory,
ws_object,
)
+from lp.testing.mail_helpers import pop_notifications
class TestSourcePackageRecipe(TestCaseWithFactory):
@@ -418,6 +419,24 @@
recipe.requestBuild(archive, recipe.owner, series,
PackagePublishingPocket.RELEASE)
+ def test_performDailyBuild_with_wrong_archive(self):
+ recipe = self.factory.makeSourcePackageRecipe(
+ daily_build_archive=self.factory.makeArchive(),
+ build_daily=True)
+ with self.expectedLog(
+ 'Owner of .*/.* cannot upload to .*/.*\. Daily builds disabled'):
+ builds = recipe.performDailyBuild()
+ self.assertEqual([], builds)
+ self.assertFalse(recipe.build_daily)
+ (notification,) = pop_notifications()
+ self.assertEqual('Daily builds disabled', notification['subject'])
+ body = notification.get_payload(decode=True)
+ import re
+ self.assertTrue(re.match(
+ 'Daily builds of your recipe .* have been disabled, because you'
+ ' do not have permission to upload to the archive .*\.',
+ body))
+
def test_sourcepackagerecipe_description(self):
"""Ensure that the SourcePackageRecipe has a proper description."""
description = u'The whoozits and whatzits.'
@@ -611,7 +630,7 @@
build_info = []
for archive in archives:
- build = recipe.requestBuild(archive, person, distroseries)
+ recipe.requestBuild(archive, person, distroseries)
build_info.insert(0, {
"distroseries": distroseries.displayname,
"archive": '%s/%s' %
@@ -1064,7 +1083,7 @@
build_info = []
for archive in archives:
ws_archive = ws_object(launchpad, archive)
- build = recipe.requestBuild(
+ recipe.requestBuild(
archive=ws_archive, distroseries=ws_distroseries,
pocket=PackagePublishingPocket.RELEASE.title)
build_info.insert(0, {
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2011-05-05 13:01:50 +0000
+++ lib/lp/soyuz/model/archive.py 2011-05-06 15:39:00 +0000
@@ -330,6 +330,10 @@
return self.displayname
@property
+ def unique_name(self):
+ return '%s/%s' % (self.owner.name, self.name)
+
+ @property
def is_ppa(self):
"""See `IArchive`."""
return self.purpose == ArchivePurpose.PPA
=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py 2011-05-03 04:39:43 +0000
+++ lib/lp/testing/__init__.py 2011-05-06 15:39:00 +0000
@@ -51,6 +51,7 @@
'ZopeTestInSubProcess',
]
+from cStringIO import StringIO
from contextlib import contextmanager
from datetime import (
datetime,
@@ -529,6 +530,19 @@
expected_vector, observed_vector = zip(*args)
return self.assertEqual(expected_vector, observed_vector)
+ @contextmanager
+ def expectedLog(self, regex):
+ """Expect a log to be written that matches the regex."""
+ output = StringIO()
+ handler = logging.StreamHandler(output)
+ logger = logging.getLogger()
+ logger.addHandler(handler)
+ try:
+ yield
+ finally:
+ logger.removeHandler(handler)
+ self.assertTrue(re.compile(regex).search(output.getvalue()))
+
def pushConfig(self, section, **kwargs):
"""Push some key-value pairs into a section of the config.