ソースを参照

pass through some global options from client to server

new: logging level options
refactored:
- umask option and remote_path
- cleanly separated ssh command from borg command
Thomas Waldmann 9 年 前
コミット
eab60cce99
4 ファイル変更54 行追加31 行削除
  1. 6 5
      borg/archiver.py
  2. 25 17
      borg/remote.py
  3. 20 6
      borg/testsuite/repository.py
  4. 3 3
      borg/testsuite/upgrader.py

+ 6 - 5
borg/archiver.py

@@ -34,6 +34,9 @@ from .remote import RepositoryServer, RemoteRepository
 
 has_lchflags = hasattr(os, 'lchflags')
 
+# default umask, overriden by --umask, defaults to read/write only for owner
+UMASK_DEFAULT = 0o077
+
 
 class ToggleAction(argparse.Action):
     """argparse action to handle "toggle" flags easily
@@ -58,7 +61,7 @@ class Archiver:
     def open_repository(self, args, create=False, exclusive=False, lock=True):
         location = args.location  # note: 'location' must be always present in args
         if location.proto == 'ssh':
-            repository = RemoteRepository(location, create=create, lock_wait=self.lock_wait, lock=lock)
+            repository = RemoteRepository(location, create=create, lock_wait=self.lock_wait, lock=lock, args=args)
         else:
             repository = Repository(location.path, create=create, exclusive=exclusive, lock_wait=self.lock_wait, lock=lock)
         repository._location = location
@@ -674,9 +677,9 @@ class Archiver:
                                    help='show/log the return code (rc)')
         common_parser.add_argument('--no-files-cache', dest='cache_files', action='store_false',
                                    help='do not load/update the file metadata cache used to detect unchanged files')
-        common_parser.add_argument('--umask', dest='umask', type=lambda s: int(s, 8), default=RemoteRepository.umask, metavar='M',
+        common_parser.add_argument('--umask', dest='umask', type=lambda s: int(s, 8), default=UMASK_DEFAULT, metavar='M',
                                    help='set umask to M (local and remote, default: %(default)04o)')
-        common_parser.add_argument('--remote-path', dest='remote_path', default=RemoteRepository.remote_path, metavar='PATH',
+        common_parser.add_argument('--remote-path', dest='remote_path', default='borg', metavar='PATH',
                                    help='set remote path to executable (default: "%(default)s")')
 
         parser = argparse.ArgumentParser(prog=prog, description='Borg - Deduplicated Backups')
@@ -1188,8 +1191,6 @@ class Archiver:
     def run(self, args):
         os.umask(args.umask)  # early, before opening files
         self.lock_wait = args.lock_wait
-        RemoteRepository.remote_path = args.remote_path
-        RemoteRepository.umask = args.umask
         setup_logging(level=args.log_level)  # do not use loggers before this!
         check_extension_modules()
         keys_dir = get_keys_dir()

+ 25 - 17
borg/remote.py

@@ -117,15 +117,12 @@ class RepositoryServer:  # pragma: no cover
 
 class RemoteRepository:
     extra_test_args = []
-    remote_path = 'borg'
-    # default umask, overriden by --umask, defaults to read/write only for owner
-    umask = 0o077
 
     class RPCError(Exception):
         def __init__(self, name):
             self.name = name
 
-    def __init__(self, location, create=False, lock_wait=None, lock=True):
+    def __init__(self, location, create=False, lock_wait=None, lock=True, args=None):
         self.location = location
         self.preload_ids = []
         self.msgid = 0
@@ -135,15 +132,11 @@ class RemoteRepository:
         self.responses = {}
         self.unpacker = msgpack.Unpacker(use_list=False)
         self.p = None
-        # XXX: ideally, the testsuite would subclass Repository and
-        # override ssh_cmd() instead of this crude hack, although
-        # __testsuite__ is not a valid domain name so this is pretty
-        # safe.
-        if location.host == '__testsuite__':
-            args = [sys.executable, '-m', 'borg.archiver', 'serve' ] + self.extra_test_args
-        else:  # pragma: no cover
-            args = self.ssh_cmd(location)
-        self.p = Popen(args, bufsize=0, stdin=PIPE, stdout=PIPE)
+        testing = location.host == '__testsuite__'
+        borg_cmd = self.borg_cmd(args, testing)
+        if not testing:
+            borg_cmd = self.ssh_cmd(location) + borg_cmd
+        self.p = Popen(borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE)
         self.stdin_fd = self.p.stdin.fileno()
         self.stdout_fd = self.p.stdout.fileno()
         fcntl.fcntl(self.stdin_fd, fcntl.F_SETFL, fcntl.fcntl(self.stdin_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
@@ -165,10 +158,27 @@ class RemoteRepository:
     def __repr__(self):
         return '<%s %s>' % (self.__class__.__name__, self.location.canonical_path())
 
-    def umask_flag(self):
-        return ['--umask', '%03o' % self.umask]
+    def borg_cmd(self, args, testing):
+        """return a borg serve command line"""
+        # give some args/options to "borg serve" process as they were given to us
+        opts = []
+        if args is not None:
+            opts.append('--umask=%03o' % args.umask)
+            if args.log_level == 'debug':
+                opts.append('--debug')
+            elif args.log_level == 'info':
+                opts.append('--info')
+            elif args.log_level == 'warning':
+                pass  # is default
+            else:
+                raise ValueError('log level missing, fix this code')
+        if testing:
+            return [sys.executable, '-m', 'borg.archiver', 'serve' ] + opts + self.extra_test_args
+        else:  # pragma: no cover
+            return [args.remote_path, 'serve'] + opts
 
     def ssh_cmd(self, location):
+        """return a ssh command line that can be prefixed to a borg command line"""
         args = shlex.split(os.environ.get('BORG_RSH', 'ssh'))
         if location.port:
             args += ['-p', str(location.port)]
@@ -176,8 +186,6 @@ class RemoteRepository:
             args.append('%s@%s' % (location.user, location.host))
         else:
             args.append('%s' % location.host)
-        # use local umask also for the remote process
-        args += [self.remote_path, 'serve'] + self.umask_flag()
         return args
 
     def call(self, cmd, *args, **kw):

+ 20 - 6
borg/testsuite/repository.py

@@ -1,5 +1,6 @@
 import os
 import shutil
+import sys
 import tempfile
 
 from mock import patch
@@ -326,13 +327,26 @@ class RemoteRepositoryTestCase(RepositoryTestCase):
         self.assert_raises(InvalidRPCMethod, lambda: self.repository.call('__init__', None))
 
     def test_ssh_cmd(self):
-        assert self.repository.umask is not None
-        assert self.repository.ssh_cmd(Location('example.com:foo')) == ['ssh', 'example.com', 'borg', 'serve'] + self.repository.umask_flag()
-        assert self.repository.ssh_cmd(Location('ssh://example.com/foo')) == ['ssh', 'example.com', 'borg', 'serve'] + self.repository.umask_flag()
-        assert self.repository.ssh_cmd(Location('ssh://user@example.com/foo')) == ['ssh', 'user@example.com', 'borg', 'serve'] + self.repository.umask_flag()
-        assert self.repository.ssh_cmd(Location('ssh://user@example.com:1234/foo')) == ['ssh', '-p', '1234', 'user@example.com', 'borg', 'serve'] + self.repository.umask_flag()
+        assert self.repository.ssh_cmd(Location('example.com:foo')) == ['ssh', 'example.com']
+        assert self.repository.ssh_cmd(Location('ssh://example.com/foo')) == ['ssh', 'example.com']
+        assert self.repository.ssh_cmd(Location('ssh://user@example.com/foo')) == ['ssh', 'user@example.com']
+        assert self.repository.ssh_cmd(Location('ssh://user@example.com:1234/foo')) == ['ssh', '-p', '1234', 'user@example.com']
         os.environ['BORG_RSH'] = 'ssh --foo'
-        assert self.repository.ssh_cmd(Location('example.com:foo')) == ['ssh', '--foo', 'example.com', 'borg', 'serve'] + self.repository.umask_flag()
+        assert self.repository.ssh_cmd(Location('example.com:foo')) == ['ssh', '--foo', 'example.com']
+
+    def test_borg_cmd(self):
+        class MockArgs:
+            remote_path = 'borg'
+            log_level = 'warning'
+            umask = 0o077
+
+        assert self.repository.borg_cmd(None, testing=True) == [sys.executable, '-m', 'borg.archiver', 'serve' ]
+        args = MockArgs()
+        assert self.repository.borg_cmd(args, testing=False) == ['borg', 'serve', '--umask=077']
+        args.log_level = 'info'
+        assert self.repository.borg_cmd(args, testing=False) == ['borg', 'serve', '--umask=077', '--info']
+        args.remote_path = 'borg-0.28.2'
+        assert self.repository.borg_cmd(args, testing=False) == ['borg-0.28.2', 'serve', '--umask=077', '--info']
 
 
 class RemoteRepositoryCheckTestCase(RepositoryCheckTestCase):

+ 3 - 3
borg/testsuite/upgrader.py

@@ -12,7 +12,7 @@ except ImportError:
 from ..upgrader import AtticRepositoryUpgrader, AtticKeyfileKey
 from ..helpers import get_keys_dir
 from ..key import KeyfileKey
-from ..remote import RemoteRepository
+from ..archiver import UMASK_DEFAULT
 from ..repository import Repository
 
 
@@ -169,7 +169,7 @@ def test_convert_all(tmpdir, attic_repo, attic_key_file, inplace):
     orig_inode = first_inode(attic_repo.path)
     repo = AtticRepositoryUpgrader(str(tmpdir), create=False)
     # replicate command dispatch, partly
-    os.umask(RemoteRepository.umask)
+    os.umask(UMASK_DEFAULT)
     backup = repo.upgrade(dryrun=False, inplace=inplace)
     if inplace:
         assert backup is None
@@ -179,7 +179,7 @@ def test_convert_all(tmpdir, attic_repo, attic_key_file, inplace):
         assert first_inode(repo.path) != first_inode(backup)
         # i have seen cases where the copied tree has world-readable
         # permissions, which is wrong
-        assert stat_segment(backup).st_mode & 0o007 == 0
+        assert stat_segment(backup).st_mode & UMASK_DEFAULT == 0
 
     assert key_valid(attic_key_file.path)
     assert repo_valid(tmpdir)