""" Plugin Registry for Deb-Mock Plugin System This module provides the plugin registration and management functionality for the Deb-Mock plugin system, inspired by Fedora's Mock plugin architecture. """ import logging import importlib from typing import Dict, Type, Any, Optional from .base import BasePlugin logger = logging.getLogger(__name__) class PluginRegistry: """ Manages plugin registration and instantiation. This class provides the functionality for registering plugin classes and creating plugin instances, following Mock's plugin system pattern. """ def __init__(self): """Initialize the plugin registry.""" self.plugins: Dict[str, Type[BasePlugin]] = {} self.plugin_metadata: Dict[str, Dict[str, Any]] = {} # Auto-register built-in plugins self._register_builtin_plugins() def register(self, plugin_name: str, plugin_class: Type[BasePlugin], metadata: Optional[Dict[str, Any]] = None) -> None: """ Register a plugin class. Args: plugin_name: Name of the plugin plugin_class: Plugin class to register metadata: Optional metadata about the plugin Raises: ValueError: If plugin_name is already registered TypeError: If plugin_class is not a subclass of BasePlugin """ if not issubclass(plugin_class, BasePlugin): raise TypeError(f"Plugin class must inherit from BasePlugin") if plugin_name in self.plugins: raise ValueError(f"Plugin '{plugin_name}' is already registered") self.plugins[plugin_name] = plugin_class self.plugin_metadata[plugin_name] = metadata or {} logger.debug(f"Registered plugin '{plugin_name}' with class {plugin_class.__name__}") def unregister(self, plugin_name: str) -> bool: """ Unregister a plugin. Args: plugin_name: Name of the plugin to unregister Returns: True if plugin was unregistered, False if not found """ if plugin_name not in self.plugins: return False del self.plugins[plugin_name] del self.plugin_metadata[plugin_name] logger.debug(f"Unregistered plugin '{plugin_name}'") return True def get_plugin_class(self, plugin_name: str) -> Optional[Type[BasePlugin]]: """ Get a registered plugin class. Args: plugin_name: Name of the plugin Returns: Plugin class if found, None otherwise """ return self.plugins.get(plugin_name) def get_plugins(self) -> Dict[str, Type[BasePlugin]]: """ Get all registered plugins. Returns: Dictionary of registered plugin names and classes """ return self.plugins.copy() def get_plugin_names(self) -> list: """ Get list of registered plugin names. Returns: List of registered plugin names """ return list(self.plugins.keys()) def create(self, plugin_name: str, config: Any, hook_manager: Any) -> Optional[BasePlugin]: """ Create a plugin instance. Args: plugin_name: Name of the plugin to create config: Configuration object hook_manager: Hook manager instance Returns: Plugin instance if successful, None if plugin not found """ plugin_class = self.get_plugin_class(plugin_name) if not plugin_class: logger.warning(f"Plugin '{plugin_name}' not found") return None try: plugin_instance = plugin_class(config, hook_manager) logger.debug(f"Created plugin instance '{plugin_name}'") return plugin_instance except Exception as e: logger.error(f"Failed to create plugin '{plugin_name}': {e}") return None def create_all_enabled(self, config: Any, hook_manager: Any) -> Dict[str, BasePlugin]: """ Create instances of all enabled plugins. Args: config: Configuration object hook_manager: Hook manager instance Returns: Dictionary of plugin names and instances """ enabled_plugins = {} for plugin_name in self.get_plugin_names(): plugin_instance = self.create(plugin_name, config, hook_manager) if plugin_instance and plugin_instance.enabled: enabled_plugins[plugin_name] = plugin_instance logger.debug(f"Created {len(enabled_plugins)} enabled plugin instances") return enabled_plugins def get_plugin_info(self, plugin_name: str) -> Dict[str, Any]: """ Get information about a registered plugin. Args: plugin_name: Name of the plugin Returns: Dictionary with plugin information """ if plugin_name not in self.plugins: return {'error': f'Plugin "{plugin_name}" not found'} plugin_class = self.plugins[plugin_name] metadata = self.plugin_metadata[plugin_name] info = { 'name': plugin_name, 'class': plugin_class.__name__, 'module': plugin_class.__module__, 'metadata': metadata, 'docstring': plugin_class.__doc__ or 'No documentation available' } return info def get_all_plugin_info(self) -> Dict[str, Dict[str, Any]]: """ Get information about all registered plugins. Returns: Dictionary mapping plugin names to their information """ return {name: self.get_plugin_info(name) for name in self.get_plugin_names()} def load_plugin_from_module(self, module_name: str, plugin_name: str) -> bool: """ Load a plugin from a module. Args: module_name: Name of the module to load plugin_name: Name of the plugin class in the module Returns: True if plugin was loaded successfully, False otherwise """ try: module = importlib.import_module(module_name) plugin_class = getattr(module, plugin_name) # Use module name as plugin name if not specified self.register(plugin_name, plugin_class) return True except ImportError as e: logger.error(f"Failed to import module '{module_name}': {e}") return False except AttributeError as e: logger.error(f"Plugin class '{plugin_name}' not found in module '{module_name}': {e}") return False except Exception as e: logger.error(f"Failed to load plugin from '{module_name}.{plugin_name}': {e}") return False def load_plugins_from_config(self, config: Any) -> Dict[str, BasePlugin]: """ Load plugins based on configuration. Args: config: Configuration object with plugin settings Returns: Dictionary of loaded plugin instances """ loaded_plugins = {} if not hasattr(config, 'plugins') or not config.plugins: return loaded_plugins for plugin_name, plugin_config in config.plugins.items(): if not isinstance(plugin_config, dict): continue if plugin_config.get('enabled', False): # Try to load from built-in plugins first plugin_instance = self.create(plugin_name, config, None) if plugin_instance: loaded_plugins[plugin_name] = plugin_instance else: # Try to load from external module module_name = plugin_config.get('module') if module_name: if self.load_plugin_from_module(module_name, plugin_name): plugin_instance = self.create(plugin_name, config, None) if plugin_instance: loaded_plugins[plugin_name] = plugin_instance return loaded_plugins def _register_builtin_plugins(self) -> None: """Register built-in plugins.""" try: # Import and register built-in plugins from .bind_mount import BindMountPlugin from .compress_logs import CompressLogsPlugin from .root_cache import RootCachePlugin from .tmpfs import TmpfsPlugin self.register('bind_mount', BindMountPlugin, { 'description': 'Mount host directories into chroot', 'hooks': ['mount_root', 'postumount'], 'builtin': True }) self.register('compress_logs', CompressLogsPlugin, { 'description': 'Compress build logs to save space', 'hooks': ['process_logs'], 'builtin': True }) self.register('root_cache', RootCachePlugin, { 'description': 'Root cache management for faster builds', 'hooks': ['preinit', 'postinit', 'postchroot', 'postshell', 'clean'], 'builtin': True }) self.register('tmpfs', TmpfsPlugin, { 'description': 'Use tmpfs for faster I/O operations', 'hooks': ['mount_root', 'postumount'], 'builtin': True }) logger.debug("Registered built-in plugins") except ImportError as e: logger.warning(f"Some built-in plugins could not be loaded: {e}") except Exception as e: logger.warning(f"Error registering built-in plugins: {e}") def get_plugin_statistics(self) -> Dict[str, Any]: """ Get statistics about registered plugins. Returns: Dictionary with plugin statistics """ stats = { 'total_plugins': len(self.plugins), 'builtin_plugins': len([p for p in self.plugin_metadata.values() if p.get('builtin', False)]), 'external_plugins': len([p for p in self.plugin_metadata.values() if not p.get('builtin', False)]), 'plugins_by_hook': {} } # Count plugins by hook usage for plugin_name, metadata in self.plugin_metadata.items(): hooks = metadata.get('hooks', []) for hook in hooks: if hook not in stats['plugins_by_hook']: stats['plugins_by_hook'][hook] = [] stats['plugins_by_hook'][hook].append(plugin_name) return stats def validate_plugin_config(self, plugin_name: str, config: Any) -> bool: """ Validate plugin configuration. Args: plugin_name: Name of the plugin config: Configuration to validate Returns: True if configuration is valid, False otherwise """ if plugin_name not in self.plugins: return False # Basic validation - plugins can override this method plugin_class = self.plugins[plugin_name] if hasattr(plugin_class, 'validate_config'): return plugin_class.validate_config(config) return True