Merge lp:~abentley/launchpad/reject-private-recipe into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 11265
Proposed branch: lp:~abentley/launchpad/reject-private-recipe
Merge into: lp:launchpad
Diff against target: 356 lines (+148/-25)
7 files modified
lib/lp/code/browser/sourcepackagerecipe.py (+12/-5)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+29/-1)
lib/lp/code/errors.py (+11/-0)
lib/lp/code/model/sourcepackagerecipedata.py (+9/-4)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+82/-13)
lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+2/-1)
lib/lp/testing/factory.py (+3/-1)
To merge this branch: bzr merge lp:~abentley/launchpad/reject-private-recipe
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+31171@code.launchpad.net

Description of the change

= 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 ','

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

+ 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.
+ """

You are missing an end ) there.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
2--- lib/lp/code/browser/sourcepackagerecipe.py 2010-07-23 03:53:46 +0000
3+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-07-30 19:51:04 +0000
4@@ -34,8 +34,8 @@
5 LaunchpadView, Link, Navigation, NavigationMenu, stepthrough, structured)
6 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
7 from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget
8-from lp.code.errors import ForbiddenInstruction
9-from lp.code.errors import BuildAlreadyPending
10+from lp.code.errors import (
11+ BuildAlreadyPending, ForbiddenInstruction, PrivateBranchRecipe)
12 from lp.code.interfaces.branch import NoSuchBranch
13 from lp.code.interfaces.sourcepackagerecipe import (
14 ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT)
15@@ -316,6 +316,9 @@
16 self.setFieldError(
17 'recipe_text', '%s is not a branch on Launchpad.' % e.name)
18 return
19+ except PrivateBranchRecipe, e:
20+ self.setFieldError('recipe_text', str(e))
21+ return
22
23 self.next_url = canonical_url(source_package_recipe)
24
25@@ -371,9 +374,13 @@
26 # XXX: bug=592513 We shouldn't be hardcoding "run" here.
27 self.setFieldError(
28 'recipe_text',
29- 'The bzr-builder instruction "run" is not permitted here.'
30- )
31- return
32+ 'The bzr-builder instruction "run" is not permitted'
33+ ' here.')
34+ return
35+ except PrivateBranchRecipe, e:
36+ self.setFieldError('recipe_text', str(e))
37+ return
38+
39
40
41 distros = data.pop('distros')
42
43=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
44--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-07-29 18:33:13 +0000
45+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-07-30 19:51:04 +0000
46@@ -28,7 +28,8 @@
47 from lp.code.interfaces.sourcepackagerecipe import MINIMAL_RECIPE_TEXT
48 from lp.registry.interfaces.pocket import PackagePublishingPocket
49 from lp.soyuz.model.processor import ProcessorFamily
50-from lp.testing import ANONYMOUS, BrowserTestCase, login, logout
51+from lp.testing import (
52+ ANONYMOUS, BrowserTestCase, login, logout, person_logged_in)
53 from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
54
55
56@@ -303,6 +304,18 @@
57 get_message_text(browser, 2),
58 'There is already a recipe owned by Master Chef with this name.')
59
60+ def test_create_recipe_private_branch(self):
61+ # If a user tries to create source package recipe with a private
62+ # base branch, they should get an error.
63+ branch = self.factory.makeAnyBranch(private=True, owner=self.user)
64+ with person_logged_in(self.user):
65+ bzr_identity = branch.bzr_identity
66+ recipe_text = MINIMAL_RECIPE_TEXT % bzr_identity
67+ browser = self.createRecipe(recipe_text)
68+ self.assertEqual(
69+ get_message_text(browser, 2),
70+ 'Recipe may not refer to private branch: %s' % bzr_identity)
71+
72
73 class TestSourcePackageRecipeEditView(TestCaseForRecipe):
74 """Test the editing behaviour of a source package recipe."""
75@@ -475,6 +488,21 @@
76 self.assertTextMatchesExpressionIgnoreWhitespace(
77 pattern, main_text)
78
79+ def test_edit_recipe_private_branch(self):
80+ # If a user tries to set source package recipe to use a private
81+ # branch, they should get an error.
82+ recipe = self.factory.makeSourcePackageRecipe(owner=self.user)
83+ branch = self.factory.makeAnyBranch(private=True, owner=self.user)
84+ with person_logged_in(self.user):
85+ bzr_identity = branch.bzr_identity
86+ recipe_text = MINIMAL_RECIPE_TEXT % bzr_identity
87+ browser = self.getViewBrowser(recipe, '+edit')
88+ browser.getControl('Recipe text').value = recipe_text
89+ browser.getControl('Update Recipe').click()
90+ self.assertEqual(
91+ get_message_text(browser, 1),
92+ 'Recipe may not refer to private branch: %s' % bzr_identity)
93+
94
95 class TestSourcePackageRecipeView(TestCaseForRecipe):
96
97
98=== modified file 'lib/lp/code/errors.py'
99--- lib/lp/code/errors.py 2010-07-20 12:35:09 +0000
100+++ lib/lp/code/errors.py 2010-07-30 19:51:04 +0000
101@@ -16,6 +16,7 @@
102 'ClaimReviewFailed',
103 'ForbiddenInstruction',
104 'InvalidBranchMergeProposal',
105+ 'PrivateBranchRecipe',
106 'ReviewNotPending',
107 'TooManyBuilds',
108 'TooNewRecipeFormat',
109@@ -53,6 +54,16 @@
110 webservice_error(400) #Bad request.
111
112
113+class PrivateBranchRecipe(Exception):
114+
115+ def __init__(self, branch):
116+ message = (
117+ 'Recipe may not refer to private branch: %s' %
118+ branch.bzr_identity)
119+ self.branch = branch
120+ Exception.__init__(self, message)
121+
122+
123 class ReviewNotPending(Exception):
124 """The requested review is not in a pending state."""
125
126
127=== modified file 'lib/lp/code/model/sourcepackagerecipedata.py'
128--- lib/lp/code/model/sourcepackagerecipedata.py 2010-06-17 20:55:11 +0000
129+++ lib/lp/code/model/sourcepackagerecipedata.py 2010-07-30 19:51:04 +0000
130@@ -27,7 +27,8 @@
131 from canonical.database.enumcol import EnumCol
132 from canonical.launchpad.interfaces.lpstorm import IStore
133
134-from lp.code.errors import ForbiddenInstruction, TooNewRecipeFormat
135+from lp.code.errors import (
136+ ForbiddenInstruction, PrivateBranchRecipe, TooNewRecipeFormat)
137 from lp.code.model.branch import Branch
138 from lp.code.interfaces.branch import NoSuchBranch
139 from lp.code.interfaces.branchlookup import IBranchLookup
140@@ -147,7 +148,7 @@
141 SourcePackageRecipeData.base_branch == branch),
142 Select(
143 SourcePackageRecipeData.sourcepackage_recipe_id,
144- And (
145+ And(
146 _SourcePackageRecipeDataInstruction.recipe_data_id ==
147 SourcePackageRecipeData.id,
148 _SourcePackageRecipeDataInstruction.branch == branch)
149@@ -203,6 +204,10 @@
150 raise ForbiddenInstruction(str(instruction))
151 db_branch = getUtility(IBranchLookup).getByUrl(
152 instruction.recipe_branch.url)
153+ if db_branch is None:
154+ raise NoSuchBranch(instruction.recipe_branch.url)
155+ if db_branch.private:
156+ raise PrivateBranchRecipe(db_branch)
157 r[instruction.recipe_branch.url] = db_branch
158 r.update(self._scanInstructions(instruction.recipe_branch))
159 return r
160@@ -224,8 +229,6 @@
161 line_number += 1
162 comment = None
163 db_branch = branch_map[instruction.recipe_branch.url]
164- if db_branch is None:
165- raise NoSuchBranch(instruction.recipe_branch.url)
166 insn = _SourcePackageRecipeDataInstruction(
167 instruction.recipe_branch.name, type, comment,
168 line_number, db_branch, instruction.recipe_branch.revspec,
169@@ -247,6 +250,8 @@
170 base_branch = branch_lookup.getByUrl(builder_recipe.url)
171 if base_branch is None:
172 raise NoSuchBranch(builder_recipe.url)
173+ if base_branch.private:
174+ raise PrivateBranchRecipe(base_branch)
175 if builder_recipe.revspec is not None:
176 self.revspec = unicode(builder_recipe.revspec)
177 self._recordInstructions(
178
179=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
180--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-07-28 16:44:00 +0000
181+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-07-30 19:51:04 +0000
182@@ -32,8 +32,8 @@
183 from lp.buildmaster.interfaces.buildqueue import IBuildQueue
184 from lp.buildmaster.model.buildqueue import BuildQueue
185 from lp.code.errors import (
186- BuildAlreadyPending, ForbiddenInstruction, TooManyBuilds,
187- TooNewRecipeFormat)
188+ BuildAlreadyPending, ForbiddenInstruction, PrivateBranchRecipe,
189+ TooManyBuilds, TooNewRecipeFormat)
190 from lp.code.interfaces.sourcepackagerecipe import (
191 ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT)
192 from lp.code.interfaces.sourcepackagerecipebuild import (
193@@ -72,25 +72,64 @@
194 registrant=registrant, owner=owner, distroseries=[distroseries],
195 name=name, description=description, builder_recipe=builder_recipe)
196
197+ def makeRecipeComponents(self, branches=()):
198+ """Return a dict of values that can be used to make a recipe.
199+
200+ Suggested use: provide as kwargs to ISourcePackageRecipeSource.new
201+ :param branches: The list of branches to use in the recipe. (If
202+ unspecified, a branch will be autogenerated.)
203+ """
204+ registrant = self.factory.makePerson()
205+ return dict(
206+ registrant=registrant,
207+ owner = self.factory.makeTeam(owner=registrant),
208+ distroseries = [self.factory.makeDistroSeries()],
209+ name = self.factory.getUniqueString(u'recipe-name'),
210+ description = self.factory.getUniqueString(u'recipe-description'),
211+ builder_recipe = self.factory.makeRecipe(*branches))
212+
213 def test_creation(self):
214 # The metadata supplied when a SourcePackageRecipe is created is
215 # present on the new object.
216- registrant = self.factory.makePerson()
217- owner = self.factory.makeTeam(owner=registrant)
218- distroseries = self.factory.makeDistroSeries()
219- name = self.factory.getUniqueString(u'recipe-name')
220- description = self.factory.getUniqueString(u'recipe-description')
221- builder_recipe = self.factory.makeRecipe()
222- recipe = getUtility(ISourcePackageRecipeSource).new(
223- registrant=registrant, owner=owner, distroseries=[distroseries],
224- name=name, description=description, builder_recipe=builder_recipe)
225+ components = self.makeRecipeComponents()
226+ recipe = getUtility(ISourcePackageRecipeSource).new(**components)
227 transaction.commit()
228 self.assertEquals(
229- (registrant, owner, set([distroseries]), name),
230+ (components['registrant'], components['owner'],
231+ set(components['distroseries']), components['name']),
232 (recipe.registrant, recipe.owner, set(recipe.distroseries),
233 recipe.name))
234 self.assertEqual(True, recipe.is_stale)
235
236+ def test_creation_private_base_branch(self):
237+ """An exception should be raised if the base branch is private."""
238+ owner = self.factory.makePerson()
239+ with person_logged_in(owner):
240+ branch = self.factory.makeAnyBranch(private=True, owner=owner)
241+ components = self.makeRecipeComponents(branches=[branch])
242+ recipe_source = getUtility(ISourcePackageRecipeSource)
243+ e = self.assertRaises(
244+ PrivateBranchRecipe, recipe_source.new, **components)
245+ self.assertEqual(
246+ 'Recipe may not refer to private branch: %s' %
247+ branch.bzr_identity, str(e))
248+
249+ def test_creation_private_referenced_branch(self):
250+ """An exception should be raised if a referenced branch is private."""
251+ owner = self.factory.makePerson()
252+ with person_logged_in(owner):
253+ base_branch = self.factory.makeAnyBranch(owner=owner)
254+ referenced_branch = self.factory.makeAnyBranch(
255+ private=True, owner=owner)
256+ branches = [base_branch, referenced_branch]
257+ components = self.makeRecipeComponents(branches=branches)
258+ recipe_source = getUtility(ISourcePackageRecipeSource)
259+ e = self.assertRaises(
260+ PrivateBranchRecipe, recipe_source.new, **components)
261+ self.assertEqual(
262+ 'Recipe may not refer to private branch: %s' %
263+ referenced_branch.bzr_identity, str(e))
264+
265 def test_exists(self):
266 # Test ISourcePackageRecipeSource.exists
267 recipe = self.factory.makeSourcePackageRecipe()
268@@ -389,10 +428,11 @@
269 def test_view_private(self):
270 """Recipes with private branches are restricted."""
271 owner = self.factory.makePerson()
272- branch = self.factory.makeAnyBranch(owner=owner, private=True)
273+ branch = self.factory.makeAnyBranch(owner=owner)
274 with person_logged_in(owner):
275 recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
276 self.assertTrue(check_permission('launchpad.View', recipe))
277+ removeSecurityProxy(branch).private=True
278 with person_logged_in(self.factory.makePerson()):
279 self.assertFalse(check_permission('launchpad.View', recipe))
280 self.assertFalse(check_permission('launchpad.View', recipe))
281@@ -703,6 +743,35 @@
282 recipe.setRecipeText(recipe_text=recipe_text2)
283 self.assertEqual(recipe_text2, recipe.recipe_text)
284
285+ def test_setRecipeText_private_base_branch(self):
286+ source_package_recipe = self.factory.makeSourcePackageRecipe()
287+ with person_logged_in(source_package_recipe.owner):
288+ branch = self.factory.makeAnyBranch(
289+ private=True, owner=source_package_recipe.owner)
290+ recipe_text = self.factory.makeRecipeText(branch)
291+ e = self.assertRaises(
292+ PrivateBranchRecipe, source_package_recipe.setRecipeText,
293+ recipe_text)
294+ self.assertEqual(
295+ 'Recipe may not refer to private branch: %s' %
296+ branch.bzr_identity, str(e))
297+
298+ def test_setRecipeText_private_referenced_branch(self):
299+ source_package_recipe = self.factory.makeSourcePackageRecipe()
300+ with person_logged_in(source_package_recipe.owner):
301+ base_branch = self.factory.makeAnyBranch(
302+ owner=source_package_recipe.owner)
303+ referenced_branch = self.factory.makeAnyBranch(
304+ private=True, owner=source_package_recipe.owner)
305+ recipe_text = self.factory.makeRecipeText(
306+ base_branch, referenced_branch)
307+ e = self.assertRaises(
308+ PrivateBranchRecipe, source_package_recipe.setRecipeText,
309+ recipe_text)
310+ self.assertEqual(
311+ 'Recipe may not refer to private branch: %s' %
312+ referenced_branch.bzr_identity, str(e))
313+
314 def test_getRecipe(self):
315 """Person.getRecipe returns the named recipe."""
316 recipe, user = self.makeRecipe()[:-1]
317
318=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
319--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-22 08:15:29 +0000
320+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-30 19:51:04 +0000
321@@ -131,11 +131,12 @@
322 def test_view_private_branch(self):
323 """Recipebuilds with private branches are restricted."""
324 owner = self.factory.makePerson()
325- branch = self.factory.makeAnyBranch(owner=owner, private=True)
326+ branch = self.factory.makeAnyBranch(owner=owner)
327 with person_logged_in(owner):
328 recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
329 build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
330 self.assertTrue(check_permission('launchpad.View', build))
331+ removeSecurityProxy(branch).private = True
332 with person_logged_in(self.factory.makePerson()):
333 self.assertFalse(check_permission('launchpad.View', build))
334 login(ANONYMOUS)
335
336=== modified file 'lib/lp/testing/factory.py'
337--- lib/lp/testing/factory.py 2010-07-30 16:30:12 +0000
338+++ lib/lp/testing/factory.py 2010-07-30 19:51:04 +0000
339@@ -37,6 +37,7 @@
340 from types import InstanceType
341 import warnings
342
343+from bzrlib.plugins.builder.recipe import BaseRecipeBranch
344 import pytz
345
346 from twisted.python.util import mergeFunctionMetadata
347@@ -2767,7 +2768,8 @@
348 # security wrappers for them, as well as for objects created by
349 # other Python libraries.
350 unwrapped_types = (
351- DSCFile, InstanceType, MergeDirective2, Message, datetime, int, str, unicode)
352+ BaseRecipeBranch, DSCFile, InstanceType, MergeDirective2, Message,
353+ datetime, int, str, unicode,)
354
355
356 def is_security_proxied_or_harmless(obj):