Merge ~cjwatson/launchpad:code-import-worker-argparse into launchpad: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)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+391808@code.launchpad.net

Commit message

Convert CodeImportSourceDetails.fromArguments to argparse

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
diff --git a/lib/lp/codehosting/codeimport/tests/test_worker.py b/lib/lp/codehosting/codeimport/tests/test_worker.py
index 9daf357..1c046ed 100644
--- a/lib/lp/codehosting/codeimport/tests/test_worker.py
+++ b/lib/lp/codehosting/codeimport/tests/test_worker.py
@@ -53,6 +53,7 @@ from CVS import (
53 )53 )
54from dulwich.repo import Repo as GitRepo54from dulwich.repo import Repo as GitRepo
55from fixtures import FakeLogger55from fixtures import FakeLogger
56from pymacaroons import Macaroon
56import scandir57import scandir
57import six58import six
58import subvertpy59import subvertpy
@@ -892,7 +893,7 @@ class TestActualImportMixin:
892 config.root, 'scripts', 'code-import-worker.py')893 config.root, 'scripts', 'code-import-worker.py')
893 output = tempfile.TemporaryFile()894 output = tempfile.TemporaryFile()
894 retcode = subprocess.call(895 retcode = subprocess.call(
895 [script_path, '--access-policy=anything'] + arguments,896 [script_path, '--access-policy=anything', '--'] + arguments,
896 stderr=output, stdout=output)897 stderr=output, stdout=output)
897 self.assertEqual(retcode, 0)898 self.assertEqual(retcode, 0)
898899
@@ -930,11 +931,11 @@ class TestActualImportMixin:
930 config.root, 'scripts', 'code-import-worker.py')931 config.root, 'scripts', 'code-import-worker.py')
931 output = tempfile.TemporaryFile()932 output = tempfile.TemporaryFile()
932 retcode = subprocess.call(933 retcode = subprocess.call(
933 [script_path, '--access-policy=anything'] + arguments,934 [script_path, '--access-policy=anything', '--'] + arguments,
934 stderr=output, stdout=output)935 stderr=output, stdout=output)
935 self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS)936 self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS)
936 retcode = subprocess.call(937 retcode = subprocess.call(
937 [script_path, '--access-policy=anything'] + arguments,938 [script_path, '--access-policy=anything', '--'] + arguments,
938 stderr=output, stdout=output)939 stderr=output, stdout=output)
939 self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS_NOCHANGE)940 self.assertEqual(retcode, CodeImportWorkerExitCode.SUCCESS_NOCHANGE)
940941
@@ -993,6 +994,25 @@ class TestCVSImport(WorkerTest, CSCVSActualImportMixin):
993 ]994 ]
994995
995996
997class TestCVSImportArgparse(TestCVSImport):
998 """Like `TestCVSImport`, but with argparse-style arguments."""
999
1000 def makeWorkerArguments(self, module_name, files, stacked_on_url=None):
1001 """Make CVS worker arguments pointing at a real CVS repo."""
1002 cvs_server = CVSServer(self.makeTemporaryDirectory())
1003 cvs_server.start_server()
1004 self.addCleanup(cvs_server.stop_server)
1005
1006 cvs_server.makeModule('trunk', [('README', 'original\n')])
1007
1008 self.foreign_commit_count = 2
1009
1010 return [
1011 str(self.factory.getUniqueInteger()), 'cvs', 'bzr',
1012 cvs_server.getRoot(), '--cvs-module', 'trunk',
1013 ]
1014
1015
996class SubversionImportHelpers:1016class SubversionImportHelpers:
997 """Implementations of `makeForeignCommit` and `makeSourceDetails` for svn.1017 """Implementations of `makeForeignCommit` and `makeSourceDetails` for svn.
998 """1018 """
@@ -1222,6 +1242,28 @@ class TestGitImport(WorkerTest, TestActualImportMixin,
1222 self.assertEqual(lastrev.message, "Message for other")1242 self.assertEqual(lastrev.message, "Message for other")
12231243
12241244
1245class TestGitImportArgparse(TestGitImport):
1246 """Like `TestGitImport`, but with argparse-style arguments."""
1247
1248 def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
1249 """Make Git worker arguments pointing at a real Git repo."""
1250 repository_store = self.makeTemporaryDirectory()
1251 git_server = GitServer(repository_store)
1252 git_server.start_server()
1253 self.addCleanup(git_server.stop_server)
1254
1255 git_server.makeRepo('source', files)
1256 self.foreign_commit_count = 1
1257
1258 arguments = [
1259 str(self.factory.getUniqueInteger()), 'git', 'bzr',
1260 git_server.get_url('source'),
1261 ]
1262 if stacked_on_url is not None:
1263 arguments.extend(['--stacked-on', stacked_on_url])
1264 return arguments
1265
1266
1225class TestBzrSvnImport(WorkerTest, SubversionImportHelpers,1267class TestBzrSvnImport(WorkerTest, SubversionImportHelpers,
1226 TestActualImportMixin, PullingImportWorkerTests):1268 TestActualImportMixin, PullingImportWorkerTests):
12271269
@@ -1283,6 +1325,27 @@ class TestBzrSvnImport(WorkerTest, SubversionImportHelpers,
1283 self.assertEqual(content, cache_file.read())1325 self.assertEqual(content, cache_file.read())
12841326
12851327
1328class TestBzrSvnImportArgparse(TestBzrSvnImport):
1329 """Like `TestBzrSvnImport`, but with argparse-style arguments."""
1330
1331 def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
1332 """Make SVN worker arguments pointing at a real SVN repo."""
1333 svn_server = SubversionServer(self.makeTemporaryDirectory())
1334 svn_server.start_server()
1335 self.addCleanup(svn_server.stop_server)
1336
1337 svn_branch_url = svn_server.makeBranch(branch_name, files)
1338 svn_branch_url = svn_branch_url.replace('://localhost/', ':///')
1339 self.foreign_commit_count = 2
1340 arguments = [
1341 str(self.factory.getUniqueInteger()), self.rcstype, 'bzr',
1342 svn_branch_url,
1343 ]
1344 if stacked_on_url is not None:
1345 arguments.extend(['--stacked-on', stacked_on_url])
1346 return arguments
1347
1348
1286class TestBzrImport(WorkerTest, TestActualImportMixin,1349class TestBzrImport(WorkerTest, TestActualImportMixin,
1287 PullingImportWorkerTests):1350 PullingImportWorkerTests):
12881351
@@ -1363,11 +1426,41 @@ class TestBzrImport(WorkerTest, TestActualImportMixin,
1363 branch.repository.get_revision(branch.last_revision()).committer)1426 branch.repository.get_revision(branch.last_revision()).committer)
13641427
13651428
1429class TestBzrImportArgparse(TestBzrImport):
1430 """Like `TestBzrImport`, but with argparse-style arguments."""
1431
1432 def makeWorkerArguments(self, branch_name, files, stacked_on_url=None):
1433 """Make Bzr worker arguments pointing at a real Bzr repo."""
1434 repository_path = self.makeTemporaryDirectory()
1435 bzr_server = BzrServer(repository_path)
1436 bzr_server.start_server()
1437 self.addCleanup(bzr_server.stop_server)
1438
1439 bzr_server.makeRepo(files)
1440 self.foreign_commit_count = 1
1441
1442 arguments = [
1443 str(self.factory.getUniqueInteger()), 'bzr', 'bzr',
1444 bzr_server.get_url(),
1445 ]
1446 if stacked_on_url is not None:
1447 arguments.extend(['--stacked-on', stacked_on_url])
1448 return arguments
1449
1450
1366class TestGitToGitImportWorker(TestCase):1451class TestGitToGitImportWorker(TestCase):
13671452
1453 def makeWorkerArguments(self):
1454 """Make Git worker arguments, pointing at a fake URL for now."""
1455 return [
1456 'git-unique-name', 'git:git',
1457 self.factory.getUniqueURL(scheme='git'),
1458 Macaroon().serialize(),
1459 ]
1460
1368 def test_throttleProgress(self):1461 def test_throttleProgress(self):
1369 source_details = self.factory.makeCodeImportSourceDetails(1462 source_details = CodeImportSourceDetails.fromArguments(
1370 rcstype="git", target_rcstype="git")1463 self.makeWorkerArguments())
1371 logger = BufferLogger()1464 logger = BufferLogger()
1372 worker = GitToGitImportWorker(1465 worker = GitToGitImportWorker(
1373 source_details, logger, AcceptAnythingPolicy())1466 source_details, logger, AcceptAnythingPolicy())
@@ -1410,6 +1503,18 @@ class TestGitToGitImportWorker(TestCase):
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)]))
14111504
14121505
1506class TestGitToGitImportWorkerArgparse(TestCase):
1507 """Like `TestGitToGitImportWorker`, but with argparse-style arguments."""
1508
1509 def makeWorkerArguments(self):
1510 """Make Git worker arguments, pointing at a fake URL for now."""
1511 return [
1512 'git-unique-name', 'git', 'git',
1513 self.factory.getUniqueURL(scheme='git'),
1514 '--macaroon', Macaroon().serialize(),
1515 ]
1516
1517
1413class CodeImportBranchOpenPolicyTests(TestCase):1518class CodeImportBranchOpenPolicyTests(TestCase):
14141519
1415 def setUp(self):1520 def setUp(self):
diff --git a/lib/lp/codehosting/codeimport/worker.py b/lib/lp/codehosting/codeimport/worker.py
index 5aacf29..bb3b559 100644
--- a/lib/lp/codehosting/codeimport/worker.py
+++ b/lib/lp/codehosting/codeimport/worker.py
@@ -19,6 +19,7 @@ __all__ = [
19 'get_default_bazaar_branch_store',19 'get_default_bazaar_branch_store',
20 ]20 ]
2121
22from argparse import ArgumentParser
22import io23import io
23import os24import os
24import shutil25import shutil
@@ -281,6 +282,17 @@ def get_default_bazaar_branch_store():
281 get_transport_from_url(config.codeimport.bazaar_branch_store))282 get_transport_from_url(config.codeimport.bazaar_branch_store))
282283
283284
285class ArgumentParserError(Exception):
286 """An argument parsing error."""
287
288
289class TolerantArgumentParser(ArgumentParser):
290 """An `ArgumentParser` that raises exceptions on errors."""
291
292 def error(self, message):
293 raise ArgumentParserError(message)
294
295
284class CodeImportSourceDetails:296class CodeImportSourceDetails:
285 """The information needed to process an import.297 """The information needed to process an import.
286298
@@ -322,37 +334,90 @@ class CodeImportSourceDetails:
322 def fromArguments(cls, arguments):334 def fromArguments(cls, arguments):
323 """Convert command line-style arguments to an instance."""335 """Convert command line-style arguments to an instance."""
324 # Keep this in sync with CodeImportJob.makeWorkerArguments.336 # Keep this in sync with CodeImportJob.makeWorkerArguments.
337 parser = TolerantArgumentParser(description='Code import worker.')
338 parser.add_argument(
339 'target_id', help='Target branch ID or repository unique name')
340 parser.add_argument(
341 'rcstype', choices=('bzr', 'bzr-svn', 'cvs', 'git'),
342 help='Source revision control system type')
343 parser.add_argument(
344 'target_rcstype', choices=('bzr', 'git'),
345 help='Target revision control system type')
346 parser.add_argument(
347 'url', help='Source URL (or $CVSROOT for rcstype cvs)')
348 parser.add_argument(
349 '--cvs-module', help='CVS module (only valid for rcstype cvs)')
350 parser.add_argument(
351 '--stacked-on',
352 help='Stacked-on URL (only valid for target_rcstype bzr)')
353 parser.add_argument(
354 '--macaroon', type=Macaroon.deserialize,
355 help=(
356 'Macaroon authorising push (only valid for target_rcstype '
357 'git)'))
325 arguments = list(arguments)358 arguments = list(arguments)
326 target_id = arguments.pop(0)359 try:
327 rcstype = arguments.pop(0)360 args = parser.parse_args(arguments)
328 if ':' not in rcstype:361 target_id = args.target_id
329 raise AssertionError(362 rcstype = args.rcstype
330 "'%s' does not contain both source and target types." %363 target_rcstype = args.target_rcstype
331 rcstype)364 url = args.url if rcstype in ('bzr', 'bzr-svn', 'git') else None
332 rcstype, target_rcstype = rcstype.split(':', 1)365 if rcstype == 'cvs':
333 if rcstype in ['bzr-svn', 'git', 'bzr']:366 if args.cvs_module is None:
334 url = arguments.pop(0)367 parser.error('rcstype cvs requires --cvs-module')
368 cvs_root = args.url
369 cvs_module = args.cvs_module
370 else:
371 cvs_root = None
372 cvs_module = None
335 if target_rcstype == 'bzr':373 if target_rcstype == 'bzr':
336 try:374 try:
337 stacked_on_url = arguments.pop(0)375 target_id = int(target_id)
338 except IndexError:376 except ValueError:
377 parser.error(
378 'rcstype bzr requires target_id to be an integer')
379 stacked_on_url = args.stacked_on
380 macaroon = None
381 elif target_rcstype == 'git':
382 if args.macaroon is None:
383 parser.error('target_rcstype git requires --macaroon')
384 stacked_on_url = None
385 macaroon = args.macaroon
386 except ArgumentParserError:
387 # XXX cjwatson 2020-10-05: Remove this old-style argument
388 # handling once the scheduler always passes something argparse
389 # can handle.
390 target_id = arguments.pop(0)
391 rcstype = arguments.pop(0)
392 if ':' not in rcstype:
393 raise AssertionError(
394 "'%s' does not contain both source and target types." %
395 rcstype)
396 rcstype, target_rcstype = rcstype.split(':', 1)
397 if rcstype in ['bzr-svn', 'git', 'bzr']:
398 url = arguments.pop(0)
399 if target_rcstype == 'bzr':
400 try:
401 stacked_on_url = arguments.pop(0)
402 except IndexError:
403 stacked_on_url = None
404 else:
339 stacked_on_url = None405 stacked_on_url = None
340 else:406 cvs_root = cvs_module = None
407 elif rcstype == 'cvs':
408 url = None
341 stacked_on_url = None409 stacked_on_url = None
342 cvs_root = cvs_module = None410 [cvs_root, cvs_module] = arguments
343 elif rcstype == 'cvs':411 else:
344 url = None412 raise AssertionError("Unknown rcstype %r." % rcstype)
345 stacked_on_url = None413 if target_rcstype == 'bzr':
346 [cvs_root, cvs_module] = arguments414 target_id = int(target_id)
347 else:415 macaroon = None
348 raise AssertionError("Unknown rcstype %r." % rcstype)416 elif target_rcstype == 'git':
349 if target_rcstype == 'bzr':417 macaroon = Macaroon.deserialize(arguments.pop(0))
350 target_id = int(target_id)418 else:
351 macaroon = None419 raise AssertionError(
352 elif target_rcstype == 'git':420 "Unknown target_rcstype %r." % target_rcstype)
353 macaroon = Macaroon.deserialize(arguments.pop(0))
354 else:
355 raise AssertionError("Unknown target_rcstype %r." % target_rcstype)
356 return cls(421 return cls(
357 target_id, rcstype, target_rcstype, url, cvs_root, cvs_module,422 target_id, rcstype, target_rcstype, url, cvs_root, cvs_module,
358 stacked_on_url, macaroon)423 stacked_on_url, macaroon)
diff --git a/lib/lp/codehosting/codeimport/workermonitor.py b/lib/lp/codehosting/codeimport/workermonitor.py
index 80cbe4b..8441978 100644
--- a/lib/lp/codehosting/codeimport/workermonitor.py
+++ b/lib/lp/codehosting/codeimport/workermonitor.py
@@ -252,6 +252,7 @@ class CodeImportWorkerMonitor:
252 args = [interpreter, self.path_to_script]252 args = [interpreter, self.path_to_script]
253 if self._access_policy is not None:253 if self._access_policy is not None:
254 args.append("--access-policy=%s" % self._access_policy)254 args.append("--access-policy=%s" % self._access_policy)
255 args.append('--')
255 command = args + worker_arguments256 command = args + worker_arguments
256 self._logger.info(257 self._logger.info(
257 "Launching worker child process %s.", command)258 "Launching worker child process %s.", command)

Subscribers

People subscribed via source and target branches

to status/vote changes: