- Fix environment variable handling in sbuild wrapper - Remove unsupported --log-dir and --env options from sbuild command - Clean up unused imports and fix linting issues - Organize examples directory with official Debian hello package - Fix YAML formatting (trailing spaces, newlines) - Remove placeholder example files - All tests passing (30/30) - Successfully tested build with official Debian hello package
361 lines
12 KiB
Python
361 lines
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 importlib
|
|
import logging
|
|
from typing import Any, Dict, Optional, Type
|
|
|
|
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("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
|