Merge ~cjwatson/launchpad:code-import-worker-argparse into launchpad:master
- Git
- lp:~cjwatson/launchpad
- code-import-worker-argparse
- Merge into master
Proposed by
Colin Watson
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | b6ffa72b9a56bc821302e0b36a3e79dc2c469d0f |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~cjwatson/launchpad:code-import-worker-argparse |
Merge into: | launchpad:master |
Diff against target: |
340 lines (+202/-31) 3 files modified
lib/lp/codehosting/codeimport/tests/test_worker.py (+110/-5) lib/lp/codehosting/codeimport/worker.py (+91/-26) lib/lp/codehosting/codeimport/workermonitor.py (+1/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ioana Lasc (community) | Approve | ||
Review via email: mp+391808@code.launchpad.net |
Commit message
Convert CodeImportSourc
Description of the change
This requires some temporary compatibility code, since the scheduler still uses the old serialisation of arguments, but in the long run this will be much better than the previous fragile and hard-to-extend scheme.
To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/codehosting/codeimport/tests/test_worker.py b/lib/lp/codehosting/codeimport/tests/test_worker.py | |||
2 | index 9daf357..1c046ed 100644 | |||
3 | --- a/lib/lp/codehosting/codeimport/tests/test_worker.py | |||
4 | +++ b/lib/lp/codehosting/codeimport/tests/test_worker.py | |||
5 | @@ -53,6 +53,7 @@ from CVS import ( | |||
6 | 53 | ) | 53 | ) |
7 | 54 | from dulwich.repo import Repo as GitRepo | 54 | from dulwich.repo import Repo as GitRepo |
8 | 55 | from fixtures import FakeLogger | 55 | from fixtures import FakeLogger |
9 | 56 | from pymacaroons import Macaroon | ||
10 | 56 | import scandir | 57 | import scandir |
11 | 57 | import six | 58 | import six |
12 | 58 | import subvertpy | 59 | import subvertpy |
13 | @@ -892,7 +893,7 @@ class TestActualImportMixin: | |||
14 | 892 | config.root, 'scripts', 'code-import-worker.py') | 893 | config.root, 'scripts', 'code-import-worker.py') |
15 | 893 | output = tempfile.TemporaryFile() | 894 | output = tempfile.TemporaryFile() |
16 | 894 | retcode = subprocess.call( | 895 | retcode = subprocess.call( |
18 | 895 | [script_path, '--access-policy=anything'] + arguments, | 896 | [script_path, '--access-policy=anything', '--'] + arguments, |
19 | 896 | stderr=output, stdout=output) | 897 | stderr=output, stdout=output) |
20 | 897 | self.assertEqual(retcode, 0) | 898 | self.assertEqual(retcode, 0) |
21 | 898 | 899 | ||
22 | @@ -930,11 +931,11 @@ class TestActualImportMixin: | |||
23 | 930 | config.root, 'scripts', 'code-import-worker.py') | 931 | config.root, 'scripts', 'code-import-worker.py') |
24 | 931 | output = tempfile.TemporaryFile() | 932 | output = tempfile.TemporaryFile() |
25 | 932 | retcode = subprocess.call( | 933 | retcode = subprocess.call( |
27 | 933 | [script_path, '--access-policy=anything'] + arguments, | 934 | [script_path, '--access-policy=anything', '--'] + arguments, |
28 | 934 | stderr=output, stdout=output) | 935 | stderr=output, stdout=output) |
29 | 935 | self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS) | 936 | self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS) |
30 | 936 | retcode = subprocess.call( | 937 | retcode = subprocess.call( |
32 | 937 | [script_path, '--access-policy=anything'] + arguments, | 938 | [script_path, '--access-policy=anything', '--'] + arguments, |
33 | 938 | stderr=output, stdout=output) | 939 | stderr=output, stdout=output) |
34 | 939 | self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS_NOCHANGE) | 940 | self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS_NOCHANGE) |
35 | 940 | 941 | ||
36 | @@ -993,6 +994,25 @@ class TestCVSImport(WorkerTest, CSCVSActualImportMixin): | |||
37 | 993 | ] | 994 | ] |
38 | 994 | 995 | ||
39 | 995 | 996 | ||
40 | 997 | class TestCVSImportArgparse(TestCVSImport): | ||
41 | 998 | """Like `TestCVSImport`, but with argparse-style arguments.""" | ||
42 | 999 | |||
43 | 1000 | def makeWorkerArguments(self, module_name, files, stacked_on_url=None): | ||
44 | 1001 | """Make CVS worker arguments pointing at a real CVS repo.""" | ||
45 | 1002 | cvs_server = CVSServer(self.makeTemporaryDirectory()) | ||
46 | 1003 | cvs_server.start_server() | ||
47 | 1004 | self.addCleanup(cvs_server.stop_server) | ||
48 | 1005 | |||
49 | 1006 | cvs_server.makeModule('trunk', [('README', 'original\n')]) | ||
50 | 1007 | |||
51 | 1008 | self.foreign_commit_count = 2 | ||
52 | 1009 | |||
53 | 1010 | return [ | ||
54 | 1011 | str(self.factory.getUniqueInteger()), 'cvs', 'bzr', | ||
55 | 1012 | cvs_server.getRoot(), '--cvs-module', 'trunk', | ||
56 | 1013 | ] | ||
57 | 1014 | |||
58 | 1015 | |||
59 | 996 | class SubversionImportHelpers: | 1016 | class SubversionImportHelpers: |
60 | 997 | """Implementations of `makeForeignCommit` and `makeSourceDetails` for svn. | 1017 | """Implementations of `makeForeignCommit` and `makeSourceDetails` for svn. |
61 | 998 | """ | 1018 | """ |
62 | @@ -1222,6 +1242,28 @@ class TestGitImport(WorkerTest, TestActualImportMixin, | |||
63 | 1222 | self.assertEqual(lastrev.message, "Message for other") | 1242 | self.assertEqual(lastrev.message, "Message for other") |
64 | 1223 | 1243 | ||
65 | 1224 | 1244 | ||
66 | 1245 | class TestGitImportArgparse(TestGitImport): | ||
67 | 1246 | """Like `TestGitImport`, but with argparse-style arguments.""" | ||
68 | 1247 | |||
69 | 1248 | def makeWorkerArguments(self, branch_name, files, stacked_on_url=None): | ||
70 | 1249 | """Make Git worker arguments pointing at a real Git repo.""" | ||
71 | 1250 | repository_store = self.makeTemporaryDirectory() | ||
72 | 1251 | git_server = GitServer(repository_store) | ||
73 | 1252 | git_server.start_server() | ||
74 | 1253 | self.addCleanup(git_server.stop_server) | ||
75 | 1254 | |||
76 | 1255 | git_server.makeRepo('source', files) | ||
77 | 1256 | self.foreign_commit_count = 1 | ||
78 | 1257 | |||
79 | 1258 | arguments = [ | ||
80 | 1259 | str(self.factory.getUniqueInteger()), 'git', 'bzr', | ||
81 | 1260 | git_server.get_url('source'), | ||
82 | 1261 | ] | ||
83 | 1262 | if stacked_on_url is not None: | ||
84 | 1263 | arguments.extend(['--stacked-on', stacked_on_url]) | ||
85 | 1264 | return arguments | ||
86 | 1265 | |||
87 | 1266 | |||
88 | 1225 | class TestBzrSvnImport(WorkerTest, SubversionImportHelpers, | 1267 | class TestBzrSvnImport(WorkerTest, SubversionImportHelpers, |
89 | 1226 | TestActualImportMixin, PullingImportWorkerTests): | 1268 | TestActualImportMixin, PullingImportWorkerTests): |
90 | 1227 | 1269 | ||
91 | @@ -1283,6 +1325,27 @@ class TestBzrSvnImport(WorkerTest, SubversionImportHelpers, | |||
92 | 1283 | self.assertEqual(content, cache_file.read()) | 1325 | self.assertEqual(content, cache_file.read()) |
93 | 1284 | 1326 | ||
94 | 1285 | 1327 | ||
95 | 1328 | class TestBzrSvnImportArgparse(TestBzrSvnImport): | ||
96 | 1329 | """Like `TestBzrSvnImport`, but with argparse-style arguments.""" | ||
97 | 1330 | |||
98 | 1331 | def makeWorkerArguments(self, branch_name, files, stacked_on_url=None): | ||
99 | 1332 | """Make SVN worker arguments pointing at a real SVN repo.""" | ||
100 | 1333 | svn_server = SubversionServer(self.makeTemporaryDirectory()) | ||
101 | 1334 | svn_server.start_server() | ||
102 | 1335 | self.addCleanup(svn_server.stop_server) | ||
103 | 1336 | |||
104 | 1337 | svn_branch_url = svn_server.makeBranch(branch_name, files) | ||
105 | 1338 | svn_branch_url = svn_branch_url.replace('://localhost/', ':///') | ||
106 | 1339 | self.foreign_commit_count = 2 | ||
107 | 1340 | arguments = [ | ||
108 | 1341 | str(self.factory.getUniqueInteger()), self.rcstype, 'bzr', | ||
109 | 1342 | svn_branch_url, | ||
110 | 1343 | ] | ||
111 | 1344 | if stacked_on_url is not None: | ||
112 | 1345 | arguments.extend(['--stacked-on', stacked_on_url]) | ||
113 | 1346 | return arguments | ||
114 | 1347 | |||
115 | 1348 | |||
116 | 1286 | class TestBzrImport(WorkerTest, TestActualImportMixin, | 1349 | class TestBzrImport(WorkerTest, TestActualImportMixin, |
117 | 1287 | PullingImportWorkerTests): | 1350 | PullingImportWorkerTests): |
118 | 1288 | 1351 | ||
119 | @@ -1363,11 +1426,41 @@ class TestBzrImport(WorkerTest, TestActualImportMixin, | |||
120 | 1363 | branch.repository.get_revision(branch.last_revision()).committer) | 1426 | branch.repository.get_revision(branch.last_revision()).committer) |
121 | 1364 | 1427 | ||
122 | 1365 | 1428 | ||
123 | 1429 | class TestBzrImportArgparse(TestBzrImport): | ||
124 | 1430 | """Like `TestBzrImport`, but with argparse-style arguments.""" | ||
125 | 1431 | |||
126 | 1432 | def makeWorkerArguments(self, branch_name, files, stacked_on_url=None): | ||
127 | 1433 | """Make Bzr worker arguments pointing at a real Bzr repo.""" | ||
128 | 1434 | repository_path = self.makeTemporaryDirectory() | ||
129 | 1435 | bzr_server = BzrServer(repository_path) | ||
130 | 1436 | bzr_server.start_server() | ||
131 | 1437 | self.addCleanup(bzr_server.stop_server) | ||
132 | 1438 | |||
133 | 1439 | bzr_server.makeRepo(files) | ||
134 | 1440 | self.foreign_commit_count = 1 | ||
135 | 1441 | |||
136 | 1442 | arguments = [ | ||
137 | 1443 | str(self.factory.getUniqueInteger()), 'bzr', 'bzr', | ||
138 | 1444 | bzr_server.get_url(), | ||
139 | 1445 | ] | ||
140 | 1446 | if stacked_on_url is not None: | ||
141 | 1447 | arguments.extend(['--stacked-on', stacked_on_url]) | ||
142 | 1448 | return arguments | ||
143 | 1449 | |||
144 | 1450 | |||
145 | 1366 | class TestGitToGitImportWorker(TestCase): | 1451 | class TestGitToGitImportWorker(TestCase): |
146 | 1367 | 1452 | ||
147 | 1453 | def makeWorkerArguments(self): | ||
148 | 1454 | """Make Git worker arguments, pointing at a fake URL for now.""" | ||
149 | 1455 | return [ | ||
150 | 1456 | 'git-unique-name', 'git:git', | ||
151 | 1457 | self.factory.getUniqueURL(scheme='git'), | ||
152 | 1458 | Macaroon().serialize(), | ||
153 | 1459 | ] | ||
154 | 1460 | |||
155 | 1368 | def test_throttleProgress(self): | 1461 | def test_throttleProgress(self): |
158 | 1369 | source_details = self.factory.makeCodeImportSourceDetails( | 1462 | source_details = CodeImportSourceDetails.fromArguments( |
159 | 1370 | rcstype="git", target_rcstype="git") | 1463 | self.makeWorkerArguments()) |
160 | 1371 | logger = BufferLogger() | 1464 | logger = BufferLogger() |
161 | 1372 | worker = GitToGitImportWorker( | 1465 | worker = GitToGitImportWorker( |
162 | 1373 | source_details, logger, AcceptAnythingPolicy()) | 1466 | source_details, logger, AcceptAnythingPolicy()) |
163 | @@ -1410,6 +1503,18 @@ class TestGitToGitImportWorker(TestCase): | |||
164 | 1410 | ContainsAll([u"%d ...\r" % i for i in (9, 19, 29, 39, 49)])) | 1503 | ContainsAll([u"%d ...\r" % i for i in (9, 19, 29, 39, 49)])) |
165 | 1411 | 1504 | ||
166 | 1412 | 1505 | ||
167 | 1506 | class TestGitToGitImportWorkerArgparse(TestCase): | ||
168 | 1507 | """Like `TestGitToGitImportWorker`, but with argparse-style arguments.""" | ||
169 | 1508 | |||
170 | 1509 | def makeWorkerArguments(self): | ||
171 | 1510 | """Make Git worker arguments, pointing at a fake URL for now.""" | ||
172 | 1511 | return [ | ||
173 | 1512 | 'git-unique-name', 'git', 'git', | ||
174 | 1513 | self.factory.getUniqueURL(scheme='git'), | ||
175 | 1514 | '--macaroon', Macaroon().serialize(), | ||
176 | 1515 | ] | ||
177 | 1516 | |||
178 | 1517 | |||
179 | 1413 | class CodeImportBranchOpenPolicyTests(TestCase): | 1518 | class CodeImportBranchOpenPolicyTests(TestCase): |
180 | 1414 | 1519 | ||
181 | 1415 | def setUp(self): | 1520 | def setUp(self): |
182 | diff --git a/lib/lp/codehosting/codeimport/worker.py b/lib/lp/codehosting/codeimport/worker.py | |||
183 | index 5aacf29..bb3b559 100644 | |||
184 | --- a/lib/lp/codehosting/codeimport/worker.py | |||
185 | +++ b/lib/lp/codehosting/codeimport/worker.py | |||
186 | @@ -19,6 +19,7 @@ __all__ = [ | |||
187 | 19 | 'get_default_bazaar_branch_store', | 19 | 'get_default_bazaar_branch_store', |
188 | 20 | ] | 20 | ] |
189 | 21 | 21 | ||
190 | 22 | from argparse import ArgumentParser | ||
191 | 22 | import io | 23 | import io |
192 | 23 | import os | 24 | import os |
193 | 24 | import shutil | 25 | import shutil |
194 | @@ -281,6 +282,17 @@ def get_default_bazaar_branch_store(): | |||
195 | 281 | get_transport_from_url(config.codeimport.bazaar_branch_store)) | 282 | get_transport_from_url(config.codeimport.bazaar_branch_store)) |
196 | 282 | 283 | ||
197 | 283 | 284 | ||
198 | 285 | class ArgumentParserError(Exception): | ||
199 | 286 | """An argument parsing error.""" | ||
200 | 287 | |||
201 | 288 | |||
202 | 289 | class TolerantArgumentParser(ArgumentParser): | ||
203 | 290 | """An `ArgumentParser` that raises exceptions on errors.""" | ||
204 | 291 | |||
205 | 292 | def error(self, message): | ||
206 | 293 | raise ArgumentParserError(message) | ||
207 | 294 | |||
208 | 295 | |||
209 | 284 | class CodeImportSourceDetails: | 296 | class CodeImportSourceDetails: |
210 | 285 | """The information needed to process an import. | 297 | """The information needed to process an import. |
211 | 286 | 298 | ||
212 | @@ -322,37 +334,90 @@ class CodeImportSourceDetails: | |||
213 | 322 | def fromArguments(cls, arguments): | 334 | def fromArguments(cls, arguments): |
214 | 323 | """Convert command line-style arguments to an instance.""" | 335 | """Convert command line-style arguments to an instance.""" |
215 | 324 | # Keep this in sync with CodeImportJob.makeWorkerArguments. | 336 | # Keep this in sync with CodeImportJob.makeWorkerArguments. |
216 | 337 | parser = TolerantArgumentParser(description='Code import worker.') | ||
217 | 338 | parser.add_argument( | ||
218 | 339 | 'target_id', help='Target branch ID or repository unique name') | ||
219 | 340 | parser.add_argument( | ||
220 | 341 | 'rcstype', choices=('bzr', 'bzr-svn', 'cvs', 'git'), | ||
221 | 342 | help='Source revision control system type') | ||
222 | 343 | parser.add_argument( | ||
223 | 344 | 'target_rcstype', choices=('bzr', 'git'), | ||
224 | 345 | help='Target revision control system type') | ||
225 | 346 | parser.add_argument( | ||
226 | 347 | 'url', help='Source URL (or $CVSROOT for rcstype cvs)') | ||
227 | 348 | parser.add_argument( | ||
228 | 349 | '--cvs-module', help='CVS module (only valid for rcstype cvs)') | ||
229 | 350 | parser.add_argument( | ||
230 | 351 | '--stacked-on', | ||
231 | 352 | help='Stacked-on URL (only valid for target_rcstype bzr)') | ||
232 | 353 | parser.add_argument( | ||
233 | 354 | '--macaroon', type=Macaroon.deserialize, | ||
234 | 355 | help=( | ||
235 | 356 | 'Macaroon authorising push (only valid for target_rcstype ' | ||
236 | 357 | 'git)')) | ||
237 | 325 | arguments = list(arguments) | 358 | arguments = list(arguments) |
247 | 326 | target_id = arguments.pop(0) | 359 | try: |
248 | 327 | rcstype = arguments.pop(0) | 360 | args = parser.parse_args(arguments) |
249 | 328 | if ':' not in rcstype: | 361 | target_id = args.target_id |
250 | 329 | raise AssertionError( | 362 | rcstype = args.rcstype |
251 | 330 | "'%s' does not contain both source and target types." % | 363 | target_rcstype = args.target_rcstype |
252 | 331 | rcstype) | 364 | url = args.url if rcstype in ('bzr', 'bzr-svn', 'git') else None |
253 | 332 | rcstype, target_rcstype = rcstype.split(':', 1) | 365 | if rcstype == 'cvs': |
254 | 333 | if rcstype in ['bzr-svn', 'git', 'bzr']: | 366 | if args.cvs_module is None: |
255 | 334 | url = arguments.pop(0) | 367 | parser.error('rcstype cvs requires --cvs-module') |
256 | 368 | cvs_root = args.url | ||
257 | 369 | cvs_module = args.cvs_module | ||
258 | 370 | else: | ||
259 | 371 | cvs_root = None | ||
260 | 372 | cvs_module = None | ||
261 | 335 | if target_rcstype == 'bzr': | 373 | if target_rcstype == 'bzr': |
262 | 336 | try: | 374 | try: |
265 | 337 | stacked_on_url = arguments.pop(0) | 375 | target_id = int(target_id) |
266 | 338 | except IndexError: | 376 | except ValueError: |
267 | 377 | parser.error( | ||
268 | 378 | 'rcstype bzr requires target_id to be an integer') | ||
269 | 379 | stacked_on_url = args.stacked_on | ||
270 | 380 | macaroon = None | ||
271 | 381 | elif target_rcstype == 'git': | ||
272 | 382 | if args.macaroon is None: | ||
273 | 383 | parser.error('target_rcstype git requires --macaroon') | ||
274 | 384 | stacked_on_url = None | ||
275 | 385 | macaroon = args.macaroon | ||
276 | 386 | except ArgumentParserError: | ||
277 | 387 | # XXX cjwatson 2020-10-05: Remove this old-style argument | ||
278 | 388 | # handling once the scheduler always passes something argparse | ||
279 | 389 | # can handle. | ||
280 | 390 | target_id = arguments.pop(0) | ||
281 | 391 | rcstype = arguments.pop(0) | ||
282 | 392 | if ':' not in rcstype: | ||
283 | 393 | raise AssertionError( | ||
284 | 394 | "'%s' does not contain both source and target types." % | ||
285 | 395 | rcstype) | ||
286 | 396 | rcstype, target_rcstype = rcstype.split(':', 1) | ||
287 | 397 | if rcstype in ['bzr-svn', 'git', 'bzr']: | ||
288 | 398 | url = arguments.pop(0) | ||
289 | 399 | if target_rcstype == 'bzr': | ||
290 | 400 | try: | ||
291 | 401 | stacked_on_url = arguments.pop(0) | ||
292 | 402 | except IndexError: | ||
293 | 403 | stacked_on_url = None | ||
294 | 404 | else: | ||
295 | 339 | stacked_on_url = None | 405 | stacked_on_url = None |
297 | 340 | else: | 406 | cvs_root = cvs_module = None |
298 | 407 | elif rcstype == 'cvs': | ||
299 | 408 | url = None | ||
300 | 341 | stacked_on_url = None | 409 | stacked_on_url = None |
315 | 342 | cvs_root = cvs_module = None | 410 | [cvs_root, cvs_module] = arguments |
316 | 343 | elif rcstype == 'cvs': | 411 | else: |
317 | 344 | url = None | 412 | raise AssertionError("Unknown rcstype %r." % rcstype) |
318 | 345 | stacked_on_url = None | 413 | if target_rcstype == 'bzr': |
319 | 346 | [cvs_root, cvs_module] = arguments | 414 | target_id = int(target_id) |
320 | 347 | else: | 415 | macaroon = None |
321 | 348 | raise AssertionError("Unknown rcstype %r." % rcstype) | 416 | elif target_rcstype == 'git': |
322 | 349 | if target_rcstype == 'bzr': | 417 | macaroon = Macaroon.deserialize(arguments.pop(0)) |
323 | 350 | target_id = int(target_id) | 418 | else: |
324 | 351 | macaroon = None | 419 | raise AssertionError( |
325 | 352 | elif target_rcstype == 'git': | 420 | "Unknown target_rcstype %r." % target_rcstype) |
312 | 353 | macaroon = Macaroon.deserialize(arguments.pop(0)) | ||
313 | 354 | else: | ||
314 | 355 | raise AssertionError("Unknown target_rcstype %r." % target_rcstype) | ||
326 | 356 | return cls( | 421 | return cls( |
327 | 357 | target_id, rcstype, target_rcstype, url, cvs_root, cvs_module, | 422 | target_id, rcstype, target_rcstype, url, cvs_root, cvs_module, |
328 | 358 | stacked_on_url, macaroon) | 423 | stacked_on_url, macaroon) |
329 | diff --git a/lib/lp/codehosting/codeimport/workermonitor.py b/lib/lp/codehosting/codeimport/workermonitor.py | |||
330 | index 80cbe4b..8441978 100644 | |||
331 | --- a/lib/lp/codehosting/codeimport/workermonitor.py | |||
332 | +++ b/lib/lp/codehosting/codeimport/workermonitor.py | |||
333 | @@ -252,6 +252,7 @@ class CodeImportWorkerMonitor: | |||
334 | 252 | args = [interpreter, self.path_to_script] | 252 | args = [interpreter, self.path_to_script] |
335 | 253 | if self._access_policy is not None: | 253 | if self._access_policy is not None: |
336 | 254 | args.append("--access-policy=%s" % self._access_policy) | 254 | args.append("--access-policy=%s" % self._access_policy) |
337 | 255 | args.append('--') | ||
338 | 255 | command = args + worker_arguments | 256 | command = args + worker_arguments |
339 | 256 | self._logger.info( | 257 | self._logger.info( |
340 | 257 | "Launching worker child process %s.", command) | 258 | "Launching worker child process %s.", command) |