deb-mock/deb_mock/plugin.py
robojerk c51819c836
Some checks failed
Build Deb-Mock Package / build (push) Failing after 1m9s
Lint Code / Lint All Code (push) Failing after 1s
Test Deb-Mock Build / test (push) Failing after 35s
Add comprehensive testing framework, performance monitoring, and plugin system
- 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
2025-08-19 20:49:32 -07:00

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)