334 lines
No EOL
12 KiB
Python
334 lines
No EOL
12 KiB
Python
"""
|
|
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 |