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

Proposed by Colin Watson on 2019-05-16
Status: Merged
Merged at revision: 18967
Proposed branch: lp:~cjwatson/launchpad/snap-request-build-select-channels
Merge into: lp:launchpad
Diff against target: 220 lines (+80/-5)
6 files modified
lib/lp/snappy/browser/snap.py (+10/-2)
lib/lp/snappy/browser/tests/test_snap.py (+39/-2)
lib/lp/snappy/interfaces/snap.py (+4/-0)
lib/lp/snappy/model/snap.py (+11/-1)
lib/lp/snappy/templates/snap-request-builds.pt (+3/-0)
lib/lp/snappy/tests/test_snap.py (+13/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/snap-request-build-select-channels
Reviewer Review Type Date Requested Status
Adam Collard (community) Approve on 2019-05-22
Launchpad code reviewers 2019-05-16 Pending
Review via email: mp+367518@code.launchpad.net

Commit message

Allow selecting source snap channels when requesting manual snap builds.

To post a comment you must log in.
Adam Collard (adam-collard) wrote :

LGTM, +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/snappy/browser/snap.py'
2--- lib/lp/snappy/browser/snap.py 2019-04-01 09:03:46 +0000
3+++ lib/lp/snappy/browser/snap.py 2019-05-16 11:33:34 +0000
4@@ -29,6 +29,7 @@
5 from zope.interface import Interface
6 from zope.schema import (
7 Choice,
8+ Dict,
9 List,
10 TextLine,
11 )
12@@ -294,10 +295,14 @@
13 description=(
14 u'The package stream within the source distribution series '
15 u'to use when building the snap package.'))
16+ channels = Dict(
17+ title=u'Source snap channels', key_type=TextLine(), required=True,
18+ description=ISnap['auto_build_channels'].description)
19
20 custom_widget_archive = SnapArchiveWidget
21 custom_widget_distro_arch_series = LabeledMultiCheckBoxWidget
22 custom_widget_pocket = LaunchpadDropdownWidget
23+ custom_widget_channels = SnapBuildChannelsWidget
24
25 help_links = {
26 "pocket": u"/+help-snappy/snap-build-pocket.html",
27@@ -320,6 +325,7 @@
28 else self.context.distro_series.main_archive),
29 'distro_arch_series': [],
30 'pocket': PackagePublishingPocket.UPDATES,
31+ 'channels': self.context.auto_build_channels,
32 }
33
34 def requestBuild(self, data):
35@@ -336,7 +342,8 @@
36 for arch in data['distro_arch_series']:
37 try:
38 build = self.context.requestBuild(
39- self.user, data['archive'], arch, data['pocket'])
40+ self.user, data['archive'], arch, data['pocket'],
41+ channels=data['channels'])
42 builds.append(build)
43 except SnapBuildAlreadyPending:
44 already_pending.append(arch)
45@@ -356,7 +363,8 @@
46 self.request.response.addNotification(notification_text)
47 else:
48 self.context.requestBuilds(
49- self.user, data['archive'], data['pocket'])
50+ self.user, data['archive'], data['pocket'],
51+ channels=data['channels'])
52 self.request.response.addNotification(
53 _('Builds will be dispatched soon.'))
54 self.next_url = self.cancel_url
55
56=== modified file 'lib/lp/snappy/browser/tests/test_snap.py'
57--- lib/lp/snappy/browser/tests/test_snap.py 2019-04-01 09:03:46 +0000
58+++ lib/lp/snappy/browser/tests/test_snap.py 2019-05-16 11:33:34 +0000
59@@ -1668,6 +1668,14 @@
60 \(\?\)
61 The package stream within the source distribution series to use
62 when building the snap package.
63+ Source snap channels:
64+ core
65+ snapcraft
66+ The channels to use for build tools when building the snap
67+ package.
68+ If unset, or if the channel for snapcraft is set to "apt", the
69+ default is to install snapcraft from the source archive using
70+ apt.
71 or
72 Cancel
73 """,
74@@ -1717,6 +1725,21 @@
75 builds = self.snap.pending_builds
76 self.assertEqual([ppa], [build.archive for build in builds])
77
78+ def test_request_builds_with_architectures_channels(self):
79+ # Selecting different channels with architectures selected creates
80+ # builds using those channels.
81+ browser = self.getViewBrowser(
82+ self.snap, "+request-builds", user=self.person)
83+ browser.getControl(name="field.channels.core").value = "edge"
84+ browser.getControl("amd64").selected = True
85+ self.assertFalse(browser.getControl("i386").selected)
86+ browser.getControl("Request builds").click()
87+
88+ login_person(self.person)
89+ builds = self.snap.pending_builds
90+ self.assertEqual(
91+ [{"core": "edge"}], [build.channels for build in builds])
92+
93 def test_request_builds_with_architectures_rejects_duplicate(self):
94 # A duplicate build request with architectures selected causes a
95 # notification.
96@@ -1753,7 +1776,7 @@
97 _job=MatchesStructure(
98 requester=Equals(self.person),
99 pocket=Equals(PackagePublishingPocket.UPDATES),
100- channels=Is(None))))
101+ channels=Equals({}))))
102
103 def test_request_builds_no_architectures_ppa(self):
104 # Selecting a different archive with no architectures selected
105@@ -1772,6 +1795,20 @@
106 [request] = self.snap.pending_build_requests
107 self.assertEqual(ppa, request.archive)
108
109+ def test_request_builds_no_architectures_channels(self):
110+ # Selecting different channels with no architectures selected
111+ # creates a build request using those channels.
112+ browser = self.getViewBrowser(
113+ self.snap, "+request-builds", user=self.person)
114+ browser.getControl(name="field.channels.core").value = "edge"
115+ self.assertFalse(browser.getControl("amd64").selected)
116+ self.assertFalse(browser.getControl("i386").selected)
117+ browser.getControl("Request builds").click()
118+
119+ login_person(self.person)
120+ [request] = self.snap.pending_build_requests
121+ self.assertEqual({"core": "edge"}, request.channels)
122+
123 def test_request_builds_no_distro_series(self):
124 # Requesting builds of a snap configured to infer an appropriate
125 # distro series from snapcraft.yaml creates a pending build request.
126@@ -1792,4 +1829,4 @@
127 _job=MatchesStructure(
128 requester=Equals(self.person),
129 pocket=Equals(PackagePublishingPocket.UPDATES),
130- channels=Is(None))))
131+ channels=Equals({}))))
132
133=== modified file 'lib/lp/snappy/interfaces/snap.py'
134--- lib/lp/snappy/interfaces/snap.py 2019-04-16 13:05:56 +0000
135+++ lib/lp/snappy/interfaces/snap.py 2019-05-16 11:33:34 +0000
136@@ -332,6 +332,10 @@
137 title=u"The source archive for builds produced by this request",
138 required=True, readonly=True)
139
140+ channels = Dict(
141+ title=_("Source snap channels for builds produced by this request"),
142+ key_type=TextLine(), required=False, readonly=True)
143+
144
145 class ISnapView(Interface):
146 """`ISnap` attributes that require launchpad.View permission."""
147
148=== modified file 'lib/lp/snappy/model/snap.py'
149--- lib/lp/snappy/model/snap.py 2019-04-16 13:05:56 +0000
150+++ lib/lp/snappy/model/snap.py 2019-05-16 11:33:34 +0000
151@@ -244,6 +244,11 @@
152 """See `ISnapBuildRequest`."""
153 return self._job.archive
154
155+ @property
156+ def channels(self):
157+ """See `ISnapBuildRequest`."""
158+ return self._job.channels
159+
160
161 @implementer(ISnap, IHasOwner)
162 class Snap(Storm, WebhookTargetMixin):
163@@ -627,13 +632,18 @@
164 if not self._isArchitectureAllowed(distro_arch_series, pocket):
165 raise SnapBuildDisallowedArchitecture(distro_arch_series, pocket)
166
167+ if not channels:
168+ channels_clause = Or(
169+ SnapBuild.channels == None, SnapBuild.channels == {})
170+ else:
171+ channels_clause = SnapBuild.channels == channels
172 pending = IStore(self).find(
173 SnapBuild,
174 SnapBuild.snap_id == self.id,
175 SnapBuild.archive_id == archive.id,
176 SnapBuild.distro_arch_series_id == distro_arch_series.id,
177 SnapBuild.pocket == pocket,
178- SnapBuild.channels == channels,
179+ channels_clause,
180 SnapBuild.status == BuildStatus.NEEDSBUILD)
181 if pending.any() is not None:
182 raise SnapBuildAlreadyPending
183
184=== modified file 'lib/lp/snappy/templates/snap-request-builds.pt'
185--- lib/lp/snappy/templates/snap-request-builds.pt 2019-02-07 10:34:20 +0000
186+++ lib/lp/snappy/templates/snap-request-builds.pt 2019-05-16 11:33:34 +0000
187@@ -28,6 +28,9 @@
188 <tal:widget define="widget nocall:view/widgets/pocket">
189 <metal:block use-macro="context/@@launchpad_form/widget_row" />
190 </tal:widget>
191+ <tal:widget define="widget nocall:view/widgets/channels">
192+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
193+ </tal:widget>
194 </table>
195 </metal:formbody>
196 </div>
197
198=== modified file 'lib/lp/snappy/tests/test_snap.py'
199--- lib/lp/snappy/tests/test_snap.py 2019-04-16 13:05:56 +0000
200+++ lib/lp/snappy/tests/test_snap.py 2019-05-16 11:33:34 +0000
201@@ -308,6 +308,19 @@
202 snap.requestBuild(
203 snap.owner, snap.distro_series.main_archive, arches[1],
204 PackagePublishingPocket.UPDATES)
205+ # channels=None and channels={} are treated as equivalent, but
206+ # anything else allows a new build.
207+ self.assertRaises(
208+ SnapBuildAlreadyPending, snap.requestBuild,
209+ snap.owner, snap.distro_series.main_archive, arches[0],
210+ PackagePublishingPocket.UPDATES, channels={})
211+ snap.requestBuild(
212+ snap.owner, snap.distro_series.main_archive, arches[0],
213+ PackagePublishingPocket.UPDATES, channels={"core": "edge"})
214+ self.assertRaises(
215+ SnapBuildAlreadyPending, snap.requestBuild,
216+ snap.owner, snap.distro_series.main_archive, arches[0],
217+ PackagePublishingPocket.UPDATES, channels={"core": "edge"})
218 # Changing the status of the old build allows a new build.
219 old_build.updateStatus(BuildStatus.BUILDING)
220 old_build.updateStatus(BuildStatus.FULLYBUILT)