Merge ~pappacena/launchpad:ocirecipe-private-reconcile-pillar into launchpad:master
- Git
- lp:~pappacena/launchpad
- ocirecipe-private-reconcile-pillar
- Merge into master
Proposed by
Thiago F. Pappacena
Status: | Merged |
---|---|
Approved by: | Thiago F. Pappacena |
Approved revision: | 55440d6a24bece7ed5797241355a3a553cae744c |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~pappacena/launchpad:ocirecipe-private-reconcile-pillar |
Merge into: | launchpad:master |
Prerequisite: | ~pappacena/launchpad:ocirecipe-private-accesspolicy |
Diff against target: |
498 lines (+156/-49) 11 files modified
lib/lp/blueprints/model/specification.py (+2/-2) lib/lp/bugs/model/bug.py (+4/-4) lib/lp/code/model/branch.py (+3/-3) lib/lp/code/model/gitrepository.py (+3/-3) lib/lp/oci/model/ocirecipe.py (+3/-3) lib/lp/oci/tests/test_ocirecipe.py (+55/-0) lib/lp/registry/model/accesspolicy.py (+9/-8) lib/lp/registry/model/ociproject.py (+24/-1) lib/lp/registry/tests/test_accesspolicy.py (+48/-20) lib/lp/registry/tests/test_sharingjob.py (+3/-3) lib/lp/snappy/model/snap.py (+2/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+399469@code.launchpad.net |
Commit message
Running reconcile for OCI recipes when an OCI project changes pillar
Description of the change
When we change an OCI project's pillar, we should reconcile access artifacts for every OCI recipe associated with that.
This MP got kind of big mostly because of a refactoring on reconcile_
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/blueprints/model/specification.py b/lib/lp/blueprints/model/specification.py |
2 | index 55ce9d5..9f3b00a 100644 |
3 | --- a/lib/lp/blueprints/model/specification.py |
4 | +++ b/lib/lp/blueprints/model/specification.py |
5 | @@ -922,14 +922,14 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin): |
6 | """See ISpecification.""" |
7 | # avoid circular imports. |
8 | from lp.registry.model.accesspolicy import ( |
9 | - reconcile_access_for_artifact, |
10 | + reconcile_access_for_artifacts, |
11 | ) |
12 | if self.information_type == information_type: |
13 | return False |
14 | if information_type not in self.getAllowedInformationTypes(who): |
15 | raise CannotChangeInformationType("Forbidden by project policy.") |
16 | self.information_type = information_type |
17 | - reconcile_access_for_artifact(self, information_type, [self.target]) |
18 | + reconcile_access_for_artifacts([self], information_type, [self.target]) |
19 | if (information_type in PRIVATE_INFORMATION_TYPES and |
20 | not self.subscribers.is_empty()): |
21 | # Grant the subscribers access if they do not have a |
22 | diff --git a/lib/lp/bugs/model/bug.py b/lib/lp/bugs/model/bug.py |
23 | index f7ebc86..f2148e0 100644 |
24 | --- a/lib/lp/bugs/model/bug.py |
25 | +++ b/lib/lp/bugs/model/bug.py |
26 | @@ -189,7 +189,7 @@ from lp.registry.interfaces.sharingjob import ( |
27 | IRemoveArtifactSubscriptionsJobSource, |
28 | ) |
29 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
30 | -from lp.registry.model.accesspolicy import reconcile_access_for_artifact |
31 | +from lp.registry.model.accesspolicy import reconcile_access_for_artifacts |
32 | from lp.registry.model.person import ( |
33 | Person, |
34 | person_sort_key, |
35 | @@ -2122,7 +2122,7 @@ class Bug(SQLBase, InformationTypeMixin): |
36 | BugSubscription.person == person).is_empty() |
37 | |
38 | def _reconcileAccess(self): |
39 | - # reconcile_access_for_artifact will only use the pillar list if |
40 | + # reconcile_access_for_artifacts will only use the pillar list if |
41 | # the information type is private. But affected_pillars iterates |
42 | # over the tasks immediately, which is needless expense for |
43 | # public bugs. |
44 | @@ -2130,8 +2130,8 @@ class Bug(SQLBase, InformationTypeMixin): |
45 | pillars = self.affected_pillars |
46 | else: |
47 | pillars = [] |
48 | - reconcile_access_for_artifact( |
49 | - self, self.information_type, pillars) |
50 | + reconcile_access_for_artifacts( |
51 | + [self], self.information_type, pillars) |
52 | |
53 | def _attachments_query(self): |
54 | """Helper for the attachments* properties.""" |
55 | diff --git a/lib/lp/code/model/branch.py b/lib/lp/code/model/branch.py |
56 | index 278db40..27746c7 100644 |
57 | --- a/lib/lp/code/model/branch.py |
58 | +++ b/lib/lp/code/model/branch.py |
59 | @@ -160,7 +160,7 @@ from lp.registry.interfaces.sharingjob import ( |
60 | ) |
61 | from lp.registry.model.accesspolicy import ( |
62 | AccessPolicyGrant, |
63 | - reconcile_access_for_artifact, |
64 | + reconcile_access_for_artifacts, |
65 | ) |
66 | from lp.registry.model.teammembership import TeamParticipation |
67 | from lp.services.config import config |
68 | @@ -259,8 +259,8 @@ class Branch(SQLBase, WebhookTargetMixin, BzrIdentityMixin): |
69 | # works, so only work for products for now. |
70 | if self.product is not None: |
71 | pillars = [self.product] |
72 | - reconcile_access_for_artifact( |
73 | - self, self.information_type, pillars, wanted_links) |
74 | + reconcile_access_for_artifacts( |
75 | + [self], self.information_type, pillars, wanted_links) |
76 | |
77 | def setPrivate(self, private, user): |
78 | """See `IBranch`.""" |
79 | diff --git a/lib/lp/code/model/gitrepository.py b/lib/lp/code/model/gitrepository.py |
80 | index 21a6f82..75c6905 100644 |
81 | --- a/lib/lp/code/model/gitrepository.py |
82 | +++ b/lib/lp/code/model/gitrepository.py |
83 | @@ -173,7 +173,7 @@ from lp.registry.interfaces.sharingjob import ( |
84 | ) |
85 | from lp.registry.model.accesspolicy import ( |
86 | AccessPolicyGrant, |
87 | - reconcile_access_for_artifact, |
88 | + reconcile_access_for_artifacts, |
89 | ) |
90 | from lp.registry.model.person import Person |
91 | from lp.registry.model.teammembership import TeamParticipation |
92 | @@ -618,8 +618,8 @@ class GitRepository(StormBase, WebhookTargetMixin, GitIdentityMixin): |
93 | # works, so only work for projects for now. |
94 | if self.project is not None: |
95 | pillars = [self.project] |
96 | - reconcile_access_for_artifact( |
97 | - self, self.information_type, pillars, wanted_links) |
98 | + reconcile_access_for_artifacts( |
99 | + [self], self.information_type, pillars, wanted_links) |
100 | |
101 | @property |
102 | def refs(self): |
103 | diff --git a/lib/lp/oci/model/ocirecipe.py b/lib/lp/oci/model/ocirecipe.py |
104 | index 1a7dda3..2aa988c 100644 |
105 | --- a/lib/lp/oci/model/ocirecipe.py |
106 | +++ b/lib/lp/oci/model/ocirecipe.py |
107 | @@ -94,7 +94,7 @@ from lp.registry.interfaces.person import ( |
108 | validate_public_person, |
109 | ) |
110 | from lp.registry.interfaces.role import IPersonRoles |
111 | -from lp.registry.model.accesspolicy import reconcile_access_for_artifact |
112 | +from lp.registry.model.accesspolicy import reconcile_access_for_artifacts |
113 | from lp.registry.model.distribution import Distribution |
114 | from lp.registry.model.distroseries import DistroSeries |
115 | from lp.registry.model.person import Person |
116 | @@ -293,8 +293,8 @@ class OCIRecipe(Storm, WebhookTargetMixin): |
117 | Takes the privacy and pillar and makes the related AccessArtifact |
118 | and AccessPolicyArtifacts match. |
119 | """ |
120 | - reconcile_access_for_artifact(self, self.information_type, |
121 | - [self.pillar]) |
122 | + reconcile_access_for_artifacts([self], self.information_type, |
123 | + [self.pillar]) |
124 | |
125 | def destroySelf(self): |
126 | """See `IOCIRecipe`.""" |
127 | diff --git a/lib/lp/oci/tests/test_ocirecipe.py b/lib/lp/oci/tests/test_ocirecipe.py |
128 | index 0cd3d54..246b069 100644 |
129 | --- a/lib/lp/oci/tests/test_ocirecipe.py |
130 | +++ b/lib/lp/oci/tests/test_ocirecipe.py |
131 | @@ -67,6 +67,11 @@ from lp.registry.enums import ( |
132 | PersonVisibility, |
133 | TeamMembershipPolicy, |
134 | ) |
135 | +from lp.registry.interfaces.accesspolicy import ( |
136 | + IAccessArtifactSource, |
137 | + IAccessPolicyArtifactSource, |
138 | + IAccessPolicySource, |
139 | + ) |
140 | from lp.registry.interfaces.series import SeriesStatus |
141 | from lp.services.config import config |
142 | from lp.services.database.constants import ( |
143 | @@ -830,6 +835,56 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory): |
144 | public_recipe, 'owner', private_team) |
145 | |
146 | |
147 | +class TestOCIRecipeAccessControl(TestCaseWithFactory, OCIConfigHelperMixin): |
148 | + layer = DatabaseFunctionalLayer |
149 | + |
150 | + def setUp(self): |
151 | + super(TestOCIRecipeAccessControl, self).setUp() |
152 | + self.setConfig() |
153 | + |
154 | + def test_change_oci_project_pillar_reconciles_access(self): |
155 | + person = self.factory.makePerson() |
156 | + initial_project = self.factory.makeProduct( |
157 | + name='initial-project', |
158 | + owner=person, registrant=person) |
159 | + final_project = self.factory.makeProduct( |
160 | + name='final-project', |
161 | + owner=person, registrant=person) |
162 | + oci_project = self.factory.makeOCIProject( |
163 | + ociprojectname='the-oci-project', pillar=initial_project, |
164 | + registrant=person) |
165 | + recipes = [] |
166 | + for i in range(10): |
167 | + recipes.append(self.factory.makeOCIRecipe( |
168 | + registrant=person, |
169 | + oci_project=oci_project, |
170 | + information_type=InformationType.USERDATA)) |
171 | + |
172 | + access_artifacts = getUtility(IAccessArtifactSource).find(recipes) |
173 | + initial_access_policy = getUtility(IAccessPolicySource).find( |
174 | + [(initial_project, InformationType.USERDATA)]).one() |
175 | + apasource = getUtility(IAccessPolicyArtifactSource) |
176 | + policy_artifacts = apasource.find( |
177 | + [(recipe_artifact, initial_access_policy) |
178 | + for recipe_artifact in access_artifacts]) |
179 | + self.assertEqual( |
180 | + {i.policy.pillar for i in policy_artifacts}, {initial_project}) |
181 | + |
182 | + # Changing OCI project's pillar should move the policy artifacts of |
183 | + # all OCI recipes associated to the new pillar. |
184 | + flush_database_caches() |
185 | + with admin_logged_in(): |
186 | + oci_project.pillar = final_project |
187 | + |
188 | + final_access_policy = getUtility(IAccessPolicySource).find( |
189 | + [(final_project, InformationType.USERDATA)]).one() |
190 | + policy_artifacts = apasource.find( |
191 | + [(recipe_artifact, final_access_policy) |
192 | + for recipe_artifact in access_artifacts]) |
193 | + self.assertEqual( |
194 | + {i.policy.pillar for i in policy_artifacts}, {final_project}) |
195 | + |
196 | + |
197 | class TestOCIRecipeProcessors(TestCaseWithFactory): |
198 | |
199 | layer = DatabaseFunctionalLayer |
200 | diff --git a/lib/lp/registry/model/accesspolicy.py b/lib/lp/registry/model/accesspolicy.py |
201 | index 64b69c8..010e49e 100644 |
202 | --- a/lib/lp/registry/model/accesspolicy.py |
203 | +++ b/lib/lp/registry/model/accesspolicy.py |
204 | @@ -11,10 +11,11 @@ __all__ = [ |
205 | 'AccessPolicyArtifact', |
206 | 'AccessPolicyGrant', |
207 | 'AccessPolicyGrantFlat', |
208 | - 'reconcile_access_for_artifact', |
209 | + 'reconcile_access_for_artifacts', |
210 | ] |
211 | |
212 | from collections import defaultdict |
213 | +from itertools import product |
214 | |
215 | import pytz |
216 | from storm.expr import ( |
217 | @@ -57,14 +58,14 @@ from lp.services.database.interfaces import IStore |
218 | from lp.services.database.stormbase import StormBase |
219 | |
220 | |
221 | -def reconcile_access_for_artifact(artifact, information_type, pillars, |
222 | - wanted_links=None): |
223 | +def reconcile_access_for_artifacts(artifacts, information_type, pillars, |
224 | + wanted_links=None): |
225 | if information_type in PUBLIC_INFORMATION_TYPES: |
226 | # If it's public we can delete all the access information. |
227 | # IAccessArtifactSource handles the cascade. |
228 | - getUtility(IAccessArtifactSource).delete([artifact]) |
229 | + getUtility(IAccessArtifactSource).delete(artifacts) |
230 | return |
231 | - [abstract_artifact] = getUtility(IAccessArtifactSource).ensure([artifact]) |
232 | + abstract_artifacts = getUtility(IAccessArtifactSource).ensure(artifacts) |
233 | aps = getUtility(IAccessPolicySource).find( |
234 | (pillar, information_type) for pillar in pillars) |
235 | missing_pillars = set(pillars) - set([ap.pillar for ap in aps]) |
236 | @@ -77,11 +78,11 @@ def reconcile_access_for_artifact(artifact, information_type, pillars, |
237 | # Now determine the existing and desired links, and make them |
238 | # match. The caller may have provided the wanted_links. |
239 | apasource = getUtility(IAccessPolicyArtifactSource) |
240 | - wanted_links = (wanted_links |
241 | - or set((abstract_artifact, policy) for policy in aps)) |
242 | + wanted_links = ( |
243 | + wanted_links or set(product(abstract_artifacts, aps))) |
244 | existing_links = set([ |
245 | (apa.abstract_artifact, apa.policy) |
246 | - for apa in apasource.findByArtifact([abstract_artifact])]) |
247 | + for apa in apasource.findByArtifact(abstract_artifacts)]) |
248 | apasource.create(wanted_links - existing_links) |
249 | apasource.delete(existing_links - wanted_links) |
250 | |
251 | diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py |
252 | index eabde9b..b034602 100644 |
253 | --- a/lib/lp/registry/model/ociproject.py |
254 | +++ b/lib/lp/registry/model/ociproject.py |
255 | @@ -1,4 +1,4 @@ |
256 | -# Copyright 2019-2020 Canonical Ltd. This software is licensed under the |
257 | +# Copyright 2019-2021 Canonical Ltd. This software is licensed under the |
258 | # GNU Affero General Public License version 3 (see the file LICENSE). |
259 | |
260 | """OCI Project implementation.""" |
261 | @@ -11,6 +11,8 @@ __all__ = [ |
262 | 'OCIProjectSet', |
263 | ] |
264 | |
265 | +from collections import defaultdict |
266 | + |
267 | import pytz |
268 | import six |
269 | from six import text_type |
270 | @@ -42,6 +44,7 @@ from lp.registry.interfaces.ociprojectname import IOCIProjectNameSet |
271 | from lp.registry.interfaces.person import IPersonSet |
272 | from lp.registry.interfaces.product import IProduct |
273 | from lp.registry.interfaces.series import SeriesStatus |
274 | +from lp.registry.model.accesspolicy import reconcile_access_for_artifacts |
275 | from lp.registry.model.ociprojectname import OCIProjectName |
276 | from lp.registry.model.ociprojectseries import OCIProjectSeries |
277 | from lp.registry.model.person import Person |
278 | @@ -115,6 +118,11 @@ class OCIProject(BugTargetBase, StormBase): |
279 | |
280 | @pillar.setter |
281 | def pillar(self, pillar): |
282 | + """See `IBugTarget`.""" |
283 | + # We need to reconcile access for all OCI recipes from this OCI |
284 | + # project if we are moving from one pillar to another. |
285 | + needs_reconcile_access = ( |
286 | + self.pillar is not None and self.pillar != pillar) |
287 | if IDistribution.providedBy(pillar): |
288 | self.distribution = pillar |
289 | self.project = None |
290 | @@ -125,6 +133,8 @@ class OCIProject(BugTargetBase, StormBase): |
291 | raise ValueError( |
292 | 'The target of an OCIProject must be either an IDistribution ' |
293 | 'or IProduct instance.') |
294 | + if needs_reconcile_access: |
295 | + self._reconcileAccess() |
296 | |
297 | @property |
298 | def display_name(self): |
299 | @@ -135,6 +145,19 @@ class OCIProject(BugTargetBase, StormBase): |
300 | bugtargetname = display_name |
301 | bugtargetdisplayname = display_name |
302 | |
303 | + def _reconcileAccess(self): |
304 | + """Reconcile access for all OCI recipes of this project.""" |
305 | + from lp.oci.model.ocirecipe import OCIRecipe |
306 | + rs = IStore(OCIRecipe).find( |
307 | + OCIRecipe, |
308 | + OCIRecipe.oci_project == self) |
309 | + recipes_per_info_type = defaultdict(set) |
310 | + for recipe in rs: |
311 | + recipes_per_info_type[recipe.information_type].add(recipe) |
312 | + for information_type, recipes in recipes_per_info_type.items(): |
313 | + reconcile_access_for_artifacts( |
314 | + recipes, information_type, [self.pillar]) |
315 | + |
316 | def newRecipe(self, name, registrant, owner, git_ref, |
317 | build_file, description=None, build_daily=False, |
318 | require_virtualized=True, build_args=None): |
319 | diff --git a/lib/lp/registry/tests/test_accesspolicy.py b/lib/lp/registry/tests/test_accesspolicy.py |
320 | index 6ab5418..ce70d43 100644 |
321 | --- a/lib/lp/registry/tests/test_accesspolicy.py |
322 | +++ b/lib/lp/registry/tests/test_accesspolicy.py |
323 | @@ -1,4 +1,4 @@ |
324 | -# Copyright 2011-2015 Canonical Ltd. This software is licensed under the |
325 | +# Copyright 2011-2021 Canonical Ltd. This software is licensed under the |
326 | # GNU Affero General Public License version 3 (see the file LICENSE). |
327 | |
328 | __metaclass__ = type |
329 | @@ -22,12 +22,18 @@ from lp.registry.interfaces.accesspolicy import ( |
330 | IAccessPolicyGrantSource, |
331 | IAccessPolicySource, |
332 | ) |
333 | -from lp.registry.model.accesspolicy import reconcile_access_for_artifact |
334 | +from lp.registry.model.accesspolicy import reconcile_access_for_artifacts |
335 | from lp.registry.model.person import Person |
336 | from lp.services.database.interfaces import IStore |
337 | -from lp.testing import TestCaseWithFactory |
338 | +from lp.testing import ( |
339 | + record_two_runs, |
340 | + TestCaseWithFactory, |
341 | + ) |
342 | from lp.testing.layers import DatabaseFunctionalLayer |
343 | -from lp.testing.matchers import Provides |
344 | +from lp.testing.matchers import ( |
345 | + HasQueryCount, |
346 | + Provides, |
347 | + ) |
348 | |
349 | |
350 | def get_policies_for_artifact(concrete_artifact): |
351 | @@ -729,57 +735,79 @@ class TestReconcileAccessPolicyArtifacts(TestCaseWithFactory): |
352 | get_policies_for_artifact(bug)) |
353 | |
354 | def test_creates_missing_accessartifact(self): |
355 | - # reconcile_access_for_artifact creates an AccessArtifact for a |
356 | + # reconcile_access_for_artifacts creates an AccessArtifact for a |
357 | # private artifact if there isn't one already. |
358 | bug = self.factory.makeBug() |
359 | |
360 | self.assertTrue( |
361 | getUtility(IAccessArtifactSource).find([bug]).is_empty()) |
362 | - reconcile_access_for_artifact(bug, InformationType.USERDATA, []) |
363 | + reconcile_access_for_artifacts([bug], InformationType.USERDATA, []) |
364 | self.assertFalse( |
365 | getUtility(IAccessArtifactSource).find([bug]).is_empty()) |
366 | |
367 | + def test_bulk_creates_missing_accessartifact_query_count(self): |
368 | + # reconcile_access_for_artifacts creates one for each AccessArtifact |
369 | + # private artifact if there isn't one already. |
370 | + bugs = [self.factory.makeBug()] |
371 | + |
372 | + def create_bugs(): |
373 | + while len(bugs): |
374 | + bugs.pop() |
375 | + for i in range(10): |
376 | + bugs.append(self.factory.makeBug()) |
377 | + |
378 | + def reconcile(): |
379 | + reconcile_access_for_artifacts(bugs, InformationType.USERDATA, []) |
380 | + |
381 | + # Runs with original `bugs` list with 1 item, then cleanup that list |
382 | + # and create another set of new bugs. |
383 | + recorder1, recorder2 = record_two_runs(reconcile, create_bugs, 0, 1) |
384 | + self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) |
385 | + |
386 | + self.assertEqual( |
387 | + 10, getUtility(IAccessArtifactSource).find(bugs).count()) |
388 | + |
389 | def test_removes_extra_accessartifact(self): |
390 | - # reconcile_access_for_artifact removes an AccessArtifact for a |
391 | + # reconcile_access_for_artifacts removes an AccessArtifact for a |
392 | # public artifact if there's one left over. |
393 | bug = self.factory.makeBug() |
394 | - reconcile_access_for_artifact(bug, InformationType.USERDATA, []) |
395 | + reconcile_access_for_artifacts([bug], InformationType.USERDATA, []) |
396 | |
397 | self.assertFalse( |
398 | getUtility(IAccessArtifactSource).find([bug]).is_empty()) |
399 | - reconcile_access_for_artifact(bug, InformationType.PUBLIC, []) |
400 | + reconcile_access_for_artifacts([bug], InformationType.PUBLIC, []) |
401 | self.assertTrue( |
402 | getUtility(IAccessArtifactSource).find([bug]).is_empty()) |
403 | |
404 | def test_adds_missing_accesspolicyartifacts(self): |
405 | - # reconcile_access_for_artifact adds missing links. |
406 | + # reconcile_access_for_artifacts adds missing links. |
407 | product = self.factory.makeProduct() |
408 | bug = self.factory.makeBug(target=product) |
409 | - reconcile_access_for_artifact(bug, InformationType.USERDATA, []) |
410 | + reconcile_access_for_artifacts([bug], InformationType.USERDATA, []) |
411 | |
412 | self.assertPoliciesForBug([], bug) |
413 | - reconcile_access_for_artifact( |
414 | - bug, InformationType.USERDATA, [product]) |
415 | + reconcile_access_for_artifacts( |
416 | + [bug], InformationType.USERDATA, [product]) |
417 | self.assertPoliciesForBug([(product, InformationType.USERDATA)], bug) |
418 | |
419 | def test_removes_extra_accesspolicyartifacts(self): |
420 | - # reconcile_access_for_artifact removes excess links. |
421 | + # reconcile_access_for_artifacts removes excess links. |
422 | bug = self.factory.makeBug() |
423 | product = self.factory.makeProduct() |
424 | other_product = self.factory.makeProduct() |
425 | - reconcile_access_for_artifact( |
426 | - bug, InformationType.USERDATA, [product, other_product]) |
427 | + reconcile_access_for_artifacts( |
428 | + [bug], InformationType.USERDATA, [product, other_product]) |
429 | |
430 | self.assertPoliciesForBug( |
431 | [(product, InformationType.USERDATA), |
432 | (other_product, InformationType.USERDATA)], |
433 | bug) |
434 | - reconcile_access_for_artifact( |
435 | - bug, InformationType.USERDATA, [product]) |
436 | + reconcile_access_for_artifacts( |
437 | + [bug], InformationType.USERDATA, [product]) |
438 | self.assertPoliciesForBug([(product, InformationType.USERDATA)], bug) |
439 | |
440 | def test_raises_exception_on_missing_policies(self): |
441 | - # reconcile_access_for_artifact raises an exception if a pillar is |
442 | + # reconcile_access_for_artifacts raises an exception if a pillar is |
443 | # missing an AccessPolicy. |
444 | product = self.factory.makeProduct() |
445 | # Creating a product will have created two APs, delete them. |
446 | @@ -792,5 +820,5 @@ class TestReconcileAccessPolicyArtifacts(TestCaseWithFactory): |
447 | "Pillar(s) %s require an access policy for information type " |
448 | "Private.") % product.name |
449 | self.assertRaisesWithContent( |
450 | - AssertionError, expected, reconcile_access_for_artifact, bug, |
451 | + AssertionError, expected, reconcile_access_for_artifacts, [bug], |
452 | InformationType.USERDATA, [product]) |
453 | diff --git a/lib/lp/registry/tests/test_sharingjob.py b/lib/lp/registry/tests/test_sharingjob.py |
454 | index 32aec0f..73ad71f 100644 |
455 | --- a/lib/lp/registry/tests/test_sharingjob.py |
456 | +++ b/lib/lp/registry/tests/test_sharingjob.py |
457 | @@ -27,7 +27,7 @@ from lp.registry.interfaces.sharingjob import ( |
458 | ISharingJob, |
459 | ISharingJobSource, |
460 | ) |
461 | -from lp.registry.model.accesspolicy import reconcile_access_for_artifact |
462 | +from lp.registry.model.accesspolicy import reconcile_access_for_artifacts |
463 | from lp.registry.model.sharingjob import ( |
464 | RemoveArtifactSubscriptionsJob, |
465 | SharingJob, |
466 | @@ -378,8 +378,8 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory): |
467 | # Change artifact attributes so that it can become inaccessible for |
468 | # some users. |
469 | change_callback(concrete_artifact) |
470 | - reconcile_access_for_artifact( |
471 | - concrete_artifact, concrete_artifact.information_type, |
472 | + reconcile_access_for_artifacts( |
473 | + [concrete_artifact], concrete_artifact.information_type, |
474 | get_pillars(concrete_artifact)) |
475 | |
476 | getUtility(IRemoveArtifactSubscriptionsJobSource).create( |
477 | diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py |
478 | index 5b1be23..afa7e7d 100644 |
479 | --- a/lib/lp/snappy/model/snap.py |
480 | +++ b/lib/lp/snappy/model/snap.py |
481 | @@ -132,7 +132,7 @@ from lp.registry.interfaces.role import ( |
482 | ) |
483 | from lp.registry.model.accesspolicy import ( |
484 | AccessPolicyGrant, |
485 | - reconcile_access_for_artifact, |
486 | + reconcile_access_for_artifacts, |
487 | ) |
488 | from lp.registry.model.distroseries import DistroSeries |
489 | from lp.registry.model.person import Person |
490 | @@ -1238,7 +1238,7 @@ class Snap(Storm, WebhookTargetMixin): |
491 | if self.project is None: |
492 | return |
493 | pillars = [self.project] |
494 | - reconcile_access_for_artifact(self, self.information_type, pillars) |
495 | + reconcile_access_for_artifacts([self], self.information_type, pillars) |
496 | |
497 | def setProject(self, project): |
498 | self.project = project |