Merge lp:~cjwatson/launchpad/request-daily-builds-permissions into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18742
Proposed branch: lp:~cjwatson/launchpad/request-daily-builds-permissions
Merge into: lp:launchpad
Diff against target: 290 lines (+146/-56)
2 files modified
database/schema/security.cfg (+3/-0)
lib/lp/code/scripts/tests/test_request_daily_builds.py (+143/-56)
To merge this branch: bzr merge lp:~cjwatson/launchpad/request-daily-builds-permissions
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+351871@code.launchpad.net

Commit message

Adjust request-daily-builds DB permissions to handle recent changes in how snap builds are requested.

Description of the change

Most of the work here is in making the functional tests extensive enough to be able to catch this. Aside from that, granting some extra SELECT permissions to particular scripts is usually fairly trivial.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/security.cfg'
2--- database/schema/security.cfg 2018-06-15 12:54:27 +0000
3+++ database/schema/security.cfg 2018-07-31 13:12:41 +0000
4@@ -799,11 +799,14 @@
5 public.distribution = SELECT
6 public.distroarchseries = SELECT
7 public.distroseries = SELECT
8+public.gitref = SELECT
9+public.gitrepository = SELECT
10 public.job = SELECT, INSERT
11 public.libraryfilealias = SELECT
12 public.person = SELECT
13 public.pocketchroot = SELECT
14 public.processor = SELECT
15+public.product = SELECT
16 public.snap = SELECT, UPDATE
17 public.snaparch = SELECT
18 public.snapbuild = SELECT, INSERT
19
20=== modified file 'lib/lp/code/scripts/tests/test_request_daily_builds.py'
21--- lib/lp/code/scripts/tests/test_request_daily_builds.py 2018-07-30 18:10:28 +0000
22+++ lib/lp/code/scripts/tests/test_request_daily_builds.py 2018-07-31 13:12:41 +0000
23@@ -3,6 +3,7 @@
24
25 """Test the request_daily_builds script."""
26
27+import base64
28 from collections import defaultdict
29 import json
30 import threading
31@@ -11,7 +12,6 @@
32 WSGIRequestHandler,
33 )
34
35-from six.moves.urllib_parse import urlparse
36 import transaction
37
38 from lp.code.interfaces.codehosting import BRANCH_ID_ALIAS_PREFIX
39@@ -28,6 +28,13 @@
40 from lp.testing.layers import ZopelessAppServerLayer
41
42
43+class SilentWSGIRequestHandler(WSGIRequestHandler):
44+ """A request handler that doesn't log requests."""
45+
46+ def log_message(self, fmt, *args):
47+ pass
48+
49+
50 class FakeLoggerheadApplication:
51 """A WSGI application that provides some fake loggerhead endpoints."""
52
53@@ -87,34 +94,81 @@
54 self.contents[branch_id][file_id] = contents
55
56
57-class FakeLoggerheadRequestHandler(WSGIRequestHandler):
58- """A request handler that doesn't log requests."""
59-
60- def log_message(self, fmt, *args):
61- pass
62-
63-
64 class FakeLoggerheadServer(threading.Thread):
65 """Thread that runs a fake loggerhead server."""
66
67- def __init__(self, address):
68+ def __init__(self):
69 super(FakeLoggerheadServer, self).__init__()
70 self.app = FakeLoggerheadApplication()
71 self.server = make_server(
72- address, 0, self.app, handler_class=FakeLoggerheadRequestHandler)
73-
74- def run(self):
75- self.server.serve_forever()
76-
77- def getURL(self):
78- host, port = self.server.server_address
79- return 'http://%s:%d/' % (host, port)
80-
81- def addInventory(self, branch_id, path, file_id):
82- self.app.addInventory(branch_id, path, file_id)
83-
84- def addBlob(self, branch_id, file_id, contents):
85- self.app.addBlob(branch_id, file_id, contents)
86+ 'localhost', 0, self.app, handler_class=SilentWSGIRequestHandler)
87+
88+ def run(self):
89+ self.server.serve_forever()
90+
91+ def getURL(self):
92+ host, port = self.server.server_address
93+ return 'http://%s:%d/' % (host, port)
94+
95+ def addInventory(self, branch, path, file_id):
96+ self.app.addInventory(branch.id, path, file_id)
97+
98+ def addBlob(self, branch, file_id, contents):
99+ self.app.addBlob(branch.id, file_id, contents)
100+
101+ def stop(self):
102+ self.server.shutdown()
103+
104+
105+class FakeTurnipApplication:
106+ """A WSGI application that provides some fake turnip endpoints."""
107+
108+ def __init__(self):
109+ self.contents = defaultdict(dict)
110+
111+ def _not_found(self, start_response):
112+ start_response('404 Not Found', [('Content-Type', 'text/plain')])
113+ return [b'']
114+
115+ def __call__(self, environ, start_response):
116+ segments = environ['PATH_INFO'].lstrip('/').split('/')
117+ if (len(segments) < 4 or
118+ segments[0] != 'repo' or segments[2] != 'blob'):
119+ return self._not_found(start_response)
120+ repository_id = segments[1]
121+ if repository_id not in self.contents:
122+ return self._not_found(start_response)
123+ filename = '/'.join(segments[3:])
124+ if filename not in self.contents[repository_id]:
125+ return self._not_found(start_response)
126+ blob = self.contents[repository_id][filename]
127+ response = {'size': len(blob), 'data': base64.b64encode(blob)}
128+ start_response(
129+ '200 OK', [('Content-Type', 'application/octet-stream')])
130+ return [json.dumps(response).encode('UTF-8')]
131+
132+ def addBlob(self, repository, filename, contents):
133+ self.contents[repository.getInternalPath()][filename] = contents
134+
135+
136+class FakeTurnipServer(threading.Thread):
137+ """Thread that runs a fake turnip server."""
138+
139+ def __init__(self):
140+ super(FakeTurnipServer, self).__init__()
141+ self.app = FakeTurnipApplication()
142+ self.server = make_server(
143+ 'localhost', 0, self.app, handler_class=SilentWSGIRequestHandler)
144+
145+ def run(self):
146+ self.server.serve_forever()
147+
148+ def getURL(self):
149+ host, port = self.server.server_address
150+ return 'http://%s:%d/' % (host, port)
151+
152+ def addBlob(self, repository_id, filename, contents):
153+ self.app.addBlob(repository_id, filename, contents)
154
155 def stop(self):
156 self.server.shutdown()
157@@ -129,11 +183,10 @@
158 self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
159
160 def makeLoggerheadServer(self):
161- loggerhead_server = FakeLoggerheadServer(
162- urlparse(config.codehosting.internal_bzr_api_endpoint).hostname)
163+ loggerhead_server = FakeLoggerheadServer()
164 config_name = self.factory.getUniqueString()
165 config_fixture = self.useFixture(ConfigFixture(
166- config_name, self.layer.config_fixture.instance_name))
167+ config_name, config.instance_name))
168 setting_lines = [
169 '[codehosting]',
170 'internal_bzr_api_endpoint: %s' % loggerhead_server.getURL(),
171@@ -144,6 +197,21 @@
172 self.addCleanup(loggerhead_server.stop)
173 return loggerhead_server
174
175+ def makeTurnipServer(self):
176+ turnip_server = FakeTurnipServer()
177+ config_name = self.factory.getUniqueString()
178+ config_fixture = self.useFixture(ConfigFixture(
179+ config_name, config.instance_name))
180+ setting_lines = [
181+ '[codehosting]',
182+ 'internal_git_api_endpoint: %s' % turnip_server.getURL(),
183+ ]
184+ config_fixture.add_section('\n' + '\n'.join(setting_lines))
185+ self.useFixture(ConfigUseFixture(config_name))
186+ turnip_server.start()
187+ self.addCleanup(turnip_server.stop)
188+ return turnip_server
189+
190 def test_request_daily_builds(self):
191 """Ensure the request_daily_builds script requests daily builds."""
192 processor = self.factory.makeProcessor(supports_virtualized=True)
193@@ -152,48 +220,67 @@
194 fake_chroot = self.factory.makeLibraryFileAlias(
195 filename="fake_chroot.tar.gz", db_only=True)
196 distroarchseries.addOrUpdateChroot(fake_chroot)
197- prod_branch = self.factory.makeProductBranch()
198- prod_recipe = self.factory.makeSourcePackageRecipe(
199+ product = self.factory.makeProduct()
200+ prod_branch = self.factory.makeBranch(product=product)
201+ [prod_ref] = self.factory.makeGitRefs(target=product)
202+ bzr_prod_recipe = self.factory.makeSourcePackageRecipe(
203 build_daily=True, is_stale=True, branches=[prod_branch])
204- prod_snap = self.factory.makeSnap(
205+ git_prod_recipe = self.factory.makeSourcePackageRecipe(
206+ build_daily=True, is_stale=True, branches=[prod_ref])
207+ bzr_prod_snap = self.factory.makeSnap(
208 distroseries=distroarchseries.distroseries,
209 processors=[distroarchseries.processor],
210 auto_build=True, is_stale=True, branch=prod_branch)
211- pack_branch = self.factory.makePackageBranch()
212- pack_recipe = self.factory.makeSourcePackageRecipe(
213+ git_prod_snap = self.factory.makeSnap(
214+ distroseries=distroarchseries.distroseries,
215+ processors=[distroarchseries.processor],
216+ auto_build=True, is_stale=True, git_ref=prod_ref)
217+ package = self.factory.makeSourcePackage()
218+ pack_branch = self.factory.makeBranch(sourcepackage=package)
219+ [pack_ref] = self.factory.makeGitRefs(
220+ target=package.distribution_sourcepackage)
221+ bzr_pack_recipe = self.factory.makeSourcePackageRecipe(
222 build_daily=True, is_stale=True, branches=[pack_branch])
223- pack_snap = self.factory.makeSnap(
224+ git_pack_recipe = self.factory.makeSourcePackageRecipe(
225+ build_daily=True, is_stale=True, branches=[pack_ref])
226+ bzr_pack_snap = self.factory.makeSnap(
227 distroseries=distroarchseries.distroseries,
228 processors=[distroarchseries.processor],
229 auto_build=True, is_stale=True, branch=pack_branch)
230- self.assertEqual(0, prod_recipe.pending_builds.count())
231- self.assertEqual(0, prod_snap.pending_builds.count())
232- self.assertEqual(0, pack_recipe.pending_builds.count())
233- self.assertEqual(0, pack_snap.pending_builds.count())
234+ git_pack_snap = self.factory.makeSnap(
235+ distroseries=distroarchseries.distroseries,
236+ processors=[distroarchseries.processor],
237+ auto_build=True, is_stale=True, git_ref=pack_ref)
238+ items = [
239+ bzr_prod_recipe, git_prod_recipe, bzr_prod_snap, git_prod_snap,
240+ bzr_pack_recipe, git_pack_recipe, bzr_pack_snap, git_pack_snap,
241+ ]
242+ for item in items:
243+ self.assertEqual(0, item.pending_builds.count())
244 transaction.commit()
245 loggerhead_server = self.makeLoggerheadServer()
246- loggerhead_server.addInventory(prod_branch.id, 'snap', 'prod_snap')
247- loggerhead_server.addInventory(
248- prod_branch.id, 'snap/snapcraft.yaml', 'prod_snapcraft_yaml')
249- loggerhead_server.addBlob(
250- prod_branch.id, 'prod_snapcraft_yaml', b'name: prod-snap')
251- loggerhead_server.addInventory(pack_branch.id, 'snap', 'pack_snap')
252- loggerhead_server.addInventory(
253- pack_branch.id, 'snap/snapcraft.yaml', 'pack_snapcraft_yaml')
254- loggerhead_server.addBlob(
255- pack_branch.id, 'pack_snapcraft_yaml', b'name: pack-snap')
256+ loggerhead_server.addInventory(prod_branch, 'snap', 'prod_snap')
257+ loggerhead_server.addInventory(
258+ prod_branch, 'snap/snapcraft.yaml', 'prod_snapcraft_yaml')
259+ loggerhead_server.addBlob(
260+ prod_branch, 'prod_snapcraft_yaml', b'name: prod-snap')
261+ loggerhead_server.addInventory(pack_branch, 'snap', 'pack_snap')
262+ loggerhead_server.addInventory(
263+ pack_branch, 'snap/snapcraft.yaml', 'pack_snapcraft_yaml')
264+ loggerhead_server.addBlob(
265+ pack_branch, 'pack_snapcraft_yaml', b'name: pack-snap')
266+ turnip_server = self.makeTurnipServer()
267+ turnip_server.addBlob(
268+ prod_ref.repository, 'snap/snapcraft.yaml', b'name: prod-snap')
269+ turnip_server.addBlob(
270+ pack_ref.repository, 'snap/snapcraft.yaml', b'name: pack-snap')
271 retcode, stdout, stderr = run_script(
272 'cronscripts/request_daily_builds.py', [])
273- self.assertIn('Requested 2 daily recipe builds.', stderr)
274- self.assertIn('Requested 2 automatic snap package builds.', stderr)
275- self.assertEqual(1, prod_recipe.pending_builds.count())
276- self.assertEqual(1, prod_snap.pending_builds.count())
277- self.assertEqual(1, pack_recipe.pending_builds.count())
278- self.assertEqual(1, pack_snap.pending_builds.count())
279- self.assertFalse(prod_recipe.is_stale)
280- self.assertFalse(prod_snap.is_stale)
281- self.assertFalse(pack_recipe.is_stale)
282- self.assertFalse(pack_snap.is_stale)
283+ self.assertIn('Requested 4 daily recipe builds.', stderr)
284+ self.assertIn('Requested 4 automatic snap package builds.', stderr)
285+ for item in items:
286+ self.assertEqual(1, item.pending_builds.count())
287+ self.assertFalse(item.is_stale)
288
289 def test_request_daily_builds_oops(self):
290 """Ensure errors are handled cleanly."""