def test_bootstrap_overlay(config_files): with open(config_files, "r") as main_file: parser = ConfigurationParser(main_file) # the host file shall win boostrap_source = SourceEntry(parser.get_bootstrap_repository()) assert parser.get_bootstrap_architecture() == "i386" assert "main" in boostrap_source.comps assert boostrap_source.uri == "http://deb.debian.org/debian/" # the all file shall provide this key expected_key = "https://ftp-master.debian.org/keys/archive-key-8.asc" assert parser.get_bootstrap_repository_key() == expected_key assert boostrap_source.dist == "jessie" assert parser.get_bootstrap_tool() == "debootstrap"
def test_create_host_folders_successful_create(config_files, monkeypatch): with open(config_files, "r") as main_file: parser = ConfigurationParser(main_file) coordinator = SharedFolderCoordinator(parser) patch_os_path(monkeypatch, False, False) def fake_mkdir_command(*popenargs, **kwargs): if get_command(popenargs) == 'mkdir' and get_sub_command(popenargs) == '-p': folder = popenargs[0][-1] assert 'edi_marker_valid_folder' in folder or 'edi_marker_work' in folder return subprocess.CompletedProcess("fakerun", 0, '') else: return subprocess.run(*popenargs, **kwargs) monkeypatch.setattr(mockablerun, 'run_mockable', fake_mkdir_command) coordinator.create_host_folders() # successful mkdir
def test_verify_container_mountpoints(config_files, monkeypatch): with mocked_executable('lxc', '/here/is/no/lxc'): with mocked_lxd_version_check(): with open(config_files, "r") as main_file: def fake_lxc_exec_command(*popenargs, **kwargs): if get_command(popenargs).endswith( 'lxc') and get_sub_command(popenargs) == 'exec': return subprocess.CompletedProcess("fakerun", 0, '') else: return subprocess.run(*popenargs, **kwargs) monkeypatch.setattr(mockablerun, 'run_mockable', fake_lxc_exec_command) parser = ConfigurationParser(main_file) coordinator = SharedFolderCoordinator(parser) coordinator.verify_container_mountpoints('fake-container')
def test_create_host_folders_not_a_folder(config_files, monkeypatch): with open(config_files, "r") as main_file: parser = ConfigurationParser(main_file) coordinator = SharedFolderCoordinator(parser) def fake_os_path_isdir(*_): return False monkeypatch.setattr(os.path, 'isdir', fake_os_path_isdir) def fake_os_path_exists(*_): return True monkeypatch.setattr(os.path, 'exists', fake_os_path_exists) with pytest.raises(FatalError) as error: coordinator.create_host_folders() # exists but not a folder assert 'valid_folder' in error.value.message
def test_verify_container_mountpoints_failure(config_files, monkeypatch): with open(config_files, "r") as main_file: def fake_lxc_exec_command(*popenargs, **kwargs): command = popenargs[0] if command[0] == 'lxc' and command[1] == 'exec': if command[command.index('--') + 1] == 'test': return subprocess.CompletedProcess("failure", 1, 'failure') else: return subprocess.CompletedProcess("fakerun", 0, '') else: return subprocess.run(*popenargs, **kwargs) monkeypatch.setattr(mockablerun, 'run_mockable', fake_lxc_exec_command) parser = ConfigurationParser(main_file) coordinator = SharedFolderCoordinator(parser) with pytest.raises(FatalError) as error: coordinator.verify_container_mountpoints('fake-container') assert 'fake-container' in error.value.message assert '/foo/bar/target_mountpoint' in error.value.message
def test_create_host_folders_failed_create(config_files, monkeypatch): with open(config_files, "r") as main_file: parser = ConfigurationParser(main_file) coordinator = SharedFolderCoordinator(parser) patch_os_path(monkeypatch, False, False) def fake_mkdir_command(*popenargs, **kwargs): if get_command(popenargs) == 'mkdir' and get_sub_command(popenargs) == '-p': cmd = ['bash', '-c', '>&2 echo -e "no permission" ; exit 1'] return subprocess.run(cmd, **kwargs) else: return subprocess.run(*popenargs, **kwargs) monkeypatch.setattr(mockablerun, 'run_mockable', fake_mkdir_command) with pytest.raises(FatalError) as error: coordinator.create_host_folders() # failed mkdir assert 'edi_marker_valid_folder' in error.value.message assert 'no permission' in error.value.message
def test_no_shared_folders_for_distributable_image(config_files, monkeypatch): with mocked_executable('lxc'): with mocked_lxd_version_check(): with open(config_files, "r") as main_file: with command_context({'edi_create_distributable_image': True}): parser = ConfigurationParser(main_file) coordinator = SharedFolderCoordinator(parser) patch_os_path(monkeypatch, False, False) def fake_run(*popenargs, **kwargs): # We should not run anything! assert False monkeypatch.setattr(mockablerun, 'run_mockable', fake_run) coordinator.create_host_folders() coordinator.verify_container_mountpoints('does-not-exist') assert coordinator.get_mountpoints() == [] assert coordinator.get_pre_config_profiles() == [] assert coordinator.get_post_config_profiles() == []
def test_verify_container_mountpoints_failure(config_files, monkeypatch): with mocked_executable('lxc', '/here/is/no/lxc'): with mocked_lxd_version_check(): with open(config_files, "r") as main_file: def fake_lxc_exec_command(*popenargs, **kwargs): if get_command(popenargs).endswith('lxc') and get_sub_command(popenargs) == 'exec': if get_command_parameter(popenargs, '--') == 'test': return subprocess.CompletedProcess("failure", 1, 'failure') else: return subprocess.CompletedProcess("fakerun", 0, '') else: return subprocess.run(*popenargs, **kwargs) monkeypatch.setattr(mockablerun, 'run_mockable', fake_lxc_exec_command) parser = ConfigurationParser(main_file) coordinator = SharedFolderCoordinator(parser) with pytest.raises(FatalError) as error: coordinator.verify_container_mountpoints('fake-container') assert 'fake-container' in error.value.message assert '/foo/bar/target_mountpoint' in error.value.message
def test_empty_overlay_file(empty_overlay_config_file): with open(empty_overlay_config_file, "r") as main_file: parser = ConfigurationParser(main_file) assert parser.get_compression() == 'gz'
def test_global_configuration_overlay(config_files): with open(config_files, "r") as main_file: parser = ConfigurationParser(main_file) assert parser.get_compression() == "gz" assert parser.get_lxc_stop_timeout() == 130
def test_project_name(config_files, config_name): with open(config_files, "r") as main_file: parser = ConfigurationParser(main_file) assert parser.get_configuration_name() == config_name
def _setup_parser(self, config_file): self.config = ConfigurationParser(config_file)
class EdiCommand(metaclass=CommandFactory): def clean(self, config_file): pass def _get_sibling_commands(self): assert len(type(self).__bases__) == 1 commands = [] parent_command = type(self).__bases__[0]._get_command_name() for _, command in get_sub_commands(parent_command).items(): if command != self.__class__: commands.append(command) return commands def _clean_siblings_and_sub_commands(self, config_file): for command in self._get_sibling_commands(): command().clean(config_file) command().clean_sub_commands(config_file) def clean_sub_commands(self, config_file): command = self._get_sub_command("clean") if command: command().clean(config_file) def _setup_parser(self, config_file): self.config = ConfigurationParser(config_file) @classmethod def _get_command_name(cls): return compose_command_name(cls) @classmethod def _get_short_command_name(cls): return cls.__name__.lower() @classmethod def _get_command_file_name_prefix(cls): return cls._get_command_name().replace(".", "_") @classmethod def _add_sub_commands(cls, parser): title = "{} commands".format(cls._get_short_command_name()) subparsers = parser.add_subparsers(title=title, dest="sub_command_name") for _, command in get_sub_commands(cls._get_command_name()).items(): command.advertise(subparsers) def _run_sub_command_cli(self, cli_args): self._get_sub_command(cli_args.sub_command_name)().run_cli(cli_args) def _get_sub_command(self, command): sub_command = "{}.{}".format(self._get_command_name(), command) return get_command(sub_command) @staticmethod def _require_config_file(parser): parser.add_argument('config_file', type=argparse.FileType('r', encoding='UTF-8')) @staticmethod def _offer_introspection_options(parser): group = parser.add_mutually_exclusive_group() group.add_argument('--dictionary', action="store_true", help='dump the load time dictionary instead of running the command') group.add_argument('--config', action="store_true", help='dump the merged configuration instead of running the command') group.add_argument('--plugins', action="store_true", help=('dump the active plugins including their dictionaries instead of ' 'running the command')) def _get_introspection_method(self, cli_args, plugin_sections): if cli_args.dictionary: return self._dump_load_time_dictionary elif cli_args.config: return self._dump_config elif cli_args.plugins: return partial(self._dump_plugins, plugin_sections) else: return None def _dump_load_time_dictionary(self): return self.config.dump_load_time_dictionary() def _dump_config(self): return self.config.dump() def _dump_plugins(self, sections): return self.config.dump_plugins(sections) def _require_sudo(self): if os.getuid() != 0: raise FatalError(("The subcommand '{0}' requires superuser " "privileges.\n" "Use 'sudo edi ...'." ).format(self._get_short_command_name())) def _pack_image(self, tempdir, datadir, name="result"): # advanced options such as numeric-owner are not supported by # python tarfile library - therefore we use the tar command line tool tempresult = "{0}.tar.{1}".format(name, self.config.get_compression()) archive_path = os.path.join(tempdir, tempresult) cmd = [] cmd.append("tar") cmd.append("--numeric-owner") cmd.extend(["-C", datadir]) cmd.extend(["-acf", archive_path]) cmd.extend(os.listdir(datadir)) run(cmd, sudo=True, log_threshold=logging.INFO) return archive_path def _unpack_image(self, image, tempdir, subfolder="rootfs"): target_folder = os.path.join(tempdir, subfolder) os.makedirs(target_folder, exist_ok=True) cmd = [] cmd.append("tar") cmd.append("--numeric-owner") cmd.extend(["-C", target_folder]) cmd.extend(["-axf", image]) run(cmd, sudo=True, log_threshold=logging.INFO) return target_folder
class EdiCommand(metaclass=CommandFactory): def __init__(self): self.clean_depth = 0 self.config = None def clean(self, config_file): pass def _get_sibling_commands(self): assert len(type(self).__bases__) == 1 commands = [] parent_command = type(self).__bases__[0]._get_command_name() for _, command in get_sub_commands(parent_command).items(): if command != self.__class__: commands.append(command) return commands def _clean_siblings_and_sub_commands(self, config_file): for command in self._get_sibling_commands(): command().clean(config_file) command().clean_sub_commands(config_file) def clean_sub_commands(self, config_file): command = self._get_sub_command("clean") if command: command().clean(config_file) def _setup_parser(self, config_file): self.config = ConfigurationParser(config_file) @classmethod def _get_command_name(cls): return compose_command_name(cls) @classmethod def _get_short_command_name(cls): return cls.__name__.lower() @classmethod def _get_command_file_name_prefix(cls): return cls._get_command_name().replace(".", "_") @classmethod def _add_sub_commands(cls, parser): title = "{} commands".format(cls._get_short_command_name()) subparsers = parser.add_subparsers(title=title, dest="sub_command_name") for _, command in get_sub_commands(cls._get_command_name()).items(): command.advertise(subparsers) def _run_sub_command_cli(self, cli_args): if not cli_args.sub_command_name: raise FatalError("Missing subcommand. Use 'edi --help' for help.") self._get_sub_command(cli_args.sub_command_name)().run_cli(cli_args) def _get_sub_command(self, command): sub_command = "{}.{}".format(self._get_command_name(), command) return get_command(sub_command) @staticmethod def _require_config_file(parser): parser.add_argument('config_file', type=argparse.FileType('r', encoding='UTF-8')) @staticmethod def _offer_options(parser, introspection=False, clean=False): group = parser.add_mutually_exclusive_group() if introspection: group.add_argument( '--dictionary', action="store_true", help= 'dump the load time dictionary instead of running the command') group.add_argument( '--config', action="store_true", help= 'dump the merged configuration instead of running the command') group.add_argument( '--plugins', action="store_true", help= ('dump the active plugins including their dictionaries instead of ' 'running the command')) if clean: group.add_argument( '--clean', action="store_true", help='clean the artifacts that got produced by this command') group.add_argument( '--recursive-clean', type=int, metavar='N', help= 'clean the artifacts that got produced by this and the preceding N commands' ) @staticmethod def _unpack_cli_args(cli_args): return [cli_args.config_file] def _get_run_method(self, cli_args): if hasattr(cli_args, 'dictionary') and cli_args.dictionary: return partial(self._print, self._get_load_time_dictionary) elif hasattr(cli_args, 'config') and cli_args.config: return partial(self._print, self._get_config) elif hasattr(cli_args, 'plugins') and cli_args.plugins: return partial( self._print, partial(self.dry_run, *self._unpack_cli_args(cli_args))) elif hasattr(cli_args, 'clean') and cli_args.clean: return partial(self.clean_recursive, *self._unpack_cli_args(cli_args), 0) elif hasattr( cli_args, 'recursive_clean') and cli_args.recursive_clean is not None: return partial(self.clean_recursive, *self._unpack_cli_args(cli_args), cli_args.recursive_clean) else: return partial(self.run, *self._unpack_cli_args(cli_args)) def run(self, *args, **kwargs): raise FatalError('''Missing 'run' implementation for '{}'.'''.format( self._get_command_name())) def dry_run(self, *args, **kwargs): raise FatalError( '''Missing 'dry_run' implementation for '{}'.'''.format( self._get_command_name())) def clean_recursive(self, *args, **kwargs): raise FatalError( '''Missing 'clean_recursive' implementation for '{}'.'''.format( self._get_command_name())) def _get_load_time_dictionary(self): return self.config.get_load_time_dictionary() def _get_config(self): return self.config.get_config() def _get_plugins(self, sections): return self.config.get_plugins(sections) @staticmethod def _dump(introspection_result): return yaml.dump(introspection_result, default_flow_style=False, width=1000) def _print(self, method): print(self._dump(method())) def _require_sudo(self): if os.getuid() != 0: raise FatalError( ("The subcommand '{0}' requires superuser " "privileges.\n" "Use 'sudo edi ...'.").format(self._get_short_command_name())) def _pack_image(self, tempdir, datadir, name="result"): # advanced options such as numeric-owner are not supported by # python tarfile library - therefore we use the tar command line tool tempresult = "{0}.tar.{1}".format(name, self.config.get_compression()) archive_path = os.path.join(tempdir, tempresult) cmd = [] cmd.append("tar") cmd.append("--numeric-owner") cmd.extend(["-C", datadir]) cmd.extend(["-acf", archive_path]) cmd.extend(os.listdir(datadir)) run(cmd, sudo=True, log_threshold=logging.INFO) return archive_path def _unpack_image(self, image, tempdir, subfolder="rootfs"): target_folder = os.path.join(tempdir, subfolder) os.makedirs(target_folder, exist_ok=True) cmd = [] cmd.append("tar") cmd.append("--numeric-owner") cmd.extend(["-C", target_folder]) cmd.extend(["-axf", image]) run(cmd, sudo=True, log_threshold=logging.INFO) return target_folder
def test_config_nodes_presence(config_files): with open(config_files, "r") as main_file: parser = ConfigurationParser(main_file) assert parser.has_bootstrap_node() assert parser.get_bootstrap_repository() assert parser.get_bootstrap_architecture()
class EdiCommand(metaclass=CommandFactory): def clean(self, config_file): pass def _get_sibling_commands(self): assert len(type(self).__bases__) == 1 commands = [] parent_command = type(self).__bases__[0]._get_command_name() for _, command in get_sub_commands(parent_command).items(): if command != self.__class__: commands.append(command) return commands def _clean_siblings_and_sub_commands(self, config_file): for command in self._get_sibling_commands(): command().clean(config_file) command().clean_sub_commands(config_file) def clean_sub_commands(self, config_file): command = self._get_sub_command("clean") if command: command().clean(config_file) def _setup_parser(self, config_file): self.config = ConfigurationParser(config_file) @classmethod def _get_command_name(cls): return compose_command_name(cls) @classmethod def _get_short_command_name(cls): return cls.__name__.lower() @classmethod def _get_command_file_name_prefix(cls): return cls._get_command_name().replace(".", "_") @classmethod def _add_sub_commands(cls, parser): title = "{} commands".format(cls._get_short_command_name()) subparsers = parser.add_subparsers(title=title, dest="sub_command_name") for _, command in get_sub_commands(cls._get_command_name()).items(): command.advertise(subparsers) def _run_sub_command_cli(self, cli_args): self._get_sub_command(cli_args.sub_command_name)().run_cli(cli_args) def _get_sub_command(self, command): sub_command = "{}.{}".format(self._get_command_name(), command) return get_command(sub_command) @staticmethod def _require_config_file(parser): parser.add_argument('config_file', type=argparse.FileType('r', encoding='UTF-8')) def _require_sudo(self): if os.getuid() != 0: raise FatalError( ("The subcommand '{0}' requires superuser " "privileges.\n" "Use 'sudo edi ...'.").format(self._get_short_command_name())) def _pack_image(self, tempdir, datadir, name="result"): # advanced options such as numeric-owner are not supported by # python tarfile library - therefore we use the tar command line tool tempresult = "{0}.tar.{1}".format(name, self.config.get_compression()) archive_path = os.path.join(tempdir, tempresult) cmd = [] cmd.append("tar") cmd.append("--numeric-owner") cmd.extend(["-C", datadir]) cmd.extend(["-acf", archive_path]) cmd.extend(os.listdir(datadir)) run(cmd, sudo=True) return archive_path def _unpack_image(self, image, tempdir, subfolder="rootfs"): target_folder = os.path.join(tempdir, subfolder) os.makedirs(target_folder, exist_ok=True) cmd = [] cmd.append("tar") cmd.append("--numeric-owner") cmd.extend(["-C", target_folder]) cmd.extend(["-axf", image]) run(cmd, sudo=True) return target_folder