Module mfplugin.manager
Functions
def get_logger(*args, **kwargs)
Classes
class PluginsManager (plugins_base_dir=None, configuration_class=None, app_class=None, extra_daemon_class=mfplugin.extra_daemon.ExtraDaemon)
-
Expand source code
class PluginsManager(object): def __init__(self, plugins_base_dir=None, configuration_class=None, app_class=None, extra_daemon_class=ExtraDaemon): self.configuration_class = get_configuration_class(configuration_class, Configuration) """Configuration class.""" self.app_class = get_app_class(app_class, App) """App class.""" self.extra_daemon_class = get_extra_daemon_class(extra_daemon_class, ExtraDaemon) """ExtraDaemon class.""" self.plugins_base_dir = plugins_base_dir \ if plugins_base_dir is not None else get_default_plugins_base_dir() """Plugin base directory (string).""" if not os.path.isdir(self.plugins_base_dir): mkdir_p_or_die(self.plugins_base_dir) self.__loaded = False def make_plugin(self, plugin_home, dont_read_config_overrides=False): return Plugin(self.plugins_base_dir, plugin_home, configuration_class=self.configuration_class, app_class=self.app_class, extra_daemon_class=self.extra_daemon_class, dont_read_config_overrides=dont_read_config_overrides) def get_plugin(self, name): label = plugin_name_to_layerapi2_label(name) home = layerapi2_label_to_plugin_home(self.plugins_base_dir, label) if home is None: raise NotInstalledPlugin("plugin: %s not installed" % name) return self.make_plugin(home) def plugin_env_context(self, name, **kwargs): return self.plugins[name].plugin_env_context(**kwargs) def _preuninstall_plugin(self, plugin): if shutil.which("_plugins.preuninstall"): env_context = { "MFMODULE_PLUGINS_BASE_DIR": self.plugins_base_dir } # FIXME: should be python methods and not shell with PluginEnvContextManager(env_context): x = BashWrapperOrRaise( "_plugins.preuninstall %s %s %s" % (plugin.name, plugin.version, plugin.release)) if len(x.stderr) != 0: print(x.stderr, file=sys.stderr) def _postinstall_plugin(self, plugin): if shutil.which("_plugins.postinstall"): env_context = { "MFMODULE_PLUGINS_BASE_DIR": self.plugins_base_dir } # FIXME: should be python methods and not shell with PluginEnvContextManager(env_context): x = BashWrapperOrRaise( "_plugins.postinstall %s %s %s" % (plugin.name, plugin.version, plugin.release)) if len(x.stderr) != 0: print(x.stderr, file=sys.stderr) def _uninstall_plugin(self, name): p = self.get_plugin(name) preuninstall_exception = None try: self._preuninstall_plugin(p) except Exception as e: preuninstall_exception = e # we keep the exception but we want to continue to remove the # plugin if p.is_dev_linked: os.unlink(p.home) else: shutil.rmtree(p.home, ignore_errors=True) self.__loaded = False try: self.get_plugin(name) except NotInstalledPlugin: pass else: raise CantUninstallPlugin("can't uninstall plugin: %s" % name) if os.path.exists(p.home): raise CantUninstallPlugin("can't uninstall plugin: %s " "(directory still here)" % name) if preuninstall_exception is not None: raise CantUninstallPlugin( "the plugin is uninstalled but we " "found some problems during preuninstall script", original_exception=preuninstall_exception) def __before_install_develop(self, name): try: self.get_plugin(name) except NotInstalledPlugin: pass else: raise AlreadyInstalledPlugin("plugin: %s is already installed" % name) def __after_install_develop(self, name): try: p = self.get_plugin(name) except NotInstalledPlugin: raise CantInstallPlugin("can't install plugin %s" % name) try: # check plugin validity (configuration...) p.load_full() # execute postinstall self._postinstall_plugin(p) except Exception: try: self._uninstall_plugin(p.name) except Exception: pass raise def _install_plugin(self, plugin_filepath, new_name=None): x = PluginFile(plugin_filepath) x.load() self.__before_install_develop(new_name if new_name is not None else x.name) if new_name is not None: name = new_name else: name = x.name try: tf = tarfile.open(plugin_filepath, "r") # extractall without filter is deprecated for Python >= 3.12 # Filter doesn't exist for Python <= 3.8 (it works as # "fully_trusted") # Default filter in Python 3.14 will be "data" # See https://peps.python.org/pep-0706/ try: tf.extractall(self.plugins_base_dir, filter="fully_trusted") except Exception: tf.extractall(self.plugins_base_dir) os.rename(os.path.join(self.plugins_base_dir, "metwork_plugin"), os.path.join(self.plugins_base_dir, name)) except Exception as e: raise CantInstallPlugin("can't install plugin %s" % x.name, original_exception=e) if new_name: lalpath = os.path.join(self.plugins_base_dir, name, ".layerapi2_label") with open(lalpath, "w") as f: f.write(plugin_name_to_layerapi2_label(new_name) + "\n") self.__loaded = False self.__after_install_develop(new_name if new_name is not None else x.name) def _develop_plugin(self, plugin_home): p = self.make_plugin(plugin_home) self.__before_install_develop(p.name) shutil.rmtree(os.path.join(self.plugins_base_dir, p.name), True) try: os.symlink(p.home, os.path.join(self.plugins_base_dir, p.name)) except OSError: pass self.__loaded = False self.__after_install_develop(p.name) @with_lock def install_plugin(self, plugin_filepath, new_name=None): """Install a plugin from a .plugin file. Args: plugin_filepath (string): the plugin file path. new_name (string): alternate plugin name if specified. Raises: BadPluginFile: if the .plugin file is not found or a bad one. AlreadyInstalledPlugin: if the plugin is already installed. CantInstallPlugin: if the plugin can't be installed. """ self._install_plugin(plugin_filepath, new_name=new_name) @with_lock def uninstall_plugin(self, name): """Uninstall a plugin. Args: name (string): the plugin name to uninstall. Raises: NotInstalledPlugin: if the plugin is not installed CantUninstallPlugin: if the plugin can't be uninstalled. """ self._uninstall_plugin(name) @with_lock def develop_plugin(self, plugin_home): """Install a plugin in development mode. Args: plugin_path (string): the plugin path to install. Raises: AlreadyInstalledPlugin: if the plugin is already installed. BadPlugin: if the provided plugin is bad. CantInstallPlugin: if the plugin can't be installed. """ self._develop_plugin(plugin_home) def repackage_plugin(self, name): p = self.get_plugin(name) p.load_full() if p.is_dev_linked: raise Exception("can't repackage a devlinked plugin") tmpdir = os.path.join(MFMODULE_RUNTIME_HOME, "tmp", "plugin_%s" % get_unique_hexa_identifier()) shutil.copytree(p.home, tmpdir, symlinks=True) # FIXME: ? clean ? newp = self.make_plugin(tmpdir, dont_read_config_overrides=True) newp.load_full() x = configupdater.ConfigUpdater(delimiters=('=',), comment_prefixes=('#',)) x.optionxform = str x.read("%s/config.ini" % tmpdir) sections = p.configuration._doc.keys() for section in sections: for option in p.configuration._doc[section].keys(): if option.startswith('_'): continue val = p.configuration._doc[section][option] try: newval = newp.configuration._doc[section][option] except Exception: # probably a new section try: x.add_section(section) except Exception: pass newval = None try: if newval is None: print("NEW [%s]/%s = %s" % (section, option, val), file=sys.stderr) else: if newval == val: continue print("CHANGED [%s]/%s: %s => %s" % (section, option, newval, val), file=sys.stderr) if isinstance(val, bool): x.set(section, option, "1" if val else "0") else: x.set(section, option, val) except Exception: pass x.update_file() new_p = self.make_plugin(tmpdir) return new_p.build() def load(self): if self.__loaded: return self.__loaded = True self._plugins = {} for directory in glob.glob(os.path.join(self.plugins_base_dir, "*")): dname = os.path.basename(directory) if dname == "base": # special directory (not a plugin one) continue try: plugin = self.make_plugin(directory) except BadPlugin as e: get_logger().warning("found bad plugin in %s => ignoring it " "(details: %s)" % (directory, e)) continue self._plugins[plugin.name] = plugin def load_full(self): self.load() [x.load_full() for x in self.plugins.values()] @property def plugins(self): self.load() return self._plugins
Instance variables
var app_class
-
App class.
var configuration_class
-
Configuration class.
var extra_daemon_class
-
ExtraDaemon class.
prop plugins
-
Expand source code
@property def plugins(self): self.load() return self._plugins
var plugins_base_dir
-
Plugin base directory (string).
Methods
def develop_plugin(self, plugin_home)
-
Install a plugin in development mode.
Args
plugin_path
:string
- the plugin path to install.
Raises
AlreadyInstalledPlugin
- if the plugin is already installed.
BadPlugin
- if the provided plugin is bad.
CantInstallPlugin
- if the plugin can't be installed.
def get_plugin(self, name)
def install_plugin(self, plugin_filepath, new_name=None)
-
Install a plugin from a .plugin file.
Args
plugin_filepath
:string
- the plugin file path.
new_name
:string
- alternate plugin name if specified.
Raises
BadPluginFile
- if the .plugin file is not found or a bad one.
AlreadyInstalledPlugin
- if the plugin is already installed.
CantInstallPlugin
- if the plugin can't be installed.
def load(self)
def load_full(self)
def make_plugin(self, plugin_home, dont_read_config_overrides=False)
def plugin_env_context(self, name, **kwargs)
def repackage_plugin(self, name)
def uninstall_plugin(self, name)
-
Uninstall a plugin.
Args
name
:string
- the plugin name to uninstall.
Raises
NotInstalledPlugin
- if the plugin is not installed
CantUninstallPlugin
- if the plugin can't be uninstalled.