Merge lp:~cjwatson/launchpad/snap-build-channels into lp:launchpad

Proposed by Colin Watson on 2018-02-08
Status: Merged
Merged at revision: 18579
Proposed branch: lp:~cjwatson/launchpad/snap-build-channels
Merge into: lp:launchpad
Diff against target: 512 lines (+177/-25)
9 files modified
lib/lp/snappy/interfaces/snap.py (+22/-3)
lib/lp/snappy/interfaces/snapbuild.py (+9/-1)
lib/lp/snappy/model/snap.py (+22/-10)
lib/lp/snappy/model/snapbuild.py (+7/-3)
lib/lp/snappy/model/snapbuildbehaviour.py (+3/-1)
lib/lp/snappy/tests/test_snap.py (+93/-1)
lib/lp/snappy/tests/test_snapbuild.py (+1/-0)
lib/lp/snappy/tests/test_snapbuildbehaviour.py (+12/-1)
lib/lp/testing/factory.py (+8/-5)
To merge this branch: bzr merge lp:~cjwatson/launchpad/snap-build-channels
Reviewer Review Type Date Requested Status
William Grant code 2018-02-08 Approve on 2018-03-08
Review via email: mp+337360@code.launchpad.net

Commit message

Add the ability to select source channels when building snaps.

Description of the change

This will let us run snap builds where snapcraft is installed as a snap. It depends on https://code.launchpad.net/~cjwatson/launchpad-buildd/snapception/+merge/337126 and https://code.launchpad.net/~cjwatson/launchpad/db-snap-build-channels/+merge/337361.

We'll probably also want a feature flag to change the default, but that can be added a little later, once the basic mechanism works.

To post a comment you must log in.
William Grant (wgrant) :
review: Approve (code)
Colin Watson (cjwatson) :
18546. By Colin Watson on 2018-03-22

Sync up ISnapView.requestBuilds channels parameter and ISnapBuildView.channels.

18547. By Colin Watson on 2018-03-22

Clarify ISnapBuildView.channels title.

18548. By Colin Watson on 2018-03-22

Simplify using IsDistinctFrom.

18549. By Colin Watson on 2018-03-22

Merge devel.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/snappy/interfaces/snap.py'
2--- lib/lp/snappy/interfaces/snap.py 2017-08-22 11:36:30 +0000
3+++ lib/lp/snappy/interfaces/snap.py 2018-03-22 16:49:25 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
6+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Snap package interfaces."""
10@@ -63,6 +63,7 @@
11 Bool,
12 Choice,
13 Datetime,
14+ Dict,
15 Int,
16 List,
17 Text,
18@@ -281,17 +282,27 @@
19 @operation_parameters(
20 archive=Reference(schema=IArchive),
21 distro_arch_series=Reference(schema=IDistroArchSeries),
22- pocket=Choice(vocabulary=PackagePublishingPocket))
23+ pocket=Choice(vocabulary=PackagePublishingPocket),
24+ channels=Dict(
25+ title=_("Source snap channels to use for this build."),
26+ description=_(
27+ "A dictionary mapping snap names to channels to use for this "
28+ "build. Currently only 'core' and 'snapcraft' keys are "
29+ "supported."),
30+ key_type=TextLine(), required=False))
31 # Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
32 @export_factory_operation(Interface, [])
33 @operation_for_version("devel")
34- def requestBuild(requester, archive, distro_arch_series, pocket):
35+ def requestBuild(requester, archive, distro_arch_series, pocket,
36+ channels=None):
37 """Request that the snap package be built.
38
39 :param requester: The person requesting the build.
40 :param archive: The IArchive to associate the build with.
41 :param distro_arch_series: The architecture to build for.
42 :param pocket: The pocket that should be targeted.
43+ :param channels: A dictionary mapping snap names to channels to use
44+ for this build.
45 :return: `ISnapBuild`.
46 """
47
48@@ -509,6 +520,14 @@
49 "The package stream within the source distribution series to use "
50 "when building the snap package.")))
51
52+ auto_build_channels = exported(Dict(
53+ title=_("Source snap channels for automatic builds"),
54+ key_type=TextLine(), required=False, readonly=False,
55+ description=_(
56+ "A dictionary mapping snap names to channels to use when building "
57+ "this snap package. Currently only 'core' and 'snapcraft' keys "
58+ "are supported.")))
59+
60 is_stale = Bool(
61 title=_("Snap package is stale and is due to be rebuilt."),
62 required=True, readonly=False)
63
64=== modified file 'lib/lp/snappy/interfaces/snapbuild.py'
65--- lib/lp/snappy/interfaces/snapbuild.py 2018-02-16 21:48:03 +0000
66+++ lib/lp/snappy/interfaces/snapbuild.py 2018-03-22 16:49:25 +0000
67@@ -1,4 +1,4 @@
68-# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
69+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
70 # GNU Affero General Public License version 3 (see the file LICENSE).
71
72 """Snap package build interfaces."""
73@@ -149,6 +149,14 @@
74 title=_("The pocket for which to build."),
75 vocabulary=PackagePublishingPocket, required=True, readonly=True))
76
77+ channels = exported(Dict(
78+ title=_("Source snap channels to use for this build."),
79+ description=_(
80+ "A dictionary mapping snap names to channels to use for this "
81+ "build. Currently only 'core' and 'snapcraft' keys are "
82+ "supported."),
83+ key_type=TextLine()))
84+
85 virtualized = Bool(
86 title=_("If True, this build is virtualized."), readonly=True)
87
88
89=== modified file 'lib/lp/snappy/model/snap.py'
90--- lib/lp/snappy/model/snap.py 2017-11-10 11:23:27 +0000
91+++ lib/lp/snappy/model/snap.py 2018-03-22 16:49:25 +0000
92@@ -1,4 +1,4 @@
93-# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
94+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
95 # GNU Affero General Public License version 3 (see the file LICENSE).
96
97 __metaclass__ = type
98@@ -94,6 +94,7 @@
99 )
100 from lp.services.database.stormexpr import (
101 Greatest,
102+ IsDistinctFrom,
103 NullsLast,
104 )
105 from lp.services.features import getFeatureFlag
106@@ -189,6 +190,8 @@
107
108 auto_build_pocket = DBEnum(enum=PackagePublishingPocket, allow_none=True)
109
110+ auto_build_channels = JSON('auto_build_channels', allow_none=True)
111+
112 is_stale = Bool(name='is_stale', allow_none=False)
113
114 require_virtualized = Bool(name='require_virtualized')
115@@ -209,9 +212,10 @@
116 def __init__(self, registrant, owner, distro_series, name,
117 description=None, branch=None, git_ref=None, auto_build=False,
118 auto_build_archive=None, auto_build_pocket=None,
119- require_virtualized=True, date_created=DEFAULT,
120- private=False, store_upload=False, store_series=None,
121- store_name=None, store_secrets=None, store_channels=None):
122+ auto_build_channels=None, require_virtualized=True,
123+ date_created=DEFAULT, private=False, store_upload=False,
124+ store_series=None, store_name=None, store_secrets=None,
125+ store_channels=None):
126 """Construct a `Snap`."""
127 super(Snap, self).__init__()
128 self.registrant = registrant
129@@ -224,6 +228,7 @@
130 self.auto_build = auto_build
131 self.auto_build_archive = auto_build_archive
132 self.auto_build_pocket = auto_build_pocket
133+ self.auto_build_channels = auto_build_channels
134 self.require_virtualized = require_virtualized
135 self.date_created = date_created
136 self.date_last_modified = date_created
137@@ -439,7 +444,8 @@
138 return False
139 return True
140
141- def requestBuild(self, requester, archive, distro_arch_series, pocket):
142+ def requestBuild(self, requester, archive, distro_arch_series, pocket,
143+ channels=None):
144 """See `ISnap`."""
145 if not requester.inTeam(self.owner):
146 raise SnapNotOwner(
147@@ -459,12 +465,14 @@
148 SnapBuild.archive_id == archive.id,
149 SnapBuild.distro_arch_series_id == distro_arch_series.id,
150 SnapBuild.pocket == pocket,
151+ SnapBuild.channels == channels,
152 SnapBuild.status == BuildStatus.NEEDSBUILD)
153 if pending.any() is not None:
154 raise SnapBuildAlreadyPending
155
156 build = getUtility(ISnapBuildSet).new(
157- requester, self, archive, distro_arch_series, pocket)
158+ requester, self, archive, distro_arch_series, pocket,
159+ channels=channels)
160 build.queueBuild()
161 return build
162
163@@ -484,7 +492,7 @@
164 try:
165 build = self.requestBuild(
166 self.owner, self.auto_build_archive, arch,
167- self.auto_build_pocket)
168+ self.auto_build_pocket, self.auto_build_channels)
169 if logger is not None:
170 logger.debug(
171 " - %s/%s/%s: Build requested.",
172@@ -659,9 +667,10 @@
173 branch=None, git_repository=None, git_repository_url=None,
174 git_path=None, git_ref=None, auto_build=False,
175 auto_build_archive=None, auto_build_pocket=None,
176- require_virtualized=True, processors=None, date_created=DEFAULT,
177- private=False, store_upload=False, store_series=None,
178- store_name=None, store_secrets=None, store_channels=None):
179+ auto_build_channels=None, require_virtualized=True,
180+ processors=None, date_created=DEFAULT, private=False,
181+ store_upload=False, store_series=None, store_name=None,
182+ store_secrets=None, store_channels=None):
183 """See `ISnapSet`."""
184 if not registrant.inTeam(owner):
185 if owner.is_team:
186@@ -702,6 +711,7 @@
187 branch=branch, git_ref=git_ref, auto_build=auto_build,
188 auto_build_archive=auto_build_archive,
189 auto_build_pocket=auto_build_pocket,
190+ auto_build_channels=auto_build_channels,
191 require_virtualized=require_virtualized, date_created=date_created,
192 private=private, store_upload=store_upload,
193 store_series=store_series, store_name=store_name,
194@@ -917,6 +927,8 @@
195 SnapBuild.snap_id == Snap.id,
196 SnapBuild.archive_id == Snap.auto_build_archive_id,
197 SnapBuild.pocket == Snap.auto_build_pocket,
198+ Not(IsDistinctFrom(
199+ SnapBuild.channels, Snap.auto_build_channels)),
200 # We only want Snaps that haven't had an automatic
201 # SnapBuild dispatched for them recently.
202 SnapBuild.date_created >= threshold_date)),
203
204=== modified file 'lib/lp/snappy/model/snapbuild.py'
205--- lib/lp/snappy/model/snapbuild.py 2018-02-16 21:48:03 +0000
206+++ lib/lp/snappy/model/snapbuild.py 2018-03-22 16:49:25 +0000
207@@ -22,6 +22,7 @@
208 DateTime,
209 Desc,
210 Int,
211+ JSON,
212 Reference,
213 Select,
214 SQL,
215@@ -142,6 +143,8 @@
216
217 pocket = DBEnum(enum=PackagePublishingPocket, allow_none=False)
218
219+ channels = JSON('channels', allow_none=True)
220+
221 processor_id = Int(name='processor', allow_none=False)
222 processor = Reference(processor_id, 'Processor.id')
223 virtualized = Bool(name='virtualized')
224@@ -171,7 +174,7 @@
225 failure_count = Int(name='failure_count', allow_none=False)
226
227 def __init__(self, build_farm_job, requester, snap, archive,
228- distro_arch_series, pocket, processor, virtualized,
229+ distro_arch_series, pocket, channels, processor, virtualized,
230 date_created):
231 """Construct a `SnapBuild`."""
232 super(SnapBuild, self).__init__()
233@@ -181,6 +184,7 @@
234 self.archive = archive
235 self.distro_arch_series = distro_arch_series
236 self.pocket = pocket
237+ self.channels = channels
238 self.processor = processor
239 self.virtualized = virtualized
240 self.date_created = date_created
241@@ -484,7 +488,7 @@
242 class SnapBuildSet(SpecificBuildFarmJobSourceMixin):
243
244 def new(self, requester, snap, archive, distro_arch_series, pocket,
245- date_created=DEFAULT):
246+ channels=None, date_created=DEFAULT):
247 """See `ISnapBuildSet`."""
248 store = IMasterStore(SnapBuild)
249 build_farm_job = getUtility(IBuildFarmJobSource).new(
250@@ -492,7 +496,7 @@
251 archive)
252 snapbuild = SnapBuild(
253 build_farm_job, requester, snap, archive, distro_arch_series,
254- pocket, distro_arch_series.processor,
255+ pocket, channels, distro_arch_series.processor,
256 not distro_arch_series.processor.supports_nonvirtualized
257 or snap.require_virtualized or archive.require_virtualized,
258 date_created)
259
260=== modified file 'lib/lp/snappy/model/snapbuildbehaviour.py'
261--- lib/lp/snappy/model/snapbuildbehaviour.py 2018-03-01 17:36:31 +0000
262+++ lib/lp/snappy/model/snapbuildbehaviour.py 2018-03-22 16:49:25 +0000
263@@ -1,4 +1,4 @@
264-# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
265+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
266 # GNU Affero General Public License version 3 (see the file LICENSE).
267
268 """An `IBuildFarmJobBehaviour` for `SnapBuild`.
269@@ -109,6 +109,8 @@
270 logger=logger))
271 args["archive_private"] = build.archive.private
272 args["build_url"] = canonical_url(build)
273+ if build.channels is not None:
274+ args["channels"] = build.channels
275 if build.snap.branch is not None:
276 args["branch"] = build.snap.branch.bzr_identity
277 elif build.snap.git_ref is not None:
278
279=== modified file 'lib/lp/snappy/tests/test_snap.py'
280--- lib/lp/snappy/tests/test_snap.py 2018-01-23 18:54:30 +0000
281+++ lib/lp/snappy/tests/test_snap.py 2018-03-22 16:49:25 +0000
282@@ -54,6 +54,7 @@
283 ONE_DAY_AGO,
284 UTC_NOW,
285 )
286+from lp.services.database.interfaces import IStore
287 from lp.services.database.sqlbase import flush_database_caches
288 from lp.services.features.testing import (
289 FeatureFixture,
290@@ -188,6 +189,7 @@
291 self.assertEqual(snap.distro_series.main_archive, build.archive)
292 self.assertEqual(distroarchseries, build.distro_arch_series)
293 self.assertEqual(PackagePublishingPocket.UPDATES, build.pocket)
294+ self.assertIsNone(build.channels)
295 self.assertEqual(BuildStatus.NEEDSBUILD, build.status)
296 store = Store.of(build)
297 store.flush()
298@@ -232,6 +234,18 @@
299 queue_record.score()
300 self.assertEqual(2610, queue_record.lastscore)
301
302+ def test_requestBuild_channels(self):
303+ # requestBuild can select non-default channels.
304+ processor = self.factory.makeProcessor(supports_virtualized=True)
305+ distroarchseries = self.makeBuildableDistroArchSeries(
306+ processor=processor)
307+ snap = self.factory.makeSnap(
308+ distroseries=distroarchseries.distroseries, processors=[processor])
309+ build = snap.requestBuild(
310+ snap.owner, snap.distro_series.main_archive, distroarchseries,
311+ PackagePublishingPocket.UPDATES, channels={"snapcraft": "edge"})
312+ self.assertEqual({"snapcraft": "edge"}, build.channels)
313+
314 def test_requestBuild_rejects_repeats(self):
315 # requestBuild refuses if there is already a pending build.
316 distroseries = self.factory.makeDistroSeries()
317@@ -355,10 +369,36 @@
318 with person_logged_in(snap.owner):
319 builds = snap.requestAutoBuilds()
320 self.assertThat(builds, MatchesSetwise(
321+ *(MatchesStructure(
322+ requester=Equals(snap.owner), snap=Equals(snap),
323+ archive=Equals(archive), distro_arch_series=Equals(das),
324+ pocket=Equals(PackagePublishingPocket.PROPOSED),
325+ channels=Is(None))
326+ for das in dases[:2])))
327+
328+ def test_requestAutoBuilds_channels(self):
329+ # requestAutoBuilds honours Snap.auto_build_channels.
330+ distroseries = self.factory.makeDistroSeries()
331+ dases = []
332+ for _ in range(3):
333+ processor = self.factory.makeProcessor(supports_virtualized=True)
334+ dases.append(self.makeBuildableDistroArchSeries(
335+ distroseries=distroseries, processor=processor))
336+ archive = self.factory.makeArchive()
337+ snap = self.factory.makeSnap(
338+ distroseries=distroseries,
339+ processors=[das.processor for das in dases[:2]],
340+ auto_build_archive=archive,
341+ auto_build_pocket=PackagePublishingPocket.PROPOSED,
342+ auto_build_channels={"snapcraft": "edge"})
343+ with person_logged_in(snap.owner):
344+ builds = snap.requestAutoBuilds()
345+ self.assertThat(builds, MatchesSetwise(
346 *(MatchesStructure.byEquality(
347 requester=snap.owner, snap=snap, archive=archive,
348 distro_arch_series=das,
349- pocket=PackagePublishingPocket.PROPOSED)
350+ pocket=PackagePublishingPocket.PROPOSED,
351+ channels={"snapcraft": "edge"})
352 for das in dases[:2])))
353
354 def test_getBuilds(self):
355@@ -610,6 +650,7 @@
356 self.assertFalse(snap.auto_build)
357 self.assertIsNone(snap.auto_build_archive)
358 self.assertIsNone(snap.auto_build_pocket)
359+ self.assertIsNone(snap.auto_build_channels)
360 self.assertTrue(snap.require_virtualized)
361 self.assertFalse(snap.private)
362
363@@ -630,6 +671,7 @@
364 self.assertFalse(snap.auto_build)
365 self.assertIsNone(snap.auto_build_archive)
366 self.assertIsNone(snap.auto_build_pocket)
367+ self.assertIsNone(snap.auto_build_channels)
368 self.assertTrue(snap.require_virtualized)
369 self.assertFalse(snap.private)
370
371@@ -1008,6 +1050,56 @@
372 self.assertEqual([], builds)
373 self.assertEqual([], logger.getLogBuffer().splitlines())
374
375+ def test_makeAutoBuilds_skips_if_built_recently_matching_channels(self):
376+ # ISnapSet.makeAutoBuilds only considers recently-requested builds
377+ # to match a snap if they match its auto_build_channels.
378+ das1, snap1 = self.makeAutoBuildableSnap(is_stale=True)
379+ das2, snap2 = self.makeAutoBuildableSnap(
380+ is_stale=True, auto_build_channels={"snapcraft": "edge"})
381+ # Create some builds with mismatched channels.
382+ self.factory.makeSnapBuild(
383+ requester=snap1.owner, snap=snap1,
384+ archive=snap1.auto_build_archive, distroarchseries=das1,
385+ channels={"snapcraft": "edge"})
386+ self.factory.makeSnapBuild(
387+ requester=snap2.owner, snap=snap2,
388+ archive=snap2.auto_build_archive, distroarchseries=das2,
389+ channels={"snapcraft": "stable"})
390+
391+ logger = BufferLogger()
392+ builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
393+ self.assertThat(builds, MatchesSetwise(
394+ MatchesStructure(
395+ requester=Equals(snap1.owner), snap=Equals(snap1),
396+ distro_arch_series=Equals(das1), channels=Is(None),
397+ status=Equals(BuildStatus.NEEDSBUILD)),
398+ MatchesStructure.byEquality(
399+ requester=snap2.owner, snap=snap2, distro_arch_series=das2,
400+ channels={"snapcraft": "edge"}, status=BuildStatus.NEEDSBUILD),
401+ ))
402+ log_entries = logger.getLogBuffer().splitlines()
403+ self.assertEqual(4, len(log_entries))
404+ for das, snap in (das1, snap1), (das2, snap2):
405+ self.assertIn(
406+ "DEBUG Scheduling builds of snap package %s/%s" % (
407+ snap.owner.name, snap.name),
408+ log_entries)
409+ self.assertIn(
410+ "DEBUG - %s/%s/%s: Build requested." % (
411+ snap.owner.name, snap.name, das.architecturetag),
412+ log_entries)
413+ self.assertFalse(snap.is_stale)
414+
415+ # Mark the two snaps stale and try again. There are now matching
416+ # builds so we don't try to request more.
417+ for snap in snap1, snap2:
418+ removeSecurityProxy(snap).is_stale = True
419+ IStore(snap).flush()
420+ logger = BufferLogger()
421+ builds = getUtility(ISnapSet).makeAutoBuilds(logger=logger)
422+ self.assertEqual([], builds)
423+ self.assertEqual([], logger.getLogBuffer().splitlines())
424+
425 def test_makeAutoBuilds_skips_non_stale_snaps(self):
426 # ISnapSet.makeAutoBuilds skips snap packages that are not stale.
427 das, snap = self.makeAutoBuildableSnap(is_stale=False)
428
429=== modified file 'lib/lp/snappy/tests/test_snapbuild.py'
430--- lib/lp/snappy/tests/test_snapbuild.py 2018-01-23 10:59:44 +0000
431+++ lib/lp/snappy/tests/test_snapbuild.py 2018-03-22 16:49:25 +0000
432@@ -597,6 +597,7 @@
433 self.assertEqual(
434 db_build.distro_arch_series.architecturetag, build["arch_tag"])
435 self.assertEqual("Updates", build["pocket"])
436+ self.assertIsNone(build["channels"])
437 self.assertIsNone(build["score"])
438 self.assertFalse(build["can_be_rescored"])
439 self.assertFalse(build["can_be_cancelled"])
440
441=== modified file 'lib/lp/snappy/tests/test_snapbuildbehaviour.py'
442--- lib/lp/snappy/tests/test_snapbuildbehaviour.py 2018-03-01 17:36:31 +0000
443+++ lib/lp/snappy/tests/test_snapbuildbehaviour.py 2018-03-22 16:49:25 +0000
444@@ -1,4 +1,4 @@
445-# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
446+# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
447 # GNU Affero General Public License version 3 (see the file LICENSE).
448
449 """Test snap package build behaviour."""
450@@ -402,6 +402,17 @@
451 ]))
452
453 @defer.inlineCallbacks
454+ def test_extraBuildArgs_channels(self):
455+ # If the build needs particular channels, _extraBuildArgs sends
456+ # them.
457+ job = self.makeJob(channels={"snapcraft": "edge"})
458+ expected_archives, expected_trusted_keys = (
459+ yield get_sources_list_for_building(
460+ job.build, job.build.distro_arch_series, None))
461+ args = yield job._extraBuildArgs()
462+ self.assertEqual({"snapcraft": "edge"}, args["channels"])
463+
464+ @defer.inlineCallbacks
465 def test_composeBuildRequest_proxy_url_set(self):
466 job = self.makeJob()
467 build_request = yield job.composeBuildRequest(None)
468
469=== modified file 'lib/lp/testing/factory.py'
470--- lib/lp/testing/factory.py 2018-02-24 09:11:39 +0000
471+++ lib/lp/testing/factory.py 2018-03-22 16:49:25 +0000
472@@ -4670,7 +4670,8 @@
473 def makeSnap(self, registrant=None, owner=None, distroseries=None,
474 name=None, branch=None, git_ref=None, auto_build=False,
475 auto_build_archive=None, auto_build_pocket=None,
476- is_stale=None, require_virtualized=True, processors=None,
477+ auto_build_channels=None, is_stale=None,
478+ require_virtualized=True, processors=None,
479 date_created=DEFAULT, private=False, store_upload=False,
480 store_series=None, store_name=None, store_secrets=None,
481 store_channels=None):
482@@ -4696,7 +4697,8 @@
483 require_virtualized=require_virtualized, processors=processors,
484 date_created=date_created, branch=branch, git_ref=git_ref,
485 auto_build=auto_build, auto_build_archive=auto_build_archive,
486- auto_build_pocket=auto_build_pocket, private=private,
487+ auto_build_pocket=auto_build_pocket,
488+ auto_build_channels=auto_build_channels, private=private,
489 store_upload=store_upload, store_series=store_series,
490 store_name=store_name, store_secrets=store_secrets,
491 store_channels=store_channels)
492@@ -4707,8 +4709,9 @@
493
494 def makeSnapBuild(self, requester=None, registrant=None, snap=None,
495 archive=None, distroarchseries=None, pocket=None,
496- date_created=DEFAULT, status=BuildStatus.NEEDSBUILD,
497- builder=None, duration=None, **kwargs):
498+ channels=None, date_created=DEFAULT,
499+ status=BuildStatus.NEEDSBUILD, builder=None,
500+ duration=None, **kwargs):
501 """Make a new SnapBuild."""
502 if requester is None:
503 requester = self.makePerson()
504@@ -4736,7 +4739,7 @@
505 pocket = PackagePublishingPocket.UPDATES
506 snapbuild = getUtility(ISnapBuildSet).new(
507 requester, snap, archive, distroarchseries, pocket,
508- date_created=date_created)
509+ channels=channels, date_created=date_created)
510 if duration is not None:
511 removeSecurityProxy(snapbuild).updateStatus(
512 BuildStatus.BUILDING, builder=builder,