Merge ~twom/launchpad:oci-actually-the-push-rule-models into launchpad:master
- Git
- lp:~twom/launchpad
- oci-actually-the-push-rule-models
- Merge into master
Proposed by
Tom Wardill
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Tom Wardill | ||||
Approved revision: | ee12bf4937e3521298cb00c036b61d42c7172d6b | ||||
Merge reported by: | Otto Co-Pilot | ||||
Merged at revision: | not available | ||||
Proposed branch: | ~twom/launchpad:oci-actually-the-push-rule-models | ||||
Merge into: | launchpad:master | ||||
Prerequisite: | ~twom/launchpad:oci-push-rule-models | ||||
Diff against target: |
514 lines (+316/-22) 12 files modified
lib/lp/_schema_circular_imports.py (+2/-0) lib/lp/oci/configure.zcml (+19/-0) lib/lp/oci/interfaces/ocipushrule.py (+79/-0) lib/lp/oci/interfaces/ocirecipe.py (+7/-0) lib/lp/oci/model/ocipushrule.py (+61/-0) lib/lp/oci/model/ocirecipe.py (+8/-0) lib/lp/oci/tests/helpers.py (+27/-0) lib/lp/oci/tests/test_ocipushrule.py (+70/-0) lib/lp/oci/tests/test_ocirecipe.py (+17/-0) lib/lp/oci/tests/test_ociregistrycredentials.py (+5/-22) lib/lp/security.py (+6/-0) lib/lp/testing/factory.py (+15/-0) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+381035@code.launchpad.net |
Commit message
Add OCIPushRule model
Description of the change
Add the interface, model and tests for OCIPushRule, rules for pushing to an OCI registry.
Alter OCIRecipe to add a convenience lookup method.
To post a comment you must log in.
Revision history for this message
Otto Co-Pilot (otto-copilot) wrote : | # |
- ee12bf4... by Tom Wardill
-
Fix rebase issues
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/_schema_circular_imports.py b/lib/lp/_schema_circular_imports.py |
2 | index a50ddd2..e74605c 100644 |
3 | --- a/lib/lp/_schema_circular_imports.py |
4 | +++ b/lib/lp/_schema_circular_imports.py |
5 | @@ -96,6 +96,7 @@ from lp.hardwaredb.interfaces.hwdb import ( |
6 | IHWSubmissionDevice, |
7 | IHWVendorID, |
8 | ) |
9 | +from lp.oci.interfaces.ocipushrule import IOCIPushRule |
10 | from lp.oci.interfaces.ocirecipe import IOCIRecipe |
11 | from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuild |
12 | from lp.registry.interfaces.commercialsubscription import ( |
13 | @@ -1106,3 +1107,4 @@ patch_collection_property(IOCIProject, 'series', IOCIProjectSeries) |
14 | patch_collection_property(IOCIRecipe, 'builds', IOCIRecipeBuild) |
15 | patch_collection_property(IOCIRecipe, 'completed_builds', IOCIRecipeBuild) |
16 | patch_collection_property(IOCIRecipe, 'pending_builds', IOCIRecipeBuild) |
17 | +patch_collection_property(IOCIRecipe, 'push_rules', IOCIPushRule) |
18 | diff --git a/lib/lp/oci/configure.zcml b/lib/lp/oci/configure.zcml |
19 | index 3246bc0..43ea399 100644 |
20 | --- a/lib/lp/oci/configure.zcml |
21 | +++ b/lib/lp/oci/configure.zcml |
22 | @@ -107,4 +107,23 @@ |
23 | <allow interface="lp.services.crypto.interfaces.IEncryptedContainer"/> |
24 | </securedutility> |
25 | |
26 | + <!-- OCIPushRule --> |
27 | + <class class="lp.oci.model.ocipushrule.OCIPushRule"> |
28 | + <require |
29 | + permission="launchpad.View" |
30 | + interface="lp.oci.interfaces.ocipushrule.IOCIPushRuleView |
31 | + lp.oci.interfaces.ocipushrule.IOCIPushRuleEditableAttributes" /> |
32 | + <require |
33 | + permission="launchpad.Edit" |
34 | + interface="lp.oci.interfaces.ocipushrule.IOCIPushRuleEdit" |
35 | + set_schema="lp.oci.interfaces.ocipushrule.IOCIPushRuleEditableAttributes" /> |
36 | + </class> |
37 | + |
38 | + <securedutility |
39 | + class="lp.oci.model.ocipushrule.OCIPushRuleSet" |
40 | + provides="lp.oci.interfaces.ocipushrule.IOCIPushRuleSet"> |
41 | + <allow |
42 | + interface="lp.oci.interfaces.ocipushrule.IOCIPushRuleSet"/> |
43 | + </securedutility> |
44 | + |
45 | </configure> |
46 | diff --git a/lib/lp/oci/interfaces/ocipushrule.py b/lib/lp/oci/interfaces/ocipushrule.py |
47 | new file mode 100644 |
48 | index 0000000..2351edf |
49 | --- /dev/null |
50 | +++ b/lib/lp/oci/interfaces/ocipushrule.py |
51 | @@ -0,0 +1,79 @@ |
52 | +# Copyright 2020 Canonical Ltd. This software is licensed under the |
53 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
54 | + |
55 | +"""Interfaces for handling credentials for OCI registry actions.""" |
56 | + |
57 | +from __future__ import absolute_import, print_function, unicode_literals |
58 | + |
59 | +__metaclass__ = type |
60 | +__all__ = [ |
61 | + 'IOCIPushRule', |
62 | + 'IOCIPushRuleSet' |
63 | + ] |
64 | + |
65 | +from lazr.restful.fields import Reference |
66 | +from zope.interface import Interface |
67 | +from zope.schema import ( |
68 | + Int, |
69 | + TextLine, |
70 | + ) |
71 | + |
72 | +from lp import _ |
73 | +from lp.oci.interfaces.ocirecipe import IOCIRecipe |
74 | +from lp.oci.interfaces.ociregistrycredentials import IOCIRegistryCredentials |
75 | + |
76 | + |
77 | +class IOCIPushRuleView(Interface): |
78 | + """`IOCIPushRule` methods that require launchpad.View |
79 | + permission. |
80 | + """ |
81 | + |
82 | + id = Int(title=_("ID"), required=True, readonly=True) |
83 | + |
84 | + |
85 | +class IOCIPushRuleEditableAttributes(Interface): |
86 | + """`IOCIPushRule` attributes that can be edited. |
87 | + |
88 | + These attributes need launchpad.View to see, and launchpad.Edit to change. |
89 | + """ |
90 | + |
91 | + recipe = Reference( |
92 | + IOCIRecipe, |
93 | + title=_("OCI recipe"), |
94 | + description=_("The recipe for which the rule is defined."), |
95 | + required=True, |
96 | + readonly=False) |
97 | + |
98 | + registry_credentials = Reference( |
99 | + IOCIRegistryCredentials, |
100 | + title=_("Registry credentials"), |
101 | + description=_("The registry credentials to use."), |
102 | + required=True, |
103 | + readonly=False) |
104 | + |
105 | + image_name = TextLine( |
106 | + title=_("Image name"), |
107 | + description=_("The intended name of the image on the registry."), |
108 | + required=True, |
109 | + readonly=False) |
110 | + |
111 | + |
112 | +class IOCIPushRuleEdit(Interface): |
113 | + """`IOCIPushRule` methods that require launchpad.Edit |
114 | + permission. |
115 | + """ |
116 | + |
117 | + def destroySelf(): |
118 | + """Destroy this push rule.""" |
119 | + |
120 | + |
121 | +class IOCIPushRule(IOCIPushRuleEdit, IOCIPushRuleEditableAttributes, |
122 | + IOCIPushRuleView): |
123 | + """A rule for pushing builds of an OCI recipe to a registry.""" |
124 | + |
125 | + |
126 | +class IOCIPushRuleSet(Interface): |
127 | + """A utility to create and access OCI Push Rules.""" |
128 | + |
129 | + def new(recipe, registry_credentials, image_name): |
130 | + """Create an `IOCIPushRule`.""" |
131 | diff --git a/lib/lp/oci/interfaces/ocirecipe.py b/lib/lp/oci/interfaces/ocirecipe.py |
132 | index d5a7c92..99a0719 100644 |
133 | --- a/lib/lp/oci/interfaces/ocirecipe.py |
134 | +++ b/lib/lp/oci/interfaces/ocirecipe.py |
135 | @@ -140,6 +140,13 @@ class IOCIRecipeView(Interface): |
136 | :return: `IOCIRecipeBuild`. |
137 | """ |
138 | |
139 | + push_rules = CollectionField( |
140 | + title=_("Push rules for this OCI recipe."), |
141 | + description=_("All of the push rules for registry upload " |
142 | + "that apply to this recipe."), |
143 | + # Really IOCIPushRule, patched in _schema_cirular_imports. |
144 | + value_type=Reference(schema=Interface), readonly=True) |
145 | + |
146 | |
147 | class IOCIRecipeEdit(IWebhookTarget): |
148 | """`IOCIRecipe` methods that require launchpad.Edit permission.""" |
149 | diff --git a/lib/lp/oci/model/ocipushrule.py b/lib/lp/oci/model/ocipushrule.py |
150 | new file mode 100644 |
151 | index 0000000..84e4a1c |
152 | --- /dev/null |
153 | +++ b/lib/lp/oci/model/ocipushrule.py |
154 | @@ -0,0 +1,61 @@ |
155 | +# Copyright 2020 Canonical Ltd. This software is licensed under the |
156 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
157 | + |
158 | +"""Registry credentials for use by an `OCIPushRule`.""" |
159 | + |
160 | +from __future__ import absolute_import, print_function, unicode_literals |
161 | + |
162 | +__metaclass__ = type |
163 | +__all__ = [ |
164 | + 'OCIPushRule', |
165 | + 'OCIPushRuleSet', |
166 | + ] |
167 | + |
168 | +from storm.locals import ( |
169 | + Int, |
170 | + Reference, |
171 | + Storm, |
172 | + Unicode, |
173 | + ) |
174 | +from zope.interface import implementer |
175 | + |
176 | +from lp.oci.interfaces.ocipushrule import ( |
177 | + IOCIPushRule, |
178 | + IOCIPushRuleSet, |
179 | + ) |
180 | +from lp.services.database.interfaces import IStore |
181 | + |
182 | + |
183 | +@implementer(IOCIPushRule) |
184 | +class OCIPushRule(Storm): |
185 | + |
186 | + __storm_table__ = 'OCIPushRule' |
187 | + |
188 | + id = Int(primary=True) |
189 | + |
190 | + recipe_id = Int(name='recipe', allow_none=False) |
191 | + recipe = Reference(recipe_id, 'OCIRecipe.id') |
192 | + |
193 | + registry_credentials_id = Int( |
194 | + name='registry_credentials', allow_none=False) |
195 | + registry_credentials = Reference( |
196 | + registry_credentials_id, 'OCIRegistryCredentials.id') |
197 | + |
198 | + image_name = Unicode(name="image_name", allow_none=False) |
199 | + |
200 | + def __init__(self, recipe, registry_credentials, image_name): |
201 | + self.recipe = recipe |
202 | + self.registry_credentials = registry_credentials |
203 | + self.image_name = image_name |
204 | + |
205 | + def destroySelf(self): |
206 | + """See `IOCIPushRule`.""" |
207 | + IStore(OCIPushRule).get(self.id).remove() |
208 | + |
209 | + |
210 | +@implementer(IOCIPushRuleSet) |
211 | +class OCIPushRuleSet: |
212 | + |
213 | + def new(self, recipe, registry_credentials, image_name): |
214 | + """See `IOCIPushRuleSet`.""" |
215 | + return OCIPushRule(recipe, registry_credentials, image_name) |
216 | diff --git a/lib/lp/oci/model/ocirecipe.py b/lib/lp/oci/model/ocirecipe.py |
217 | index 9a4bd8e..68f5e92 100644 |
218 | --- a/lib/lp/oci/model/ocirecipe.py |
219 | +++ b/lib/lp/oci/model/ocirecipe.py |
220 | @@ -46,6 +46,7 @@ from lp.oci.interfaces.ocirecipe import ( |
221 | OCIRecipeNotOwner, |
222 | ) |
223 | from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet |
224 | +from lp.oci.model.ocipushrule import OCIPushRule |
225 | from lp.oci.model.ocirecipebuild import OCIRecipeBuild |
226 | from lp.registry.interfaces.person import IPersonSet |
227 | from lp.services.database.constants import ( |
228 | @@ -189,6 +190,13 @@ class OCIRecipe(Storm, WebhookTargetMixin): |
229 | return build |
230 | |
231 | @property |
232 | + def push_rules(self): |
233 | + rules = IStore(self).find( |
234 | + OCIPushRule, |
235 | + OCIPushRule.recipe == self.id) |
236 | + return rules |
237 | + |
238 | + @property |
239 | def _pending_states(self): |
240 | """All the build states we consider pending (non-final).""" |
241 | return [ |
242 | diff --git a/lib/lp/oci/tests/helpers.py b/lib/lp/oci/tests/helpers.py |
243 | new file mode 100644 |
244 | index 0000000..dc0e82f |
245 | --- /dev/null |
246 | +++ b/lib/lp/oci/tests/helpers.py |
247 | @@ -0,0 +1,27 @@ |
248 | +# Copyright 2020 Canonical Ltd. This software is licensed under the |
249 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
250 | + |
251 | +"""Helper methods and mixins for OCI tests.""" |
252 | + |
253 | +from __future__ import absolute_import, print_function, unicode_literals |
254 | + |
255 | +__metaclass__ = type |
256 | +__all__ = [] |
257 | + |
258 | +import base64 |
259 | + |
260 | +from nacl.public import PrivateKey |
261 | + |
262 | + |
263 | +class OCIConfigHelperMixin: |
264 | + |
265 | + def setConfig(self): |
266 | + self.private_key = PrivateKey.generate() |
267 | + self.pushConfig( |
268 | + "oci", |
269 | + registry_secrets_public_key=base64.b64encode( |
270 | + bytes(self.private_key.public_key)).decode("UTF-8")) |
271 | + self.pushConfig( |
272 | + "oci", |
273 | + registry_secrets_private_key=base64.b64encode( |
274 | + bytes(self.private_key)).decode("UTF-8")) |
275 | diff --git a/lib/lp/oci/tests/test_ocipushrule.py b/lib/lp/oci/tests/test_ocipushrule.py |
276 | new file mode 100644 |
277 | index 0000000..90ddd96 |
278 | --- /dev/null |
279 | +++ b/lib/lp/oci/tests/test_ocipushrule.py |
280 | @@ -0,0 +1,70 @@ |
281 | +# Copyright 2020 Canonical Ltd. This software is licensed under the |
282 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
283 | + |
284 | +"""Tests for OCI registry push rules.""" |
285 | + |
286 | +from __future__ import absolute_import, print_function, unicode_literals |
287 | + |
288 | +from testtools.matchers import MatchesStructure |
289 | +from zope.component import getUtility |
290 | + |
291 | +from lp.oci.interfaces.ocipushrule import ( |
292 | + IOCIPushRule, |
293 | + IOCIPushRuleSet, |
294 | + ) |
295 | +from lp.oci.tests.helpers import OCIConfigHelperMixin |
296 | +from lp.testing import ( |
297 | + person_logged_in, |
298 | + TestCaseWithFactory, |
299 | + ) |
300 | +from lp.testing.layers import LaunchpadZopelessLayer |
301 | + |
302 | + |
303 | +class TestOCIPushRule(OCIConfigHelperMixin, TestCaseWithFactory): |
304 | + |
305 | + layer = LaunchpadZopelessLayer |
306 | + |
307 | + def setUp(self): |
308 | + super(TestOCIPushRule, self).setUp() |
309 | + self.setConfig() |
310 | + |
311 | + def test_implements_interface(self): |
312 | + push_rule = self.factory.makeOCIPushRule() |
313 | + self.assertProvides(push_rule, IOCIPushRule) |
314 | + |
315 | + def test_change_attribute(self): |
316 | + push_rule = self.factory.makeOCIPushRule() |
317 | + with person_logged_in(push_rule.recipe.owner): |
318 | + push_rule.image_name = 'new image name' |
319 | + |
320 | + found_rule = push_rule.recipe.push_rules[0] |
321 | + self.assertEqual(found_rule.image_name, 'new image name') |
322 | + |
323 | + |
324 | +class TestOCIPushRuleSet(OCIConfigHelperMixin, TestCaseWithFactory): |
325 | + |
326 | + layer = LaunchpadZopelessLayer |
327 | + |
328 | + def setUp(self): |
329 | + super(TestOCIPushRuleSet, self).setUp() |
330 | + self.setConfig() |
331 | + |
332 | + def test_implements_interface(self): |
333 | + push_rule_set = getUtility(IOCIPushRuleSet) |
334 | + self.assertProvides(push_rule_set, IOCIPushRuleSet) |
335 | + |
336 | + def test_new(self): |
337 | + recipe = self.factory.makeOCIRecipe() |
338 | + registry_credentials = self.factory.makeOCIRegistryCredentials() |
339 | + image_name = self.factory.getUniqueUnicode() |
340 | + push_rule = getUtility(IOCIPushRuleSet).new( |
341 | + recipe=recipe, |
342 | + registry_credentials=registry_credentials, |
343 | + image_name=image_name) |
344 | + |
345 | + self.assertThat( |
346 | + push_rule, |
347 | + MatchesStructure.byEquality( |
348 | + recipe=recipe, |
349 | + registry_credentials=registry_credentials, |
350 | + image_name=image_name)) |
351 | diff --git a/lib/lp/oci/tests/test_ocirecipe.py b/lib/lp/oci/tests/test_ocirecipe.py |
352 | index 379d0ce..3c60e7e 100644 |
353 | --- a/lib/lp/oci/tests/test_ocirecipe.py |
354 | +++ b/lib/lp/oci/tests/test_ocirecipe.py |
355 | @@ -5,10 +5,12 @@ |
356 | |
357 | from __future__ import absolute_import, print_function, unicode_literals |
358 | |
359 | +import base64 |
360 | import json |
361 | |
362 | from fixtures import FakeLogger |
363 | from six import string_types |
364 | +from nacl.public import PrivateKey |
365 | from storm.exceptions import LostObjectError |
366 | from testtools.matchers import ( |
367 | ContainsDict, |
368 | @@ -192,6 +194,21 @@ class TestOCIRecipe(TestCaseWithFactory): |
369 | [fullybuilt, instacancelled], list(oci_recipe.completed_builds)) |
370 | self.assertEqual([], list(oci_recipe.pending_builds)) |
371 | |
372 | + def test_push_rules(self): |
373 | + self.pushConfig( |
374 | + "oci", |
375 | + registry_secrets_public_key=base64.b64encode( |
376 | + bytes(PrivateKey.generate().public_key)).decode("UTF-8")) |
377 | + oci_recipe = self.factory.makeOCIRecipe() |
378 | + for _ in range(3): |
379 | + self.factory.makeOCIPushRule(recipe=oci_recipe) |
380 | + # Add some others |
381 | + for _ in range(3): |
382 | + self.factory.makeOCIPushRule() |
383 | + |
384 | + for rule in oci_recipe.push_rules: |
385 | + self.assertEqual(rule.recipe, oci_recipe) |
386 | + |
387 | |
388 | class TestOCIRecipeSet(TestCaseWithFactory): |
389 | |
390 | diff --git a/lib/lp/oci/tests/test_ociregistrycredentials.py b/lib/lp/oci/tests/test_ociregistrycredentials.py |
391 | index 283ff0c..fba4b1a 100644 |
392 | --- a/lib/lp/oci/tests/test_ociregistrycredentials.py |
393 | +++ b/lib/lp/oci/tests/test_ociregistrycredentials.py |
394 | @@ -5,10 +5,8 @@ |
395 | |
396 | from __future__ import absolute_import, print_function, unicode_literals |
397 | |
398 | -import base64 |
399 | import json |
400 | |
401 | -from nacl.public import PrivateKey |
402 | from testtools.matchers import ( |
403 | AfterPreprocessing, |
404 | Equals, |
405 | @@ -21,6 +19,7 @@ from lp.oci.interfaces.ociregistrycredentials import ( |
406 | IOCIRegistryCredentials, |
407 | IOCIRegistryCredentialsSet, |
408 | ) |
409 | +from lp.oci.tests.helpers import OCIConfigHelperMixin |
410 | from lp.services.crypto.interfaces import IEncryptedContainer |
411 | from lp.testing import ( |
412 | person_logged_in, |
413 | @@ -29,21 +28,13 @@ from lp.testing import ( |
414 | from lp.testing.layers import LaunchpadZopelessLayer |
415 | |
416 | |
417 | -class TestOCIRegistryCredentials(TestCaseWithFactory): |
418 | +class TestOCIRegistryCredentials(OCIConfigHelperMixin, TestCaseWithFactory): |
419 | |
420 | layer = LaunchpadZopelessLayer |
421 | |
422 | def setUp(self): |
423 | super(TestOCIRegistryCredentials, self).setUp() |
424 | - self.private_key = PrivateKey.generate() |
425 | - self.pushConfig( |
426 | - "oci", |
427 | - registry_secrets_public_key=base64.b64encode( |
428 | - bytes(self.private_key.public_key)).decode("UTF-8")) |
429 | - self.pushConfig( |
430 | - "oci", |
431 | - registry_secrets_private_key=base64.b64encode( |
432 | - bytes(self.private_key)).decode("UTF-8")) |
433 | + self.setConfig() |
434 | |
435 | def test_implements_interface(self): |
436 | oci_credentials = getUtility(IOCIRegistryCredentialsSet).new( |
437 | @@ -77,21 +68,13 @@ class TestOCIRegistryCredentials(TestCaseWithFactory): |
438 | })) |
439 | |
440 | |
441 | -class TestOCIRegistryCredentialsSet(TestCaseWithFactory): |
442 | +class TestOCIRegistryCredentialsSet(OCIConfigHelperMixin, TestCaseWithFactory): |
443 | |
444 | layer = LaunchpadZopelessLayer |
445 | |
446 | def setUp(self): |
447 | super(TestOCIRegistryCredentialsSet, self).setUp() |
448 | - self.private_key = PrivateKey.generate() |
449 | - self.pushConfig( |
450 | - "oci", |
451 | - registry_secrets_public_key=base64.b64encode( |
452 | - bytes(self.private_key.public_key)).decode("UTF-8")) |
453 | - self.pushConfig( |
454 | - "oci", |
455 | - registry_secrets_private_key=base64.b64encode( |
456 | - bytes(self.private_key)).decode("UTF-8")) |
457 | + self.setConfig() |
458 | |
459 | def test_implements_interface(self): |
460 | credentials_set = getUtility(IOCIRegistryCredentialsSet) |
461 | diff --git a/lib/lp/security.py b/lib/lp/security.py |
462 | index 10aad34..a44393a 100644 |
463 | --- a/lib/lp/security.py |
464 | +++ b/lib/lp/security.py |
465 | @@ -112,6 +112,7 @@ from lp.hardwaredb.interfaces.hwdb import ( |
466 | IHWSubmissionDevice, |
467 | IHWVendorID, |
468 | ) |
469 | +from lp.oci.interfaces.ocipushrule import IOCIPushRule |
470 | from lp.oci.interfaces.ocirecipe import IOCIRecipe |
471 | from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuild |
472 | from lp.oci.interfaces.ociregistrycredentials import IOCIRegistryCredentials |
473 | @@ -3521,3 +3522,8 @@ class ViewOCIRegistryCredentials(AuthorizationBase): |
474 | return ( |
475 | user.isOwner(self.obj) or |
476 | user.in_admin) |
477 | + |
478 | + |
479 | +class ViewOCIPushRule(AnonymousAuthorization): |
480 | + """Anyone can view an `IOCIPushRule`.""" |
481 | + usedfor = IOCIPushRule |
482 | diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py |
483 | index c66175d..8e0f1c1 100644 |
484 | --- a/lib/lp/testing/factory.py |
485 | +++ b/lib/lp/testing/factory.py |
486 | @@ -157,6 +157,7 @@ from lp.hardwaredb.interfaces.hwdb import ( |
487 | IHWSubmissionDeviceSet, |
488 | IHWSubmissionSet, |
489 | ) |
490 | +from lp.oci.interfaces.ocipushrule import IOCIPushRuleSet |
491 | from lp.oci.interfaces.ocirecipe import IOCIRecipeSet |
492 | from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuildSet |
493 | from lp.oci.interfaces.ociregistrycredentials import IOCIRegistryCredentialsSet |
494 | @@ -5051,6 +5052,20 @@ class BareLaunchpadObjectFactory(ObjectFactory): |
495 | url=url, |
496 | credentials=credentials) |
497 | |
498 | + def makeOCIPushRule(self, recipe=None, registry_credentials=None, |
499 | + image_name=None): |
500 | + """Make a new OCIPushRule.""" |
501 | + if recipe is None: |
502 | + recipe = self.makeOCIRecipe() |
503 | + if registry_credentials is None: |
504 | + registry_credentials = self.makeOCIRegistryCredentials() |
505 | + if image_name is None: |
506 | + image_name = self.getUniqueUnicode(u"oci-image-name") |
507 | + return getUtility(IOCIPushRuleSet).new( |
508 | + recipe=recipe, |
509 | + registry_credentials=registry_credentials, |
510 | + image_name=image_name) |
511 | + |
512 | |
513 | # Some factory methods return simple Python types. We don't add |
514 | # security wrappers for them, as well as for objects created by |
This can't land until master has database/ schema/ patch-2210- 08-6.sql.