deb-mock/deb_mock/plugins/registry.py
2025-08-03 22:16:04 +00:00

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