launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #00322
[Merge] lp:~abentley/launchpad/reject-private-recipe into lp:launchpad/devel
Aaron Bentley has proposed merging lp:~abentley/launchpad/reject-private-recipe into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#607908 recipes with private branches should fail earlier
https://bugs.launchpad.net/bugs/607908
= Summary =
Fix bug #607908: recipes with private branches should fail earlier
== Proposed fix ==
Raise an exception when creating or updating SourcePackageRecipeData that uses
private branches. Forward the exception to the user as a user error.
== Pre-implementation notes ==
None
== Implementation details ==
I fixed some lint, e.g. whitespace between 'And' and '('.
== Tests ==
bin/test -t test_creation -t setRecipeText -t test_edit_recipe_private_branch test_sourcepackagerecipe
== Demo and Q/A ==
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/code/model/sourcepackagerecipedata.py
lib/lp/code/errors.py
lib/lp/code/model/tests/test_sourcepackagerecipe.py
lib/lp/testing/factory.py
lib/lp/code/browser/sourcepackagerecipe.py
lib/lp/code/browser/tests/test_sourcepackagerecipe.py
./lib/lp/code/model/sourcepackagerecipedata.py
154: E202 whitespace before ')'
./lib/lp/code/model/tests/test_sourcepackagerecipe.py
255: E231 missing whitespace after ','
279: E231 missing whitespace after ','
287: E231 missing whitespace after ','
294: E231 missing whitespace after ','
302: E231 missing whitespace after ','
328: E231 missing whitespace after ','
388: E231 missing whitespace after ','
685: Line exceeds 78 characters.
./lib/lp/testing/factory.py
160: 'logout' imported but unused
846: E231 missing whitespace after ','
2752: E231 missing whitespace after ','
2754: E302 expected 2 blank lines, found 1
2781: E301 expected 1 blank line, found 0
1052: Line exceeds 78 characters.
2755: Line exceeds 78 characters.
./lib/lp/code/browser/sourcepackagerecipe.py
133: E231 missing whitespace after ','
242: E202 whitespace before ']'
354: E231 missing whitespace after ','
386: E303 too many blank lines (3)
./lib/lp/code/browser/tests/test_sourcepackagerecipe.py
244: E231 missing whitespace after ','
--
https://code.launchpad.net/~abentley/launchpad/reject-private-recipe/+merge/31171
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/reject-private-recipe into lp:launchpad/devel.
=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
--- lib/lp/code/browser/sourcepackagerecipe.py 2010-07-23 03:53:46 +0000
+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-07-28 15:52:48 +0000
@@ -34,8 +34,8 @@
LaunchpadView, Link, Navigation, NavigationMenu, stepthrough, structured)
from canonical.launchpad.webapp.breadcrumb import Breadcrumb
from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget
-from lp.code.errors import ForbiddenInstruction
-from lp.code.errors import BuildAlreadyPending
+from lp.code.errors import (
+ BuildAlreadyPending, ForbiddenInstruction, PrivateBranchRecipe)
from lp.code.interfaces.branch import NoSuchBranch
from lp.code.interfaces.sourcepackagerecipe import (
ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT)
@@ -316,6 +316,9 @@
self.setFieldError(
'recipe_text', '%s is not a branch on Launchpad.' % e.name)
return
+ except PrivateBranchRecipe, e:
+ self.setFieldError('recipe_text', str(e))
+ return
self.next_url = canonical_url(source_package_recipe)
@@ -371,9 +374,13 @@
# XXX: bug=592513 We shouldn't be hardcoding "run" here.
self.setFieldError(
'recipe_text',
- 'The bzr-builder instruction "run" is not permitted here.'
- )
- return
+ 'The bzr-builder instruction "run" is not permitted'
+ ' here.')
+ return
+ except PrivateBranchRecipe, e:
+ self.setFieldError('recipe_text', str(e))
+ return
+
distros = data.pop('distros')
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-07-26 06:21:45 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-07-28 15:52:48 +0000
@@ -28,7 +28,8 @@
from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.soyuz.model.processor import ProcessorFamily
-from lp.testing import ANONYMOUS, BrowserTestCase, login, logout
+from lp.testing import (
+ ANONYMOUS, BrowserTestCase, login, logout, person_logged_in)
from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
@@ -303,6 +304,18 @@
get_message_text(browser, 2),
'There is already a recipe owned by Master Chef with this name.')
+ def test_create_recipe_private_branch(self):
+ # If a user tries to create source package recipe with a private
+ # base branch, they should get an error.
+ branch = self.factory.makeAnyBranch(private=True, owner=self.user)
+ with person_logged_in(self.user):
+ bzr_identity = branch.bzr_identity
+ recipe_text = MINIMAL_RECIPE_TEXT % bzr_identity
+ browser = self.createRecipe(recipe_text)
+ self.assertEqual(
+ get_message_text(browser, 2),
+ 'Recipe may not refer to private branch: %s' % bzr_identity)
+
class TestSourcePackageRecipeEditView(TestCaseForRecipe):
"""Test the editing behaviour of a source package recipe."""
@@ -475,6 +488,21 @@
self.assertTextMatchesExpressionIgnoreWhitespace(
pattern, main_text)
+ def test_edit_recipe_private_branch(self):
+ # If a user tries to set source package recipe to use a private
+ # branch, they should get an error.
+ recipe = self.factory.makeSourcePackageRecipe(owner=self.user)
+ branch = self.factory.makeAnyBranch(private=True, owner=self.user)
+ with person_logged_in(self.user):
+ bzr_identity = branch.bzr_identity
+ recipe_text = MINIMAL_RECIPE_TEXT % bzr_identity
+ browser = self.getViewBrowser(recipe, '+edit')
+ browser.getControl('Recipe text').value = recipe_text
+ browser.getControl('Update Recipe').click()
+ self.assertEqual(
+ get_message_text(browser, 1),
+ 'Recipe may not refer to private branch: %s' % bzr_identity)
+
class TestSourcePackageRecipeView(TestCaseForRecipe):
=== modified file 'lib/lp/code/errors.py'
--- lib/lp/code/errors.py 2010-07-20 12:35:09 +0000
+++ lib/lp/code/errors.py 2010-07-28 15:52:48 +0000
@@ -16,6 +16,7 @@
'ClaimReviewFailed',
'ForbiddenInstruction',
'InvalidBranchMergeProposal',
+ 'PrivateBranchRecipe',
'ReviewNotPending',
'TooManyBuilds',
'TooNewRecipeFormat',
@@ -53,6 +54,16 @@
webservice_error(400) #Bad request.
+class PrivateBranchRecipe(Exception):
+
+ def __init__(self, branch):
+ message = (
+ 'Recipe may not refer to private branch: %s' %
+ branch.bzr_identity)
+ self.branch = branch
+ Exception.__init__(self, message)
+
+
class ReviewNotPending(Exception):
"""The requested review is not in a pending state."""
=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
--- lib/lp/code/model/sourcepackagerecipedata.py 2010-06-17 20:55:11 +0000
+++ lib/lp/code/model/sourcepackagerecipedata.py 2010-07-28 15:52:48 +0000
@@ -27,7 +27,8 @@
from canonical.database.enumcol import EnumCol
from canonical.launchpad.interfaces.lpstorm import IStore
-from lp.code.errors import ForbiddenInstruction, TooNewRecipeFormat
+from lp.code.errors import (
+ ForbiddenInstruction, PrivateBranchRecipe, TooNewRecipeFormat)
from lp.code.model.branch import Branch
from lp.code.interfaces.branch import NoSuchBranch
from lp.code.interfaces.branchlookup import IBranchLookup
@@ -147,7 +148,7 @@
SourcePackageRecipeData.base_branch == branch),
Select(
SourcePackageRecipeData.sourcepackage_recipe_id,
- And (
+ And(
_SourcePackageRecipeDataInstruction.recipe_data_id ==
SourcePackageRecipeData.id,
_SourcePackageRecipeDataInstruction.branch == branch)
@@ -203,6 +204,8 @@
raise ForbiddenInstruction(str(instruction))
db_branch = getUtility(IBranchLookup).getByUrl(
instruction.recipe_branch.url)
+ if db_branch.private:
+ raise PrivateBranchRecipe(db_branch)
r[instruction.recipe_branch.url] = db_branch
r.update(self._scanInstructions(instruction.recipe_branch))
return r
@@ -245,6 +248,8 @@
self.instructions.find().remove()
branch_lookup = getUtility(IBranchLookup)
base_branch = branch_lookup.getByUrl(builder_recipe.url)
+ if base_branch.private:
+ raise PrivateBranchRecipe(base_branch)
if base_branch is None:
raise NoSuchBranch(builder_recipe.url)
if builder_recipe.revspec is not None:
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-07-22 08:39:05 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-07-28 15:52:48 +0000
@@ -32,8 +32,8 @@
from lp.buildmaster.interfaces.buildqueue import IBuildQueue
from lp.buildmaster.model.buildqueue import BuildQueue
from lp.code.errors import (
- BuildAlreadyPending, ForbiddenInstruction, TooManyBuilds,
- TooNewRecipeFormat)
+ BuildAlreadyPending, ForbiddenInstruction, PrivateBranchRecipe,
+ TooManyBuilds, TooNewRecipeFormat)
from lp.code.interfaces.sourcepackagerecipe import (
ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT)
from lp.code.interfaces.sourcepackagerecipebuild import (
@@ -72,25 +72,64 @@
registrant=registrant, owner=owner, distroseries=[distroseries],
name=name, description=description, builder_recipe=builder_recipe)
+ def makeRecipeComponents(self, branches=()):
+ """Return a dict of values that can be used to make a recipe.
+
+ Suggested use: provide as kwargs to ISourcePackageRecipeSource.new
+ :param branches: The list of branches to use in the recipe. (If
+ unspecified, a branch will be autogenerated.
+ """
+ registrant = self.factory.makePerson()
+ return dict(
+ registrant=registrant,
+ owner = self.factory.makeTeam(owner=registrant),
+ distroseries = [self.factory.makeDistroSeries()],
+ name = self.factory.getUniqueString(u'recipe-name'),
+ description = self.factory.getUniqueString(u'recipe-description'),
+ builder_recipe = self.factory.makeRecipe(*branches))
+
def test_creation(self):
# The metadata supplied when a SourcePackageRecipe is created is
# present on the new object.
- registrant = self.factory.makePerson()
- owner = self.factory.makeTeam(owner=registrant)
- distroseries = self.factory.makeDistroSeries()
- name = self.factory.getUniqueString(u'recipe-name')
- description = self.factory.getUniqueString(u'recipe-description')
- builder_recipe = self.factory.makeRecipe()
- recipe = getUtility(ISourcePackageRecipeSource).new(
- registrant=registrant, owner=owner, distroseries=[distroseries],
- name=name, description=description, builder_recipe=builder_recipe)
+ components = self.makeRecipeComponents()
+ recipe = getUtility(ISourcePackageRecipeSource).new(**components)
transaction.commit()
self.assertEquals(
- (registrant, owner, set([distroseries]), name),
+ (components['registrant'], components['owner'],
+ set(components['distroseries']), components['name']),
(recipe.registrant, recipe.owner, set(recipe.distroseries),
recipe.name))
self.assertEqual(True, recipe.is_stale)
+ def test_creation_private_base_branch(self):
+ """An exception should be raised if the base branch is private."""
+ owner = self.factory.makePerson()
+ with person_logged_in(owner):
+ branch = self.factory.makeAnyBranch(private=True, owner=owner)
+ components = self.makeRecipeComponents(branches=[branch])
+ recipe_source = getUtility(ISourcePackageRecipeSource)
+ e = self.assertRaises(
+ PrivateBranchRecipe, recipe_source.new, **components)
+ self.assertEqual(
+ 'Recipe may not refer to private branch: %s' %
+ branch.bzr_identity, str(e))
+
+ def test_creation_private_referenced_branch(self):
+ """An exception should be raised if a referenced branch is private."""
+ owner = self.factory.makePerson()
+ with person_logged_in(owner):
+ base_branch = self.factory.makeAnyBranch(owner=owner)
+ referenced_branch = self.factory.makeAnyBranch(
+ private=True, owner=owner)
+ branches = [base_branch, referenced_branch]
+ components = self.makeRecipeComponents(branches=branches)
+ recipe_source = getUtility(ISourcePackageRecipeSource)
+ e = self.assertRaises(
+ PrivateBranchRecipe, recipe_source.new, **components)
+ self.assertEqual(
+ 'Recipe may not refer to private branch: %s' %
+ referenced_branch.bzr_identity, str(e))
+
def test_exists(self):
# Test ISourcePackageRecipeSource.exists
recipe = self.factory.makeSourcePackageRecipe()
@@ -691,6 +730,35 @@
recipe.setRecipeText(recipe_text=recipe_text2)
self.assertEqual(recipe_text2, recipe.recipe_text)
+ def test_setRecipeText_private_base_branch(self):
+ source_package_recipe = self.factory.makeSourcePackageRecipe()
+ with person_logged_in(source_package_recipe.owner):
+ branch = self.factory.makeAnyBranch(
+ private=True, owner=source_package_recipe.owner)
+ recipe_text = self.factory.makeRecipeText(branch)
+ e = self.assertRaises(
+ PrivateBranchRecipe, source_package_recipe.setRecipeText,
+ recipe_text)
+ self.assertEqual(
+ 'Recipe may not refer to private branch: %s' %
+ branch.bzr_identity, str(e))
+
+ def test_setRecipeText_private_referenced_branch(self):
+ source_package_recipe = self.factory.makeSourcePackageRecipe()
+ with person_logged_in(source_package_recipe.owner):
+ base_branch = self.factory.makeAnyBranch(
+ owner=source_package_recipe.owner)
+ referenced_branch = self.factory.makeAnyBranch(
+ private=True, owner=source_package_recipe.owner)
+ recipe_text = self.factory.makeRecipeText(
+ base_branch, referenced_branch)
+ e = self.assertRaises(
+ PrivateBranchRecipe, source_package_recipe.setRecipeText,
+ recipe_text)
+ self.assertEqual(
+ 'Recipe may not refer to private branch: %s' %
+ referenced_branch.bzr_identity, str(e))
+
def test_getRecipe(self):
"""Person.getRecipe returns the named recipe."""
recipe, user = self.makeRecipe()[:-1]
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-07-27 05:05:19 +0000
+++ lib/lp/testing/factory.py 2010-07-28 15:52:48 +0000
@@ -36,6 +36,7 @@
from threading import local
from types import InstanceType
+from bzrlib.plugins.builder.recipe import BaseRecipeBranch
import pytz
from twisted.python.util import mergeFunctionMetadata
@@ -2747,7 +2748,8 @@
# security wrappers for them, as well as for objects created by
# other Python libraries.
unwrapped_types = (
- DSCFile, InstanceType, MergeDirective2, Message, datetime, int, str, unicode)
+ BaseRecipeBranch, DSCFile, InstanceType, MergeDirective2, Message,
+ datetime, int, str, unicode,)
def is_security_proxied_or_harmless(obj):
"""Check that the given object is security wrapped or a harmless object."""