Merge ~pappacena/launchpad:snap-pillar into launchpad:master
- Git
- lp:~pappacena/launchpad
- snap-pillar
- Merge into master
Proposed by
Thiago F. Pappacena
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Thiago F. Pappacena | ||||
Approved revision: | c7955b9825801193bb479a9d3528abff9a661144 | ||||
Merge reported by: | Otto Co-Pilot | ||||
Merged at revision: | not available | ||||
Proposed branch: | ~pappacena/launchpad:snap-pillar | ||||
Merge into: | launchpad:master | ||||
Diff against target: |
372 lines (+119/-14) 5 files modified
database/schema/security.cfg (+3/-0) lib/lp/snappy/interfaces/snap.py (+33/-4) lib/lp/snappy/model/snap.py (+58/-5) lib/lp/snappy/tests/test_snap.py (+16/-2) lib/lp/testing/factory.py (+9/-3) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+397458@code.launchpad.net |
Commit message
Adding Snap.project to be the (optional) pillar of snaps
Description of the change
This adds a project to be the optional pillar of Snaps (mandatory for private Snaps). Once we create the UI to set this, we should start validating and only allowing private Snaps that belongs to a given pillar (so we can use the pillar's sharing options to control Snap's privacy).
Database patch is available here: https:/
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Revision history for this message
Thiago F. Pappacena (pappacena) : | # |
Revision history for this message
Colin Watson (cjwatson) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/database/schema/security.cfg b/database/schema/security.cfg | |||
2 | index bf4b81c..e343a5f 100644 | |||
3 | --- a/database/schema/security.cfg | |||
4 | +++ b/database/schema/security.cfg | |||
5 | @@ -302,6 +302,7 @@ public.snapbuild = SELECT, INSERT, UPDATE, DELETE | |||
6 | 302 | public.snapbuildjob = SELECT, INSERT, UPDATE, DELETE | 302 | public.snapbuildjob = SELECT, INSERT, UPDATE, DELETE |
7 | 303 | public.snapfile = SELECT, INSERT, UPDATE, DELETE | 303 | public.snapfile = SELECT, INSERT, UPDATE, DELETE |
8 | 304 | public.snapjob = SELECT, INSERT, UPDATE, DELETE | 304 | public.snapjob = SELECT, INSERT, UPDATE, DELETE |
9 | 305 | public.snapsubscription = SELECT, INSERT, UPDATE, DELETE | ||
10 | 305 | public.snappydistroseries = SELECT, INSERT, UPDATE, DELETE | 306 | public.snappydistroseries = SELECT, INSERT, UPDATE, DELETE |
11 | 306 | public.snappyseries = SELECT, INSERT, UPDATE, DELETE | 307 | public.snappyseries = SELECT, INSERT, UPDATE, DELETE |
12 | 307 | public.sourcepackageformatselection = SELECT | 308 | public.sourcepackageformatselection = SELECT |
13 | @@ -2246,6 +2247,7 @@ type=user | |||
14 | 2246 | 2247 | ||
15 | 2247 | [person-merge-job] | 2248 | [person-merge-job] |
16 | 2248 | groups=script | 2249 | groups=script |
17 | 2250 | public.accesspolicyartifact = SELECT | ||
18 | 2249 | public.accessartifactgrant = SELECT, UPDATE, DELETE | 2251 | public.accessartifactgrant = SELECT, UPDATE, DELETE |
19 | 2250 | public.accesspolicy = SELECT, UPDATE, DELETE | 2252 | public.accesspolicy = SELECT, UPDATE, DELETE |
20 | 2251 | public.accesspolicygrant = SELECT, UPDATE, DELETE | 2253 | public.accesspolicygrant = SELECT, UPDATE, DELETE |
21 | @@ -2363,6 +2365,7 @@ public.signedcodeofconduct = SELECT, UPDATE | |||
22 | 2363 | public.snap = SELECT, UPDATE | 2365 | public.snap = SELECT, UPDATE |
23 | 2364 | public.snapbase = SELECT, UPDATE | 2366 | public.snapbase = SELECT, UPDATE |
24 | 2365 | public.snapbuild = SELECT, UPDATE | 2367 | public.snapbuild = SELECT, UPDATE |
25 | 2368 | public.snapsubscription = SELECT, UPDATE, DELETE | ||
26 | 2366 | public.snappyseries = SELECT, UPDATE | 2369 | public.snappyseries = SELECT, UPDATE |
27 | 2367 | public.sourcepackagename = SELECT | 2370 | public.sourcepackagename = SELECT |
28 | 2368 | public.sourcepackagepublishinghistory = SELECT, UPDATE | 2371 | public.sourcepackagepublishinghistory = SELECT, UPDATE |
29 | diff --git a/lib/lp/snappy/interfaces/snap.py b/lib/lp/snappy/interfaces/snap.py | |||
30 | index 3835b23..9b19b1b 100644 | |||
31 | --- a/lib/lp/snappy/interfaces/snap.py | |||
32 | +++ b/lib/lp/snappy/interfaces/snap.py | |||
33 | @@ -1,4 +1,4 @@ | |||
35 | 1 | # Copyright 2015-2020 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2015-2021 Canonical Ltd. This software is licensed under the |
36 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
37 | 3 | 3 | ||
38 | 4 | """Snap package interfaces.""" | 4 | """Snap package interfaces.""" |
39 | @@ -36,6 +36,7 @@ __all__ = [ | |||
40 | 36 | 'SnapBuildRequestStatus', | 36 | 'SnapBuildRequestStatus', |
41 | 37 | 'SnapNotOwner', | 37 | 'SnapNotOwner', |
42 | 38 | 'SnapPrivacyMismatch', | 38 | 'SnapPrivacyMismatch', |
43 | 39 | 'SnapPrivacyPillarError', | ||
44 | 39 | 'SnapPrivateFeatureDisabled', | 40 | 'SnapPrivateFeatureDisabled', |
45 | 40 | ] | 41 | ] |
46 | 41 | 42 | ||
47 | @@ -88,7 +89,9 @@ from zope.security.interfaces import ( | |||
48 | 88 | ) | 89 | ) |
49 | 89 | 90 | ||
50 | 90 | from lp import _ | 91 | from lp import _ |
51 | 92 | from lp.app.enums import InformationType | ||
52 | 91 | from lp.app.errors import NameLookupFailed | 93 | from lp.app.errors import NameLookupFailed |
53 | 94 | from lp.app.interfaces.informationtype import IInformationType | ||
54 | 92 | from lp.app.interfaces.launchpad import IPrivacy | 95 | from lp.app.interfaces.launchpad import IPrivacy |
55 | 93 | from lp.app.validators.name import name_validator | 96 | from lp.app.validators.name import name_validator |
56 | 94 | from lp.buildmaster.interfaces.processor import IProcessor | 97 | from lp.buildmaster.interfaces.processor import IProcessor |
57 | @@ -98,6 +101,7 @@ from lp.code.interfaces.gitrepository import IGitRepository | |||
58 | 98 | from lp.registry.interfaces.distroseries import IDistroSeries | 101 | from lp.registry.interfaces.distroseries import IDistroSeries |
59 | 99 | from lp.registry.interfaces.person import IPerson | 102 | from lp.registry.interfaces.person import IPerson |
60 | 100 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 103 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
61 | 104 | from lp.registry.interfaces.product import IProduct | ||
62 | 101 | from lp.registry.interfaces.role import IHasOwner | 105 | from lp.registry.interfaces.role import IHasOwner |
63 | 102 | from lp.services.fields import ( | 106 | from lp.services.fields import ( |
64 | 103 | PersonChoice, | 107 | PersonChoice, |
65 | @@ -212,7 +216,16 @@ class SnapPrivacyMismatch(Exception): | |||
66 | 212 | def __init__(self, message=None): | 216 | def __init__(self, message=None): |
67 | 213 | super(SnapPrivacyMismatch, self).__init__( | 217 | super(SnapPrivacyMismatch, self).__init__( |
68 | 214 | message or | 218 | message or |
70 | 215 | "Snap contains private information and cannot be public.") | 219 | "Snap recipe contains private information and cannot be public.") |
71 | 220 | |||
72 | 221 | |||
73 | 222 | @error_status(http_client.BAD_REQUEST) | ||
74 | 223 | class SnapPrivacyPillarError(Exception): | ||
75 | 224 | """Private Snaps should be based in a pillar.""" | ||
76 | 225 | |||
77 | 226 | def __init__(self, message=None): | ||
78 | 227 | super(SnapPrivacyPillarError, self).__init__( | ||
79 | 228 | message or "Private Snap recipes should have a pillar.") | ||
80 | 216 | 229 | ||
81 | 217 | 230 | ||
82 | 218 | class BadSnapSearchContext(Exception): | 231 | class BadSnapSearchContext(Exception): |
83 | @@ -658,6 +671,11 @@ class ISnapEditableAttributes(IHasOwner): | |||
84 | 658 | vocabulary="AllUserTeamsParticipationPlusSelf", | 671 | vocabulary="AllUserTeamsParticipationPlusSelf", |
85 | 659 | description=_("The owner of this snap package."))) | 672 | description=_("The owner of this snap package."))) |
86 | 660 | 673 | ||
87 | 674 | project = ReferenceChoice( | ||
88 | 675 | title=_('The project that this Snap is associated with.'), | ||
89 | 676 | schema=IProduct, vocabulary='Product', | ||
90 | 677 | required=False, readonly=False) | ||
91 | 678 | |||
92 | 661 | distro_series = exported(Reference( | 679 | distro_series = exported(Reference( |
93 | 662 | IDistroSeries, title=_("Distro Series"), | 680 | IDistroSeries, title=_("Distro Series"), |
94 | 663 | required=False, readonly=False, | 681 | required=False, readonly=False, |
95 | @@ -825,6 +843,12 @@ class ISnapAdminAttributes(Interface): | |||
96 | 825 | title=_("Private"), required=False, readonly=False, | 843 | title=_("Private"), required=False, readonly=False, |
97 | 826 | description=_("Whether or not this snap is private."))) | 844 | description=_("Whether or not this snap is private."))) |
98 | 827 | 845 | ||
99 | 846 | information_type = exported(Choice( | ||
100 | 847 | title=_("Information type"), vocabulary=InformationType, | ||
101 | 848 | required=True, readonly=True, default=InformationType.PUBLIC, | ||
102 | 849 | description=_( | ||
103 | 850 | "The type of information contained in this Snap recipe."))) | ||
104 | 851 | |||
105 | 828 | require_virtualized = exported(Bool( | 852 | require_virtualized = exported(Bool( |
106 | 829 | title=_("Require virtualized builders"), required=True, readonly=False, | 853 | title=_("Require virtualized builders"), required=True, readonly=False, |
107 | 830 | description=_("Only build this snap package on virtual builders."))) | 854 | description=_("Only build this snap package on virtual builders."))) |
108 | @@ -850,7 +874,7 @@ class ISnapAdminAttributes(Interface): | |||
109 | 850 | @exported_as_webservice_entry(as_of="beta") | 874 | @exported_as_webservice_entry(as_of="beta") |
110 | 851 | class ISnap( | 875 | class ISnap( |
111 | 852 | ISnapView, ISnapEdit, ISnapEditableAttributes, ISnapAdminAttributes, | 876 | ISnapView, ISnapEdit, ISnapEditableAttributes, ISnapAdminAttributes, |
113 | 853 | IPrivacy): | 877 | IPrivacy, IInformationType): |
114 | 854 | """A buildable snap package.""" | 878 | """A buildable snap package.""" |
115 | 855 | 879 | ||
116 | 856 | 880 | ||
117 | @@ -876,7 +900,8 @@ class ISnapSet(Interface): | |||
118 | 876 | auto_build_archive=None, auto_build_pocket=None, | 900 | auto_build_archive=None, auto_build_pocket=None, |
119 | 877 | require_virtualized=True, processors=None, date_created=None, | 901 | require_virtualized=True, processors=None, date_created=None, |
120 | 878 | private=False, store_upload=False, store_series=None, | 902 | private=False, store_upload=False, store_series=None, |
122 | 879 | store_name=None, store_secrets=None, store_channels=None): | 903 | store_name=None, store_secrets=None, store_channels=None, |
123 | 904 | project=None): | ||
124 | 880 | """Create an `ISnap`.""" | 905 | """Create an `ISnap`.""" |
125 | 881 | 906 | ||
126 | 882 | def exists(owner, name): | 907 | def exists(owner, name): |
127 | @@ -885,6 +910,10 @@ class ISnapSet(Interface): | |||
128 | 885 | def isValidPrivacy(private, owner, branch=None, git_ref=None): | 910 | def isValidPrivacy(private, owner, branch=None, git_ref=None): |
129 | 886 | """Whether or not the privacy context is valid.""" | 911 | """Whether or not the privacy context is valid.""" |
130 | 887 | 912 | ||
131 | 913 | def isValidInformationType( | ||
132 | 914 | information_type, owner, branch=None, git_ref=None): | ||
133 | 915 | """Whether or not the information type context is valid.""" | ||
134 | 916 | |||
135 | 888 | @operation_parameters( | 917 | @operation_parameters( |
136 | 889 | owner=Reference(IPerson, title=_("Owner"), required=True), | 918 | owner=Reference(IPerson, title=_("Owner"), required=True), |
137 | 890 | name=TextLine(title=_("Snap name"), required=True)) | 919 | name=TextLine(title=_("Snap name"), required=True)) |
138 | diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py | |||
139 | index 7e3d14c..b298a86 100644 | |||
140 | --- a/lib/lp/snappy/model/snap.py | |||
141 | +++ b/lib/lp/snappy/model/snap.py | |||
142 | @@ -1,4 +1,4 @@ | |||
144 | 1 | # Copyright 2015-2020 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2015-2021 Canonical Ltd. This software is licensed under the |
145 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
146 | 3 | 3 | ||
147 | 4 | from __future__ import absolute_import, print_function, unicode_literals | 4 | from __future__ import absolute_import, print_function, unicode_literals |
148 | @@ -57,6 +57,10 @@ from lp.app.browser.tales import ( | |||
149 | 57 | ArchiveFormatterAPI, | 57 | ArchiveFormatterAPI, |
150 | 58 | DateTimeFormatterAPI, | 58 | DateTimeFormatterAPI, |
151 | 59 | ) | 59 | ) |
152 | 60 | from lp.app.enums import ( | ||
153 | 61 | InformationType, | ||
154 | 62 | PRIVATE_INFORMATION_TYPES, | ||
155 | 63 | ) | ||
156 | 60 | from lp.app.errors import IncompatibleArguments | 64 | from lp.app.errors import IncompatibleArguments |
157 | 61 | from lp.app.interfaces.security import IAuthorization | 65 | from lp.app.interfaces.security import IAuthorization |
158 | 62 | from lp.buildmaster.enums import BuildStatus | 66 | from lp.buildmaster.enums import BuildStatus |
159 | @@ -298,6 +302,9 @@ class Snap(Storm, WebhookTargetMixin): | |||
160 | 298 | owner_id = Int(name='owner', allow_none=False, validator=_validate_owner) | 302 | owner_id = Int(name='owner', allow_none=False, validator=_validate_owner) |
161 | 299 | owner = Reference(owner_id, 'Person.id') | 303 | owner = Reference(owner_id, 'Person.id') |
162 | 300 | 304 | ||
163 | 305 | project_id = Int(name='project', allow_none=True) | ||
164 | 306 | project = Reference(project_id, 'Product.id') | ||
165 | 307 | |||
166 | 301 | distro_series_id = Int(name='distro_series', allow_none=True) | 308 | distro_series_id = Int(name='distro_series', allow_none=True) |
167 | 302 | distro_series = Reference(distro_series_id, 'DistroSeries.id') | 309 | distro_series = Reference(distro_series_id, 'DistroSeries.id') |
168 | 303 | 310 | ||
169 | @@ -352,6 +359,17 @@ class Snap(Storm, WebhookTargetMixin): | |||
170 | 352 | 359 | ||
171 | 353 | private = Bool(name='private', validator=_validate_private) | 360 | private = Bool(name='private', validator=_validate_private) |
172 | 354 | 361 | ||
173 | 362 | def _valid_information_type(self, attr, value): | ||
174 | 363 | if not getUtility(ISnapSet).isValidInformationType( | ||
175 | 364 | value, self.owner, self.branch, self.git_ref): | ||
176 | 365 | raise SnapPrivacyMismatch | ||
177 | 366 | return value | ||
178 | 367 | |||
179 | 368 | _information_type = DBEnum( | ||
180 | 369 | enum=InformationType, default=InformationType.PUBLIC, | ||
181 | 370 | name="information_type", | ||
182 | 371 | validator=_valid_information_type) | ||
183 | 372 | |||
184 | 355 | allow_internet = Bool(name='allow_internet', allow_none=False) | 373 | allow_internet = Bool(name='allow_internet', allow_none=False) |
185 | 356 | 374 | ||
186 | 357 | build_source_tarball = Bool(name='build_source_tarball', allow_none=False) | 375 | build_source_tarball = Bool(name='build_source_tarball', allow_none=False) |
187 | @@ -374,13 +392,17 @@ class Snap(Storm, WebhookTargetMixin): | |||
188 | 374 | date_created=DEFAULT, private=False, allow_internet=True, | 392 | date_created=DEFAULT, private=False, allow_internet=True, |
189 | 375 | build_source_tarball=False, store_upload=False, | 393 | build_source_tarball=False, store_upload=False, |
190 | 376 | store_series=None, store_name=None, store_secrets=None, | 394 | store_series=None, store_name=None, store_secrets=None, |
192 | 377 | store_channels=None): | 395 | store_channels=None, project=None): |
193 | 378 | """Construct a `Snap`.""" | 396 | """Construct a `Snap`.""" |
194 | 379 | super(Snap, self).__init__() | 397 | super(Snap, self).__init__() |
195 | 380 | 398 | ||
196 | 381 | # Set the private flag first so that other validators can perform | 399 | # Set the private flag first so that other validators can perform |
198 | 382 | # suitable privacy checks. | 400 | # suitable privacy checks, but pillar should also be set, since it's |
199 | 401 | # mandatory for private snaps. | ||
200 | 402 | self.project = project | ||
201 | 383 | self.private = private | 403 | self.private = private |
202 | 404 | self.information_type = (InformationType.PROPRIETARY if private else | ||
203 | 405 | InformationType.PUBLIC) | ||
204 | 384 | 406 | ||
205 | 385 | self.registrant = registrant | 407 | self.registrant = registrant |
206 | 386 | self.owner = owner | 408 | self.owner = owner |
207 | @@ -408,6 +430,17 @@ class Snap(Storm, WebhookTargetMixin): | |||
208 | 408 | return "<Snap ~%s/+snap/%s>" % (self.owner.name, self.name) | 430 | return "<Snap ~%s/+snap/%s>" % (self.owner.name, self.name) |
209 | 409 | 431 | ||
210 | 410 | @property | 432 | @property |
211 | 433 | def information_type(self): | ||
212 | 434 | if self._information_type is None: | ||
213 | 435 | return (InformationType.PROPRIETARY if self.private | ||
214 | 436 | else InformationType.PUBLIC) | ||
215 | 437 | return self._information_type | ||
216 | 438 | |||
217 | 439 | @information_type.setter | ||
218 | 440 | def information_type(self, information_type): | ||
219 | 441 | self._information_type = information_type | ||
220 | 442 | |||
221 | 443 | @property | ||
222 | 411 | def valid_webhook_event_types(self): | 444 | def valid_webhook_event_types(self): |
223 | 412 | return ["snap:build:0.1"] | 445 | return ["snap:build:0.1"] |
224 | 413 | 446 | ||
225 | @@ -461,6 +494,21 @@ class Snap(Storm, WebhookTargetMixin): | |||
226 | 461 | return None | 494 | return None |
227 | 462 | 495 | ||
228 | 463 | @property | 496 | @property |
229 | 497 | def pillar(self): | ||
230 | 498 | """See `ISnap`.""" | ||
231 | 499 | return self.project | ||
232 | 500 | |||
233 | 501 | @pillar.setter | ||
234 | 502 | def pillar(self, pillar): | ||
235 | 503 | if pillar is None: | ||
236 | 504 | self.project = None | ||
237 | 505 | elif IProduct.providedBy(pillar): | ||
238 | 506 | self.project = pillar | ||
239 | 507 | else: | ||
240 | 508 | raise ValueError( | ||
241 | 509 | 'The pillar of a Snap must be an IProduct instance.') | ||
242 | 510 | |||
243 | 511 | @property | ||
244 | 464 | def available_processors(self): | 512 | def available_processors(self): |
245 | 465 | """See `ISnap`.""" | 513 | """See `ISnap`.""" |
246 | 466 | clauses = [Processor.id == DistroArchSeries.processor_id] | 514 | clauses = [Processor.id == DistroArchSeries.processor_id] |
247 | @@ -1098,7 +1146,7 @@ class SnapSet: | |||
248 | 1098 | processors=None, date_created=DEFAULT, private=False, | 1146 | processors=None, date_created=DEFAULT, private=False, |
249 | 1099 | allow_internet=True, build_source_tarball=False, | 1147 | allow_internet=True, build_source_tarball=False, |
250 | 1100 | store_upload=False, store_series=None, store_name=None, | 1148 | store_upload=False, store_series=None, store_name=None, |
252 | 1101 | store_secrets=None, store_channels=None): | 1149 | store_secrets=None, store_channels=None, project=None): |
253 | 1102 | """See `ISnapSet`.""" | 1150 | """See `ISnapSet`.""" |
254 | 1103 | if not registrant.inTeam(owner): | 1151 | if not registrant.inTeam(owner): |
255 | 1104 | if owner.is_team: | 1152 | if owner.is_team: |
256 | @@ -1150,7 +1198,7 @@ class SnapSet: | |||
257 | 1150 | build_source_tarball=build_source_tarball, | 1198 | build_source_tarball=build_source_tarball, |
258 | 1151 | store_upload=store_upload, store_series=store_series, | 1199 | store_upload=store_upload, store_series=store_series, |
259 | 1152 | store_name=store_name, store_secrets=store_secrets, | 1200 | store_name=store_name, store_secrets=store_secrets, |
261 | 1153 | store_channels=store_channels) | 1201 | store_channels=store_channels, project=project) |
262 | 1154 | store.add(snap) | 1202 | store.add(snap) |
263 | 1155 | 1203 | ||
264 | 1156 | if processors is None: | 1204 | if processors is None: |
265 | @@ -1180,6 +1228,11 @@ class SnapSet: | |||
266 | 1180 | 1228 | ||
267 | 1181 | return True | 1229 | return True |
268 | 1182 | 1230 | ||
269 | 1231 | def isValidInformationType(self, information_type, owner, branch=None, | ||
270 | 1232 | git_ref=None): | ||
271 | 1233 | private = information_type in PRIVATE_INFORMATION_TYPES | ||
272 | 1234 | return self.isValidPrivacy(private, owner, branch, git_ref) | ||
273 | 1235 | |||
274 | 1183 | def _getByName(self, owner, name): | 1236 | def _getByName(self, owner, name): |
275 | 1184 | return IStore(Snap).find( | 1237 | return IStore(Snap).find( |
276 | 1185 | Snap, Snap.owner == owner, Snap.name == name).one() | 1238 | Snap, Snap.owner == owner, Snap.name == name).one() |
277 | diff --git a/lib/lp/snappy/tests/test_snap.py b/lib/lp/snappy/tests/test_snap.py | |||
278 | index 706255a..9c2fda4 100644 | |||
279 | --- a/lib/lp/snappy/tests/test_snap.py | |||
280 | +++ b/lib/lp/snappy/tests/test_snap.py | |||
281 | @@ -1,4 +1,4 @@ | |||
283 | 1 | # Copyright 2015-2020 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2015-2021 Canonical Ltd. This software is licensed under the |
284 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
285 | 3 | 3 | ||
286 | 4 | """Test snap packages.""" | 4 | """Test snap packages.""" |
287 | @@ -131,6 +131,7 @@ from lp.testing import ( | |||
288 | 131 | ANONYMOUS, | 131 | ANONYMOUS, |
289 | 132 | api_url, | 132 | api_url, |
290 | 133 | login, | 133 | login, |
291 | 134 | login_admin, | ||
292 | 134 | logout, | 135 | logout, |
293 | 135 | person_logged_in, | 136 | person_logged_in, |
294 | 136 | record_two_runs, | 137 | record_two_runs, |
295 | @@ -1416,11 +1417,24 @@ class TestSnapSet(TestCaseWithFactory): | |||
296 | 1416 | self.assertEqual(ref.path, snap.git_path) | 1417 | self.assertEqual(ref.path, snap.git_path) |
297 | 1417 | self.assertEqual(ref, snap.git_ref) | 1418 | self.assertEqual(ref, snap.git_ref) |
298 | 1418 | 1419 | ||
299 | 1420 | def test_private_snap_information_type_compatibility(self): | ||
300 | 1421 | login_admin() | ||
301 | 1422 | private_snap = getUtility(ISnapSet).new( | ||
302 | 1423 | private=True, **self.makeSnapComponents()) | ||
303 | 1424 | self.assertEqual( | ||
304 | 1425 | InformationType.PROPRIETARY, private_snap.information_type) | ||
305 | 1426 | |||
306 | 1427 | public_snap = getUtility(ISnapSet).new( | ||
307 | 1428 | private=False, **self.makeSnapComponents()) | ||
308 | 1429 | self.assertEqual( | ||
309 | 1430 | InformationType.PUBLIC, public_snap.information_type) | ||
310 | 1431 | |||
311 | 1419 | def test_private_snap_for_public_sources(self): | 1432 | def test_private_snap_for_public_sources(self): |
312 | 1420 | # Creating private snaps for public sources is allowed. | 1433 | # Creating private snaps for public sources is allowed. |
313 | 1421 | [ref] = self.factory.makeGitRefs() | 1434 | [ref] = self.factory.makeGitRefs() |
314 | 1422 | components = self.makeSnapComponents(git_ref=ref) | 1435 | components = self.makeSnapComponents(git_ref=ref) |
315 | 1423 | components['private'] = True | 1436 | components['private'] = True |
316 | 1437 | components['project'] = self.factory.makeProduct() | ||
317 | 1424 | snap = getUtility(ISnapSet).new(**components) | 1438 | snap = getUtility(ISnapSet).new(**components) |
318 | 1425 | with person_logged_in(components['owner']): | 1439 | with person_logged_in(components['owner']): |
319 | 1426 | self.assertTrue(snap.private) | 1440 | self.assertTrue(snap.private) |
320 | @@ -2646,7 +2660,7 @@ class TestSnapWebservice(TestCaseWithFactory): | |||
321 | 2646 | snap_url, "application/json", json.dumps({"private": False})) | 2660 | snap_url, "application/json", json.dumps({"private": False})) |
322 | 2647 | self.assertEqual(400, response.status) | 2661 | self.assertEqual(400, response.status) |
323 | 2648 | self.assertEqual( | 2662 | self.assertEqual( |
325 | 2649 | b"Snap contains private information and cannot be public.", | 2663 | b"Snap recipe contains private information and cannot be public.", |
326 | 2650 | response.body) | 2664 | response.body) |
327 | 2651 | 2665 | ||
328 | 2652 | def test_cannot_set_private_components_of_public_snap(self): | 2666 | def test_cannot_set_private_components_of_public_snap(self): |
329 | diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py | |||
330 | index c833ddf..9914cc8 100644 | |||
331 | --- a/lib/lp/testing/factory.py | |||
332 | +++ b/lib/lp/testing/factory.py | |||
333 | @@ -2,7 +2,7 @@ | |||
334 | 2 | # NOTE: The first line above must stay first; do not move the copyright | 2 | # NOTE: The first line above must stay first; do not move the copyright |
335 | 3 | # notice to the top. See http://www.python.org/dev/peps/pep-0263/. | 3 | # notice to the top. See http://www.python.org/dev/peps/pep-0263/. |
336 | 4 | # | 4 | # |
338 | 5 | # Copyright 2009-2020 Canonical Ltd. This software is licensed under the | 5 | # Copyright 2009-2021 Canonical Ltd. This software is licensed under the |
339 | 6 | # GNU Affero General Public License version 3 (see the file LICENSE). | 6 | # GNU Affero General Public License version 3 (see the file LICENSE). |
340 | 7 | 7 | ||
341 | 8 | """Testing infrastructure for the Launchpad application. | 8 | """Testing infrastructure for the Launchpad application. |
342 | @@ -4754,7 +4754,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
343 | 4754 | date_created=DEFAULT, private=False, allow_internet=True, | 4754 | date_created=DEFAULT, private=False, allow_internet=True, |
344 | 4755 | build_source_tarball=False, store_upload=False, | 4755 | build_source_tarball=False, store_upload=False, |
345 | 4756 | store_series=None, store_name=None, store_secrets=None, | 4756 | store_series=None, store_name=None, store_secrets=None, |
347 | 4757 | store_channels=None): | 4757 | store_channels=None, project=_DEFAULT): |
348 | 4758 | """Make a new Snap.""" | 4758 | """Make a new Snap.""" |
349 | 4759 | if registrant is None: | 4759 | if registrant is None: |
350 | 4760 | registrant = self.makePerson() | 4760 | registrant = self.makePerson() |
351 | @@ -4772,6 +4772,12 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
352 | 4772 | distribution=distroseries.distribution, owner=owner) | 4772 | distribution=distroseries.distribution, owner=owner) |
353 | 4773 | if auto_build_pocket is None: | 4773 | if auto_build_pocket is None: |
354 | 4774 | auto_build_pocket = PackagePublishingPocket.UPDATES | 4774 | auto_build_pocket = PackagePublishingPocket.UPDATES |
355 | 4775 | if private and project is _DEFAULT: | ||
356 | 4776 | # If we are creating a private snap and didn't explictly set a | ||
357 | 4777 | # pillar for it, we must create a pillar. | ||
358 | 4778 | project = self.makeProduct() | ||
359 | 4779 | if project is _DEFAULT: | ||
360 | 4780 | project = None | ||
361 | 4775 | snap = getUtility(ISnapSet).new( | 4781 | snap = getUtility(ISnapSet).new( |
362 | 4776 | registrant, owner, distroseries, name, | 4782 | registrant, owner, distroseries, name, |
363 | 4777 | require_virtualized=require_virtualized, processors=processors, | 4783 | require_virtualized=require_virtualized, processors=processors, |
364 | @@ -4783,7 +4789,7 @@ class BareLaunchpadObjectFactory(ObjectFactory): | |||
365 | 4783 | build_source_tarball=build_source_tarball, | 4789 | build_source_tarball=build_source_tarball, |
366 | 4784 | store_upload=store_upload, store_series=store_series, | 4790 | store_upload=store_upload, store_series=store_series, |
367 | 4785 | store_name=store_name, store_secrets=store_secrets, | 4791 | store_name=store_name, store_secrets=store_secrets, |
369 | 4786 | store_channels=store_channels) | 4792 | store_channels=store_channels, project=project) |
370 | 4787 | if is_stale is not None: | 4793 | if is_stale is not None: |
371 | 4788 | removeSecurityProxy(snap).is_stale = is_stale | 4794 | removeSecurityProxy(snap).is_stale = is_stale |
372 | 4789 | IStore(snap).flush() | 4795 | IStore(snap).flush() |
Pushed the requested change.
I've also removed the XXX comment about validating (project + private) attributes at model level. It would break old snaps if we've added that validation in the future.