class SystemdNspawn(Base): base = Sudo() def __init__(self): super().__init__('wrapper/systemd_nspawn') version = SYSTEM_CTL.property_VersionNumber() bucket = PartitionBucket.parse_option_list(version, self.option_list) if bucket.has_supported(): supported_list = render_option_list(bucket.supported_list()) self.with_params(self.binary, supported_list) if bucket.has_unsupported(): unsupported_list = render_option_list(bucket.unsupported_list()) self.logger.warning( f"Removing unsuppoted options: {unsupported_list}") def machine_command(self, machine: str, command: Command) -> Command: return ['--machine', machine] + command def execute_unit(self, machine: str, command: Command) -> ExecuteResult: return super().execute_unit(command=self.machine_command( machine, command), ) def execute_flow(self, machine: str, command: Command) -> ExecuteResult: return super().execute_flow(command=self.machine_command( machine, command), )
class Tar(Base): base = Sudo() def __init__(self): super().__init__('wrapper/tar') def with_archive(self, path): self.with_option('file', path) return self def with_extract(self, path): self.with_option('directory', path) return self def with_compress(self, path): self.with_option('use-compress-program', path) return self def with_make_pack(self): self.with_option('create', '.') return self def with_make_unpack(self): self.with_option('extract') return self def with_packer(self, file): self.with_compress(compressor_for(file)) return self
class NSEnter(Base): base = Sudo() systemctl = SystemCtl() def __init__(self): super().__init__('wrapper/nsenter') def execute_invoke(self, machine: str, script: str = None) -> None: service = f"{machine}.service" init_pid = self.systemctl.machine_init_pid(service) root_dir = f"/var/lib/machines/{machine}" work_dir = f"/var/lib/machines/{machine}" TERM = os.environ.get('TERM') option_list = [ f'-t{init_pid}', f'-r{root_dir}', f'-w{work_dir}', '/usr/bin/env', '-i', f'TERM={TERM}', 'sh' ] if script: option_list.extend(['-c', script]) command = self.full_command(option_list) try: self.logger.info(f"Invoke nsenter: {command}") os.execlp(command[0], *command) except Exception as error: sys.exit(f"Invoke failure: {error}")
class Curl(Base): base = Sudo() def __init__(self): super().__init__('wrapper/curl') def with_url(self, url): self.with_option('url', url) return self def with_file_get(self, path): self.with_option('output', path) return self def with_file_put(self, path): self.with_option('upload-file', path) return self def with_file_head(self, path): self.with_option('output', path) self.with_option('head') return self def with_dump_header(self, path): self.with_option('dump-header', path) return self def with_header(self, text): self.with_option('header', text) return self # needs curl 7.49 def with_connect_to(self, source_host, source_port, target_host, target_port): self.with_option( 'connect-to', f"{source_host}:{source_port}:{target_host}:{target_port}") return self def with_auth_basic(self, username, password): self.with_option('user', f"{username}:{password}") return self def with_auth_token(self, token): self.with_header(f"Authorization: Token {token}") return self def with_content_type(self, content_type): self.with_header(f"Content-Type: {content_type}") return self def with_no_proxy(self, entry_list: str): self.with_option('noproxy', entry_list) return self def with_proxy_entry(self, proxy_entry: str): self.with_option('proxy', proxy_entry) return self
def test_sudo_folder_transfer(): sudo = Sudo() source = "/tmp/nspawn-tester-source" target = "/tmp/nspawn-tester-target" sudo.folder_ensure(source) sudo.files_sync_full(source, target) sudo.files_delete(source) sudo.files_delete(target)
def test_sudo_file_save_load(): sudo = Sudo() source = "abrakadabra" file = "/tmp/test_sudo_file_save_load" sudo.file_save(file, source) target = sudo.file_load(file) sudo.files_delete(file) assert source == target
def test_make_file_digest(): print() sudo = Sudo() text = "" path = "/tmp/test_make_file_digest" sudo.file_save(path, text) digest = make_file_digest(path) sudo.files_delete(path) assert digest == "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
def test_sudo_xattr_get_set(): sudo = Sudo() name = 'tester' source = "abra '{' kadabra '}' abra" file = "/var/test-sudo-xattr-get-set" sudo.file_save(file, source) sudo.xattr_set(file, name, source) target = sudo.xattr_get(file, name) sudo.files_delete(file) assert source == target
class MachineCtl(Base): base = Sudo() def __init__(self): super().__init__('wrapper/machinectl') def status(self, machine: str): command = ['status', machine] return self.execute_unit(command) def start(self, machine: str): command = ['start', machine] return self.execute_unit(command) def stop(self, machine: str): command = ['stop', machine] return self.execute_unit(command) def shell(self, machine, script=['pwd']): script = ['shell', '--quiet', machine] + script return self.execute_unit(script) def show(self, machine: str): command = ['show', machine] return self.execute_unit(command) def show_property(self, machine, name): command = ['show', '--name', name, '--value', machine] return self.execute_unit(command) def pid_get(self, machine: str) -> str: result = self.show_property(machine, 'Leader') result.assert_return() return result.stdout.strip() def list(self) -> List[MachineDescriptor]: command = ['list', '--no-legend'] result = self.execute_unit(command) line_list = result.stdout.splitlines() meta_list = list(map(descriptor_from_line, line_list)) return meta_list def has_machine(self, machine: str) -> bool: meta_list = self.list() machine_list = list(map(lambda store: store.MACHINE, meta_list)) return machine in machine_list
def test_sudo_xattr_load_save(): sudo = Sudo() assert sudo.xattr_space() == 'user.nspawn.' assert sudo.xattr_regex() == '^user[.]nspawn[.]' source = dict( num1='1', num2='2.0', one="one ':' one ':' one", two="two '{'} two [']' two", any="hello '{'} (###) [']' kitty", ) file = "/var/test-sudo-xattr-load-save" sudo.file_save(file, "") sudo.xattr_save(file, source) target = sudo.xattr_load(file) sudo.files_delete(file) assert source == target
class CmdZip(Base): base = Sudo() def __init__(self): super().__init__('wrapper/zip') def with_archive(self, path): self.with_option('xxx', path) return self def with_extract(self, path): self.with_option('xxx', path) return self def with_make_pack(self): return self def with_make_unpack(self): raise RuntimeError("wrong operation")
class CmdUnZip(Base): base = Sudo() def __init__(self): super().__init__('wrapper/unzip') def with_archive(self, path): self.with_option('file', path) return self def with_extract(self, path): self.with_option('directory', path) return self def with_make_pack(self): raise RuntimeError("Invalid operation") def with_make_unpack(self): return self
class CmdUnZip(Base): base = Sudo() def __init__(self): super().__init__('wrapper/unzip') def with_archive(self, path: str): assert path.endswith(".zip") self.option_list.extend([path]) return self def with_extract(self, path: str): self.option_list.extend(['-d', path]) return self def with_make_pack(self): raise RuntimeError("wrong operation") def with_make_unpack(self): return self
def test_sudo_folder_assert(): sudo = Sudo() sudo.folder_assert("/tmp")
class SystemCtl(Base): base = Sudo() def __init__(self): super().__init__('wrapper/systemctl') def status(self, service: str) -> ExecuteResult: command = ['status', service] return self.execute_unit(command) def start(self, service: str) -> ExecuteResult: command = ['start', service] return self.execute_unit(command) def stop(self, service: str) -> ExecuteResult: command = ['stop', service] return self.execute_unit(command) def enable(self, service: str) -> ExecuteResult: command = ['enable', service] return self.execute_unit(command) def disable(self, service: str) -> ExecuteResult: command = ['disable', service] return self.execute_unit(command) def daemon_reload(self) -> ExecuteResult: command = ['daemon-reload'] return self.execute_unit(command) def list_unit_files(self, pattern) -> ExecuteResult: command = ['list-unit-files', '--no-legend', pattern] return self.execute_unit(command) def has_unit(self, service: str) -> bool: result = self.list_unit_files(service) # result.assert_return() has_stdout = result.stdout not in ('', '\n') return has_stdout def has_active(self, service: str) -> bool: command = ['is-active', service] return self.has_success(command) def has_failed(self, service: str) -> bool: command = ['is-failed', service] return self.has_success(command) def has_enabled(self, service: str) -> bool: command = ['is-enabled', service] return self.has_success(command) def has_machine(self, service: str) -> bool: prop_start = 'ExecStart' if self.has_property(prop_start, service): from nspawn.wrapper.systemd_nspawn import SYSTEMD_NSPAWN supervisor = SYSTEMD_NSPAWN.binary exec_info = self.show_exec_info(prop_start, service) return supervisor == exec_info.path else: return False def has_property(self, name: str, service: str) -> bool: value = self.show_property(name, service) return value != '' def show_property(self, name: str, service: str = None) -> str: command = ['show', '--property', name] if service: command.append(service) result = self.execute_unit(command) # result.assert_return() entry = result.stdout.strip() value = entry.replace(f"{name}=", "") return value def show_exec_info(self, name: str, service: str) -> ExecInfo: value = self.show_property(name, service) exec_info = parse_ExecInfo(value) return exec_info def property_Version(self) -> str: return self.show_property('Version') def property_VersionNumber(self) -> int: version = self.property_Version() pattern = re.compile("^([0-9]+).*$") matcher = pattern.search(version) return int(matcher.group(1)) def property_ExecMainPID(self, service: str) -> str: return self.show_property('ExecMainPID', service) def property_ControlGroup(self, service: str) -> str: return self.show_property('ControlGroup', service) def machine_init_pid(self, service: str) -> str: """ Discover systemd-nspawn init process pid """ # pid of systemd-nspawn main_pid = self.property_ExecMainPID(service) # service pid hierarchy group = self.property_ControlGroup(service) # machine group group_path = f"/sys/fs/cgroup/systemd/{group}" # since systemd v 239 payload_path = f"{group_path}/payload" def process_pid(proc_list: List[str]) -> str: assert proc_list assert main_pid not in proc_list return proc_list[0] def process_list(path: str) -> List[str]: try: with open(path, 'r') as file: return file.read().splitlines() except: return [] # native, i.e. systemd inside systemd-nspawn native_init_procs = f"{payload_path}/init.scope/cgroup.procs" proc_list = process_list(native_init_procs) if proc_list: return process_pid(proc_list) # foreing, i.e. non-systemd inside systemd-nspawn foreing_init_procs = f"{payload_path}/cgroup.procs" proc_list = process_list(foreing_init_procs) if proc_list: return process_pid(proc_list) # combined parent + child process list legacy_init_procs = f"{group_path}/cgroup.procs" proc_list = process_list(legacy_init_procs) proc_list.remove(main_pid) if proc_list: return process_pid(proc_list) # unsupported case raise RuntimeError(f"Missing init proc for '{service}'")
def test_sudo_folder_check(): sudo = Sudo() assert sudo.folder_check("/tmp") is True
def test_sudo(): sudo = Sudo() result = sudo.execute_unit(['ls', '-las'])