Merge ~pappacena/launchpad:ocirecipe-subscribe-removal-job into launchpad:master

Proposed by Thiago F. Pappacena
Status: Merged
Approved by: Thiago F. Pappacena
Approved revision: 8e5bd69ecdd6bccdc1e76c20ecc8bb6cce70b852
Merged at revision: 65142a38e43e863baf2068006265af09ceac7aaa
Proposed branch: ~pappacena/launchpad:ocirecipe-subscribe-removal-job
Merge into: launchpad:master
Prerequisite: ~pappacena/launchpad:ocirecipe-filter-private
Diff against target: 384 lines (+128/-25)
4 files modified
database/schema/security.cfg (+3/-0)
lib/lp/registry/model/sharingjob.py (+52/-3)
lib/lp/registry/services/sharingservice.py (+1/-10)
lib/lp/registry/tests/test_sharingjob.py (+72/-12)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+399889@code.launchpad.net

Commit message

Consider OCIRecipeSubscription on IRemoveArtifactSubscriptionsJobSource

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
1diff --git a/database/schema/security.cfg b/database/schema/security.cfg
2index 5e5e000..e149a3c 100644
3--- a/database/schema/security.cfg
4+++ b/database/schema/security.cfg
5@@ -2110,6 +2110,9 @@ public.gitrepository = SELECT
6 public.gitsubscription = SELECT, UPDATE, DELETE
7 public.emailaddress = SELECT
8 public.job = SELECT, INSERT, UPDATE
9+public.ociproject = SELECT
10+public.ocirecipe = SELECT
11+public.ocirecipesubscription = SELECT, UPDATE, DELETE
12 public.person = SELECT
13 public.product = SELECT
14 public.sharingjob = SELECT, INSERT, UPDATE
15diff --git a/lib/lp/registry/model/sharingjob.py b/lib/lp/registry/model/sharingjob.py
16index 81e6a9e..b123547 100644
17--- a/lib/lp/registry/model/sharingjob.py
18+++ b/lib/lp/registry/model/sharingjob.py
19@@ -69,6 +69,12 @@ from lp.code.model.gitrepository import (
20 GitRepository,
21 )
22 from lp.code.model.gitsubscription import GitSubscription
23+from lp.oci.interfaces.ocirecipe import IOCIRecipe
24+from lp.oci.model.ocirecipe import (
25+ get_ocirecipe_privacy_filter,
26+ OCIRecipe,
27+ )
28+from lp.oci.model.ocirecipesubscription import OCIRecipeSubscription
29 from lp.registry.interfaces.person import IPersonSet
30 from lp.registry.interfaces.product import IProduct
31 from lp.registry.interfaces.sharingjob import (
32@@ -78,6 +84,7 @@ from lp.registry.interfaces.sharingjob import (
33 ISharingJobSource,
34 )
35 from lp.registry.model.distribution import Distribution
36+from lp.registry.model.ociproject import OCIProject
37 from lp.registry.model.person import Person
38 from lp.registry.model.product import Product
39 from lp.registry.model.teammembership import TeamParticipation
40@@ -271,6 +278,7 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
41 gitrepository_ids = []
42 snap_ids = []
43 specification_ids = []
44+ ocirecipe_ids = []
45 if artifacts:
46 for artifact in artifacts:
47 if IBug.providedBy(artifact):
48@@ -283,6 +291,8 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
49 snap_ids.append(artifact.id)
50 elif ISpecification.providedBy(artifact):
51 specification_ids.append(artifact.id)
52+ elif IOCIRecipe.providedBy(artifact):
53+ ocirecipe_ids.append(artifact.id)
54 else:
55 raise ValueError(
56 'Unsupported artifact: %r' % artifact)
57@@ -295,6 +305,7 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
58 'gitrepository_ids': gitrepository_ids,
59 'snap_ids': snap_ids,
60 'specification_ids': specification_ids,
61+ 'ocirecipe_ids': ocirecipe_ids,
62 'information_types': information_types,
63 'requestor.id': requestor.id
64 }
65@@ -338,6 +349,10 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
66 return self.metadata.get('specification_ids', [])
67
68 @property
69+ def ocirecipe_ids(self):
70+ return self.metadata.get('ocirecipe_ids', [])
71+
72+ @property
73 def information_types(self):
74 if not 'information_types' in self.metadata:
75 return []
76@@ -365,6 +380,7 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
77 'gitrepository_ids': self.gitrepository_ids,
78 'snap_ids': self.snap_ids,
79 'specification_ids': self.specification_ids,
80+ 'ocirecipe_ids': self.ocirecipe_ids,
81 'pillar': getattr(self.pillar, 'name', None),
82 'grantee': getattr(self.grantee, 'name', None)
83 }
84@@ -382,6 +398,7 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
85 gitrepository_filters = []
86 snap_filters = []
87 specification_filters = []
88+ ocirecipe_filters = []
89
90 if self.branch_ids:
91 branch_filters.append(Branch.id.is_in(self.branch_ids))
92@@ -393,6 +410,8 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
93 if self.specification_ids:
94 specification_filters.append(Specification.id.is_in(
95 self.specification_ids))
96+ if self.ocirecipe_ids:
97+ ocirecipe_filters.append(OCIRecipe.id.is_in(self.ocirecipe_ids))
98 if self.bug_ids:
99 bug_filters.append(BugTaskFlat.bug_id.is_in(self.bug_ids))
100 else:
101@@ -410,6 +429,8 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
102 specification_filters.append(
103 Specification.information_type.is_in(
104 self.information_types))
105+ ocirecipe_filters.append(OCIRecipe._information_type.is_in(
106+ self.information_types))
107 if self.product:
108 bug_filters.append(
109 BugTaskFlat.product == self.product)
110@@ -418,6 +439,11 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
111 GitRepository.project == self.product)
112 specification_filters.append(
113 Specification.product == self.product)
114+ ocirecipe_filters.append(
115+ In(OCIRecipe.id,
116+ Select(OCIRecipe.id,
117+ And(OCIRecipe.oci_project_id == OCIProject.id,
118+ OCIProject.project == self.product))))
119 if self.distro:
120 bug_filters.append(
121 BugTaskFlat.distribution == self.distro)
122@@ -426,6 +452,11 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
123 GitRepository.distribution == self.distro)
124 specification_filters.append(
125 Specification.distribution == self.distro)
126+ ocirecipe_filters.append(
127+ In(OCIRecipe.id,
128+ Select(OCIRecipe.id,
129+ And(OCIRecipe.oci_project_id == OCIProject.id,
130+ OCIProject.distribution == self.distro))))
131
132 if self.grantee:
133 bug_filters.append(
134@@ -450,9 +481,14 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
135 where=TeamParticipation.team == self.grantee)))
136 specification_filters.append(
137 In(SpecificationSubscription.person_id,
138- Select(
139- TeamParticipation.personID,
140- where=TeamParticipation.team == self.grantee)))
141+ Select(
142+ TeamParticipation.personID,
143+ where=TeamParticipation.team == self.grantee)))
144+ ocirecipe_filters.append(
145+ In(OCIRecipeSubscription.person_id,
146+ Select(
147+ TeamParticipation.personID,
148+ where=TeamParticipation.team == self.grantee)))
149
150 if bug_filters:
151 bug_filters.append(Not(
152@@ -517,3 +553,16 @@ class RemoveArtifactSubscriptionsJob(SharingJobDerived):
153 for sub in specifications_subscriptions:
154 sub.specification.unsubscribe(
155 sub.person, self.requestor, ignore_permissions=True)
156+ if ocirecipe_filters:
157+ ocirecipe_filters.append(
158+ Not(get_ocirecipe_privacy_filter(
159+ OCIRecipeSubscription.person_id)))
160+ ocirecipe_subscriptions = IStore(OCIRecipeSubscription).using(
161+ OCIRecipeSubscription,
162+ Join(OCIRecipe,
163+ OCIRecipe.id == OCIRecipeSubscription.recipe_id)
164+ ).find(OCIRecipeSubscription, *ocirecipe_filters).config(
165+ distinct=True)
166+ for sub in ocirecipe_subscriptions:
167+ sub.recipe.unsubscribe(
168+ sub.person, self.requestor, ignore_permissions=True)
169diff --git a/lib/lp/registry/services/sharingservice.py b/lib/lp/registry/services/sharingservice.py
170index a0e755b..a311aff 100644
171--- a/lib/lp/registry/services/sharingservice.py
172+++ b/lib/lp/registry/services/sharingservice.py
173@@ -39,10 +39,7 @@ from lp.bugs.interfaces.bugtask import IBugTaskSet
174 from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
175 from lp.code.interfaces.branchcollection import IAllBranches
176 from lp.code.interfaces.gitcollection import IAllGitRepositories
177-from lp.oci.interfaces.ocirecipe import (
178- IOCIRecipe,
179- IOCIRecipeSet,
180- )
181+from lp.oci.interfaces.ocirecipe import IOCIRecipeSet
182 from lp.oci.model.ocirecipe import OCIRecipe
183 from lp.registry.enums import (
184 BranchSharingPolicy,
185@@ -857,12 +854,6 @@ class SharingService:
186 getUtility(IAccessArtifactGrantSource).revokeByArtifact(
187 artifacts_to_delete, [grantee])
188
189- # XXX: Pappacena 2021-03-09: OCI recipes should not trigger this job,
190- # since we do not have a "OCIRecipeSubscription" yet.
191- artifacts = [i for i in artifacts if not IOCIRecipe.providedBy(i)]
192- if not artifacts:
193- return
194-
195 # Create a job to remove subscriptions for artifacts the grantee can no
196 # longer see.
197 return getUtility(IRemoveArtifactSubscriptionsJobSource).create(
198diff --git a/lib/lp/registry/tests/test_sharingjob.py b/lib/lp/registry/tests/test_sharingjob.py
199index 73ad71f..65f382b 100644
200--- a/lib/lp/registry/tests/test_sharingjob.py
201+++ b/lib/lp/registry/tests/test_sharingjob.py
202@@ -16,6 +16,10 @@ from lp.code.enums import (
203 BranchSubscriptionNotificationLevel,
204 CodeReviewNotificationLevel,
205 )
206+from lp.oci.interfaces.ocirecipe import (
207+ OCI_RECIPE_ALLOW_CREATE,
208+ OCI_RECIPE_PRIVATE_FEATURE_FLAG,
209+ )
210 from lp.registry.enums import SpecificationSharingPolicy
211 from lp.registry.interfaces.accesspolicy import (
212 IAccessArtifactGrantSource,
213@@ -148,6 +152,21 @@ class SharingJobDerivedTestCase(TestCaseWithFactory):
214 'for requestor=%s, specification_ids=[%d]>'
215 % (requestor.name, specification.id), repr(job))
216
217+ def test_repr_ocirecipe(self):
218+ features = {
219+ OCI_RECIPE_ALLOW_CREATE: 'on',
220+ OCI_RECIPE_PRIVATE_FEATURE_FLAG: 'on'
221+ }
222+ self.useFixture(FeatureFixture(features))
223+ requestor = self.factory.makePerson()
224+ recipe = self.factory.makeOCIRecipe()
225+ job = getUtility(IRemoveArtifactSubscriptionsJobSource).create(
226+ requestor, artifacts=[recipe])
227+ self.assertEqual(
228+ '<REMOVE_ARTIFACT_SUBSCRIPTIONS job reconciling subscriptions '
229+ 'for ocirecipe_ids=[%d], requestor=%s>'
230+ % (recipe.id, requestor.name), repr(job))
231+
232 def test_create_success(self):
233 # Create an instance of SharingJobDerived that delegates to SharingJob.
234 self.assertIs(True, ISharingJobSource.providedBy(SharingJobDerived))
235@@ -254,6 +273,8 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
236 def setUp(self):
237 features = {
238 'jobs.celery.enabled_classes': 'RemoveArtifactSubscriptionsJob',
239+ OCI_RECIPE_ALLOW_CREATE: 'on',
240+ OCI_RECIPE_PRIVATE_FEATURE_FLAG: 'on'
241 }
242 features.update(SNAP_TESTING_FLAGS)
243 self.useFixture(FeatureFixture(features))
244@@ -334,6 +355,10 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
245 specification = self.factory.makeSpecification(
246 owner=owner, product=product,
247 information_type=InformationType.PROPRIETARY)
248+ ocirecipe = self.factory.makeOCIRecipe(
249+ owner=owner, registrant=owner,
250+ oci_project=self.factory.makeOCIProject(pillar=product),
251+ information_type=InformationType.USERDATA)
252
253 # The artifact grantees will not lose access when the job is run.
254 artifact_indirect_grantee = self.factory.makePerson()
255@@ -349,6 +374,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
256 BranchSubscriptionNotificationLevel.NOEMAIL, None,
257 CodeReviewNotificationLevel.NOEMAIL, owner)
258 snap.subscribe(artifact_indirect_grantee, owner)
259+ ocirecipe.subscribe(artifact_indirect_grantee, owner)
260 # Subscribing somebody to a specification does not automatically
261 # create an artifact grant.
262 spec_artifact = self.factory.makeAccessArtifact(specification)
263@@ -358,9 +384,9 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
264 specification.subscribe(artifact_indirect_grantee, owner)
265
266 # pick one of the concrete artifacts (bug, branch, Git repository,
267- # snap, or spec) and subscribe the teams and persons.
268+ # snap, spec or ocirecipe) and subscribe the teams and persons.
269 concrete_artifact, get_pillars, get_subscribers = configure_test(
270- bug, branch, gitrepository, snap, specification,
271+ bug, branch, gitrepository, snap, specification, ocirecipe,
272 policy_team_grantee, policy_indirect_grantee,
273 artifact_team_grantee, owner)
274
275@@ -396,6 +422,7 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
276 self.assertIn(artifact_indirect_grantee, branch.subscribers)
277 self.assertIn(artifact_indirect_grantee, gitrepository.subscribers)
278 self.assertIn(artifact_indirect_grantee, snap.subscribers)
279+ self.assertIn(artifact_indirect_grantee, ocirecipe.subscribers)
280 self.assertIn(artifact_indirect_grantee,
281 removeSecurityProxy(specification).subscribers)
282
283@@ -409,8 +436,9 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
284 concrete_artifact).getDirectSubscribers()
285
286 def configure_test(bug, branch, gitrepository, snap, specification,
287- policy_team_grantee, policy_indirect_grantee,
288- artifact_team_grantee, owner):
289+ ocirecipe, policy_team_grantee,
290+ policy_indirect_grantee, artifact_team_grantee,
291+ owner):
292 concrete_artifact = bug
293 bug.subscribe(policy_team_grantee, owner)
294 bug.subscribe(policy_indirect_grantee, owner)
295@@ -429,8 +457,9 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
296 return concrete_artifact.subscribers
297
298 def configure_test(bug, branch, gitrepository, snap, specification,
299- policy_team_grantee, policy_indirect_grantee,
300- artifact_team_grantee, owner):
301+ ocirecipe, policy_team_grantee,
302+ policy_indirect_grantee, artifact_team_grantee,
303+ owner):
304 concrete_artifact = branch
305 branch.subscribe(
306 policy_team_grantee,
307@@ -458,8 +487,9 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
308 return concrete_artifact.subscribers
309
310 def configure_test(bug, branch, gitrepository, snap, specification,
311- policy_team_grantee, policy_indirect_grantee,
312- artifact_team_grantee, owner):
313+ ocirecipe, policy_team_grantee,
314+ policy_indirect_grantee, artifact_team_grantee,
315+ owner):
316 concrete_artifact = gitrepository
317 gitrepository.subscribe(
318 policy_team_grantee,
319@@ -487,8 +517,9 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
320 return concrete_artifact.subscribers
321
322 def configure_test(bug, branch, gitrepository, snap, specification,
323- policy_team_grantee, policy_indirect_grantee,
324- artifact_team_grantee, owner):
325+ ocirecipe, policy_team_grantee,
326+ policy_indirect_grantee, artifact_team_grantee,
327+ owner):
328 concrete_artifact = snap
329 snap.subscribe(policy_team_grantee, owner)
330 snap.subscribe(policy_indirect_grantee, owner)
331@@ -507,8 +538,9 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
332 return concrete_artifact.subscribers
333
334 def configure_test(bug, branch, gitrepository, snap, specification,
335- policy_team_grantee, policy_indirect_grantee,
336- artifact_team_grantee, owner):
337+ ocirecipe, policy_team_grantee,
338+ policy_indirect_grantee, artifact_team_grantee,
339+ owner):
340 naked_spec = removeSecurityProxy(specification)
341 naked_spec.subscribe(policy_team_grantee, owner)
342 naked_spec.subscribe(policy_indirect_grantee, owner)
343@@ -521,6 +553,27 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
344 self._assert_artifact_change_unsubscribes(
345 change_callback, configure_test)
346
347+ def _assert_ocirecipe_change_unsubscribes(self, change_callback):
348+
349+ def get_pillars(concrete_artifact):
350+ return [concrete_artifact.oci_project.project]
351+
352+ def get_subscribers(concrete_artifact):
353+ return concrete_artifact.subscribers
354+
355+ def configure_test(bug, branch, gitrepository, snap, specification,
356+ ocirecipe, policy_team_grantee,
357+ policy_indirect_grantee, artifact_team_grantee,
358+ owner):
359+ concrete_artifact = ocirecipe
360+ ocirecipe.subscribe(policy_team_grantee, owner)
361+ ocirecipe.subscribe(policy_indirect_grantee, owner)
362+ ocirecipe.subscribe(artifact_team_grantee, owner)
363+ return concrete_artifact, get_pillars, get_subscribers
364+
365+ self._assert_artifact_change_unsubscribes(
366+ change_callback, configure_test)
367+
368 def test_change_information_type_branch(self):
369 def change_information_type(branch):
370 removeSecurityProxy(branch).information_type = (
371@@ -549,6 +602,13 @@ class RemoveArtifactSubscriptionsJobTestCase(TestCaseWithFactory):
372
373 self._assert_specification_change_unsubscribes(change_information_type)
374
375+ def test_change_information_type_ocirecipe(self):
376+ def change_information_type(ocirecipe):
377+ removeSecurityProxy(ocirecipe).information_type = (
378+ InformationType.PRIVATESECURITY)
379+
380+ self._assert_ocirecipe_change_unsubscribes(change_information_type)
381+
382 def test_change_information_type(self):
383 # Changing the information type of a bug unsubscribes users who can no
384 # longer see the bug.