summaryrefslogtreecommitdiff
path: root/targets.py
diff options
context:
space:
mode:
authorAnton Khirnov <anton@khirnov.net>2020-02-13 21:39:41 +0100
committerAnton Khirnov <anton@khirnov.net>2020-02-13 21:39:41 +0100
commitf1ef9c053c84515761f59412a91ca3adbc8e7392 (patch)
tree9adfb42a73cf1aa61556d16ee5665e6c3f611dd4 /targets.py
parentb476a22f4a85d90ccaec2ee27a528d5bfe428a82 (diff)
Move the python files to a package.
Diffstat (limited to 'targets.py')
-rw-r--r--targets.py186
1 files changed, 0 insertions, 186 deletions
diff --git a/targets.py b/targets.py
deleted file mode 100644
index e56b71b..0000000
--- a/targets.py
+++ /dev/null
@@ -1,186 +0,0 @@
-
-from abc import ABC, abstractmethod
-import re
-import subprocess
-
-from .exceptions import RemoteExecException
-from . import repository
-from . import ssh_remote
-from . import _ssh_client
-
-def _parse_name(name):
- """
- Parse a backup name into a remote specification.
- """
- # split off the username
- if not '@' in name:
- raise ValueError('Invalid backup name: "%s", must be of format user@host')
- username, _, host = name.partition('@')
-
- port = 22 # overridden later if specified in name
- colons = host.count(':')
- if colons >= 2: # IPv6 literal, possibly with port
- m = re.match(r'\[(.+)\](:\d+)?', host, re.ASCII | re.IGNORECASE)
- if m is not None: # [literal]:port
- host, port = m.groups()
- elif colons == 1: # host:port
- host, _, port = host.partition(':')
-
- return ssh_remote.SSHRemote(host, port, username)
-
-class Target(ABC):
- name = None
- dirs = None
- excludes = None
- def __init__(self, name, dirs, excludes = None):
- if excludes is None:
- excludes = []
-
- self.name = name
- self.dirs = dirs
- self.excludes = excludes
-
- @abstractmethod
- def save(self, data_dir):
- pass
-
-class TargetLocal(Target):
- def save(self, data_dir):
- cmd = ['bup', 'index', '--update', '--one-file-system']
- cmd.extend(['--exclude=%s' % e for e in self.excludes])
- cmd.extend(self.dirs)
- res_idx = subprocess.run(cmd, capture_output = True)
-
- cmd = ['bup', 'save', '-n', self.name] + self.dirs
- res_save = subprocess.run(cmd, capture_output = True)
-
- retcode = 0
- output = b''
- if res_idx.returncode != 0:
- retcode = res_idx.returncode
- output += res_idx.stderr + res_idx.stdout
- if res_save.returncode != 0:
- retcode = res_save.returncode
- output += res_save.stderr + res_save.stdout
-
- result = repository.StepResult(retcode, output)
-
- return result
-
-class TargetSSH(Target):
- _remote = None
-
- def __init__(self, name, dirs, excludes = None,
- remote = None):
- super().__init__(name, dirs, excludes)
-
- if remote is None:
- remote = _parse_name(name)
- if remote.proxy_remote is not None:
- raise NotImplementedError('Proxy remote not implemented')
- if remote.port != 22:
- raise NotImplementedError('Specifying port not implemented')
- self._remote = remote
-
- def save(self, data_dir):
- cmd = ['bup', 'on', '%s@%s' % (self._remote.username, self._remote.host), 'index', '--update', '--one-file-system']
- cmd.extend(['--exclude=%s' % e for e in self.excludes])
- cmd.extend(self.dirs)
- res_idx = subprocess.run(cmd, capture_output = True)
-
- cmd = ['bup', 'on', '%s@%s' %(self._remote.username, self._remote.host), 'save', '-n', self.name] + self.dirs
- res_save = subprocess.run(cmd, capture_output = True)
-
- retcode = 0
- output = b''
- if res_idx.returncode != 0:
- retcode = res_idx.returncode
- output += res_idx.stderr + res_idx.stdout
- if res_save.returncode != 0:
- retcode = res_save.returncode
- output += res_save.stderr + res_save.stdout
-
- result = repository.StepResult(retcode, output)
-
- return result
-
-def _paramiko_exec_cmd(client, cmd):
- res = client.exec_command(cmd)
- chan = res[0].channel
- out, err = res[1].read(), res[2].read()
- if chan.exit_status != 0:
- raise RemoteExecException('Error executing "%s"' % cmd,
- chan.exit_status, err + out)
- return out.decode('utf-8', errors = 'backslashreplace')
-
-class TargetSSHLXCLVM(TargetSSH):
- """
- This target backs up an LXC container that lives on its own LVM logical
- volume. Requires root-capable login on the container's host.
-
- :param SSHRemote parent_remote:
- """
- _parent_remote = None
- _lxc_username = None
- _lxc_containername = None
-
- def __init__(self, name, dirs, excludes = None,
- target_remote = None, parent_remote = None,
- lxc_username = None, lxc_containername = None,
- snapshot_size = '20G'):
- super().__init__(name, dirs, excludes, target_remote)
-
- if parent_remote is None:
- raise ValueError('parent_remote not specified')
- if lxc_username is None:
- lxc_username = parent_remote.usename
-
- self._parent_remote = parent_remote
- self._lxc_username = lxc_username
- self._lxc_containername = lxc_containername
- self._snapshot_size = snapshot_size
-
- def save(self, data_dir):
- with (_ssh_client.SSHConnection(self._parent_remote) as parent,
- _ssh_client.SSHConnection(self._remote) as container):
- # get the PID of the container's init
- cmd_template = 'su -s /bin/sh -c "{command}" %s' % self._lxc_username
- container_pid = _paramiko_exec_cmd(parent, cmd_template.format(
- command = 'lxc-info -H -p -n %s' % self._lxc_containername)).rstrip('\n')
-
- # get the LV/VG for the container's rootfs
- container_rootfs = _paramiko_exec_cmd(parent, cmd_template.format(
- command = 'lxc-info -H -c lxc.rootfs -n %s' %
- self._lxc_containername))\
- .rstrip('\n')\
- .translate({ord(' ') : r'\040', ord('\t') : r'\011',
- ord('\n') : r'\012', ord('\\') : r'\O134'})
- mountline = _paramiko_exec_cmd(parent, 'grep "%s" /proc/mounts' %
- container_rootfs).rstrip('\n').split()
- if len(mountline) < 2 or mountline[1] != container_rootfs:
- raise RemoteExecException('Invalid mount line: %s' % mountline)
- lv_path = mountline[0]
- lv_name, vg_name = _paramiko_exec_cmd(parent,
- 'lvdisplay -C --noheadings -o lv_name,vg_name ' + lv_path)\
- .strip().split()
-
- # we cannot trust any binaries located inside the container, since a
- # compromised container could use them to execute arbitrary code
- # with real root privileges, thus nullifying the point of
- # unprivileged containers)
- # so we now create a temporary
-
-
- # create a read-only snapshot
- snapshot_name = 'bupper_' + lv_name
- _paramiko_exec_cmd(parent,
- 'lvcreate --permission r --snapshot -L {size} -n {name} {origin}'
- .format(size = self._snapshot_size, name = snapshot_name,
- origin = lv_path))
-
- # execute the backup
- try:
- print(container_pid, vg_name, lv_path, snapshot_name)
- finally:
- # delete the snapshot
- _paramiko_exec_cmd(parent, 'lvremove -f %s/%s' % (vg_name, snapshot_name))