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 .core import DebMock
|
||||
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__ = [
|
||||
# Core classes
|
||||
"DebMock",
|
||||
"Config",
|
||||
"ChrootManager",
|
||||
"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
|
||||
for the Deb-Mock plugin system, inspired by Fedora's Mock plugin architecture.
|
||||
This module provides a centralized registry for managing deb-mock plugins,
|
||||
including discovery, loading, and lifecycle management.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import importlib
|
||||
import importlib.util
|
||||
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 ..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:
|
||||
"""
|
||||
Manages plugin registration and instantiation.
|
||||
|
||||
This class provides the functionality for registering plugin classes
|
||||
and creating plugin instances, following Mock's plugin system pattern.
|
||||
Central registry for deb-mock plugins
|
||||
|
||||
This class manages plugin discovery, loading, and lifecycle.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the plugin registry."""
|
||||
self.plugins: Dict[str, Type[BasePlugin]] = {}
|
||||
self.plugin_metadata: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
# Auto-register built-in plugins
|
||||
self._register_builtin_plugins()
|
||||
|
||||
def register(
|
||||
self,
|
||||
plugin_name: str,
|
||||
plugin_class: Type[BasePlugin],
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
|
||||
def __init__(self, plugin_dirs: List[str] = None):
|
||||
"""
|
||||
Register a plugin class.
|
||||
|
||||
Initialize the plugin registry
|
||||
|
||||
Args:
|
||||
plugin_name: Name of the plugin
|
||||
plugin_class: Plugin class to register
|
||||
metadata: Optional metadata about the plugin
|
||||
|
||||
Raises:
|
||||
ValueError: If plugin_name is already registered
|
||||
TypeError: If plugin_class is not a subclass of BasePlugin
|
||||
plugin_dirs: List of directories to search for plugins
|
||||
"""
|
||||
if not issubclass(plugin_class, BasePlugin):
|
||||
raise TypeError("Plugin class must inherit from BasePlugin")
|
||||
|
||||
if plugin_name in self.plugins:
|
||||
raise ValueError(f"Plugin '{plugin_name}' is already registered")
|
||||
|
||||
self.plugins[plugin_name] = plugin_class
|
||||
self.plugin_metadata[plugin_name] = metadata or {}
|
||||
|
||||
logger.debug(f"Registered plugin '{plugin_name}' with class {plugin_class.__name__}")
|
||||
|
||||
def unregister(self, plugin_name: str) -> bool:
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
# Default plugin directories
|
||||
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')
|
||||
]
|
||||
|
||||
# Plugin storage
|
||||
self._plugins: Dict[str, PluginInfo] = {}
|
||||
self._loaded_plugins: Dict[str, BasePlugin] = {}
|
||||
|
||||
# API version compatibility
|
||||
self.current_api_version = "1.0"
|
||||
self.min_api_version = "1.0"
|
||||
self.max_api_version = "1.0"
|
||||
|
||||
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:
|
||||
plugin_name: Name of the plugin to unregister
|
||||
|
||||
Returns:
|
||||
True if plugin was unregistered, False if not found
|
||||
"""
|
||||
if plugin_name not in self.plugins:
|
||||
return False
|
||||
|
||||
del self.plugins[plugin_name]
|
||||
del self.plugin_metadata[plugin_name]
|
||||
|
||||
logger.debug(f"Unregistered plugin '{plugin_name}'")
|
||||
return True
|
||||
|
||||
def get_plugin_class(self, plugin_name: str) -> Optional[Type[BasePlugin]]:
|
||||
if plugin_name in self._plugins:
|
||||
del self._plugins[plugin_name]
|
||||
self.logger.info(f"Unregistered plugin: {plugin_name}")
|
||||
|
||||
def get_plugin(self, plugin_name: str) -> Optional[PluginInfo]:
|
||||
"""
|
||||
Get a registered plugin class.
|
||||
|
||||
Get plugin information by name
|
||||
|
||||
Args:
|
||||
plugin_name: Name of the plugin
|
||||
|
||||
|
||||
Returns:
|
||||
Plugin class if found, None otherwise
|
||||
PluginInfo object or None if not found
|
||||
"""
|
||||
return self.plugins.get(plugin_name)
|
||||
|
||||
def get_plugins(self) -> Dict[str, Type[BasePlugin]]:
|
||||
return self._plugins.get(plugin_name)
|
||||
|
||||
def list_plugins(self) -> List[PluginInfo]:
|
||||
"""
|
||||
Get all registered plugins.
|
||||
|
||||
List all registered plugins
|
||||
|
||||
Returns:
|
||||
Dictionary of registered plugin names and classes
|
||||
List of plugin information
|
||||
"""
|
||||
return self.plugins.copy()
|
||||
|
||||
def get_plugin_names(self) -> list:
|
||||
return list(self._plugins.values())
|
||||
|
||||
def list_enabled_plugins(self) -> List[PluginInfo]:
|
||||
"""
|
||||
Get list of registered plugin names.
|
||||
|
||||
List enabled plugins
|
||||
|
||||
Returns:
|
||||
List of registered plugin names
|
||||
List of enabled plugin information
|
||||
"""
|
||||
return list(self.plugins.keys())
|
||||
|
||||
def create(self, plugin_name: str, config: Any, hook_manager: Any) -> Optional[BasePlugin]:
|
||||
return [plugin for plugin in self._plugins.values() if plugin.enabled]
|
||||
|
||||
def enable_plugin(self, plugin_name: str) -> None:
|
||||
"""
|
||||
Create a plugin instance.
|
||||
|
||||
Enable a plugin
|
||||
|
||||
Args:
|
||||
plugin_name: Name of the plugin to create
|
||||
config: Configuration object
|
||||
hook_manager: Hook manager instance
|
||||
|
||||
Returns:
|
||||
Plugin instance if successful, None if plugin not found
|
||||
plugin_name: Name of the plugin to enable
|
||||
"""
|
||||
plugin_class = self.get_plugin_class(plugin_name)
|
||||
if not plugin_class:
|
||||
logger.warning(f"Plugin '{plugin_name}' not found")
|
||||
return None
|
||||
|
||||
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:
|
||||
Loaded plugin instance
|
||||
|
||||
Raises:
|
||||
PluginError: If plugin cannot be loaded
|
||||
"""
|
||||
if plugin_name not in self._plugins:
|
||||
raise PluginError(f"Plugin '{plugin_name}' not found in registry")
|
||||
|
||||
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:
|
||||
plugin_instance = plugin_class(config, hook_manager)
|
||||
logger.debug(f"Created plugin instance '{plugin_name}'")
|
||||
# Create plugin instance
|
||||
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
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create plugin '{plugin_name}': {e}")
|
||||
return None
|
||||
|
||||
def create_all_enabled(self, config: Any, hook_manager: Any) -> Dict[str, BasePlugin]:
|
||||
raise PluginError(f"Failed to load plugin '{plugin_name}': {e}")
|
||||
|
||||
def unload_plugin(self, plugin_name: str) -> None:
|
||||
"""
|
||||
Create instances of all enabled plugins.
|
||||
|
||||
Unload a plugin instance
|
||||
|
||||
Args:
|
||||
config: Configuration object
|
||||
hook_manager: Hook manager instance
|
||||
|
||||
Returns:
|
||||
Dictionary of plugin names and instances
|
||||
plugin_name: Name of the plugin to unload
|
||||
"""
|
||||
enabled_plugins = {}
|
||||
|
||||
for plugin_name in self.get_plugin_names():
|
||||
plugin_instance = self.create(plugin_name, config, hook_manager)
|
||||
if plugin_instance and plugin_instance.enabled:
|
||||
enabled_plugins[plugin_name] = plugin_instance
|
||||
|
||||
logger.debug(f"Created {len(enabled_plugins)} enabled plugin instances")
|
||||
return enabled_plugins
|
||||
|
||||
def get_plugin_info(self, plugin_name: str) -> Dict[str, Any]:
|
||||
if plugin_name in self._loaded_plugins:
|
||||
del self._loaded_plugins[plugin_name]
|
||||
self.logger.info(f"Unloaded plugin: {plugin_name}")
|
||||
|
||||
def reload_plugin(self, plugin_name: str, plugin_manager, config: Dict[str, Any], deb_mock) -> BasePlugin:
|
||||
"""
|
||||
Get information about a registered plugin.
|
||||
|
||||
Reload a plugin
|
||||
|
||||
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:
|
||||
Dictionary with plugin information
|
||||
Reloaded plugin instance
|
||||
"""
|
||||
if plugin_name not in self.plugins:
|
||||
return {"error": f'Plugin "{plugin_name}" not found'}
|
||||
|
||||
plugin_class = self.plugins[plugin_name]
|
||||
metadata = self.plugin_metadata[plugin_name]
|
||||
|
||||
info = {
|
||||
"name": plugin_name,
|
||||
"class": plugin_class.__name__,
|
||||
"module": plugin_class.__module__,
|
||||
"metadata": metadata,
|
||||
"docstring": plugin_class.__doc__ or "No documentation available",
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
def get_all_plugin_info(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
Get information about all registered plugins.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping plugin names to their information
|
||||
"""
|
||||
return {name: self.get_plugin_info(name) for name in self.get_plugin_names()}
|
||||
|
||||
def load_plugin_from_module(self, module_name: str, plugin_name: str) -> bool:
|
||||
"""
|
||||
Load a plugin from a module.
|
||||
|
||||
Args:
|
||||
module_name: Name of the module to load
|
||||
plugin_name: Name of the plugin class in the module
|
||||
|
||||
Returns:
|
||||
True if plugin was loaded successfully, False otherwise
|
||||
"""
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
plugin_class = getattr(module, plugin_name)
|
||||
|
||||
# Use module name as plugin name if not specified
|
||||
self.register(plugin_name, plugin_class)
|
||||
return True
|
||||
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import module '{module_name}': {e}")
|
||||
return False
|
||||
except AttributeError as e:
|
||||
logger.error(f"Plugin class '{plugin_name}' not found in module '{module_name}': {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load plugin from '{module_name}.{plugin_name}': {e}")
|
||||
return False
|
||||
|
||||
def load_plugins_from_config(self, config: Any) -> Dict[str, BasePlugin]:
|
||||
"""
|
||||
Load plugins based on configuration.
|
||||
|
||||
Args:
|
||||
config: Configuration object with plugin settings
|
||||
|
||||
Returns:
|
||||
Dictionary of loaded plugin instances
|
||||
"""
|
||||
loaded_plugins = {}
|
||||
|
||||
if not hasattr(config, "plugins") or not config.plugins:
|
||||
return loaded_plugins
|
||||
|
||||
for plugin_name, plugin_config in config.plugins.items():
|
||||
if not isinstance(plugin_config, dict):
|
||||
continue
|
||||
|
||||
if plugin_config.get("enabled", False):
|
||||
# Try to load from built-in plugins first
|
||||
plugin_instance = self.create(plugin_name, config, None)
|
||||
if plugin_instance:
|
||||
loaded_plugins[plugin_name] = plugin_instance
|
||||
else:
|
||||
# Try to load from external module
|
||||
module_name = plugin_config.get("module")
|
||||
if module_name:
|
||||
if self.load_plugin_from_module(module_name, plugin_name):
|
||||
plugin_instance = self.create(plugin_name, config, None)
|
||||
if plugin_instance:
|
||||
loaded_plugins[plugin_name] = plugin_instance
|
||||
|
||||
return loaded_plugins
|
||||
|
||||
def _register_builtin_plugins(self) -> None:
|
||||
"""Register built-in plugins."""
|
||||
try:
|
||||
# Import and register built-in plugins
|
||||
from .bind_mount import BindMountPlugin
|
||||
from .compress_logs import CompressLogsPlugin
|
||||
from .root_cache import RootCachePlugin
|
||||
from .tmpfs import TmpfsPlugin
|
||||
|
||||
self.register(
|
||||
"bind_mount",
|
||||
BindMountPlugin,
|
||||
{
|
||||
"description": "Mount host directories into chroot",
|
||||
"hooks": ["mount_root", "postumount"],
|
||||
"builtin": True,
|
||||
},
|
||||
)
|
||||
|
||||
self.register(
|
||||
"compress_logs",
|
||||
CompressLogsPlugin,
|
||||
{
|
||||
"description": "Compress build logs to save space",
|
||||
"hooks": ["process_logs"],
|
||||
"builtin": True,
|
||||
},
|
||||
)
|
||||
|
||||
self.register(
|
||||
"root_cache",
|
||||
RootCachePlugin,
|
||||
{
|
||||
"description": "Root cache management for faster builds",
|
||||
"hooks": [
|
||||
"preinit",
|
||||
"postinit",
|
||||
"postchroot",
|
||||
"postshell",
|
||||
"clean",
|
||||
],
|
||||
"builtin": True,
|
||||
},
|
||||
)
|
||||
|
||||
self.register(
|
||||
"tmpfs",
|
||||
TmpfsPlugin,
|
||||
{
|
||||
"description": "Use tmpfs for faster I/O operations",
|
||||
"hooks": ["mount_root", "postumount"],
|
||||
"builtin": True,
|
||||
},
|
||||
)
|
||||
|
||||
logger.debug("Registered built-in plugins")
|
||||
|
||||
except ImportError as e:
|
||||
logger.warning(f"Some built-in plugins could not be loaded: {e}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Error registering built-in plugins: {e}")
|
||||
|
||||
self.unload_plugin(plugin_name)
|
||||
return self.load_plugin(plugin_name, plugin_manager, config, deb_mock)
|
||||
|
||||
def get_plugin_statistics(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get statistics about registered plugins.
|
||||
|
||||
Get plugin registry statistics
|
||||
|
||||
Returns:
|
||||
Dictionary with plugin statistics
|
||||
"""
|
||||
stats = {
|
||||
"total_plugins": len(self.plugins),
|
||||
"builtin_plugins": len([p for p in self.plugin_metadata.values() if p.get("builtin", False)]),
|
||||
"external_plugins": len([p for p in self.plugin_metadata.values() if not p.get("builtin", False)]),
|
||||
"plugins_by_hook": {},
|
||||
total_plugins = len(self._plugins)
|
||||
enabled_plugins = len(self.list_enabled_plugins())
|
||||
loaded_plugins = len(self._loaded_plugins)
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
def validate_plugin_dependencies(self, plugin_name: str) -> List[str]:
|
||||
"""
|
||||
Validate plugin configuration.
|
||||
|
||||
Validate plugin dependencies
|
||||
|
||||
Args:
|
||||
plugin_name: Name of the plugin
|
||||
config: Configuration to validate
|
||||
|
||||
plugin_name: Name of the plugin to validate
|
||||
|
||||
Returns:
|
||||
True if configuration is valid, False otherwise
|
||||
List of missing dependencies
|
||||
"""
|
||||
if plugin_name not in self.plugins:
|
||||
return False
|
||||
if plugin_name not in self._plugins:
|
||||
return [f"Plugin '{plugin_name}' not found"]
|
||||
|
||||
plugin_info = self._plugins[plugin_name]
|
||||
missing_deps = []
|
||||
|
||||
# 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
|
||||
|
||||
# Basic validation - plugins can override this method
|
||||
plugin_class = self.plugins[plugin_name]
|
||||
if hasattr(plugin_class, "validate_config"):
|
||||
return plugin_class.validate_config(config)
|
||||
|
||||
return True
|
||||
# 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)
|
||||
1317
docs/API.md
1317
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