- Add complete pytest testing framework with conftest.py and test files - Add performance monitoring and benchmarking capabilities - Add plugin system with ccache plugin example - Add comprehensive documentation (API, deployment, testing, etc.) - Add Docker API wrapper for service deployment - Add advanced configuration examples - Remove old wget package file - Update core modules with enhanced functionality
248 lines
8.8 KiB
Python
248 lines
8.8 KiB
Python
"""
|
|
Plugin system for deb-mock
|
|
Based on Fedora Mock's plugin architecture
|
|
"""
|
|
|
|
import importlib.machinery
|
|
import importlib.util
|
|
import sys
|
|
import os
|
|
import logging
|
|
from typing import Dict, List, Any, Callable, Optional
|
|
from pathlib import Path
|
|
|
|
from .exceptions import PluginError
|
|
|
|
|
|
class PluginManager:
|
|
"""Manages plugins for deb-mock"""
|
|
|
|
# Current API version
|
|
CURRENT_API_VERSION = "1.0"
|
|
|
|
def __init__(self, config):
|
|
self.config = config
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
# Plugin configuration
|
|
self.plugins = getattr(config, 'plugins', [])
|
|
self.plugin_conf = getattr(config, 'plugin_conf', {})
|
|
self.plugin_dir = getattr(config, 'plugin_dir', '/usr/share/deb-mock/plugins')
|
|
|
|
# Hook system
|
|
self._hooks = {}
|
|
self._initialized_plugins = []
|
|
|
|
# Plugin state tracking
|
|
self.already_initialized = False
|
|
|
|
def __repr__(self):
|
|
return f"<deb_mock.plugin.PluginManager: plugins={len(self.plugins)}, hooks={len(self._hooks)}>"
|
|
|
|
def init_plugins(self, deb_mock):
|
|
"""Initialize all enabled plugins"""
|
|
if self.already_initialized:
|
|
return
|
|
|
|
self.already_initialized = True
|
|
self.logger.info("Initializing plugins...")
|
|
|
|
# Update plugin configuration with deb-mock context
|
|
for key in list(self.plugin_conf.keys()):
|
|
if key.endswith('_opts'):
|
|
self.plugin_conf[key].update({
|
|
'basedir': getattr(deb_mock.config, 'basedir', '/var/lib/deb-mock'),
|
|
'chroot_dir': deb_mock.config.chroot_dir,
|
|
'output_dir': deb_mock.config.output_dir,
|
|
'cache_dir': deb_mock.config.cache_dir,
|
|
})
|
|
|
|
# Import and initialize plugins
|
|
for plugin_name in self.plugins:
|
|
if self.plugin_conf.get(f"{plugin_name}_enable", True):
|
|
try:
|
|
self._load_plugin(plugin_name, deb_mock)
|
|
except Exception as e:
|
|
self.logger.error(f"Failed to load plugin {plugin_name}: {e}")
|
|
if self.plugin_conf.get(f"{plugin_name}_required", False):
|
|
raise PluginError(f"Required plugin {plugin_name} failed to load: {e}")
|
|
|
|
self.logger.info(f"Plugin initialization complete. Loaded {len(self._initialized_plugins)} plugins")
|
|
|
|
def _load_plugin(self, plugin_name: str, deb_mock):
|
|
"""Load and initialize a single plugin"""
|
|
self.logger.debug(f"Loading plugin: {plugin_name}")
|
|
|
|
# Find plugin module
|
|
spec = importlib.machinery.PathFinder.find_spec(plugin_name, [self.plugin_dir])
|
|
if not spec:
|
|
# Try to find in local plugins directory
|
|
local_plugin_dir = os.path.join(os.getcwd(), 'plugins')
|
|
spec = importlib.machinery.PathFinder.find_spec(plugin_name, [local_plugin_dir])
|
|
|
|
if not spec:
|
|
raise PluginError(f"Plugin {plugin_name} not found in {self.plugin_dir} or local plugins directory")
|
|
|
|
# Load plugin module
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
sys.modules[spec.name] = module
|
|
|
|
# Validate plugin API version
|
|
if not hasattr(module, 'requires_api_version'):
|
|
raise PluginError(f'Plugin "{plugin_name}" doesn\'t specify required API version')
|
|
|
|
requested_api_version = module.requires_api_version
|
|
if requested_api_version != self.CURRENT_API_VERSION:
|
|
raise PluginError(f'Plugin version mismatch - requested = {requested_api_version}, current = {self.CURRENT_API_VERSION}')
|
|
|
|
# Check if plugin should run in bootstrap chroots
|
|
run_in_bootstrap = getattr(module, "run_in_bootstrap", True)
|
|
|
|
# Initialize plugin
|
|
plugin_conf = self.plugin_conf.get(f"{plugin_name}_opts", {})
|
|
module.init(self, plugin_conf, deb_mock)
|
|
|
|
self._initialized_plugins.append(plugin_name)
|
|
self.logger.info(f"Plugin {plugin_name} loaded successfully")
|
|
|
|
def call_hooks(self, stage: str, *args, **kwargs):
|
|
"""Call all hooks registered for a specific stage"""
|
|
required = kwargs.pop('required', False)
|
|
hooks = self._hooks.get(stage, [])
|
|
|
|
if required and not hooks:
|
|
raise PluginError(f"Feature {stage} is not provided by any of enabled plugins")
|
|
|
|
self.logger.debug(f"Calling {len(hooks)} hooks for stage: {stage}")
|
|
|
|
for hook in hooks:
|
|
try:
|
|
hook(*args, **kwargs)
|
|
except Exception as e:
|
|
self.logger.error(f"Hook {hook.__name__} failed for stage {stage}: {e}")
|
|
if required:
|
|
raise PluginError(f"Required hook {hook.__name__} failed: {e}")
|
|
|
|
def add_hook(self, stage: str, function: Callable):
|
|
"""Add a hook function for a specific stage"""
|
|
if stage not in self._hooks:
|
|
self._hooks[stage] = []
|
|
|
|
if function not in self._hooks[stage]:
|
|
self._hooks[stage].append(function)
|
|
self.logger.debug(f"Added hook {function.__name__} for stage {stage}")
|
|
|
|
def remove_hook(self, stage: str, function: Callable):
|
|
"""Remove a hook function from a specific stage"""
|
|
if stage in self._hooks and function in self._hooks[stage]:
|
|
self._hooks[stage].remove(function)
|
|
self.logger.debug(f"Removed hook {function.__name__} from stage {stage}")
|
|
|
|
def get_hooks(self, stage: str) -> List[Callable]:
|
|
"""Get all hooks registered for a specific stage"""
|
|
return self._hooks.get(stage, [])
|
|
|
|
def list_stages(self) -> List[str]:
|
|
"""List all available hook stages"""
|
|
return list(self._hooks.keys())
|
|
|
|
def get_plugin_info(self) -> Dict[str, Any]:
|
|
"""Get information about loaded plugins"""
|
|
return {
|
|
'total_plugins': len(self.plugins),
|
|
'loaded_plugins': self._initialized_plugins,
|
|
'available_stages': self.list_stages(),
|
|
'plugin_dir': self.plugin_dir,
|
|
'api_version': self.CURRENT_API_VERSION
|
|
}
|
|
|
|
|
|
# Standard hook stages for deb-mock
|
|
class HookStages:
|
|
"""Standard hook stages for deb-mock plugins"""
|
|
|
|
# Chroot lifecycle
|
|
PRECHROOT_INIT = "prechroot_init"
|
|
POSTCHROOT_INIT = "postchroot_init"
|
|
PRECHROOT_CLEAN = "prechroot_clean"
|
|
POSTCHROOT_CLEAN = "postchroot_clean"
|
|
|
|
# Build lifecycle
|
|
PREBUILD = "prebuild"
|
|
POSTBUILD = "postbuild"
|
|
BUILD_START = "build_start"
|
|
BUILD_END = "build_end"
|
|
|
|
# Package management
|
|
PRE_INSTALL_DEPS = "pre_install_deps"
|
|
POST_INSTALL_DEPS = "post_install_deps"
|
|
PRE_INSTALL_PACKAGE = "pre_install_package"
|
|
POST_INSTALL_PACKAGE = "post_install_package"
|
|
|
|
# Mount management
|
|
PRE_MOUNT = "pre_mount"
|
|
POST_MOUNT = "post_mount"
|
|
PRE_UNMOUNT = "pre_unmount"
|
|
POST_UNMOUNT = "post_unmount"
|
|
|
|
# Cache management
|
|
PRE_CACHE_CREATE = "pre_cache_create"
|
|
POST_CACHE_CREATE = "post_cache_create"
|
|
PRE_CACHE_RESTORE = "pre_cache_restore"
|
|
POST_CACHE_RESTORE = "post_cache_restore"
|
|
|
|
# Parallel build hooks
|
|
PRE_PARALLEL_BUILD = "pre_parallel_build"
|
|
POST_PARALLEL_BUILD = "post_parallel_build"
|
|
PARALLEL_BUILD_START = "parallel_build_start"
|
|
PARALLEL_BUILD_END = "parallel_build_end"
|
|
|
|
# Error handling
|
|
ON_ERROR = "on_error"
|
|
ON_WARNING = "on_warning"
|
|
|
|
# Custom stages can be added by plugins
|
|
CUSTOM = "custom"
|
|
|
|
|
|
# Plugin base class for easier plugin development
|
|
class BasePlugin:
|
|
"""Base class for deb-mock plugins"""
|
|
|
|
def __init__(self, plugin_manager, config, deb_mock):
|
|
self.plugin_manager = plugin_manager
|
|
self.config = config
|
|
self.deb_mock = deb_mock
|
|
self.logger = logging.getLogger(f"deb_mock.plugin.{self.__class__.__name__}")
|
|
|
|
# Register hooks
|
|
self._register_hooks()
|
|
|
|
def _register_hooks(self):
|
|
"""Override this method to register hooks"""
|
|
pass
|
|
|
|
def get_config(self, key: str, default=None):
|
|
"""Get plugin configuration value"""
|
|
return self.config.get(key, default)
|
|
|
|
def set_config(self, key: str, value):
|
|
"""Set plugin configuration value"""
|
|
self.config[key] = value
|
|
|
|
def log_info(self, message: str):
|
|
"""Log info message"""
|
|
self.logger.info(message)
|
|
|
|
def log_warning(self, message: str):
|
|
"""Log warning message"""
|
|
self.logger.warning(message)
|
|
|
|
def log_error(self, message: str):
|
|
"""Log error message"""
|
|
self.logger.error(message)
|
|
|
|
def log_debug(self, message: str):
|
|
"""Log debug message"""
|
|
self.logger.debug(message)
|