Add stable Python API and comprehensive environment management
- Add MockAPIClient and MockEnvironment for external integration - Implement EnvironmentManager with full lifecycle support - Enhance plugin system with registry and BasePlugin class - Add comprehensive test suite and documentation - Include practical usage examples and plugin development guide
This commit is contained in:
parent
c51819c836
commit
8c585e2e33
9 changed files with 3413 additions and 1267 deletions
|
|
@ -13,10 +13,27 @@ from .chroot import ChrootManager
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .core import DebMock
|
from .core import DebMock
|
||||||
from .sbuild import SbuildWrapper
|
from .sbuild import SbuildWrapper
|
||||||
|
from .api import MockAPIClient, MockEnvironment, MockConfigBuilder, create_client, create_config, quick_build
|
||||||
|
from .environment_manager import EnvironmentManager, EnvironmentInfo, BuildResult, create_environment_manager
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
# Core classes
|
||||||
"DebMock",
|
"DebMock",
|
||||||
"Config",
|
"Config",
|
||||||
"ChrootManager",
|
"ChrootManager",
|
||||||
"SbuildWrapper",
|
"SbuildWrapper",
|
||||||
|
|
||||||
|
# API classes
|
||||||
|
"MockAPIClient",
|
||||||
|
"MockEnvironment",
|
||||||
|
"MockConfigBuilder",
|
||||||
|
"EnvironmentManager",
|
||||||
|
"EnvironmentInfo",
|
||||||
|
"BuildResult",
|
||||||
|
|
||||||
|
# Convenience functions
|
||||||
|
"create_client",
|
||||||
|
"create_config",
|
||||||
|
"create_environment_manager",
|
||||||
|
"quick_build",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
427
deb_mock/api.py
Normal file
427
deb_mock/api.py
Normal file
|
|
@ -0,0 +1,427 @@
|
||||||
|
"""
|
||||||
|
Stable Python API for deb-mock integration
|
||||||
|
|
||||||
|
This module provides a stable, well-documented API for external tools
|
||||||
|
to integrate with deb-mock for build environment management.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Any, Optional, Union
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from .core import DebMock
|
||||||
|
from .config import Config
|
||||||
|
from .exceptions import ConfigurationError, ChrootError, SbuildError
|
||||||
|
|
||||||
|
|
||||||
|
class MockEnvironment:
|
||||||
|
"""Represents a mock environment for building packages"""
|
||||||
|
|
||||||
|
def __init__(self, name: str, deb_mock: DebMock):
|
||||||
|
self.name = name
|
||||||
|
self.deb_mock = deb_mock
|
||||||
|
self._active = False
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
"""Context manager entry"""
|
||||||
|
self.activate()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
"""Context manager exit"""
|
||||||
|
self.deactivate()
|
||||||
|
|
||||||
|
def activate(self):
|
||||||
|
"""Activate the environment"""
|
||||||
|
if not self.deb_mock.chroot_manager.chroot_exists(self.name):
|
||||||
|
raise ChrootError(f"Environment '{self.name}' does not exist")
|
||||||
|
self._active = True
|
||||||
|
|
||||||
|
def deactivate(self):
|
||||||
|
"""Deactivate the environment"""
|
||||||
|
self._active = False
|
||||||
|
|
||||||
|
def is_active(self) -> bool:
|
||||||
|
"""Check if environment is active"""
|
||||||
|
return self._active
|
||||||
|
|
||||||
|
def execute(self, command: Union[str, List[str]],
|
||||||
|
capture_output: bool = True,
|
||||||
|
check: bool = True) -> subprocess.CompletedProcess:
|
||||||
|
"""Execute a command in the environment"""
|
||||||
|
if not self._active:
|
||||||
|
raise RuntimeError("Environment is not active")
|
||||||
|
|
||||||
|
if isinstance(command, str):
|
||||||
|
command = command.split()
|
||||||
|
|
||||||
|
return self.deb_mock.chroot_manager.execute_in_chroot(
|
||||||
|
self.name, command, capture_output=capture_output
|
||||||
|
)
|
||||||
|
|
||||||
|
def install_packages(self, packages: List[str]) -> Dict[str, Any]:
|
||||||
|
"""Install packages in the environment"""
|
||||||
|
if not self._active:
|
||||||
|
raise RuntimeError("Environment is not active")
|
||||||
|
|
||||||
|
return self.deb_mock.install_packages(packages)
|
||||||
|
|
||||||
|
def copy_in(self, source: str, destination: str) -> None:
|
||||||
|
"""Copy files into the environment"""
|
||||||
|
if not self._active:
|
||||||
|
raise RuntimeError("Environment is not active")
|
||||||
|
|
||||||
|
self.deb_mock.chroot_manager.copy_to_chroot(source, destination, self.name)
|
||||||
|
|
||||||
|
def copy_out(self, source: str, destination: str) -> None:
|
||||||
|
"""Copy files out of the environment"""
|
||||||
|
if not self._active:
|
||||||
|
raise RuntimeError("Environment is not active")
|
||||||
|
|
||||||
|
self.deb_mock.chroot_manager.copy_from_chroot(source, destination, self.name)
|
||||||
|
|
||||||
|
def get_info(self) -> Dict[str, Any]:
|
||||||
|
"""Get information about the environment"""
|
||||||
|
return self.deb_mock.chroot_manager.get_chroot_info(self.name)
|
||||||
|
|
||||||
|
|
||||||
|
class MockAPIClient:
|
||||||
|
"""
|
||||||
|
Stable API client for deb-mock integration
|
||||||
|
|
||||||
|
This class provides a stable interface for external tools to interact
|
||||||
|
with deb-mock for build environment management.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config: Optional[Config] = None):
|
||||||
|
"""
|
||||||
|
Initialize the API client
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: Optional configuration object. If None, uses default config.
|
||||||
|
"""
|
||||||
|
if config is None:
|
||||||
|
config = Config.default()
|
||||||
|
|
||||||
|
self.config = config
|
||||||
|
self.deb_mock = DebMock(config)
|
||||||
|
self._environments = {}
|
||||||
|
|
||||||
|
def create_environment(self, name: str,
|
||||||
|
arch: str = None,
|
||||||
|
suite: str = None,
|
||||||
|
packages: List[str] = None) -> MockEnvironment:
|
||||||
|
"""
|
||||||
|
Create a new mock environment
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name for the environment
|
||||||
|
arch: Target architecture (defaults to config.architecture)
|
||||||
|
suite: Debian suite (defaults to config.suite)
|
||||||
|
packages: List of packages to install initially
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MockEnvironment instance
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Create the chroot environment
|
||||||
|
self.deb_mock.init_chroot(name, arch, suite)
|
||||||
|
|
||||||
|
# Install initial packages if specified
|
||||||
|
if packages:
|
||||||
|
self.deb_mock.install_packages(packages)
|
||||||
|
|
||||||
|
# Create environment wrapper
|
||||||
|
env = MockEnvironment(name, self.deb_mock)
|
||||||
|
self._environments[name] = env
|
||||||
|
|
||||||
|
return env
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Failed to create environment '{name}': {e}")
|
||||||
|
|
||||||
|
def get_environment(self, name: str) -> MockEnvironment:
|
||||||
|
"""
|
||||||
|
Get an existing environment
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name of the environment
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MockEnvironment instance
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If environment doesn't exist
|
||||||
|
"""
|
||||||
|
if name not in self._environments:
|
||||||
|
if not self.deb_mock.chroot_manager.chroot_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
|
||||||
|
# Create wrapper for existing environment
|
||||||
|
env = MockEnvironment(name, self.deb_mock)
|
||||||
|
self._environments[name] = env
|
||||||
|
|
||||||
|
return self._environments[name]
|
||||||
|
|
||||||
|
def list_environments(self) -> List[str]:
|
||||||
|
"""List all available environments"""
|
||||||
|
return self.deb_mock.list_chroots()
|
||||||
|
|
||||||
|
def remove_environment(self, name: str) -> None:
|
||||||
|
"""Remove an environment"""
|
||||||
|
if name in self._environments:
|
||||||
|
del self._environments[name]
|
||||||
|
|
||||||
|
self.deb_mock.clean_chroot(name)
|
||||||
|
|
||||||
|
def build_package(self, source_package: str,
|
||||||
|
environment: str = None,
|
||||||
|
output_dir: str = None,
|
||||||
|
**kwargs) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Build a package in a mock environment
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_package: Path to source package (.dsc file or directory)
|
||||||
|
environment: Environment name (uses default if None)
|
||||||
|
output_dir: Output directory for artifacts
|
||||||
|
**kwargs: Additional build options
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Build result dictionary
|
||||||
|
"""
|
||||||
|
if environment:
|
||||||
|
kwargs['chroot_name'] = environment
|
||||||
|
|
||||||
|
if output_dir:
|
||||||
|
kwargs['output_dir'] = output_dir
|
||||||
|
|
||||||
|
return self.deb_mock.build(source_package, **kwargs)
|
||||||
|
|
||||||
|
def build_parallel(self, source_packages: List[str],
|
||||||
|
max_workers: int = None,
|
||||||
|
**kwargs) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Build multiple packages in parallel
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_packages: List of source package paths
|
||||||
|
max_workers: Maximum number of parallel workers
|
||||||
|
**kwargs: Additional build options
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of build results
|
||||||
|
"""
|
||||||
|
return self.deb_mock.build_parallel(source_packages, max_workers, **kwargs)
|
||||||
|
|
||||||
|
def build_chain(self, source_packages: List[str], **kwargs) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Build a chain of packages that depend on each other
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_packages: List of source package paths in dependency order
|
||||||
|
**kwargs: Additional build options
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of build results
|
||||||
|
"""
|
||||||
|
return self.deb_mock.build_chain(source_packages, **kwargs)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def environment(self, name: str,
|
||||||
|
arch: str = None,
|
||||||
|
suite: str = None,
|
||||||
|
packages: List[str] = None):
|
||||||
|
"""
|
||||||
|
Context manager for environment operations
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Environment name
|
||||||
|
arch: Target architecture
|
||||||
|
suite: Debian suite
|
||||||
|
packages: Initial packages to install
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
MockEnvironment instance
|
||||||
|
"""
|
||||||
|
env = None
|
||||||
|
try:
|
||||||
|
# Try to get existing environment first
|
||||||
|
try:
|
||||||
|
env = self.get_environment(name)
|
||||||
|
except ValueError:
|
||||||
|
# Create new environment if it doesn't exist
|
||||||
|
env = self.create_environment(name, arch, suite, packages)
|
||||||
|
|
||||||
|
env.activate()
|
||||||
|
yield env
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if env:
|
||||||
|
env.deactivate()
|
||||||
|
|
||||||
|
def get_cache_stats(self) -> Dict[str, Any]:
|
||||||
|
"""Get cache statistics"""
|
||||||
|
return self.deb_mock.get_cache_stats()
|
||||||
|
|
||||||
|
def cleanup_caches(self) -> Dict[str, int]:
|
||||||
|
"""Clean up old cache files"""
|
||||||
|
return self.deb_mock.cleanup_caches()
|
||||||
|
|
||||||
|
def get_performance_summary(self) -> Dict[str, Any]:
|
||||||
|
"""Get performance monitoring summary"""
|
||||||
|
if hasattr(self.deb_mock, 'performance_monitor'):
|
||||||
|
return self.deb_mock.performance_monitor.get_performance_summary()
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def export_metrics(self, output_file: str = None) -> str:
|
||||||
|
"""Export performance metrics to file"""
|
||||||
|
if hasattr(self.deb_mock, 'performance_monitor'):
|
||||||
|
return self.deb_mock.performance_monitor.export_metrics(output_file)
|
||||||
|
raise RuntimeError("Performance monitoring not available")
|
||||||
|
|
||||||
|
|
||||||
|
class MockConfigBuilder:
|
||||||
|
"""Builder class for creating mock configurations"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._config = {}
|
||||||
|
|
||||||
|
def environment(self, name: str) -> 'MockConfigBuilder':
|
||||||
|
"""Set environment name"""
|
||||||
|
self._config['chroot_name'] = name
|
||||||
|
return self
|
||||||
|
|
||||||
|
def architecture(self, arch: str) -> 'MockConfigBuilder':
|
||||||
|
"""Set target architecture"""
|
||||||
|
self._config['architecture'] = arch
|
||||||
|
return self
|
||||||
|
|
||||||
|
def suite(self, suite: str) -> 'MockConfigBuilder':
|
||||||
|
"""Set Debian suite"""
|
||||||
|
self._config['suite'] = suite
|
||||||
|
return self
|
||||||
|
|
||||||
|
def mirror(self, url: str) -> 'MockConfigBuilder':
|
||||||
|
"""Set package mirror URL"""
|
||||||
|
self._config['mirror'] = url
|
||||||
|
return self
|
||||||
|
|
||||||
|
def packages(self, packages: List[str]) -> 'MockConfigBuilder':
|
||||||
|
"""Set initial packages to install"""
|
||||||
|
self._config['chroot_additional_packages'] = packages
|
||||||
|
return self
|
||||||
|
|
||||||
|
def output_dir(self, path: str) -> 'MockConfigBuilder':
|
||||||
|
"""Set output directory"""
|
||||||
|
self._config['output_dir'] = path
|
||||||
|
return self
|
||||||
|
|
||||||
|
def cache_enabled(self, enabled: bool = True) -> 'MockConfigBuilder':
|
||||||
|
"""Enable/disable caching"""
|
||||||
|
self._config['use_root_cache'] = enabled
|
||||||
|
return self
|
||||||
|
|
||||||
|
def parallel_jobs(self, jobs: int) -> 'MockConfigBuilder':
|
||||||
|
"""Set number of parallel jobs"""
|
||||||
|
self._config['parallel_jobs'] = jobs
|
||||||
|
return self
|
||||||
|
|
||||||
|
def verbose(self, enabled: bool = True) -> 'MockConfigBuilder':
|
||||||
|
"""Enable verbose output"""
|
||||||
|
self._config['verbose'] = enabled
|
||||||
|
return self
|
||||||
|
|
||||||
|
def debug(self, enabled: bool = True) -> 'MockConfigBuilder':
|
||||||
|
"""Enable debug output"""
|
||||||
|
self._config['debug'] = enabled
|
||||||
|
return self
|
||||||
|
|
||||||
|
def build(self) -> Config:
|
||||||
|
"""Build the configuration object"""
|
||||||
|
return Config(**self._config)
|
||||||
|
|
||||||
|
|
||||||
|
# Convenience functions for common operations
|
||||||
|
def create_client(config: Optional[Config] = None) -> MockAPIClient:
|
||||||
|
"""Create a new API client"""
|
||||||
|
return MockAPIClient(config)
|
||||||
|
|
||||||
|
|
||||||
|
def create_config() -> MockConfigBuilder:
|
||||||
|
"""Create a new configuration builder"""
|
||||||
|
return MockConfigBuilder()
|
||||||
|
|
||||||
|
|
||||||
|
def quick_build(source_package: str,
|
||||||
|
environment: str = "debian-trixie-amd64",
|
||||||
|
arch: str = "amd64",
|
||||||
|
suite: str = "trixie") -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Quick build function for simple use cases
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_package: Path to source package
|
||||||
|
environment: Environment name
|
||||||
|
arch: Target architecture
|
||||||
|
suite: Debian suite
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Build result dictionary
|
||||||
|
"""
|
||||||
|
config = MockConfigBuilder().environment(environment).architecture(arch).suite(suite).build()
|
||||||
|
client = MockAPIClient(config)
|
||||||
|
|
||||||
|
return client.build_package(source_package)
|
||||||
|
|
||||||
|
|
||||||
|
# Example usage and integration patterns
|
||||||
|
def example_integration():
|
||||||
|
"""Example of how to use the API for integration"""
|
||||||
|
|
||||||
|
# Create a configuration
|
||||||
|
config = (MockConfigBuilder()
|
||||||
|
.environment("my-build-env")
|
||||||
|
.architecture("amd64")
|
||||||
|
.suite("trixie")
|
||||||
|
.mirror("http://deb.debian.org/debian/")
|
||||||
|
.packages(["build-essential", "devscripts"])
|
||||||
|
.cache_enabled(True)
|
||||||
|
.parallel_jobs(4)
|
||||||
|
.verbose(True)
|
||||||
|
.build())
|
||||||
|
|
||||||
|
# Create API client
|
||||||
|
client = MockAPIClient(config)
|
||||||
|
|
||||||
|
# Create environment
|
||||||
|
env = client.create_environment("my-build-env")
|
||||||
|
|
||||||
|
# Use environment context manager
|
||||||
|
with client.environment("my-build-env") as env:
|
||||||
|
# Install additional packages
|
||||||
|
env.install_packages(["cmake", "ninja-build"])
|
||||||
|
|
||||||
|
# Execute commands
|
||||||
|
result = env.execute(["ls", "-la", "/usr/bin"])
|
||||||
|
print(f"Command output: {result.stdout}")
|
||||||
|
|
||||||
|
# Copy files
|
||||||
|
env.copy_in("/local/source", "/build/source")
|
||||||
|
|
||||||
|
# Build package
|
||||||
|
build_result = client.build_package("/build/source", "my-build-env")
|
||||||
|
print(f"Build successful: {build_result['success']}")
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
client.remove_environment("my-build-env")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Example usage
|
||||||
|
example_integration()
|
||||||
475
deb_mock/environment_manager.py
Normal file
475
deb_mock/environment_manager.py
Normal file
|
|
@ -0,0 +1,475 @@
|
||||||
|
"""
|
||||||
|
Environment Management API for deb-mock
|
||||||
|
|
||||||
|
This module provides comprehensive environment management capabilities
|
||||||
|
for external tools integrating with deb-mock.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import subprocess
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Any, Optional, Union, Iterator
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from .core import DebMock
|
||||||
|
from .config import Config
|
||||||
|
from .exceptions import ConfigurationError, ChrootError, SbuildError
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EnvironmentInfo:
|
||||||
|
"""Information about a mock environment"""
|
||||||
|
name: str
|
||||||
|
architecture: str
|
||||||
|
suite: str
|
||||||
|
status: str
|
||||||
|
created: Optional[datetime] = None
|
||||||
|
modified: Optional[datetime] = None
|
||||||
|
size: int = 0
|
||||||
|
packages_installed: List[str] = None
|
||||||
|
mounts: List[Dict[str, str]] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BuildResult:
|
||||||
|
"""Result of a build operation"""
|
||||||
|
success: bool
|
||||||
|
artifacts: List[str]
|
||||||
|
output_dir: str
|
||||||
|
log_file: str
|
||||||
|
metadata: Dict[str, Any]
|
||||||
|
error: Optional[str] = None
|
||||||
|
duration: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
class EnvironmentManager:
|
||||||
|
"""
|
||||||
|
Comprehensive environment management for deb-mock
|
||||||
|
|
||||||
|
This class provides a high-level interface for managing mock environments,
|
||||||
|
executing commands, and collecting artifacts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config: Optional[Config] = None):
|
||||||
|
"""Initialize the environment manager"""
|
||||||
|
if config is None:
|
||||||
|
config = Config.default()
|
||||||
|
|
||||||
|
self.config = config
|
||||||
|
self.deb_mock = DebMock(config)
|
||||||
|
self._active_environments = {}
|
||||||
|
|
||||||
|
def create_environment(self,
|
||||||
|
name: str,
|
||||||
|
arch: str = None,
|
||||||
|
suite: str = None,
|
||||||
|
packages: List[str] = None,
|
||||||
|
force: bool = False) -> EnvironmentInfo:
|
||||||
|
"""
|
||||||
|
Create a new mock environment
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name for the environment
|
||||||
|
arch: Target architecture
|
||||||
|
suite: Debian suite
|
||||||
|
packages: Initial packages to install
|
||||||
|
force: Force creation even if environment exists
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EnvironmentInfo object
|
||||||
|
"""
|
||||||
|
if not force and self.environment_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' already exists")
|
||||||
|
|
||||||
|
# Remove existing environment if force is True
|
||||||
|
if force and self.environment_exists(name):
|
||||||
|
self.remove_environment(name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create the chroot environment
|
||||||
|
self.deb_mock.init_chroot(name, arch, suite)
|
||||||
|
|
||||||
|
# Install initial packages if specified
|
||||||
|
if packages:
|
||||||
|
self.deb_mock.install_packages(packages)
|
||||||
|
|
||||||
|
# Get environment info
|
||||||
|
info = self.get_environment_info(name)
|
||||||
|
self._active_environments[name] = info
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Failed to create environment '{name}': {e}")
|
||||||
|
|
||||||
|
def environment_exists(self, name: str) -> bool:
|
||||||
|
"""Check if an environment exists"""
|
||||||
|
return self.deb_mock.chroot_manager.chroot_exists(name)
|
||||||
|
|
||||||
|
def get_environment_info(self, name: str) -> EnvironmentInfo:
|
||||||
|
"""Get detailed information about an environment"""
|
||||||
|
if not self.environment_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
|
||||||
|
# Get basic chroot info
|
||||||
|
chroot_info = self.deb_mock.chroot_manager.get_chroot_info(name)
|
||||||
|
|
||||||
|
# Get installed packages
|
||||||
|
packages = self._get_installed_packages(name)
|
||||||
|
|
||||||
|
# Get mount information
|
||||||
|
mounts = self.deb_mock.chroot_manager.list_mounts(name)
|
||||||
|
|
||||||
|
return EnvironmentInfo(
|
||||||
|
name=name,
|
||||||
|
architecture=self.config.architecture,
|
||||||
|
suite=self.config.suite,
|
||||||
|
status=chroot_info.get('status', 'unknown'),
|
||||||
|
created=chroot_info.get('created'),
|
||||||
|
modified=chroot_info.get('modified'),
|
||||||
|
size=chroot_info.get('size', 0),
|
||||||
|
packages_installed=packages,
|
||||||
|
mounts=mounts
|
||||||
|
)
|
||||||
|
|
||||||
|
def list_environments(self) -> List[EnvironmentInfo]:
|
||||||
|
"""List all available environments"""
|
||||||
|
environments = []
|
||||||
|
|
||||||
|
for name in self.deb_mock.list_chroots():
|
||||||
|
try:
|
||||||
|
info = self.get_environment_info(name)
|
||||||
|
environments.append(info)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Failed to get info for environment '{name}': {e}")
|
||||||
|
|
||||||
|
return environments
|
||||||
|
|
||||||
|
def remove_environment(self, name: str, force: bool = False) -> None:
|
||||||
|
"""Remove an environment"""
|
||||||
|
if not self.environment_exists(name):
|
||||||
|
if not force:
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Clean up active environment tracking
|
||||||
|
if name in self._active_environments:
|
||||||
|
del self._active_environments[name]
|
||||||
|
|
||||||
|
# Remove the chroot
|
||||||
|
self.deb_mock.clean_chroot(name)
|
||||||
|
|
||||||
|
def update_environment(self, name: str) -> None:
|
||||||
|
"""Update packages in an environment"""
|
||||||
|
if not self.environment_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
|
||||||
|
self.deb_mock.update_chroot(name)
|
||||||
|
|
||||||
|
def execute_command(self,
|
||||||
|
name: str,
|
||||||
|
command: Union[str, List[str]],
|
||||||
|
capture_output: bool = True,
|
||||||
|
check: bool = True,
|
||||||
|
timeout: Optional[int] = None) -> subprocess.CompletedProcess:
|
||||||
|
"""
|
||||||
|
Execute a command in an environment
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Environment name
|
||||||
|
command: Command to execute
|
||||||
|
capture_output: Whether to capture output
|
||||||
|
check: Whether to raise exception on non-zero exit
|
||||||
|
timeout: Command timeout in seconds
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CompletedProcess object
|
||||||
|
"""
|
||||||
|
if not self.environment_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
|
||||||
|
if isinstance(command, str):
|
||||||
|
command = command.split()
|
||||||
|
|
||||||
|
# Prepare command with timeout if specified
|
||||||
|
if timeout:
|
||||||
|
command = ['timeout', str(timeout)] + command
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = self.deb_mock.chroot_manager.execute_in_chroot(
|
||||||
|
name, command, capture_output=capture_output
|
||||||
|
)
|
||||||
|
|
||||||
|
if check and result.returncode != 0:
|
||||||
|
raise subprocess.CalledProcessError(
|
||||||
|
result.returncode, command, result.stdout, result.stderr
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
if check:
|
||||||
|
raise
|
||||||
|
return e
|
||||||
|
|
||||||
|
def install_packages(self, name: str, packages: List[str]) -> Dict[str, Any]:
|
||||||
|
"""Install packages in an environment"""
|
||||||
|
if not self.environment_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
|
||||||
|
return self.deb_mock.install_packages(packages)
|
||||||
|
|
||||||
|
def copy_files(self,
|
||||||
|
name: str,
|
||||||
|
source: str,
|
||||||
|
destination: str,
|
||||||
|
direction: str = "in") -> None:
|
||||||
|
"""
|
||||||
|
Copy files to/from an environment
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Environment name
|
||||||
|
source: Source path
|
||||||
|
destination: Destination path
|
||||||
|
direction: "in" to copy into environment, "out" to copy out
|
||||||
|
"""
|
||||||
|
if not self.environment_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
|
||||||
|
if direction == "in":
|
||||||
|
self.deb_mock.chroot_manager.copy_to_chroot(source, destination, name)
|
||||||
|
elif direction == "out":
|
||||||
|
self.deb_mock.chroot_manager.copy_from_chroot(source, destination, name)
|
||||||
|
else:
|
||||||
|
raise ValueError("Direction must be 'in' or 'out'")
|
||||||
|
|
||||||
|
def collect_artifacts(self,
|
||||||
|
name: str,
|
||||||
|
source_patterns: List[str] = None,
|
||||||
|
output_dir: str = None) -> List[str]:
|
||||||
|
"""
|
||||||
|
Collect build artifacts from an environment
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Environment name
|
||||||
|
source_patterns: File patterns to search for
|
||||||
|
output_dir: Output directory for artifacts
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of collected artifact paths
|
||||||
|
"""
|
||||||
|
if not self.environment_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
|
||||||
|
if source_patterns is None:
|
||||||
|
source_patterns = [
|
||||||
|
'*.deb',
|
||||||
|
'*.changes',
|
||||||
|
'*.buildinfo',
|
||||||
|
'*.dsc',
|
||||||
|
'*.tar.*',
|
||||||
|
'*.orig.tar.*',
|
||||||
|
'*.debian.tar.*'
|
||||||
|
]
|
||||||
|
|
||||||
|
if output_dir is None:
|
||||||
|
output_dir = tempfile.mkdtemp(prefix='deb-mock-artifacts-')
|
||||||
|
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
artifacts = []
|
||||||
|
|
||||||
|
for pattern in source_patterns:
|
||||||
|
# Find files matching pattern
|
||||||
|
result = self.execute_command(
|
||||||
|
name, ['find', '/build', '-name', pattern, '-type', 'f'],
|
||||||
|
capture_output=True, check=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
for line in result.stdout.strip().split('\n'):
|
||||||
|
if line.strip():
|
||||||
|
source_path = line.strip()
|
||||||
|
filename = os.path.basename(source_path)
|
||||||
|
dest_path = os.path.join(output_dir, filename)
|
||||||
|
|
||||||
|
# Copy artifact
|
||||||
|
self.copy_files(name, source_path, dest_path, "out")
|
||||||
|
artifacts.append(dest_path)
|
||||||
|
|
||||||
|
return artifacts
|
||||||
|
|
||||||
|
def build_package(self,
|
||||||
|
name: str,
|
||||||
|
source_package: str,
|
||||||
|
output_dir: str = None,
|
||||||
|
**kwargs) -> BuildResult:
|
||||||
|
"""
|
||||||
|
Build a package in an environment
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Environment name
|
||||||
|
source_package: Path to source package
|
||||||
|
output_dir: Output directory for build artifacts
|
||||||
|
**kwargs: Additional build options
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BuildResult object
|
||||||
|
"""
|
||||||
|
if not self.environment_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
|
||||||
|
start_time = datetime.now()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Set chroot name for build
|
||||||
|
kwargs['chroot_name'] = name
|
||||||
|
if output_dir:
|
||||||
|
kwargs['output_dir'] = output_dir
|
||||||
|
|
||||||
|
# Build the package
|
||||||
|
result = self.deb_mock.build(source_package, **kwargs)
|
||||||
|
|
||||||
|
# Calculate duration
|
||||||
|
duration = (datetime.now() - start_time).total_seconds()
|
||||||
|
|
||||||
|
return BuildResult(
|
||||||
|
success=result.get('success', False),
|
||||||
|
artifacts=result.get('artifacts', []),
|
||||||
|
output_dir=result.get('output_dir', ''),
|
||||||
|
log_file=result.get('log_file', ''),
|
||||||
|
metadata=result.get('metadata', {}),
|
||||||
|
duration=duration
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
duration = (datetime.now() - start_time).total_seconds()
|
||||||
|
return BuildResult(
|
||||||
|
success=False,
|
||||||
|
artifacts=[],
|
||||||
|
output_dir=output_dir or '',
|
||||||
|
log_file='',
|
||||||
|
metadata={},
|
||||||
|
error=str(e),
|
||||||
|
duration=duration
|
||||||
|
)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def environment(self,
|
||||||
|
name: str,
|
||||||
|
arch: str = None,
|
||||||
|
suite: str = None,
|
||||||
|
packages: List[str] = None,
|
||||||
|
create_if_missing: bool = True) -> Iterator[EnvironmentInfo]:
|
||||||
|
"""
|
||||||
|
Context manager for environment operations
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Environment name
|
||||||
|
arch: Target architecture
|
||||||
|
suite: Debian suite
|
||||||
|
packages: Initial packages to install
|
||||||
|
create_if_missing: Create environment if it doesn't exist
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
EnvironmentInfo object
|
||||||
|
"""
|
||||||
|
env_info = None
|
||||||
|
created = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get or create environment
|
||||||
|
if self.environment_exists(name):
|
||||||
|
env_info = self.get_environment_info(name)
|
||||||
|
elif create_if_missing:
|
||||||
|
env_info = self.create_environment(name, arch, suite, packages)
|
||||||
|
created = True
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
|
||||||
|
yield env_info
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up if we created the environment
|
||||||
|
if created and env_info:
|
||||||
|
try:
|
||||||
|
self.remove_environment(name)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Failed to cleanup environment '{name}': {e}")
|
||||||
|
|
||||||
|
def _get_installed_packages(self, name: str) -> List[str]:
|
||||||
|
"""Get list of installed packages in environment"""
|
||||||
|
try:
|
||||||
|
result = self.execute_command(
|
||||||
|
name, ['dpkg', '-l'], capture_output=True, check=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
packages = []
|
||||||
|
for line in result.stdout.split('\n'):
|
||||||
|
if line.startswith('ii'):
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) >= 3:
|
||||||
|
packages.append(parts[1])
|
||||||
|
return packages
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def export_environment(self, name: str, output_path: str) -> None:
|
||||||
|
"""Export environment to a tar archive"""
|
||||||
|
if not self.environment_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' does not exist")
|
||||||
|
|
||||||
|
chroot_path = self.deb_mock.config.get_chroot_path()
|
||||||
|
|
||||||
|
# Create tar archive
|
||||||
|
subprocess.run([
|
||||||
|
'tar', '-czf', output_path, '-C', chroot_path, '.'
|
||||||
|
], check=True)
|
||||||
|
|
||||||
|
def import_environment(self, name: str, archive_path: str) -> None:
|
||||||
|
"""Import environment from a tar archive"""
|
||||||
|
if self.environment_exists(name):
|
||||||
|
raise ValueError(f"Environment '{name}' already exists")
|
||||||
|
|
||||||
|
# Create environment directory
|
||||||
|
chroot_path = os.path.join(self.config.chroot_dir, name)
|
||||||
|
os.makedirs(chroot_path, exist_ok=True)
|
||||||
|
|
||||||
|
# Extract archive
|
||||||
|
subprocess.run([
|
||||||
|
'tar', '-xzf', archive_path, '-C', chroot_path
|
||||||
|
], check=True)
|
||||||
|
|
||||||
|
# Create schroot configuration
|
||||||
|
self.deb_mock.chroot_manager._create_schroot_config(
|
||||||
|
name, chroot_path, self.config.architecture, self.config.suite
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Convenience functions
|
||||||
|
def create_environment_manager(config: Optional[Config] = None) -> EnvironmentManager:
|
||||||
|
"""Create a new environment manager"""
|
||||||
|
return EnvironmentManager(config)
|
||||||
|
|
||||||
|
|
||||||
|
def quick_environment(name: str = "quick-build",
|
||||||
|
arch: str = "amd64",
|
||||||
|
suite: str = "trixie",
|
||||||
|
packages: List[str] = None) -> EnvironmentManager:
|
||||||
|
"""Create a quick environment manager with default settings"""
|
||||||
|
config = Config(
|
||||||
|
chroot_name=name,
|
||||||
|
architecture=arch,
|
||||||
|
suite=suite,
|
||||||
|
chroot_additional_packages=packages or []
|
||||||
|
)
|
||||||
|
return EnvironmentManager(config)
|
||||||
234
deb_mock/plugins/example_plugin.py
Normal file
234
deb_mock/plugins/example_plugin.py
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
"""
|
||||||
|
Example plugin for deb-mock
|
||||||
|
|
||||||
|
This plugin demonstrates how to create custom plugins for deb-mock
|
||||||
|
and provides examples of common plugin patterns.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
from ..plugin import BasePlugin, HookStages
|
||||||
|
|
||||||
|
|
||||||
|
class ExamplePlugin(BasePlugin):
|
||||||
|
"""
|
||||||
|
Example plugin demonstrating deb-mock plugin capabilities
|
||||||
|
|
||||||
|
This plugin shows how to:
|
||||||
|
- Register hooks for different stages
|
||||||
|
- Access configuration and deb-mock context
|
||||||
|
- Perform custom operations during build lifecycle
|
||||||
|
- Log information and errors
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Plugin metadata
|
||||||
|
requires_api_version = "1.0"
|
||||||
|
plugin_name = "example"
|
||||||
|
plugin_version = "1.0.0"
|
||||||
|
plugin_description = "Example plugin for deb-mock"
|
||||||
|
|
||||||
|
def __init__(self, plugin_manager, config, deb_mock):
|
||||||
|
super().__init__(plugin_manager, config, deb_mock)
|
||||||
|
|
||||||
|
# Plugin-specific configuration
|
||||||
|
self.enabled = self.get_config('enabled', True)
|
||||||
|
self.log_level = self.get_config('log_level', 'INFO')
|
||||||
|
self.custom_option = self.get_config('custom_option', 'default_value')
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
self.logger.setLevel(getattr(logging, self.log_level.upper()))
|
||||||
|
|
||||||
|
self.log_info(f"ExamplePlugin initialized with config: {config}")
|
||||||
|
|
||||||
|
def _register_hooks(self):
|
||||||
|
"""Register hooks for different build stages"""
|
||||||
|
|
||||||
|
# Chroot lifecycle hooks
|
||||||
|
self.plugin_manager.add_hook(HookStages.PRECHROOT_INIT, self.prechroot_init)
|
||||||
|
self.plugin_manager.add_hook(HookStages.POSTCHROOT_INIT, self.postchroot_init)
|
||||||
|
self.plugin_manager.add_hook(HookStages.PRECHROOT_CLEAN, self.prechroot_clean)
|
||||||
|
self.plugin_manager.add_hook(HookStages.POSTCHROOT_CLEAN, self.postchroot_clean)
|
||||||
|
|
||||||
|
# Build lifecycle hooks
|
||||||
|
self.plugin_manager.add_hook(HookStages.PREBUILD, self.prebuild)
|
||||||
|
self.plugin_manager.add_hook(HookStages.POSTBUILD, self.postbuild)
|
||||||
|
self.plugin_manager.add_hook(HookStages.BUILD_START, self.build_start)
|
||||||
|
self.plugin_manager.add_hook(HookStages.BUILD_END, self.build_end)
|
||||||
|
|
||||||
|
# Package management hooks
|
||||||
|
self.plugin_manager.add_hook(HookStages.PRE_INSTALL_DEPS, self.pre_install_deps)
|
||||||
|
self.plugin_manager.add_hook(HookStages.POST_INSTALL_DEPS, self.post_install_deps)
|
||||||
|
|
||||||
|
# Mount management hooks
|
||||||
|
self.plugin_manager.add_hook(HookStages.PRE_MOUNT, self.pre_mount)
|
||||||
|
self.plugin_manager.add_hook(HookStages.POST_MOUNT, self.post_mount)
|
||||||
|
|
||||||
|
# Cache management hooks
|
||||||
|
self.plugin_manager.add_hook(HookStages.PRE_CACHE_CREATE, self.pre_cache_create)
|
||||||
|
self.plugin_manager.add_hook(HookStages.POST_CACHE_CREATE, self.post_cache_create)
|
||||||
|
|
||||||
|
# Error handling hooks
|
||||||
|
self.plugin_manager.add_hook(HookStages.ON_ERROR, self.on_error)
|
||||||
|
self.plugin_manager.add_hook(HookStages.ON_WARNING, self.on_warning)
|
||||||
|
|
||||||
|
self.log_debug("Registered all hooks for ExamplePlugin")
|
||||||
|
|
||||||
|
def prechroot_init(self, chroot_name: str, **kwargs):
|
||||||
|
"""Called before chroot initialization"""
|
||||||
|
self.log_info(f"Pre-chroot init for {chroot_name}")
|
||||||
|
|
||||||
|
# Example: Create custom directory structure
|
||||||
|
if self.get_config('create_custom_dirs', False):
|
||||||
|
custom_dirs = self.get_config('custom_dirs', ['/build/custom'])
|
||||||
|
for dir_path in custom_dirs:
|
||||||
|
self.log_debug(f"Would create directory: {dir_path}")
|
||||||
|
|
||||||
|
def postchroot_init(self, chroot_name: str, **kwargs):
|
||||||
|
"""Called after chroot initialization"""
|
||||||
|
self.log_info(f"Post-chroot init for {chroot_name}")
|
||||||
|
|
||||||
|
# Example: Install additional packages
|
||||||
|
extra_packages = self.get_config('extra_packages', [])
|
||||||
|
if extra_packages:
|
||||||
|
self.log_info(f"Installing extra packages: {extra_packages}")
|
||||||
|
try:
|
||||||
|
result = self.deb_mock.install_packages(extra_packages)
|
||||||
|
if result.get('success', False):
|
||||||
|
self.log_info("Extra packages installed successfully")
|
||||||
|
else:
|
||||||
|
self.log_warning(f"Failed to install extra packages: {result}")
|
||||||
|
except Exception as e:
|
||||||
|
self.log_error(f"Error installing extra packages: {e}")
|
||||||
|
|
||||||
|
def prechroot_clean(self, chroot_name: str, **kwargs):
|
||||||
|
"""Called before chroot cleanup"""
|
||||||
|
self.log_info(f"Pre-chroot clean for {chroot_name}")
|
||||||
|
|
||||||
|
# Example: Backup important files before cleanup
|
||||||
|
if self.get_config('backup_before_clean', False):
|
||||||
|
self.log_info("Backing up important files before cleanup")
|
||||||
|
# Implementation would backup files here
|
||||||
|
|
||||||
|
def postchroot_clean(self, chroot_name: str, **kwargs):
|
||||||
|
"""Called after chroot cleanup"""
|
||||||
|
self.log_info(f"Post-chroot clean for {chroot_name}")
|
||||||
|
|
||||||
|
def prebuild(self, source_package: str, **kwargs):
|
||||||
|
"""Called before package build"""
|
||||||
|
self.log_info(f"Pre-build for {source_package}")
|
||||||
|
|
||||||
|
# Example: Validate source package
|
||||||
|
if not os.path.exists(source_package):
|
||||||
|
self.log_error(f"Source package not found: {source_package}")
|
||||||
|
raise FileNotFoundError(f"Source package not found: {source_package}")
|
||||||
|
|
||||||
|
# Example: Check build dependencies
|
||||||
|
if self.get_config('check_deps', True):
|
||||||
|
self.log_info("Checking build dependencies")
|
||||||
|
# Implementation would check dependencies here
|
||||||
|
|
||||||
|
def postbuild(self, build_result: Dict[str, Any], source_package: str, **kwargs):
|
||||||
|
"""Called after package build"""
|
||||||
|
self.log_info(f"Post-build for {source_package}")
|
||||||
|
|
||||||
|
success = build_result.get('success', False)
|
||||||
|
if success:
|
||||||
|
self.log_info("Build completed successfully")
|
||||||
|
artifacts = build_result.get('artifacts', [])
|
||||||
|
self.log_info(f"Generated {len(artifacts)} artifacts")
|
||||||
|
else:
|
||||||
|
self.log_error("Build failed")
|
||||||
|
|
||||||
|
def build_start(self, source_package: str, chroot_name: str, **kwargs):
|
||||||
|
"""Called when build starts"""
|
||||||
|
self.log_info(f"Build started: {source_package} in {chroot_name}")
|
||||||
|
|
||||||
|
# Example: Set build environment variables
|
||||||
|
if self.get_config('set_build_env', False):
|
||||||
|
env_vars = self.get_config('build_env_vars', {})
|
||||||
|
for key, value in env_vars.items():
|
||||||
|
os.environ[key] = value
|
||||||
|
self.log_debug(f"Set environment variable: {key}={value}")
|
||||||
|
|
||||||
|
def build_end(self, build_result: Dict[str, Any], source_package: str, chroot_name: str, **kwargs):
|
||||||
|
"""Called when build ends"""
|
||||||
|
self.log_info(f"Build ended: {source_package} in {chroot_name}")
|
||||||
|
|
||||||
|
# Example: Collect build statistics
|
||||||
|
if self.get_config('collect_stats', True):
|
||||||
|
stats = {
|
||||||
|
'package': source_package,
|
||||||
|
'chroot': chroot_name,
|
||||||
|
'success': build_result.get('success', False),
|
||||||
|
'artifacts_count': len(build_result.get('artifacts', [])),
|
||||||
|
'duration': build_result.get('duration', 0)
|
||||||
|
}
|
||||||
|
self.log_info(f"Build statistics: {stats}")
|
||||||
|
|
||||||
|
def pre_install_deps(self, dependencies: List[str], chroot_name: str, **kwargs):
|
||||||
|
"""Called before installing dependencies"""
|
||||||
|
self.log_info(f"Pre-install deps: {dependencies} in {chroot_name}")
|
||||||
|
|
||||||
|
# Example: Filter dependencies
|
||||||
|
if self.get_config('filter_deps', False):
|
||||||
|
filtered_deps = [dep for dep in dependencies if not dep.startswith('lib')]
|
||||||
|
self.log_info(f"Filtered dependencies: {filtered_deps}")
|
||||||
|
return filtered_deps
|
||||||
|
|
||||||
|
def post_install_deps(self, dependencies: List[str], chroot_name: str, **kwargs):
|
||||||
|
"""Called after installing dependencies"""
|
||||||
|
self.log_info(f"Post-install deps: {dependencies} in {chroot_name}")
|
||||||
|
|
||||||
|
def pre_mount(self, mount_type: str, mount_path: str, chroot_name: str, **kwargs):
|
||||||
|
"""Called before mounting"""
|
||||||
|
self.log_debug(f"Pre-mount: {mount_type} at {mount_path} in {chroot_name}")
|
||||||
|
|
||||||
|
def post_mount(self, mount_type: str, mount_path: str, chroot_name: str, **kwargs):
|
||||||
|
"""Called after mounting"""
|
||||||
|
self.log_debug(f"Post-mount: {mount_type} at {mount_path} in {chroot_name}")
|
||||||
|
|
||||||
|
def pre_cache_create(self, cache_path: str, chroot_name: str, **kwargs):
|
||||||
|
"""Called before creating cache"""
|
||||||
|
self.log_info(f"Pre-cache create: {cache_path} for {chroot_name}")
|
||||||
|
|
||||||
|
def post_cache_create(self, cache_path: str, chroot_name: str, **kwargs):
|
||||||
|
"""Called after creating cache"""
|
||||||
|
self.log_info(f"Post-cache create: {cache_path} for {chroot_name}")
|
||||||
|
|
||||||
|
def on_error(self, error: Exception, stage: str, **kwargs):
|
||||||
|
"""Called when an error occurs"""
|
||||||
|
self.log_error(f"Error in {stage}: {error}")
|
||||||
|
|
||||||
|
# Example: Send error notification
|
||||||
|
if self.get_config('notify_on_error', False):
|
||||||
|
self.log_info("Would send error notification")
|
||||||
|
|
||||||
|
def on_warning(self, warning: str, stage: str, **kwargs):
|
||||||
|
"""Called when a warning occurs"""
|
||||||
|
self.log_warning(f"Warning in {stage}: {warning}")
|
||||||
|
|
||||||
|
def get_plugin_info(self) -> Dict[str, Any]:
|
||||||
|
"""Return plugin information"""
|
||||||
|
return {
|
||||||
|
'name': self.plugin_name,
|
||||||
|
'version': self.plugin_version,
|
||||||
|
'description': self.plugin_description,
|
||||||
|
'enabled': self.enabled,
|
||||||
|
'config': {
|
||||||
|
'log_level': self.log_level,
|
||||||
|
'custom_option': self.custom_option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Plugin initialization function (required by deb-mock)
|
||||||
|
def init(plugin_manager, config, deb_mock):
|
||||||
|
"""
|
||||||
|
Initialize the plugin
|
||||||
|
|
||||||
|
This function is called by deb-mock when the plugin is loaded.
|
||||||
|
It should create and return an instance of the plugin class.
|
||||||
|
"""
|
||||||
|
return ExamplePlugin(plugin_manager, config, deb_mock)
|
||||||
|
|
@ -1,361 +1,413 @@
|
||||||
"""
|
"""
|
||||||
Plugin Registry for Deb-Mock Plugin System
|
Plugin registry and management for deb-mock
|
||||||
|
|
||||||
This module provides the plugin registration and management functionality
|
This module provides a centralized registry for managing deb-mock plugins,
|
||||||
for the Deb-Mock plugin system, inspired by Fedora's Mock plugin architecture.
|
including discovery, loading, and lifecycle management.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
import importlib
|
import importlib
|
||||||
|
import importlib.util
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, Optional, Type
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Any, Optional, Type, Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from .base import BasePlugin
|
from .base import BasePlugin
|
||||||
|
from ..exceptions import PluginError
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
@dataclass
|
||||||
|
class PluginInfo:
|
||||||
|
"""Information about a registered plugin"""
|
||||||
|
name: str
|
||||||
|
version: str
|
||||||
|
description: str
|
||||||
|
author: str
|
||||||
|
requires_api_version: str
|
||||||
|
plugin_class: Type[BasePlugin]
|
||||||
|
init_function: Callable
|
||||||
|
file_path: str
|
||||||
|
loaded_at: datetime
|
||||||
|
enabled: bool = True
|
||||||
|
config: Dict[str, Any] = None
|
||||||
|
|
||||||
|
|
||||||
class PluginRegistry:
|
class PluginRegistry:
|
||||||
"""
|
"""
|
||||||
Manages plugin registration and instantiation.
|
Central registry for deb-mock plugins
|
||||||
|
|
||||||
This class provides the functionality for registering plugin classes
|
This class manages plugin discovery, loading, and lifecycle.
|
||||||
and creating plugin instances, following Mock's plugin system pattern.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, plugin_dirs: List[str] = None):
|
||||||
"""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.
|
Initialize the plugin registry
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
plugin_name: Name of the plugin
|
plugin_dirs: List of directories to search for plugins
|
||||||
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):
|
self.logger = logging.getLogger(__name__)
|
||||||
raise TypeError("Plugin class must inherit from BasePlugin")
|
|
||||||
|
|
||||||
if plugin_name in self.plugins:
|
# Default plugin directories
|
||||||
raise ValueError(f"Plugin '{plugin_name}' is already registered")
|
self.plugin_dirs = plugin_dirs or [
|
||||||
|
'/usr/share/deb-mock/plugins',
|
||||||
|
'/usr/local/share/deb-mock/plugins',
|
||||||
|
os.path.join(os.path.expanduser('~'), '.local', 'share', 'deb-mock', 'plugins'),
|
||||||
|
os.path.join(os.getcwd(), 'plugins')
|
||||||
|
]
|
||||||
|
|
||||||
self.plugins[plugin_name] = plugin_class
|
# Plugin storage
|
||||||
self.plugin_metadata[plugin_name] = metadata or {}
|
self._plugins: Dict[str, PluginInfo] = {}
|
||||||
|
self._loaded_plugins: Dict[str, BasePlugin] = {}
|
||||||
|
|
||||||
logger.debug(f"Registered plugin '{plugin_name}' with class {plugin_class.__name__}")
|
# API version compatibility
|
||||||
|
self.current_api_version = "1.0"
|
||||||
|
self.min_api_version = "1.0"
|
||||||
|
self.max_api_version = "1.0"
|
||||||
|
|
||||||
def unregister(self, plugin_name: str) -> bool:
|
def discover_plugins(self) -> List[PluginInfo]:
|
||||||
"""
|
"""
|
||||||
Unregister a plugin.
|
Discover available plugins in plugin directories
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of discovered plugin information
|
||||||
|
"""
|
||||||
|
discovered = []
|
||||||
|
|
||||||
|
for plugin_dir in self.plugin_dirs:
|
||||||
|
if not os.path.exists(plugin_dir):
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.logger.debug(f"Scanning plugin directory: {plugin_dir}")
|
||||||
|
|
||||||
|
for file_path in Path(plugin_dir).glob("*.py"):
|
||||||
|
if file_path.name.startswith('_'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
plugin_info = self._load_plugin_info(file_path)
|
||||||
|
if plugin_info:
|
||||||
|
discovered.append(plugin_info)
|
||||||
|
self.logger.debug(f"Discovered plugin: {plugin_info.name}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Failed to load plugin from {file_path}: {e}")
|
||||||
|
|
||||||
|
return discovered
|
||||||
|
|
||||||
|
def _load_plugin_info(self, file_path: Path) -> Optional[PluginInfo]:
|
||||||
|
"""
|
||||||
|
Load plugin information from a file
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the plugin file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PluginInfo object or None if not a valid plugin
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Load module
|
||||||
|
spec = importlib.util.spec_from_file_location(file_path.stem, file_path)
|
||||||
|
if not spec or not spec.loader:
|
||||||
|
return None
|
||||||
|
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
|
# Check if it's a valid plugin
|
||||||
|
if not hasattr(module, 'init'):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get plugin metadata
|
||||||
|
plugin_name = getattr(module, 'plugin_name', file_path.stem)
|
||||||
|
plugin_version = getattr(module, 'plugin_version', '1.0.0')
|
||||||
|
plugin_description = getattr(module, 'plugin_description', 'No description')
|
||||||
|
plugin_author = getattr(module, 'plugin_author', 'Unknown')
|
||||||
|
requires_api_version = getattr(module, 'requires_api_version', '1.0')
|
||||||
|
|
||||||
|
# Check API version compatibility
|
||||||
|
if not self._is_api_version_compatible(requires_api_version):
|
||||||
|
self.logger.warning(
|
||||||
|
f"Plugin {plugin_name} requires API version {requires_api_version}, "
|
||||||
|
f"but current version is {self.current_api_version}"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get plugin class
|
||||||
|
plugin_class = getattr(module, 'Plugin', None)
|
||||||
|
if not plugin_class:
|
||||||
|
# Look for classes that inherit from BasePlugin
|
||||||
|
for attr_name in dir(module):
|
||||||
|
attr = getattr(module, attr_name)
|
||||||
|
if (isinstance(attr, type) and
|
||||||
|
issubclass(attr, BasePlugin) and
|
||||||
|
attr != BasePlugin):
|
||||||
|
plugin_class = attr
|
||||||
|
break
|
||||||
|
|
||||||
|
if not plugin_class:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return PluginInfo(
|
||||||
|
name=plugin_name,
|
||||||
|
version=plugin_version,
|
||||||
|
description=plugin_description,
|
||||||
|
author=plugin_author,
|
||||||
|
requires_api_version=requires_api_version,
|
||||||
|
plugin_class=plugin_class,
|
||||||
|
init_function=module.init,
|
||||||
|
file_path=str(file_path),
|
||||||
|
loaded_at=datetime.now(),
|
||||||
|
enabled=True
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error loading plugin info from {file_path}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _is_api_version_compatible(self, required_version: str) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a plugin's required API version is compatible
|
||||||
|
|
||||||
|
Args:
|
||||||
|
required_version: Required API version string
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if compatible, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
required_major, required_minor = map(int, required_version.split('.'))
|
||||||
|
current_major, current_minor = map(int, self.current_api_version.split('.'))
|
||||||
|
|
||||||
|
# Same major version, minor version can be higher
|
||||||
|
return required_major == current_major and required_minor <= current_minor
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def register_plugin(self, plugin_info: PluginInfo) -> None:
|
||||||
|
"""
|
||||||
|
Register a plugin in the registry
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_info: Plugin information
|
||||||
|
"""
|
||||||
|
self._plugins[plugin_info.name] = plugin_info
|
||||||
|
self.logger.info(f"Registered plugin: {plugin_info.name} v{plugin_info.version}")
|
||||||
|
|
||||||
|
def unregister_plugin(self, plugin_name: str) -> None:
|
||||||
|
"""
|
||||||
|
Unregister a plugin from the registry
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
plugin_name: Name of the plugin to unregister
|
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:
|
if plugin_name in self._plugins:
|
||||||
return False
|
del self._plugins[plugin_name]
|
||||||
|
self.logger.info(f"Unregistered plugin: {plugin_name}")
|
||||||
|
|
||||||
del self.plugins[plugin_name]
|
def get_plugin(self, plugin_name: str) -> Optional[PluginInfo]:
|
||||||
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.
|
Get plugin information by name
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
plugin_name: Name of the plugin
|
plugin_name: Name of the plugin
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Plugin class if found, None otherwise
|
PluginInfo object or None if not found
|
||||||
"""
|
"""
|
||||||
return self.plugins.get(plugin_name)
|
return self._plugins.get(plugin_name)
|
||||||
|
|
||||||
def get_plugins(self) -> Dict[str, Type[BasePlugin]]:
|
def list_plugins(self) -> List[PluginInfo]:
|
||||||
"""
|
"""
|
||||||
Get all registered plugins.
|
List all registered plugins
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary of registered plugin names and classes
|
List of plugin information
|
||||||
"""
|
"""
|
||||||
return self.plugins.copy()
|
return list(self._plugins.values())
|
||||||
|
|
||||||
def get_plugin_names(self) -> list:
|
def list_enabled_plugins(self) -> List[PluginInfo]:
|
||||||
"""
|
"""
|
||||||
Get list of registered plugin names.
|
List enabled plugins
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of registered plugin names
|
List of enabled plugin information
|
||||||
"""
|
"""
|
||||||
return list(self.plugins.keys())
|
return [plugin for plugin in self._plugins.values() if plugin.enabled]
|
||||||
|
|
||||||
def create(self, plugin_name: str, config: Any, hook_manager: Any) -> Optional[BasePlugin]:
|
def enable_plugin(self, plugin_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Create a plugin instance.
|
Enable a plugin
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
plugin_name: Name of the plugin to create
|
plugin_name: Name of the plugin to enable
|
||||||
config: Configuration object
|
"""
|
||||||
hook_manager: Hook manager instance
|
if plugin_name in self._plugins:
|
||||||
|
self._plugins[plugin_name].enabled = True
|
||||||
|
self.logger.info(f"Enabled plugin: {plugin_name}")
|
||||||
|
|
||||||
|
def disable_plugin(self, plugin_name: str) -> None:
|
||||||
|
"""
|
||||||
|
Disable a plugin
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_name: Name of the plugin to disable
|
||||||
|
"""
|
||||||
|
if plugin_name in self._plugins:
|
||||||
|
self._plugins[plugin_name].enabled = False
|
||||||
|
self.logger.info(f"Disabled plugin: {plugin_name}")
|
||||||
|
|
||||||
|
def load_plugin(self, plugin_name: str, plugin_manager, config: Dict[str, Any], deb_mock) -> BasePlugin:
|
||||||
|
"""
|
||||||
|
Load a plugin instance
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_name: Name of the plugin to load
|
||||||
|
plugin_manager: Plugin manager instance
|
||||||
|
config: Plugin configuration
|
||||||
|
deb_mock: DebMock instance
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Plugin instance if successful, None if plugin not found
|
Loaded plugin instance
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
PluginError: If plugin cannot be loaded
|
||||||
"""
|
"""
|
||||||
plugin_class = self.get_plugin_class(plugin_name)
|
if plugin_name not in self._plugins:
|
||||||
if not plugin_class:
|
raise PluginError(f"Plugin '{plugin_name}' not found in registry")
|
||||||
logger.warning(f"Plugin '{plugin_name}' not found")
|
|
||||||
return None
|
plugin_info = self._plugins[plugin_name]
|
||||||
|
|
||||||
|
if not plugin_info.enabled:
|
||||||
|
raise PluginError(f"Plugin '{plugin_name}' is disabled")
|
||||||
|
|
||||||
|
if plugin_name in self._loaded_plugins:
|
||||||
|
return self._loaded_plugins[plugin_name]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
plugin_instance = plugin_class(config, hook_manager)
|
# Create plugin instance
|
||||||
logger.debug(f"Created plugin instance '{plugin_name}'")
|
plugin_instance = plugin_info.init_function(plugin_manager, config, deb_mock)
|
||||||
|
|
||||||
|
if not isinstance(plugin_instance, BasePlugin):
|
||||||
|
raise PluginError(f"Plugin '{plugin_name}' did not return a BasePlugin instance")
|
||||||
|
|
||||||
|
# Store loaded plugin
|
||||||
|
self._loaded_plugins[plugin_name] = plugin_instance
|
||||||
|
|
||||||
|
self.logger.info(f"Loaded plugin: {plugin_name}")
|
||||||
return plugin_instance
|
return plugin_instance
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to create plugin '{plugin_name}': {e}")
|
raise PluginError(f"Failed to load plugin '{plugin_name}': {e}")
|
||||||
return None
|
|
||||||
|
|
||||||
def create_all_enabled(self, config: Any, hook_manager: Any) -> Dict[str, BasePlugin]:
|
def unload_plugin(self, plugin_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
Create instances of all enabled plugins.
|
Unload a plugin instance
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config: Configuration object
|
plugin_name: Name of the plugin to unload
|
||||||
hook_manager: Hook manager instance
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dictionary of plugin names and instances
|
|
||||||
"""
|
"""
|
||||||
enabled_plugins = {}
|
if plugin_name in self._loaded_plugins:
|
||||||
|
del self._loaded_plugins[plugin_name]
|
||||||
|
self.logger.info(f"Unloaded plugin: {plugin_name}")
|
||||||
|
|
||||||
for plugin_name in self.get_plugin_names():
|
def reload_plugin(self, plugin_name: str, plugin_manager, config: Dict[str, Any], deb_mock) -> BasePlugin:
|
||||||
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.
|
Reload a plugin
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
plugin_name: Name of the plugin
|
plugin_name: Name of the plugin to reload
|
||||||
|
plugin_manager: Plugin manager instance
|
||||||
|
config: Plugin configuration
|
||||||
|
deb_mock: DebMock instance
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary with plugin information
|
Reloaded plugin instance
|
||||||
"""
|
"""
|
||||||
if plugin_name not in self.plugins:
|
self.unload_plugin(plugin_name)
|
||||||
return {"error": f'Plugin "{plugin_name}" not found'}
|
return self.load_plugin(plugin_name, plugin_manager, config, deb_mock)
|
||||||
|
|
||||||
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]:
|
def get_plugin_statistics(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get statistics about registered plugins.
|
Get plugin registry statistics
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary with plugin statistics
|
Dictionary with plugin statistics
|
||||||
"""
|
"""
|
||||||
stats = {
|
total_plugins = len(self._plugins)
|
||||||
"total_plugins": len(self.plugins),
|
enabled_plugins = len(self.list_enabled_plugins())
|
||||||
"builtin_plugins": len([p for p in self.plugin_metadata.values() if p.get("builtin", False)]),
|
loaded_plugins = len(self._loaded_plugins)
|
||||||
"external_plugins": len([p for p in self.plugin_metadata.values() if not p.get("builtin", False)]),
|
|
||||||
"plugins_by_hook": {},
|
return {
|
||||||
|
'total_plugins': total_plugins,
|
||||||
|
'enabled_plugins': enabled_plugins,
|
||||||
|
'loaded_plugins': loaded_plugins,
|
||||||
|
'disabled_plugins': total_plugins - enabled_plugins,
|
||||||
|
'api_version': self.current_api_version,
|
||||||
|
'plugin_directories': self.plugin_dirs
|
||||||
}
|
}
|
||||||
|
|
||||||
# Count plugins by hook usage
|
def validate_plugin_dependencies(self, plugin_name: str) -> List[str]:
|
||||||
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.
|
Validate plugin dependencies
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
plugin_name: Name of the plugin
|
plugin_name: Name of the plugin to validate
|
||||||
config: Configuration to validate
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if configuration is valid, False otherwise
|
List of missing dependencies
|
||||||
"""
|
"""
|
||||||
if plugin_name not in self.plugins:
|
if plugin_name not in self._plugins:
|
||||||
return False
|
return [f"Plugin '{plugin_name}' not found"]
|
||||||
|
|
||||||
# Basic validation - plugins can override this method
|
plugin_info = self._plugins[plugin_name]
|
||||||
plugin_class = self.plugins[plugin_name]
|
missing_deps = []
|
||||||
if hasattr(plugin_class, "validate_config"):
|
|
||||||
return plugin_class.validate_config(config)
|
|
||||||
|
|
||||||
return True
|
# Check if plugin file exists
|
||||||
|
if not os.path.exists(plugin_info.file_path):
|
||||||
|
missing_deps.append(f"Plugin file not found: {plugin_info.file_path}")
|
||||||
|
|
||||||
|
# Check Python dependencies
|
||||||
|
try:
|
||||||
|
spec = importlib.util.spec_from_file_location(plugin_name, plugin_info.file_path)
|
||||||
|
if spec and spec.loader:
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
except Exception as e:
|
||||||
|
missing_deps.append(f"Failed to load plugin module: {e}")
|
||||||
|
|
||||||
|
return missing_deps
|
||||||
|
|
||||||
|
|
||||||
|
# Global plugin registry instance
|
||||||
|
_global_registry = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugin_registry() -> PluginRegistry:
|
||||||
|
"""Get the global plugin registry instance"""
|
||||||
|
global _global_registry
|
||||||
|
if _global_registry is None:
|
||||||
|
_global_registry = PluginRegistry()
|
||||||
|
return _global_registry
|
||||||
|
|
||||||
|
|
||||||
|
def discover_plugins() -> List[PluginInfo]:
|
||||||
|
"""Discover all available plugins"""
|
||||||
|
registry = get_plugin_registry()
|
||||||
|
return registry.discover_plugins()
|
||||||
|
|
||||||
|
|
||||||
|
def register_plugin(plugin_info: PluginInfo) -> None:
|
||||||
|
"""Register a plugin in the global registry"""
|
||||||
|
registry = get_plugin_registry()
|
||||||
|
registry.register_plugin(plugin_info)
|
||||||
|
|
||||||
|
|
||||||
|
def get_plugin(plugin_name: str) -> Optional[PluginInfo]:
|
||||||
|
"""Get plugin information by name"""
|
||||||
|
registry = get_plugin_registry()
|
||||||
|
return registry.get_plugin(plugin_name)
|
||||||
1309
docs/API.md
1309
docs/API.md
File diff suppressed because it is too large
Load diff
675
docs/PLUGIN_DEVELOPMENT.md
Normal file
675
docs/PLUGIN_DEVELOPMENT.md
Normal file
|
|
@ -0,0 +1,675 @@
|
||||||
|
# deb-mock Plugin Development Guide
|
||||||
|
|
||||||
|
This guide explains how to develop custom plugins for deb-mock to extend its functionality.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Plugin Overview](#plugin-overview)
|
||||||
|
- [Plugin Structure](#plugin-structure)
|
||||||
|
- [Hook System](#hook-system)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Plugin Development](#plugin-development)
|
||||||
|
- [Testing Plugins](#testing-plugins)
|
||||||
|
- [Plugin Distribution](#plugin-distribution)
|
||||||
|
- [Examples](#examples)
|
||||||
|
|
||||||
|
## Plugin Overview
|
||||||
|
|
||||||
|
deb-mock plugins allow you to extend the build environment management system with custom functionality. Plugins can hook into various stages of the build process to:
|
||||||
|
|
||||||
|
- Modify build environments
|
||||||
|
- Install additional packages
|
||||||
|
- Execute custom commands
|
||||||
|
- Collect build artifacts
|
||||||
|
- Monitor build performance
|
||||||
|
- Handle errors and warnings
|
||||||
|
- Integrate with external tools
|
||||||
|
|
||||||
|
## Plugin Structure
|
||||||
|
|
||||||
|
A deb-mock plugin is a Python module that follows a specific structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
my_plugin.py
|
||||||
|
├── Plugin metadata
|
||||||
|
├── init() function
|
||||||
|
├── Plugin class (inherits from BasePlugin)
|
||||||
|
└── Hook implementations
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic Plugin Template
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""
|
||||||
|
My Custom Plugin for deb-mock
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
from deb_mock.plugin import BasePlugin, HookStages
|
||||||
|
|
||||||
|
|
||||||
|
class MyPlugin(BasePlugin):
|
||||||
|
"""My custom plugin"""
|
||||||
|
|
||||||
|
# Plugin metadata
|
||||||
|
requires_api_version = "1.0"
|
||||||
|
plugin_name = "my_plugin"
|
||||||
|
plugin_version = "1.0.0"
|
||||||
|
plugin_description = "My custom deb-mock plugin"
|
||||||
|
plugin_author = "Your Name"
|
||||||
|
|
||||||
|
def __init__(self, plugin_manager, config, deb_mock):
|
||||||
|
super().__init__(plugin_manager, config, deb_mock)
|
||||||
|
|
||||||
|
# Plugin-specific configuration
|
||||||
|
self.enabled = self.get_config('enabled', True)
|
||||||
|
self.custom_option = self.get_config('custom_option', 'default')
|
||||||
|
|
||||||
|
self.log_info(f"MyPlugin initialized with config: {config}")
|
||||||
|
|
||||||
|
def _register_hooks(self):
|
||||||
|
"""Register hooks for different build stages"""
|
||||||
|
# Register your hooks here
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def init(plugin_manager, config, deb_mock):
|
||||||
|
"""Initialize the plugin"""
|
||||||
|
return MyPlugin(plugin_manager, config, deb_mock)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hook System
|
||||||
|
|
||||||
|
The hook system allows plugins to execute code at specific points in the build process.
|
||||||
|
|
||||||
|
### Available Hook Stages
|
||||||
|
|
||||||
|
#### Chroot Lifecycle Hooks
|
||||||
|
|
||||||
|
- **`PRECHROOT_INIT`**: Called before chroot initialization
|
||||||
|
- **`POSTCHROOT_INIT`**: Called after chroot initialization
|
||||||
|
- **`PRECHROOT_CLEAN`**: Called before chroot cleanup
|
||||||
|
- **`POSTCHROOT_CLEAN`**: Called after chroot cleanup
|
||||||
|
|
||||||
|
#### Build Lifecycle Hooks
|
||||||
|
|
||||||
|
- **`PREBUILD`**: Called before package build
|
||||||
|
- **`POSTBUILD`**: Called after package build
|
||||||
|
- **`BUILD_START`**: Called when build starts
|
||||||
|
- **`BUILD_END`**: Called when build ends
|
||||||
|
|
||||||
|
#### Package Management Hooks
|
||||||
|
|
||||||
|
- **`PRE_INSTALL_DEPS`**: Called before installing dependencies
|
||||||
|
- **`POST_INSTALL_DEPS`**: Called after installing dependencies
|
||||||
|
- **`PRE_INSTALL_PACKAGE`**: Called before installing a package
|
||||||
|
- **`POST_INSTALL_PACKAGE`**: Called after installing a package
|
||||||
|
|
||||||
|
#### Mount Management Hooks
|
||||||
|
|
||||||
|
- **`PRE_MOUNT`**: Called before mounting
|
||||||
|
- **`POST_MOUNT`**: Called after mounting
|
||||||
|
- **`PRE_UNMOUNT`**: Called before unmounting
|
||||||
|
- **`POST_UNMOUNT`**: Called after unmounting
|
||||||
|
|
||||||
|
#### Cache Management Hooks
|
||||||
|
|
||||||
|
- **`PRE_CACHE_CREATE`**: Called before creating cache
|
||||||
|
- **`POST_CACHE_CREATE`**: Called after creating cache
|
||||||
|
- **`PRE_CACHE_RESTORE`**: Called before restoring cache
|
||||||
|
- **`POST_CACHE_RESTORE`**: Called after restoring cache
|
||||||
|
|
||||||
|
#### Error Handling Hooks
|
||||||
|
|
||||||
|
- **`ON_ERROR`**: Called when an error occurs
|
||||||
|
- **`ON_WARNING`**: Called when a warning occurs
|
||||||
|
|
||||||
|
### Registering Hooks
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _register_hooks(self):
|
||||||
|
"""Register hooks for different build stages"""
|
||||||
|
|
||||||
|
# Chroot lifecycle
|
||||||
|
self.plugin_manager.add_hook(HookStages.PRECHROOT_INIT, self.prechroot_init)
|
||||||
|
self.plugin_manager.add_hook(HookStages.POSTCHROOT_INIT, self.postchroot_init)
|
||||||
|
|
||||||
|
# Build lifecycle
|
||||||
|
self.plugin_manager.add_hook(HookStages.PREBUILD, self.prebuild)
|
||||||
|
self.plugin_manager.add_hook(HookStages.POSTBUILD, self.postbuild)
|
||||||
|
|
||||||
|
# Error handling
|
||||||
|
self.plugin_manager.add_hook(HookStages.ON_ERROR, self.on_error)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hook Implementation
|
||||||
|
|
||||||
|
```python
|
||||||
|
def prebuild(self, source_package: str, **kwargs):
|
||||||
|
"""Called before package build"""
|
||||||
|
self.log_info(f"Pre-build hook for {source_package}")
|
||||||
|
|
||||||
|
# Your custom logic here
|
||||||
|
if self.get_config('validate_source', True):
|
||||||
|
self._validate_source_package(source_package)
|
||||||
|
|
||||||
|
def postbuild(self, build_result: Dict[str, Any], source_package: str, **kwargs):
|
||||||
|
"""Called after package build"""
|
||||||
|
success = build_result.get('success', False)
|
||||||
|
if success:
|
||||||
|
self.log_info(f"Build successful for {source_package}")
|
||||||
|
else:
|
||||||
|
self.log_error(f"Build failed for {source_package}")
|
||||||
|
|
||||||
|
def on_error(self, error: Exception, stage: str, **kwargs):
|
||||||
|
"""Called when an error occurs"""
|
||||||
|
self.log_error(f"Error in {stage}: {error}")
|
||||||
|
|
||||||
|
# Send notification, log to file, etc.
|
||||||
|
if self.get_config('notify_on_error', False):
|
||||||
|
self._send_notification(error, stage)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Plugins can access configuration through the `get_config()` method:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __init__(self, plugin_manager, config, deb_mock):
|
||||||
|
super().__init__(plugin_manager, config, deb_mock)
|
||||||
|
|
||||||
|
# Get configuration values with defaults
|
||||||
|
self.enabled = self.get_config('enabled', True)
|
||||||
|
self.log_level = self.get_config('log_level', 'INFO')
|
||||||
|
self.custom_option = self.get_config('custom_option', 'default_value')
|
||||||
|
|
||||||
|
# Get complex configuration
|
||||||
|
self.packages = self.get_config('packages', [])
|
||||||
|
self.env_vars = self.get_config('environment_variables', {})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# deb-mock configuration
|
||||||
|
plugins:
|
||||||
|
- my_plugin
|
||||||
|
|
||||||
|
plugin_conf:
|
||||||
|
my_plugin_enable: true
|
||||||
|
my_plugin_opts:
|
||||||
|
enabled: true
|
||||||
|
log_level: DEBUG
|
||||||
|
custom_option: "my_value"
|
||||||
|
packages:
|
||||||
|
- "build-essential"
|
||||||
|
- "cmake"
|
||||||
|
environment_variables:
|
||||||
|
CC: "gcc"
|
||||||
|
CXX: "g++"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plugin Development
|
||||||
|
|
||||||
|
### 1. Create Plugin Directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.local/share/deb-mock/plugins
|
||||||
|
cd ~/.local/share/deb-mock/plugins
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create Plugin File
|
||||||
|
|
||||||
|
```python
|
||||||
|
# my_custom_plugin.py
|
||||||
|
"""
|
||||||
|
Custom Plugin for deb-mock
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
from deb_mock.plugin import BasePlugin, HookStages
|
||||||
|
|
||||||
|
|
||||||
|
class CustomPlugin(BasePlugin):
|
||||||
|
"""Custom plugin for deb-mock"""
|
||||||
|
|
||||||
|
requires_api_version = "1.0"
|
||||||
|
plugin_name = "custom_plugin"
|
||||||
|
plugin_version = "1.0.0"
|
||||||
|
plugin_description = "Custom deb-mock plugin"
|
||||||
|
plugin_author = "Your Name"
|
||||||
|
|
||||||
|
def __init__(self, plugin_manager, config, deb_mock):
|
||||||
|
super().__init__(plugin_manager, config, deb_mock)
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
self.enabled = self.get_config('enabled', True)
|
||||||
|
self.packages = self.get_config('packages', [])
|
||||||
|
self.commands = self.get_config('commands', [])
|
||||||
|
|
||||||
|
self.log_info("CustomPlugin initialized")
|
||||||
|
|
||||||
|
def _register_hooks(self):
|
||||||
|
"""Register hooks"""
|
||||||
|
self.plugin_manager.add_hook(HookStages.POSTCHROOT_INIT, self.postchroot_init)
|
||||||
|
self.plugin_manager.add_hook(HookStages.PREBUILD, self.prebuild)
|
||||||
|
self.plugin_manager.add_hook(HookStages.POSTBUILD, self.postbuild)
|
||||||
|
|
||||||
|
def postchroot_init(self, chroot_name: str, **kwargs):
|
||||||
|
"""Called after chroot initialization"""
|
||||||
|
self.log_info(f"Setting up custom environment in {chroot_name}")
|
||||||
|
|
||||||
|
# Install additional packages
|
||||||
|
if self.packages:
|
||||||
|
self.log_info(f"Installing packages: {self.packages}")
|
||||||
|
try:
|
||||||
|
result = self.deb_mock.install_packages(self.packages)
|
||||||
|
if result.get('success', False):
|
||||||
|
self.log_info("Packages installed successfully")
|
||||||
|
else:
|
||||||
|
self.log_warning(f"Failed to install packages: {result}")
|
||||||
|
except Exception as e:
|
||||||
|
self.log_error(f"Error installing packages: {e}")
|
||||||
|
|
||||||
|
# Execute setup commands
|
||||||
|
for command in self.commands:
|
||||||
|
self.log_info(f"Executing command: {command}")
|
||||||
|
try:
|
||||||
|
result = self.deb_mock.chroot_manager.execute_in_chroot(
|
||||||
|
chroot_name, command.split(), capture_output=True
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
self.log_info(f"Command succeeded: {command}")
|
||||||
|
else:
|
||||||
|
self.log_warning(f"Command failed: {command}")
|
||||||
|
except Exception as e:
|
||||||
|
self.log_error(f"Error executing command {command}: {e}")
|
||||||
|
|
||||||
|
def prebuild(self, source_package: str, **kwargs):
|
||||||
|
"""Called before package build"""
|
||||||
|
self.log_info(f"Pre-build setup for {source_package}")
|
||||||
|
|
||||||
|
# Your custom pre-build logic
|
||||||
|
if self.get_config('validate_source', True):
|
||||||
|
self._validate_source_package(source_package)
|
||||||
|
|
||||||
|
def postbuild(self, build_result: Dict[str, Any], source_package: str, **kwargs):
|
||||||
|
"""Called after package build"""
|
||||||
|
success = build_result.get('success', False)
|
||||||
|
if success:
|
||||||
|
self.log_info(f"Build successful for {source_package}")
|
||||||
|
self._handle_successful_build(build_result)
|
||||||
|
else:
|
||||||
|
self.log_error(f"Build failed for {source_package}")
|
||||||
|
self._handle_failed_build(build_result)
|
||||||
|
|
||||||
|
def _validate_source_package(self, source_package: str):
|
||||||
|
"""Validate source package"""
|
||||||
|
if not os.path.exists(source_package):
|
||||||
|
raise FileNotFoundError(f"Source package not found: {source_package}")
|
||||||
|
|
||||||
|
self.log_info(f"Source package validated: {source_package}")
|
||||||
|
|
||||||
|
def _handle_successful_build(self, build_result: Dict[str, Any]):
|
||||||
|
"""Handle successful build"""
|
||||||
|
artifacts = build_result.get('artifacts', [])
|
||||||
|
self.log_info(f"Build produced {len(artifacts)} artifacts")
|
||||||
|
|
||||||
|
# Process artifacts, send notifications, etc.
|
||||||
|
|
||||||
|
def _handle_failed_build(self, build_result: Dict[str, Any]):
|
||||||
|
"""Handle failed build"""
|
||||||
|
error = build_result.get('error', 'Unknown error')
|
||||||
|
self.log_error(f"Build failed: {error}")
|
||||||
|
|
||||||
|
# Send error notifications, log to file, etc.
|
||||||
|
|
||||||
|
|
||||||
|
def init(plugin_manager, config, deb_mock):
|
||||||
|
"""Initialize the plugin"""
|
||||||
|
return CustomPlugin(plugin_manager, config, deb_mock)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Configure Plugin
|
||||||
|
|
||||||
|
Add your plugin to the deb-mock configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# config.yaml
|
||||||
|
plugins:
|
||||||
|
- custom_plugin
|
||||||
|
|
||||||
|
plugin_conf:
|
||||||
|
custom_plugin_enable: true
|
||||||
|
custom_plugin_opts:
|
||||||
|
enabled: true
|
||||||
|
packages:
|
||||||
|
- "build-essential"
|
||||||
|
- "cmake"
|
||||||
|
- "ninja-build"
|
||||||
|
commands:
|
||||||
|
- "apt update"
|
||||||
|
- "apt install -y git"
|
||||||
|
validate_source: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Test Plugin
|
||||||
|
|
||||||
|
```python
|
||||||
|
# test_plugin.py
|
||||||
|
from deb_mock import create_client, MockConfigBuilder
|
||||||
|
|
||||||
|
# Create configuration with plugin
|
||||||
|
config = (MockConfigBuilder()
|
||||||
|
.environment("test-env")
|
||||||
|
.architecture("amd64")
|
||||||
|
.suite("trixie")
|
||||||
|
.build())
|
||||||
|
|
||||||
|
# Add plugin configuration
|
||||||
|
config.plugins = ["custom_plugin"]
|
||||||
|
config.plugin_conf = {
|
||||||
|
"custom_plugin_enable": True,
|
||||||
|
"custom_plugin_opts": {
|
||||||
|
"enabled": True,
|
||||||
|
"packages": ["build-essential"],
|
||||||
|
"commands": ["apt update"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create client and test
|
||||||
|
client = create_client(config)
|
||||||
|
env = client.create_environment("test-env")
|
||||||
|
print("Plugin should have executed during environment creation")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Plugins
|
||||||
|
|
||||||
|
### Unit Testing
|
||||||
|
|
||||||
|
```python
|
||||||
|
# test_my_plugin.py
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
from my_plugin import CustomPlugin
|
||||||
|
|
||||||
|
|
||||||
|
class TestCustomPlugin(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.mock_plugin_manager = Mock()
|
||||||
|
self.mock_deb_mock = Mock()
|
||||||
|
self.config = {
|
||||||
|
'enabled': True,
|
||||||
|
'packages': ['build-essential'],
|
||||||
|
'commands': ['apt update']
|
||||||
|
}
|
||||||
|
|
||||||
|
self.plugin = CustomPlugin(
|
||||||
|
self.mock_plugin_manager,
|
||||||
|
self.config,
|
||||||
|
self.mock_deb_mock
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_plugin_initialization(self):
|
||||||
|
"""Test plugin initialization"""
|
||||||
|
self.assertTrue(self.plugin.enabled)
|
||||||
|
self.assertEqual(self.plugin.packages, ['build-essential'])
|
||||||
|
self.assertEqual(self.plugin.commands, ['apt update'])
|
||||||
|
|
||||||
|
def test_postchroot_init(self):
|
||||||
|
"""Test postchroot_init hook"""
|
||||||
|
self.mock_deb_mock.install_packages.return_value = {'success': True}
|
||||||
|
|
||||||
|
self.plugin.postchroot_init("test-chroot")
|
||||||
|
|
||||||
|
self.mock_deb_mock.install_packages.assert_called_once_with(['build-essential'])
|
||||||
|
|
||||||
|
def test_prebuild(self):
|
||||||
|
"""Test prebuild hook"""
|
||||||
|
with patch('os.path.exists', return_value=True):
|
||||||
|
self.plugin.prebuild("/path/to/package.dsc")
|
||||||
|
# Test passes if no exception is raised
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Testing
|
||||||
|
|
||||||
|
```python
|
||||||
|
# integration_test.py
|
||||||
|
from deb_mock import create_client, MockConfigBuilder
|
||||||
|
|
||||||
|
def test_plugin_integration():
|
||||||
|
"""Test plugin integration with deb-mock"""
|
||||||
|
|
||||||
|
# Create configuration with plugin
|
||||||
|
config = (MockConfigBuilder()
|
||||||
|
.environment("integration-test")
|
||||||
|
.architecture("amd64")
|
||||||
|
.suite("trixie")
|
||||||
|
.build())
|
||||||
|
|
||||||
|
config.plugins = ["custom_plugin"]
|
||||||
|
config.plugin_conf = {
|
||||||
|
"custom_plugin_enable": True,
|
||||||
|
"custom_plugin_opts": {
|
||||||
|
"enabled": True,
|
||||||
|
"packages": ["build-essential"],
|
||||||
|
"commands": ["apt update"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create client
|
||||||
|
client = create_client(config)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create environment (should trigger plugin)
|
||||||
|
env = client.create_environment("integration-test")
|
||||||
|
|
||||||
|
# Verify plugin executed
|
||||||
|
# Check logs, installed packages, etc.
|
||||||
|
|
||||||
|
print("Plugin integration test passed")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
client.remove_environment("integration-test")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_plugin_integration()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plugin Distribution
|
||||||
|
|
||||||
|
### 1. Package Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
my-deb-mock-plugin/
|
||||||
|
├── setup.py
|
||||||
|
├── README.md
|
||||||
|
├── LICENSE
|
||||||
|
├── my_plugin/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── plugin.py
|
||||||
|
└── tests/
|
||||||
|
└── test_plugin.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. setup.py
|
||||||
|
|
||||||
|
```python
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="my-deb-mock-plugin",
|
||||||
|
version="1.0.0",
|
||||||
|
description="My custom deb-mock plugin",
|
||||||
|
author="Your Name",
|
||||||
|
author_email="your.email@example.com",
|
||||||
|
packages=find_packages(),
|
||||||
|
install_requires=[
|
||||||
|
"deb-mock>=0.1.0",
|
||||||
|
],
|
||||||
|
entry_points={
|
||||||
|
"deb_mock.plugins": [
|
||||||
|
"my_plugin = my_plugin.plugin:init",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
classifiers=[
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install from source
|
||||||
|
pip install -e .
|
||||||
|
|
||||||
|
# Install from PyPI (when published)
|
||||||
|
pip install my-deb-mock-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Example 1: Package Installation Plugin
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""
|
||||||
|
Plugin to automatically install packages in chroots
|
||||||
|
"""
|
||||||
|
|
||||||
|
from deb_mock.plugin import BasePlugin, HookStages
|
||||||
|
|
||||||
|
|
||||||
|
class PackageInstallPlugin(BasePlugin):
|
||||||
|
"""Plugin to install packages in chroots"""
|
||||||
|
|
||||||
|
requires_api_version = "1.0"
|
||||||
|
plugin_name = "package_install"
|
||||||
|
plugin_version = "1.0.0"
|
||||||
|
plugin_description = "Automatically install packages in chroots"
|
||||||
|
|
||||||
|
def __init__(self, plugin_manager, config, deb_mock):
|
||||||
|
super().__init__(plugin_manager, config, deb_mock)
|
||||||
|
|
||||||
|
self.packages = self.get_config('packages', [])
|
||||||
|
self.auto_install = self.get_config('auto_install', True)
|
||||||
|
|
||||||
|
def _register_hooks(self):
|
||||||
|
self.plugin_manager.add_hook(HookStages.POSTCHROOT_INIT, self.install_packages)
|
||||||
|
|
||||||
|
def install_packages(self, chroot_name: str, **kwargs):
|
||||||
|
"""Install packages after chroot initialization"""
|
||||||
|
if not self.auto_install or not self.packages:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log_info(f"Installing packages in {chroot_name}: {self.packages}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = self.deb_mock.install_packages(self.packages)
|
||||||
|
if result.get('success', False):
|
||||||
|
self.log_info("Packages installed successfully")
|
||||||
|
else:
|
||||||
|
self.log_warning(f"Failed to install packages: {result}")
|
||||||
|
except Exception as e:
|
||||||
|
self.log_error(f"Error installing packages: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def init(plugin_manager, config, deb_mock):
|
||||||
|
return PackageInstallPlugin(plugin_manager, config, deb_mock)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Build Notification Plugin
|
||||||
|
|
||||||
|
```python
|
||||||
|
"""
|
||||||
|
Plugin to send notifications about build results
|
||||||
|
"""
|
||||||
|
|
||||||
|
import smtplib
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from deb_mock.plugin import BasePlugin, HookStages
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationPlugin(BasePlugin):
|
||||||
|
"""Plugin to send build notifications"""
|
||||||
|
|
||||||
|
requires_api_version = "1.0"
|
||||||
|
plugin_name = "notification"
|
||||||
|
plugin_version = "1.0.0"
|
||||||
|
plugin_description = "Send notifications about build results"
|
||||||
|
|
||||||
|
def __init__(self, plugin_manager, config, deb_mock):
|
||||||
|
super().__init__(plugin_manager, config, deb_mock)
|
||||||
|
|
||||||
|
self.smtp_server = self.get_config('smtp_server', 'localhost')
|
||||||
|
self.smtp_port = self.get_config('smtp_port', 587)
|
||||||
|
self.smtp_user = self.get_config('smtp_user', '')
|
||||||
|
self.smtp_password = self.get_config('smtp_password', '')
|
||||||
|
self.recipients = self.get_config('recipients', [])
|
||||||
|
self.notify_on_success = self.get_config('notify_on_success', True)
|
||||||
|
self.notify_on_failure = self.get_config('notify_on_failure', True)
|
||||||
|
|
||||||
|
def _register_hooks(self):
|
||||||
|
self.plugin_manager.add_hook(HookStages.POSTBUILD, self.send_notification)
|
||||||
|
self.plugin_manager.add_hook(HookStages.ON_ERROR, self.send_error_notification)
|
||||||
|
|
||||||
|
def postbuild(self, build_result: Dict[str, Any], source_package: str, **kwargs):
|
||||||
|
"""Send notification after build"""
|
||||||
|
success = build_result.get('success', False)
|
||||||
|
|
||||||
|
if success and self.notify_on_success:
|
||||||
|
self._send_notification("Build Successful", f"Package {source_package} built successfully")
|
||||||
|
elif not success and self.notify_on_failure:
|
||||||
|
self._send_notification("Build Failed", f"Package {source_package} build failed")
|
||||||
|
|
||||||
|
def on_error(self, error: Exception, stage: str, **kwargs):
|
||||||
|
"""Send notification on error"""
|
||||||
|
if self.notify_on_failure:
|
||||||
|
self._send_notification("Build Error", f"Error in {stage}: {error}")
|
||||||
|
|
||||||
|
def _send_notification(self, subject: str, body: str):
|
||||||
|
"""Send email notification"""
|
||||||
|
if not self.recipients:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg = MIMEText(body)
|
||||||
|
msg['Subject'] = f"deb-mock: {subject}"
|
||||||
|
msg['From'] = self.smtp_user
|
||||||
|
msg['To'] = ', '.join(self.recipients)
|
||||||
|
|
||||||
|
with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
|
||||||
|
if self.smtp_user and self.smtp_password:
|
||||||
|
server.starttls()
|
||||||
|
server.login(self.smtp_user, self.smtp_password)
|
||||||
|
|
||||||
|
server.send_message(msg)
|
||||||
|
self.log_info(f"Notification sent: {subject}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_error(f"Failed to send notification: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def init(plugin_manager, config, deb_mock):
|
||||||
|
return NotificationPlugin(plugin_manager, config, deb_mock)
|
||||||
|
```
|
||||||
|
|
||||||
|
This guide provides everything you need to develop custom plugins for deb-mock. The plugin system is designed to be flexible and powerful, allowing you to extend deb-mock's functionality in any way you need.
|
||||||
396
examples/api_usage_example.py
Normal file
396
examples/api_usage_example.py
Normal file
|
|
@ -0,0 +1,396 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Example usage of deb-mock API
|
||||||
|
|
||||||
|
This script demonstrates how to use the deb-mock API for various
|
||||||
|
build environment management tasks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Add deb-mock to Python path
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
|
from deb_mock.api import MockAPIClient, MockConfigBuilder, create_client, quick_build
|
||||||
|
from deb_mock.environment_manager import EnvironmentManager, create_environment_manager
|
||||||
|
from deb_mock.config import Config
|
||||||
|
|
||||||
|
|
||||||
|
def example_basic_usage():
|
||||||
|
"""Example of basic API usage"""
|
||||||
|
print("=== Basic API Usage Example ===")
|
||||||
|
|
||||||
|
# Create a configuration using the builder
|
||||||
|
config = (MockConfigBuilder()
|
||||||
|
.environment("example-env")
|
||||||
|
.architecture("amd64")
|
||||||
|
.suite("trixie")
|
||||||
|
.mirror("http://deb.debian.org/debian/")
|
||||||
|
.packages(["build-essential", "devscripts", "cmake"])
|
||||||
|
.cache_enabled(True)
|
||||||
|
.parallel_jobs(4)
|
||||||
|
.verbose(True)
|
||||||
|
.build())
|
||||||
|
|
||||||
|
# Create API client
|
||||||
|
client = MockAPIClient(config)
|
||||||
|
|
||||||
|
print(f"Created client with environment: {config.chroot_name}")
|
||||||
|
print(f"Architecture: {config.architecture}")
|
||||||
|
print(f"Suite: {config.suite}")
|
||||||
|
print(f"Mirror: {config.mirror}")
|
||||||
|
print(f"Initial packages: {config.chroot_additional_packages}")
|
||||||
|
|
||||||
|
|
||||||
|
def example_environment_management():
|
||||||
|
"""Example of environment management"""
|
||||||
|
print("\n=== Environment Management Example ===")
|
||||||
|
|
||||||
|
# Create environment manager
|
||||||
|
manager = create_environment_manager()
|
||||||
|
|
||||||
|
# Create a new environment
|
||||||
|
print("Creating environment...")
|
||||||
|
env_info = manager.create_environment(
|
||||||
|
name="test-build-env",
|
||||||
|
arch="amd64",
|
||||||
|
suite="trixie",
|
||||||
|
packages=["build-essential", "cmake", "ninja-build"]
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Created environment: {env_info.name}")
|
||||||
|
print(f"Architecture: {env_info.architecture}")
|
||||||
|
print(f"Suite: {env_info.suite}")
|
||||||
|
print(f"Status: {env_info.status}")
|
||||||
|
|
||||||
|
# List all environments
|
||||||
|
print("\nListing all environments...")
|
||||||
|
environments = manager.list_environments()
|
||||||
|
for env in environments:
|
||||||
|
print(f" - {env.name} ({env.architecture}/{env.suite})")
|
||||||
|
|
||||||
|
# Get environment info
|
||||||
|
print(f"\nGetting info for {env_info.name}...")
|
||||||
|
info = manager.get_environment_info(env_info.name)
|
||||||
|
print(f" Status: {info.status}")
|
||||||
|
print(f" Size: {info.size} bytes")
|
||||||
|
print(f" Packages installed: {len(info.packages_installed or [])}")
|
||||||
|
|
||||||
|
|
||||||
|
def example_command_execution():
|
||||||
|
"""Example of command execution in environments"""
|
||||||
|
print("\n=== Command Execution Example ===")
|
||||||
|
|
||||||
|
manager = create_environment_manager()
|
||||||
|
|
||||||
|
# Create environment
|
||||||
|
env_info = manager.create_environment(
|
||||||
|
name="command-test-env",
|
||||||
|
arch="amd64",
|
||||||
|
suite="trixie",
|
||||||
|
packages=["build-essential"]
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Execute commands
|
||||||
|
print("Executing commands in environment...")
|
||||||
|
|
||||||
|
# List installed packages
|
||||||
|
result = manager.execute_command(
|
||||||
|
env_info.name,
|
||||||
|
["dpkg", "-l", "|", "grep", "build-essential"],
|
||||||
|
capture_output=True
|
||||||
|
)
|
||||||
|
print(f"Build tools check: {result.returncode == 0}")
|
||||||
|
|
||||||
|
# Check system info
|
||||||
|
result = manager.execute_command(
|
||||||
|
env_info.name,
|
||||||
|
["uname", "-a"],
|
||||||
|
capture_output=True
|
||||||
|
)
|
||||||
|
print(f"System info: {result.stdout.strip()}")
|
||||||
|
|
||||||
|
# Check available space
|
||||||
|
result = manager.execute_command(
|
||||||
|
env_info.name,
|
||||||
|
["df", "-h", "/"],
|
||||||
|
capture_output=True
|
||||||
|
)
|
||||||
|
print(f"Disk usage:\n{result.stdout}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
manager.remove_environment(env_info.name)
|
||||||
|
|
||||||
|
|
||||||
|
def example_package_building():
|
||||||
|
"""Example of package building"""
|
||||||
|
print("\n=== Package Building Example ===")
|
||||||
|
|
||||||
|
client = create_client()
|
||||||
|
|
||||||
|
# Create environment for building
|
||||||
|
env = client.create_environment(
|
||||||
|
name="build-env",
|
||||||
|
arch="amd64",
|
||||||
|
suite="trixie",
|
||||||
|
packages=["build-essential", "devscripts", "debhelper"]
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"Created build environment: {env.name}")
|
||||||
|
|
||||||
|
# Example: Build a simple package (this would need actual source)
|
||||||
|
print("Note: This example shows the API structure.")
|
||||||
|
print("In practice, you would provide a real source package path.")
|
||||||
|
|
||||||
|
# Example build call (commented out as it needs real source)
|
||||||
|
# result = client.build_package("/path/to/package.dsc", "build-env")
|
||||||
|
# print(f"Build result: {result}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
client.remove_environment(env.name)
|
||||||
|
|
||||||
|
|
||||||
|
def example_context_manager():
|
||||||
|
"""Example of using context managers"""
|
||||||
|
print("\n=== Context Manager Example ===")
|
||||||
|
|
||||||
|
manager = create_environment_manager()
|
||||||
|
|
||||||
|
# Use environment context manager
|
||||||
|
with manager.environment("context-test-env",
|
||||||
|
arch="amd64",
|
||||||
|
suite="trixie",
|
||||||
|
packages=["build-essential"]) as env:
|
||||||
|
|
||||||
|
print(f"Environment active: {env.is_active()}")
|
||||||
|
print(f"Environment name: {env.name}")
|
||||||
|
print(f"Architecture: {env.architecture}")
|
||||||
|
print(f"Suite: {env.suite}")
|
||||||
|
|
||||||
|
# Environment is automatically cleaned up when exiting context
|
||||||
|
print("Environment will be cleaned up automatically")
|
||||||
|
|
||||||
|
print("Context exited, environment cleaned up")
|
||||||
|
|
||||||
|
|
||||||
|
def example_parallel_building():
|
||||||
|
"""Example of parallel building"""
|
||||||
|
print("\n=== Parallel Building Example ===")
|
||||||
|
|
||||||
|
client = create_client()
|
||||||
|
|
||||||
|
# Example source packages (would be real paths in practice)
|
||||||
|
source_packages = [
|
||||||
|
"/path/to/package1.dsc",
|
||||||
|
"/path/to/package2.dsc",
|
||||||
|
"/path/to/package3.dsc"
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Parallel building example:")
|
||||||
|
print(f"Would build {len(source_packages)} packages in parallel")
|
||||||
|
print("Packages:", source_packages)
|
||||||
|
|
||||||
|
# Example parallel build call (commented out as it needs real sources)
|
||||||
|
# results = client.build_parallel(source_packages, max_workers=2)
|
||||||
|
# print(f"Build results: {len(results)} packages processed")
|
||||||
|
|
||||||
|
|
||||||
|
def example_chain_building():
|
||||||
|
"""Example of chain building"""
|
||||||
|
print("\n=== Chain Building Example ===")
|
||||||
|
|
||||||
|
client = create_client()
|
||||||
|
|
||||||
|
# Example dependency chain (would be real paths in practice)
|
||||||
|
chain_packages = [
|
||||||
|
"/path/to/base-package.dsc",
|
||||||
|
"/path/to/depends-on-base.dsc",
|
||||||
|
"/path/to/final-package.dsc"
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Chain building example:")
|
||||||
|
print("Would build packages in dependency order:")
|
||||||
|
for i, pkg in enumerate(chain_packages, 1):
|
||||||
|
print(f" {i}. {pkg}")
|
||||||
|
|
||||||
|
# Example chain build call (commented out as it needs real sources)
|
||||||
|
# results = client.build_chain(chain_packages)
|
||||||
|
# print(f"Chain build results: {len(results)} packages processed")
|
||||||
|
|
||||||
|
|
||||||
|
def example_artifact_collection():
|
||||||
|
"""Example of artifact collection"""
|
||||||
|
print("\n=== Artifact Collection Example ===")
|
||||||
|
|
||||||
|
manager = create_environment_manager()
|
||||||
|
|
||||||
|
# Create environment
|
||||||
|
env_info = manager.create_environment(
|
||||||
|
name="artifact-test-env",
|
||||||
|
arch="amd64",
|
||||||
|
suite="trixie",
|
||||||
|
packages=["build-essential"]
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"Created environment: {env_info.name}")
|
||||||
|
|
||||||
|
# Example artifact collection (would work with real build artifacts)
|
||||||
|
print("Artifact collection example:")
|
||||||
|
print("Would collect artifacts like:")
|
||||||
|
print(" - *.deb files")
|
||||||
|
print(" - *.changes files")
|
||||||
|
print(" - *.buildinfo files")
|
||||||
|
print(" - *.dsc files")
|
||||||
|
print(" - Source tarballs")
|
||||||
|
|
||||||
|
# Example artifact collection call (commented out as it needs real artifacts)
|
||||||
|
# artifacts = manager.collect_artifacts(env_info.name)
|
||||||
|
# print(f"Collected {len(artifacts)} artifacts")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
manager.remove_environment(env_info.name)
|
||||||
|
|
||||||
|
|
||||||
|
def example_performance_monitoring():
|
||||||
|
"""Example of performance monitoring"""
|
||||||
|
print("\n=== Performance Monitoring Example ===")
|
||||||
|
|
||||||
|
client = create_client()
|
||||||
|
|
||||||
|
# Get cache statistics
|
||||||
|
cache_stats = client.get_cache_stats()
|
||||||
|
print(f"Cache statistics: {cache_stats}")
|
||||||
|
|
||||||
|
# Get performance summary (if available)
|
||||||
|
try:
|
||||||
|
perf_summary = client.get_performance_summary()
|
||||||
|
print(f"Performance summary: {perf_summary}")
|
||||||
|
except RuntimeError as e:
|
||||||
|
print(f"Performance monitoring not available: {e}")
|
||||||
|
|
||||||
|
# Export metrics (if available)
|
||||||
|
try:
|
||||||
|
metrics_file = client.export_metrics()
|
||||||
|
print(f"Metrics exported to: {metrics_file}")
|
||||||
|
except RuntimeError as e:
|
||||||
|
print(f"Metrics export not available: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def example_error_handling():
|
||||||
|
"""Example of error handling"""
|
||||||
|
print("\n=== Error Handling Example ===")
|
||||||
|
|
||||||
|
manager = create_environment_manager()
|
||||||
|
|
||||||
|
# Try to get non-existent environment
|
||||||
|
try:
|
||||||
|
manager.get_environment_info("nonexistent-env")
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Expected error: {e}")
|
||||||
|
|
||||||
|
# Try to execute command in non-existent environment
|
||||||
|
try:
|
||||||
|
manager.execute_command("nonexistent-env", ["ls"])
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Expected error: {e}")
|
||||||
|
|
||||||
|
# Try to remove non-existent environment
|
||||||
|
try:
|
||||||
|
manager.remove_environment("nonexistent-env")
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Expected error: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def example_configuration_variations():
|
||||||
|
"""Example of different configuration options"""
|
||||||
|
print("\n=== Configuration Variations Example ===")
|
||||||
|
|
||||||
|
# Ubuntu configuration
|
||||||
|
ubuntu_config = (MockConfigBuilder()
|
||||||
|
.environment("ubuntu-jammy-amd64")
|
||||||
|
.architecture("amd64")
|
||||||
|
.suite("jammy")
|
||||||
|
.mirror("http://archive.ubuntu.com/ubuntu/")
|
||||||
|
.packages(["build-essential", "cmake"])
|
||||||
|
.build())
|
||||||
|
|
||||||
|
print("Ubuntu configuration:")
|
||||||
|
print(f" Environment: {ubuntu_config.chroot_name}")
|
||||||
|
print(f" Suite: {ubuntu_config.suite}")
|
||||||
|
print(f" Mirror: {ubuntu_config.mirror}")
|
||||||
|
|
||||||
|
# ARM64 configuration
|
||||||
|
arm64_config = (MockConfigBuilder()
|
||||||
|
.environment("debian-trixie-arm64")
|
||||||
|
.architecture("arm64")
|
||||||
|
.suite("trixie")
|
||||||
|
.mirror("http://deb.debian.org/debian/")
|
||||||
|
.packages(["crossbuild-essential-arm64"])
|
||||||
|
.build())
|
||||||
|
|
||||||
|
print("\nARM64 configuration:")
|
||||||
|
print(f" Environment: {arm64_config.chroot_name}")
|
||||||
|
print(f" Architecture: {arm64_config.architecture}")
|
||||||
|
print(f" Cross-compilation packages: {arm64_config.chroot_additional_packages}")
|
||||||
|
|
||||||
|
# Development configuration
|
||||||
|
dev_config = (MockConfigBuilder()
|
||||||
|
.environment("dev-env")
|
||||||
|
.architecture("amd64")
|
||||||
|
.suite("sid") # Debian unstable
|
||||||
|
.packages(["build-essential", "devscripts", "cmake", "ninja-build", "git"])
|
||||||
|
.cache_enabled(True)
|
||||||
|
.parallel_jobs(8)
|
||||||
|
.verbose(True)
|
||||||
|
.debug(True)
|
||||||
|
.build())
|
||||||
|
|
||||||
|
print("\nDevelopment configuration:")
|
||||||
|
print(f" Environment: {dev_config.chroot_name}")
|
||||||
|
print(f" Suite: {dev_config.suite}")
|
||||||
|
print(f" Packages: {dev_config.chroot_additional_packages}")
|
||||||
|
print(f" Parallel jobs: {dev_config.parallel_jobs}")
|
||||||
|
print(f" Verbose: {dev_config.verbose}")
|
||||||
|
print(f" Debug: {dev_config.debug}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run all examples"""
|
||||||
|
print("deb-mock API Usage Examples")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
example_basic_usage()
|
||||||
|
example_environment_management()
|
||||||
|
example_command_execution()
|
||||||
|
example_package_building()
|
||||||
|
example_context_manager()
|
||||||
|
example_parallel_building()
|
||||||
|
example_chain_building()
|
||||||
|
example_artifact_collection()
|
||||||
|
example_performance_monitoring()
|
||||||
|
example_error_handling()
|
||||||
|
example_configuration_variations()
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("All examples completed successfully!")
|
||||||
|
print("\nNote: These examples demonstrate the API structure.")
|
||||||
|
print("In practice, you would use real source packages and environments.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nError running examples: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
479
tests/test_api.py
Normal file
479
tests/test_api.py
Normal file
|
|
@ -0,0 +1,479 @@
|
||||||
|
"""
|
||||||
|
Tests for deb-mock API
|
||||||
|
|
||||||
|
This module contains comprehensive tests for the deb-mock API to ensure
|
||||||
|
stability and reliability for external integrations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import Mock, patch, MagicMock
|
||||||
|
|
||||||
|
# Add deb-mock to path
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
|
from deb_mock.api import MockAPIClient, MockEnvironment, MockConfigBuilder, create_client
|
||||||
|
from deb_mock.environment_manager import EnvironmentManager, EnvironmentInfo, BuildResult
|
||||||
|
from deb_mock.config import Config
|
||||||
|
from deb_mock.exceptions import ChrootError, ConfigurationError
|
||||||
|
|
||||||
|
|
||||||
|
class TestMockConfigBuilder(unittest.TestCase):
|
||||||
|
"""Test the MockConfigBuilder class"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.builder = MockConfigBuilder()
|
||||||
|
|
||||||
|
def test_basic_configuration(self):
|
||||||
|
"""Test basic configuration building"""
|
||||||
|
config = (self.builder
|
||||||
|
.environment("test-env")
|
||||||
|
.architecture("amd64")
|
||||||
|
.suite("trixie")
|
||||||
|
.mirror("http://test.debian.org/debian/")
|
||||||
|
.build())
|
||||||
|
|
||||||
|
self.assertEqual(config.chroot_name, "test-env")
|
||||||
|
self.assertEqual(config.architecture, "amd64")
|
||||||
|
self.assertEqual(config.suite, "trixie")
|
||||||
|
self.assertEqual(config.mirror, "http://test.debian.org/debian/")
|
||||||
|
|
||||||
|
def test_packages_configuration(self):
|
||||||
|
"""Test packages configuration"""
|
||||||
|
packages = ["build-essential", "devscripts", "cmake"]
|
||||||
|
config = self.builder.packages(packages).build()
|
||||||
|
|
||||||
|
self.assertEqual(config.chroot_additional_packages, packages)
|
||||||
|
|
||||||
|
def test_output_directory(self):
|
||||||
|
"""Test output directory configuration"""
|
||||||
|
output_dir = "/tmp/test-output"
|
||||||
|
config = self.builder.output_dir(output_dir).build()
|
||||||
|
|
||||||
|
self.assertEqual(config.output_dir, output_dir)
|
||||||
|
|
||||||
|
def test_cache_settings(self):
|
||||||
|
"""Test cache configuration"""
|
||||||
|
config = self.builder.cache_enabled(True).build()
|
||||||
|
self.assertTrue(config.use_root_cache)
|
||||||
|
|
||||||
|
config = self.builder.cache_enabled(False).build()
|
||||||
|
self.assertFalse(config.use_root_cache)
|
||||||
|
|
||||||
|
def test_parallel_jobs(self):
|
||||||
|
"""Test parallel jobs configuration"""
|
||||||
|
config = self.builder.parallel_jobs(8).build()
|
||||||
|
self.assertEqual(config.parallel_jobs, 8)
|
||||||
|
|
||||||
|
def test_verbose_debug(self):
|
||||||
|
"""Test verbose and debug configuration"""
|
||||||
|
config = (self.builder
|
||||||
|
.verbose(True)
|
||||||
|
.debug(True)
|
||||||
|
.build())
|
||||||
|
|
||||||
|
self.assertTrue(config.verbose)
|
||||||
|
self.assertTrue(config.debug)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMockAPIClient(unittest.TestCase):
|
||||||
|
"""Test the MockAPIClient class"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.temp_dir = tempfile.mkdtemp()
|
||||||
|
self.config = Config(
|
||||||
|
chroot_dir=self.temp_dir,
|
||||||
|
output_dir=os.path.join(self.temp_dir, "output"),
|
||||||
|
chroot_config_dir=os.path.join(self.temp_dir, "config")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create necessary directories
|
||||||
|
os.makedirs(self.config.chroot_config_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Mock the DebMock class to avoid actual chroot operations
|
||||||
|
with patch('deb_mock.api.DebMock') as mock_deb_mock:
|
||||||
|
self.mock_deb_mock_instance = Mock()
|
||||||
|
mock_deb_mock.return_value = self.mock_deb_mock_instance
|
||||||
|
|
||||||
|
# Mock chroot manager
|
||||||
|
self.mock_chroot_manager = Mock()
|
||||||
|
self.mock_deb_mock_instance.chroot_manager = self.mock_chroot_manager
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = False
|
||||||
|
self.mock_chroot_manager.get_chroot_info.return_value = {
|
||||||
|
'name': 'test-env',
|
||||||
|
'status': 'active',
|
||||||
|
'size': 1024,
|
||||||
|
'created': None,
|
||||||
|
'modified': None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mock other methods
|
||||||
|
self.mock_deb_mock_instance.init_chroot = Mock()
|
||||||
|
self.mock_deb_mock_instance.install_packages = Mock(return_value={'success': True})
|
||||||
|
self.mock_deb_mock_instance.clean_chroot = Mock()
|
||||||
|
self.mock_deb_mock_instance.list_chroots = Mock(return_value=[])
|
||||||
|
self.mock_deb_mock_instance.build = Mock(return_value={'success': True, 'artifacts': []})
|
||||||
|
self.mock_deb_mock_instance.build_parallel = Mock(return_value=[{'success': True}])
|
||||||
|
self.mock_deb_mock_instance.build_chain = Mock(return_value=[{'success': True}])
|
||||||
|
self.mock_deb_mock_instance.get_cache_stats = Mock(return_value={})
|
||||||
|
self.mock_deb_mock_instance.cleanup_caches = Mock(return_value={})
|
||||||
|
|
||||||
|
self.client = MockAPIClient(self.config)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
def test_client_initialization(self):
|
||||||
|
"""Test client initialization"""
|
||||||
|
self.assertIsInstance(self.client, MockAPIClient)
|
||||||
|
self.assertEqual(self.client.config, self.config)
|
||||||
|
|
||||||
|
def test_create_environment(self):
|
||||||
|
"""Test environment creation"""
|
||||||
|
env = self.client.create_environment("test-env", "amd64", "trixie", ["build-essential"])
|
||||||
|
|
||||||
|
self.assertIsInstance(env, MockEnvironment)
|
||||||
|
self.assertEqual(env.name, "test-env")
|
||||||
|
self.mock_deb_mock_instance.init_chroot.assert_called_once_with("test-env", "amd64", "trixie")
|
||||||
|
self.mock_deb_mock_instance.install_packages.assert_called_once_with(["build-essential"])
|
||||||
|
|
||||||
|
def test_get_environment(self):
|
||||||
|
"""Test getting existing environment"""
|
||||||
|
# Mock existing environment
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = True
|
||||||
|
|
||||||
|
env = self.client.get_environment("test-env")
|
||||||
|
|
||||||
|
self.assertIsInstance(env, MockEnvironment)
|
||||||
|
self.assertEqual(env.name, "test-env")
|
||||||
|
|
||||||
|
def test_get_nonexistent_environment(self):
|
||||||
|
"""Test getting non-existent environment"""
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = False
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.client.get_environment("nonexistent-env")
|
||||||
|
|
||||||
|
def test_list_environments(self):
|
||||||
|
"""Test listing environments"""
|
||||||
|
self.mock_deb_mock_instance.list_chroots.return_value = ["env1", "env2"]
|
||||||
|
|
||||||
|
environments = self.client.list_environments()
|
||||||
|
|
||||||
|
self.assertEqual(environments, ["env1", "env2"])
|
||||||
|
|
||||||
|
def test_remove_environment(self):
|
||||||
|
"""Test removing environment"""
|
||||||
|
self.client.remove_environment("test-env")
|
||||||
|
|
||||||
|
self.mock_deb_mock_instance.clean_chroot.assert_called_once_with("test-env")
|
||||||
|
|
||||||
|
def test_build_package(self):
|
||||||
|
"""Test building a package"""
|
||||||
|
result = self.client.build_package("/path/to/package.dsc", "test-env")
|
||||||
|
|
||||||
|
self.mock_deb_mock_instance.build.assert_called_once()
|
||||||
|
self.assertEqual(result['success'], True)
|
||||||
|
|
||||||
|
def test_build_parallel(self):
|
||||||
|
"""Test parallel building"""
|
||||||
|
packages = ["/path/to/pkg1.dsc", "/path/to/pkg2.dsc"]
|
||||||
|
results = self.client.build_parallel(packages, max_workers=2)
|
||||||
|
|
||||||
|
self.mock_deb_mock_instance.build_parallel.assert_called_once_with(packages, 2)
|
||||||
|
self.assertEqual(len(results), 1)
|
||||||
|
self.assertEqual(results[0]['success'], True)
|
||||||
|
|
||||||
|
def test_build_chain(self):
|
||||||
|
"""Test chain building"""
|
||||||
|
packages = ["/path/to/pkg1.dsc", "/path/to/pkg2.dsc"]
|
||||||
|
results = self.client.build_chain(packages)
|
||||||
|
|
||||||
|
self.mock_deb_mock_instance.build_chain.assert_called_once_with(packages)
|
||||||
|
self.assertEqual(len(results), 1)
|
||||||
|
self.assertEqual(results[0]['success'], True)
|
||||||
|
|
||||||
|
def test_environment_context_manager(self):
|
||||||
|
"""Test environment context manager"""
|
||||||
|
# Mock existing environment
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = True
|
||||||
|
|
||||||
|
with self.client.environment("test-env") as env:
|
||||||
|
self.assertIsInstance(env, MockEnvironment)
|
||||||
|
self.assertEqual(env.name, "test-env")
|
||||||
|
self.assertTrue(env.is_active())
|
||||||
|
|
||||||
|
# Environment should be deactivated after context
|
||||||
|
self.assertFalse(env.is_active())
|
||||||
|
|
||||||
|
|
||||||
|
class TestMockEnvironment(unittest.TestCase):
|
||||||
|
"""Test the MockEnvironment class"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.mock_deb_mock = Mock()
|
||||||
|
self.mock_chroot_manager = Mock()
|
||||||
|
self.mock_deb_mock.chroot_manager = self.mock_chroot_manager
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = True
|
||||||
|
self.mock_chroot_manager.execute_in_chroot.return_value = Mock(returncode=0, stdout="test output")
|
||||||
|
self.mock_chroot_manager.get_chroot_info.return_value = {'status': 'active'}
|
||||||
|
|
||||||
|
self.env = MockEnvironment("test-env", self.mock_deb_mock)
|
||||||
|
|
||||||
|
def test_environment_initialization(self):
|
||||||
|
"""Test environment initialization"""
|
||||||
|
self.assertEqual(self.env.name, "test-env")
|
||||||
|
self.assertFalse(self.env.is_active())
|
||||||
|
|
||||||
|
def test_activate_deactivate(self):
|
||||||
|
"""Test environment activation and deactivation"""
|
||||||
|
self.env.activate()
|
||||||
|
self.assertTrue(self.env.is_active())
|
||||||
|
|
||||||
|
self.env.deactivate()
|
||||||
|
self.assertFalse(self.env.is_active())
|
||||||
|
|
||||||
|
def test_activate_nonexistent_environment(self):
|
||||||
|
"""Test activating non-existent environment"""
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = False
|
||||||
|
|
||||||
|
with self.assertRaises(ChrootError):
|
||||||
|
self.env.activate()
|
||||||
|
|
||||||
|
def test_execute_command(self):
|
||||||
|
"""Test command execution"""
|
||||||
|
self.env.activate()
|
||||||
|
|
||||||
|
result = self.env.execute(["ls", "-la"])
|
||||||
|
|
||||||
|
self.mock_chroot_manager.execute_in_chroot.assert_called_once_with(
|
||||||
|
"test-env", ["ls", "-la"], capture_output=True
|
||||||
|
)
|
||||||
|
self.assertEqual(result.returncode, 0)
|
||||||
|
|
||||||
|
def test_execute_command_string(self):
|
||||||
|
"""Test command execution with string command"""
|
||||||
|
self.env.activate()
|
||||||
|
|
||||||
|
result = self.env.execute("ls -la")
|
||||||
|
|
||||||
|
self.mock_chroot_manager.execute_in_chroot.assert_called_once_with(
|
||||||
|
"test-env", ["ls", "-la"], capture_output=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_execute_command_inactive(self):
|
||||||
|
"""Test executing command on inactive environment"""
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
self.env.execute(["ls"])
|
||||||
|
|
||||||
|
def test_install_packages(self):
|
||||||
|
"""Test package installation"""
|
||||||
|
self.env.activate()
|
||||||
|
|
||||||
|
result = self.env.install_packages(["build-essential"])
|
||||||
|
|
||||||
|
self.mock_deb_mock.install_packages.assert_called_once_with(["build-essential"])
|
||||||
|
self.assertEqual(result['success'], True)
|
||||||
|
|
||||||
|
def test_copy_in(self):
|
||||||
|
"""Test copying files into environment"""
|
||||||
|
self.env.activate()
|
||||||
|
|
||||||
|
self.env.copy_in("/local/file", "/chroot/file")
|
||||||
|
|
||||||
|
self.mock_chroot_manager.copy_to_chroot.assert_called_once_with(
|
||||||
|
"/local/file", "/chroot/file", "test-env"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_copy_out(self):
|
||||||
|
"""Test copying files out of environment"""
|
||||||
|
self.env.activate()
|
||||||
|
|
||||||
|
self.env.copy_out("/chroot/file", "/local/file")
|
||||||
|
|
||||||
|
self.mock_chroot_manager.copy_from_chroot.assert_called_once_with(
|
||||||
|
"/chroot/file", "/local/file", "test-env"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_info(self):
|
||||||
|
"""Test getting environment info"""
|
||||||
|
info = self.env.get_info()
|
||||||
|
|
||||||
|
self.mock_chroot_manager.get_chroot_info.assert_called_once_with("test-env")
|
||||||
|
self.assertEqual(info['status'], 'active')
|
||||||
|
|
||||||
|
|
||||||
|
class TestEnvironmentManager(unittest.TestCase):
|
||||||
|
"""Test the EnvironmentManager class"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.temp_dir = tempfile.mkdtemp()
|
||||||
|
self.config = Config(
|
||||||
|
chroot_dir=self.temp_dir,
|
||||||
|
output_dir=os.path.join(self.temp_dir, "output"),
|
||||||
|
chroot_config_dir=os.path.join(self.temp_dir, "config")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create necessary directories
|
||||||
|
os.makedirs(self.config.chroot_config_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Mock the DebMock class
|
||||||
|
with patch('deb_mock.environment_manager.DebMock') as mock_deb_mock:
|
||||||
|
self.mock_deb_mock_instance = Mock()
|
||||||
|
mock_deb_mock.return_value = self.mock_deb_mock_instance
|
||||||
|
|
||||||
|
# Mock chroot manager
|
||||||
|
self.mock_chroot_manager = Mock()
|
||||||
|
self.mock_deb_mock_instance.chroot_manager = self.mock_chroot_manager
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = False
|
||||||
|
self.mock_chroot_manager.get_chroot_info.return_value = {
|
||||||
|
'name': 'test-env',
|
||||||
|
'status': 'active',
|
||||||
|
'size': 1024,
|
||||||
|
'created': None,
|
||||||
|
'modified': None
|
||||||
|
}
|
||||||
|
self.mock_chroot_manager.list_mounts.return_value = []
|
||||||
|
|
||||||
|
# Mock other methods
|
||||||
|
self.mock_deb_mock_instance.init_chroot = Mock()
|
||||||
|
self.mock_deb_mock_instance.install_packages = Mock(return_value={'success': True})
|
||||||
|
self.mock_deb_mock_instance.clean_chroot = Mock()
|
||||||
|
self.mock_deb_mock_instance.list_chroots = Mock(return_value=[])
|
||||||
|
self.mock_deb_mock_instance.build = Mock(return_value={'success': True, 'artifacts': []})
|
||||||
|
self.mock_deb_mock_instance.update_chroot = Mock()
|
||||||
|
|
||||||
|
self.manager = EnvironmentManager(self.config)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
def test_manager_initialization(self):
|
||||||
|
"""Test manager initialization"""
|
||||||
|
self.assertIsInstance(self.manager, EnvironmentManager)
|
||||||
|
self.assertEqual(self.manager.config, self.config)
|
||||||
|
|
||||||
|
def test_create_environment(self):
|
||||||
|
"""Test environment creation"""
|
||||||
|
info = self.manager.create_environment("test-env", "amd64", "trixie", ["build-essential"])
|
||||||
|
|
||||||
|
self.assertIsInstance(info, EnvironmentInfo)
|
||||||
|
self.assertEqual(info.name, "test-env")
|
||||||
|
self.assertEqual(info.architecture, "amd64")
|
||||||
|
self.assertEqual(info.suite, "trixie")
|
||||||
|
self.mock_deb_mock_instance.init_chroot.assert_called_once_with("test-env", "amd64", "trixie")
|
||||||
|
|
||||||
|
def test_environment_exists(self):
|
||||||
|
"""Test checking if environment exists"""
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = True
|
||||||
|
|
||||||
|
self.assertTrue(self.manager.environment_exists("test-env"))
|
||||||
|
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = False
|
||||||
|
|
||||||
|
self.assertFalse(self.manager.environment_exists("test-env"))
|
||||||
|
|
||||||
|
def test_get_environment_info(self):
|
||||||
|
"""Test getting environment info"""
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = True
|
||||||
|
|
||||||
|
info = self.manager.get_environment_info("test-env")
|
||||||
|
|
||||||
|
self.assertIsInstance(info, EnvironmentInfo)
|
||||||
|
self.assertEqual(info.name, "test-env")
|
||||||
|
|
||||||
|
def test_get_nonexistent_environment_info(self):
|
||||||
|
"""Test getting info for non-existent environment"""
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = False
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.manager.get_environment_info("nonexistent-env")
|
||||||
|
|
||||||
|
def test_list_environments(self):
|
||||||
|
"""Test listing environments"""
|
||||||
|
self.mock_deb_mock_instance.list_chroots.return_value = ["env1", "env2"]
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = True
|
||||||
|
|
||||||
|
environments = self.manager.list_environments()
|
||||||
|
|
||||||
|
self.assertEqual(len(environments), 2)
|
||||||
|
self.assertIsInstance(environments[0], EnvironmentInfo)
|
||||||
|
|
||||||
|
def test_remove_environment(self):
|
||||||
|
"""Test removing environment"""
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = True
|
||||||
|
|
||||||
|
self.manager.remove_environment("test-env")
|
||||||
|
|
||||||
|
self.mock_deb_mock_instance.clean_chroot.assert_called_once_with("test-env")
|
||||||
|
|
||||||
|
def test_execute_command(self):
|
||||||
|
"""Test command execution"""
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = True
|
||||||
|
self.mock_chroot_manager.execute_in_chroot.return_value = Mock(returncode=0, stdout="test output")
|
||||||
|
|
||||||
|
result = self.manager.execute_command("test-env", ["ls", "-la"])
|
||||||
|
|
||||||
|
self.mock_chroot_manager.execute_in_chroot.assert_called_once_with(
|
||||||
|
"test-env", ["ls", "-la"], capture_output=True
|
||||||
|
)
|
||||||
|
self.assertEqual(result.returncode, 0)
|
||||||
|
|
||||||
|
def test_install_packages(self):
|
||||||
|
"""Test package installation"""
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = True
|
||||||
|
|
||||||
|
result = self.manager.install_packages("test-env", ["build-essential"])
|
||||||
|
|
||||||
|
self.mock_deb_mock_instance.install_packages.assert_called_once_with(["build-essential"])
|
||||||
|
self.assertEqual(result['success'], True)
|
||||||
|
|
||||||
|
def test_build_package(self):
|
||||||
|
"""Test package building"""
|
||||||
|
self.mock_chroot_manager.chroot_exists.return_value = True
|
||||||
|
|
||||||
|
result = self.manager.build_package("test-env", "/path/to/package.dsc")
|
||||||
|
|
||||||
|
self.mock_deb_mock_instance.build.assert_called_once()
|
||||||
|
self.assertIsInstance(result, BuildResult)
|
||||||
|
self.assertTrue(result.success)
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntegration(unittest.TestCase):
|
||||||
|
"""Integration tests for the API"""
|
||||||
|
|
||||||
|
def test_create_client_with_config_builder(self):
|
||||||
|
"""Test creating client with config builder"""
|
||||||
|
config = (MockConfigBuilder()
|
||||||
|
.environment("test-env")
|
||||||
|
.architecture("amd64")
|
||||||
|
.suite("trixie")
|
||||||
|
.build())
|
||||||
|
|
||||||
|
client = MockAPIClient(config)
|
||||||
|
|
||||||
|
self.assertIsInstance(client, MockAPIClient)
|
||||||
|
self.assertEqual(client.config.chroot_name, "test-env")
|
||||||
|
|
||||||
|
def test_quick_build_function(self):
|
||||||
|
"""Test the quick_build convenience function"""
|
||||||
|
from deb_mock.api import quick_build
|
||||||
|
|
||||||
|
with patch('deb_mock.api.MockAPIClient') as mock_client_class:
|
||||||
|
mock_client = Mock()
|
||||||
|
mock_client.build_package.return_value = {'success': True}
|
||||||
|
mock_client_class.return_value = mock_client
|
||||||
|
|
||||||
|
result = quick_build("/path/to/package.dsc")
|
||||||
|
|
||||||
|
self.assertEqual(result['success'], True)
|
||||||
|
mock_client.build_package.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue