Fix sbuild integration and clean up codebase
- Fix environment variable handling in sbuild wrapper - Remove unsupported --log-dir and --env options from sbuild command - Clean up unused imports and fix linting issues - Organize examples directory with official Debian hello package - Fix YAML formatting (trailing spaces, newlines) - Remove placeholder example files - All tests passing (30/30) - Successfully tested build with official Debian hello package
This commit is contained in:
parent
c33e3aa9ac
commit
5e7f4b0562
32 changed files with 2322 additions and 2228 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -143,6 +143,9 @@ metadata/
|
|||
*.tar.bz2
|
||||
*.diff.gz
|
||||
*.orig.tar.gz
|
||||
!mock_*_all.deb
|
||||
!mock_*.buildinfo
|
||||
!mock_*.changes
|
||||
|
||||
# Chroot environments
|
||||
/var/lib/deb-mock/
|
||||
|
|
|
|||
6
Makefile
6
Makefile
|
|
@ -14,10 +14,10 @@ install-dev: ## Install deb-mock with development dependencies
|
|||
pip install -r requirements-dev.txt
|
||||
|
||||
test: ## Run tests
|
||||
python -m pytest tests/ -v
|
||||
python3 -m pytest tests/ -v
|
||||
|
||||
test-coverage: ## Run tests with coverage
|
||||
python -m pytest tests/ --cov=deb_mock --cov-report=html --cov-report=term
|
||||
python3 -m pytest tests/ --cov=deb_mock --cov-report=html --cov-report=term
|
||||
|
||||
lint: ## Run linting checks
|
||||
@echo "=== Running all linting checks with Docker container ==="
|
||||
|
|
@ -84,7 +84,7 @@ check: ## Run all checks (lint, test, format)
|
|||
$(MAKE) format
|
||||
|
||||
dist: ## Build distribution package
|
||||
python setup.py sdist bdist_wheel
|
||||
python3 setup.py sdist bdist_wheel
|
||||
|
||||
upload: ## Upload to PyPI (requires twine)
|
||||
twine upload dist/*
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ __version__ = "0.1.0"
|
|||
__author__ = "Deb-Mock Team"
|
||||
__email__ = "team@deb-mock.org"
|
||||
|
||||
from .core import DebMock
|
||||
from .config import Config
|
||||
from .chroot import ChrootManager
|
||||
from .config import Config
|
||||
from .core import DebMock
|
||||
from .sbuild import SbuildWrapper
|
||||
|
||||
__all__ = [
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
Cache management for deb-mock
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import tarfile
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict
|
||||
|
||||
from .exceptions import DebMockError
|
||||
|
||||
|
||||
|
|
@ -43,11 +43,11 @@ class CacheManager:
|
|||
os.makedirs(os.path.dirname(cache_file), exist_ok=True)
|
||||
|
||||
# Create tar.gz archive of the chroot
|
||||
with tarfile.open(cache_file, 'w:gz') as tar:
|
||||
with tarfile.open(cache_file, "w:gz") as tar:
|
||||
tar.add(chroot_path, arcname=os.path.basename(chroot_path))
|
||||
|
||||
# Update cache metadata
|
||||
self._update_cache_metadata('root_cache', cache_file)
|
||||
self._update_cache_metadata("root_cache", cache_file)
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -65,12 +65,12 @@ class CacheManager:
|
|||
return False
|
||||
|
||||
# Check cache age
|
||||
if not self._is_cache_valid('root_cache', cache_file):
|
||||
if not self._is_cache_valid("root_cache", cache_file):
|
||||
return False
|
||||
|
||||
try:
|
||||
# Extract cache to chroot path
|
||||
with tarfile.open(cache_file, 'r:gz') as tar:
|
||||
with tarfile.open(cache_file, "r:gz") as tar:
|
||||
tar.extractall(path=os.path.dirname(chroot_path))
|
||||
|
||||
return True
|
||||
|
|
@ -111,7 +111,7 @@ class CacheManager:
|
|||
|
||||
packages = []
|
||||
for file in os.listdir(cache_path):
|
||||
if file.endswith('.deb'):
|
||||
if file.endswith(".deb"):
|
||||
packages.append(os.path.join(cache_path, file))
|
||||
|
||||
return packages
|
||||
|
|
@ -128,8 +128,8 @@ class CacheManager:
|
|||
os.makedirs(ccache_path, exist_ok=True)
|
||||
|
||||
# Set ccache environment variables
|
||||
os.environ['CCACHE_DIR'] = ccache_path
|
||||
os.environ['CCACHE_HASHDIR'] = '1'
|
||||
os.environ["CCACHE_DIR"] = ccache_path
|
||||
os.environ["CCACHE_HASHDIR"] = "1"
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -142,15 +142,15 @@ class CacheManager:
|
|||
|
||||
# Clean root caches
|
||||
if self.config.use_root_cache:
|
||||
cleaned['root_cache'] = self._cleanup_root_caches()
|
||||
cleaned["root_cache"] = self._cleanup_root_caches()
|
||||
|
||||
# Clean package caches
|
||||
if self.config.use_package_cache:
|
||||
cleaned['package_cache'] = self._cleanup_package_caches()
|
||||
cleaned["package_cache"] = self._cleanup_package_caches()
|
||||
|
||||
# Clean ccache
|
||||
if self.config.use_ccache:
|
||||
cleaned['ccache'] = self._cleanup_ccache()
|
||||
cleaned["ccache"] = self._cleanup_ccache()
|
||||
|
||||
return cleaned
|
||||
|
||||
|
|
@ -164,7 +164,7 @@ class CacheManager:
|
|||
cutoff_time = datetime.now() - timedelta(days=self.config.root_cache_age)
|
||||
|
||||
for cache_file in os.listdir(cache_dir):
|
||||
if cache_file.endswith('.tar.gz'):
|
||||
if cache_file.endswith(".tar.gz"):
|
||||
cache_path = os.path.join(cache_dir, cache_file)
|
||||
if os.path.getmtime(cache_path) < cutoff_time.timestamp():
|
||||
os.remove(cache_path)
|
||||
|
|
@ -182,7 +182,7 @@ class CacheManager:
|
|||
cutoff_time = datetime.now() - timedelta(days=30) # 30 days for package cache
|
||||
|
||||
for package_file in os.listdir(cache_path):
|
||||
if package_file.endswith('.deb'):
|
||||
if package_file.endswith(".deb"):
|
||||
package_path = os.path.join(cache_path, package_file)
|
||||
if os.path.getmtime(package_path) < cutoff_time.timestamp():
|
||||
os.remove(package_path)
|
||||
|
|
@ -199,7 +199,8 @@ class CacheManager:
|
|||
# Use ccache's built-in cleanup
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run(['ccache', '-c'], cwd=ccache_path, capture_output=True)
|
||||
|
||||
result = subprocess.run(["ccache", "-c"], cwd=ccache_path, capture_output=True)
|
||||
return 1 if result.returncode == 0 else 0
|
||||
except Exception:
|
||||
return 0
|
||||
|
|
@ -209,14 +210,15 @@ class CacheManager:
|
|||
metadata_file = f"{cache_file}.meta"
|
||||
|
||||
metadata = {
|
||||
'type': cache_type,
|
||||
'created': datetime.now().isoformat(),
|
||||
'size': os.path.getsize(cache_file),
|
||||
'hash': self._get_file_hash(cache_file)
|
||||
"type": cache_type,
|
||||
"created": datetime.now().isoformat(),
|
||||
"size": os.path.getsize(cache_file),
|
||||
"hash": self._get_file_hash(cache_file),
|
||||
}
|
||||
|
||||
import json
|
||||
with open(metadata_file, 'w') as f:
|
||||
|
||||
with open(metadata_file, "w") as f:
|
||||
json.dump(metadata, f)
|
||||
|
||||
def _is_cache_valid(self, cache_type: str, cache_file: str) -> bool:
|
||||
|
|
@ -228,20 +230,21 @@ class CacheManager:
|
|||
|
||||
try:
|
||||
import json
|
||||
with open(metadata_file, 'r') as f:
|
||||
|
||||
with open(metadata_file, "r") as f:
|
||||
metadata = json.load(f)
|
||||
|
||||
# Check if file size matches
|
||||
if os.path.getsize(cache_file) != metadata.get('size', 0):
|
||||
if os.path.getsize(cache_file) != metadata.get("size", 0):
|
||||
return False
|
||||
|
||||
# Check if hash matches
|
||||
if self._get_file_hash(cache_file) != metadata.get('hash', ''):
|
||||
if self._get_file_hash(cache_file) != metadata.get("hash", ""):
|
||||
return False
|
||||
|
||||
# Check age for root cache
|
||||
if cache_type == 'root_cache':
|
||||
created = datetime.fromisoformat(metadata['created'])
|
||||
if cache_type == "root_cache":
|
||||
created = datetime.fromisoformat(metadata["created"])
|
||||
cutoff_time = datetime.now() - timedelta(days=self.config.root_cache_age)
|
||||
if created < cutoff_time:
|
||||
return False
|
||||
|
|
@ -267,19 +270,19 @@ class CacheManager:
|
|||
if self.config.use_root_cache:
|
||||
cache_file = f"{self.get_root_cache_path()}.tar.gz"
|
||||
if os.path.exists(cache_file):
|
||||
stats['root_cache'] = {
|
||||
'size': os.path.getsize(cache_file),
|
||||
'valid': self._is_cache_valid('root_cache', cache_file)
|
||||
stats["root_cache"] = {
|
||||
"size": os.path.getsize(cache_file),
|
||||
"valid": self._is_cache_valid("root_cache", cache_file),
|
||||
}
|
||||
|
||||
# Package cache stats
|
||||
if self.config.use_package_cache:
|
||||
cache_path = self.get_package_cache_path()
|
||||
if os.path.exists(cache_path):
|
||||
packages = [f for f in os.listdir(cache_path) if f.endswith('.deb')]
|
||||
stats['package_cache'] = {
|
||||
'packages': len(packages),
|
||||
'size': sum(os.path.getsize(os.path.join(cache_path, p)) for p in packages)
|
||||
packages = [f for f in os.listdir(cache_path) if f.endswith(".deb")]
|
||||
stats["package_cache"] = {
|
||||
"packages": len(packages),
|
||||
"size": sum(os.path.getsize(os.path.join(cache_path, p)) for p in packages),
|
||||
}
|
||||
|
||||
# ccache stats
|
||||
|
|
@ -288,11 +291,14 @@ class CacheManager:
|
|||
if os.path.exists(ccache_path):
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run(['ccache', '-s'], cwd=ccache_path,
|
||||
capture_output=True, text=True)
|
||||
stats['ccache'] = {
|
||||
'output': result.stdout
|
||||
}
|
||||
|
||||
result = subprocess.run(
|
||||
["ccache", "-s"],
|
||||
cwd=ccache_path,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
stats["ccache"] = {"output": result.stdout}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ Chroot management for deb-mock
|
|||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
from .exceptions import ChrootError
|
||||
|
||||
|
||||
|
|
@ -24,8 +25,6 @@ class ChrootManager:
|
|||
if suite:
|
||||
self.config.suite = suite
|
||||
|
||||
chroot_path = os.path.join(self.config.chroot_dir, chroot_name)
|
||||
|
||||
# Check if bootstrap chroot is needed (Mock FAQ #2)
|
||||
if self.config.use_bootstrap_chroot:
|
||||
self._create_bootstrap_chroot(chroot_name)
|
||||
|
|
@ -50,11 +49,12 @@ class ChrootManager:
|
|||
try:
|
||||
# Create final chroot using debootstrap from within bootstrap
|
||||
cmd = [
|
||||
'debootstrap',
|
||||
'--arch', self.config.architecture,
|
||||
"debootstrap",
|
||||
"--arch",
|
||||
self.config.architecture,
|
||||
self.config.suite,
|
||||
f'/var/lib/deb-mock/chroots/{chroot_name}',
|
||||
self.config.mirror
|
||||
f"/var/lib/deb-mock/chroots/{chroot_name}",
|
||||
self.config.mirror,
|
||||
]
|
||||
|
||||
# Execute debootstrap within bootstrap chroot
|
||||
|
|
@ -64,7 +64,7 @@ class ChrootManager:
|
|||
raise ChrootError(
|
||||
f"Failed to create chroot using bootstrap: {result.stderr}",
|
||||
chroot_name=chroot_name,
|
||||
operation="bootstrap_debootstrap"
|
||||
operation="bootstrap_debootstrap",
|
||||
)
|
||||
|
||||
# Configure the new chroot
|
||||
|
|
@ -74,7 +74,7 @@ class ChrootManager:
|
|||
raise ChrootError(
|
||||
f"Bootstrap chroot creation failed: {e}",
|
||||
chroot_name=chroot_name,
|
||||
operation="bootstrap_creation"
|
||||
operation="bootstrap_creation",
|
||||
)
|
||||
|
||||
def _create_standard_chroot(self, chroot_name: str) -> None:
|
||||
|
|
@ -85,7 +85,7 @@ class ChrootManager:
|
|||
raise ChrootError(
|
||||
f"Chroot '{chroot_name}' already exists",
|
||||
chroot_name=chroot_name,
|
||||
operation="create"
|
||||
operation="create",
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
@ -94,11 +94,12 @@ class ChrootManager:
|
|||
|
||||
# Run debootstrap
|
||||
cmd = [
|
||||
'debootstrap',
|
||||
'--arch', self.config.architecture,
|
||||
"debootstrap",
|
||||
"--arch",
|
||||
self.config.architecture,
|
||||
self.config.suite,
|
||||
chroot_path,
|
||||
self.config.mirror
|
||||
self.config.mirror,
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
||||
|
|
@ -108,7 +109,7 @@ class ChrootManager:
|
|||
f"debootstrap failed: {result.stderr}",
|
||||
chroot_name=chroot_name,
|
||||
operation="debootstrap",
|
||||
chroot_path=chroot_path
|
||||
chroot_path=chroot_path,
|
||||
)
|
||||
|
||||
# Configure the chroot
|
||||
|
|
@ -119,7 +120,7 @@ class ChrootManager:
|
|||
f"Failed to create chroot: {e}",
|
||||
chroot_name=chroot_name,
|
||||
operation="create",
|
||||
chroot_path=chroot_path
|
||||
chroot_path=chroot_path,
|
||||
)
|
||||
|
||||
def _configure_chroot(self, chroot_name: str) -> None:
|
||||
|
|
@ -127,7 +128,7 @@ class ChrootManager:
|
|||
chroot_path = os.path.join(self.config.chroot_dir, chroot_name)
|
||||
|
||||
# Create schroot configuration
|
||||
self._create_schroot_config(chroot_name, chroot_path)
|
||||
self._create_schroot_config(chroot_name, chroot_path, self.config.architecture, self.config.suite)
|
||||
|
||||
# Install additional packages if specified
|
||||
if self.config.chroot_additional_packages:
|
||||
|
|
@ -141,24 +142,24 @@ class ChrootManager:
|
|||
"""Install additional packages in the chroot"""
|
||||
try:
|
||||
# Update package lists
|
||||
self.execute_in_chroot(chroot_name, ['apt-get', 'update'], capture_output=True)
|
||||
self.execute_in_chroot(chroot_name, ["apt-get", "update"], capture_output=True)
|
||||
|
||||
# Install packages
|
||||
cmd = ['apt-get', 'install', '-y'] + self.config.chroot_additional_packages
|
||||
cmd = ["apt-get", "install", "-y"] + self.config.chroot_additional_packages
|
||||
result = self.execute_in_chroot(chroot_name, cmd, capture_output=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise ChrootError(
|
||||
f"Failed to install additional packages: {result.stderr}",
|
||||
chroot_name=chroot_name,
|
||||
operation="install_packages"
|
||||
operation="install_packages",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise ChrootError(
|
||||
f"Failed to install additional packages: {e}",
|
||||
chroot_name=chroot_name,
|
||||
operation="install_packages"
|
||||
operation="install_packages",
|
||||
)
|
||||
|
||||
def _run_setup_commands(self, chroot_name: str) -> None:
|
||||
|
|
@ -171,14 +172,14 @@ class ChrootManager:
|
|||
raise ChrootError(
|
||||
f"Setup command failed: {result.stderr}",
|
||||
chroot_name=chroot_name,
|
||||
operation="setup_command"
|
||||
operation="setup_command",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise ChrootError(
|
||||
f"Failed to run setup command '{cmd}': {e}",
|
||||
chroot_name=chroot_name,
|
||||
operation="setup_command"
|
||||
operation="setup_command",
|
||||
)
|
||||
|
||||
def _create_schroot_config(self, chroot_name: str, chroot_path: str, arch: str, suite: str) -> None:
|
||||
|
|
@ -196,7 +197,7 @@ preserve-environment=true
|
|||
config_file = os.path.join(self.config.chroot_config_dir, f"{chroot_name}.conf")
|
||||
|
||||
try:
|
||||
with open(config_file, 'w') as f:
|
||||
with open(config_file, "w") as f:
|
||||
f.write(config_content)
|
||||
except Exception as e:
|
||||
raise ChrootError(f"Failed to create schroot config: {e}")
|
||||
|
|
@ -204,16 +205,17 @@ preserve-environment=true
|
|||
def _initialize_chroot(self, chroot_path: str, arch: str, suite: str) -> None:
|
||||
"""Initialize chroot using debootstrap"""
|
||||
cmd = [
|
||||
'debootstrap',
|
||||
'--arch', arch,
|
||||
'--variant=buildd',
|
||||
"debootstrap",
|
||||
"--arch",
|
||||
arch,
|
||||
"--variant=buildd",
|
||||
suite,
|
||||
chroot_path,
|
||||
'http://deb.debian.org/debian/'
|
||||
"http://deb.debian.org/debian/",
|
||||
]
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise ChrootError(f"debootstrap failed: {e.stderr}")
|
||||
except FileNotFoundError:
|
||||
|
|
@ -222,23 +224,31 @@ preserve-environment=true
|
|||
def _install_build_tools(self, chroot_name: str) -> None:
|
||||
"""Install essential build tools in the chroot"""
|
||||
packages = [
|
||||
'build-essential',
|
||||
'devscripts',
|
||||
'debhelper',
|
||||
'dh-make',
|
||||
'fakeroot',
|
||||
'lintian',
|
||||
'sbuild',
|
||||
'schroot'
|
||||
"build-essential",
|
||||
"devscripts",
|
||||
"debhelper",
|
||||
"dh-make",
|
||||
"fakeroot",
|
||||
"lintian",
|
||||
"sbuild",
|
||||
"schroot",
|
||||
]
|
||||
|
||||
cmd = ['schroot', '-c', chroot_name, '--', 'apt-get', 'update']
|
||||
cmd = ["schroot", "-c", chroot_name, "--", "apt-get", "update"]
|
||||
try:
|
||||
subprocess.run(cmd, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise ChrootError(f"Failed to update package lists: {e}")
|
||||
|
||||
cmd = ['schroot', '-c', chroot_name, '--', 'apt-get', 'install', '-y'] + packages
|
||||
cmd = [
|
||||
"schroot",
|
||||
"-c",
|
||||
chroot_name,
|
||||
"--",
|
||||
"apt-get",
|
||||
"install",
|
||||
"-y",
|
||||
] + packages
|
||||
try:
|
||||
subprocess.run(cmd, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
|
|
@ -294,19 +304,19 @@ preserve-environment=true
|
|||
chroot_path = os.path.join(self.config.chroot_dir, chroot_name)
|
||||
|
||||
info = {
|
||||
'name': chroot_name,
|
||||
'path': chroot_path,
|
||||
'exists': True,
|
||||
'size': 0,
|
||||
'created': None,
|
||||
'modified': None
|
||||
"name": chroot_name,
|
||||
"path": chroot_path,
|
||||
"exists": True,
|
||||
"size": 0,
|
||||
"created": None,
|
||||
"modified": None,
|
||||
}
|
||||
|
||||
try:
|
||||
stat = os.stat(chroot_path)
|
||||
info['size'] = stat.st_size
|
||||
info['created'] = stat.st_ctime
|
||||
info['modified'] = stat.st_mtime
|
||||
info["size"] = stat.st_size
|
||||
info["created"] = stat.st_ctime
|
||||
info["modified"] = stat.st_mtime
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
@ -319,19 +329,23 @@ preserve-environment=true
|
|||
|
||||
try:
|
||||
# Update package lists
|
||||
cmd = ['schroot', '-c', chroot_name, '--', 'apt-get', 'update']
|
||||
cmd = ["schroot", "-c", chroot_name, "--", "apt-get", "update"]
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
# Upgrade packages
|
||||
cmd = ['schroot', '-c', chroot_name, '--', 'apt-get', 'upgrade', '-y']
|
||||
cmd = ["schroot", "-c", chroot_name, "--", "apt-get", "upgrade", "-y"]
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise ChrootError(f"Failed to update chroot '{chroot_name}': {e}")
|
||||
|
||||
def execute_in_chroot(self, chroot_name: str, command: list,
|
||||
def execute_in_chroot(
|
||||
self,
|
||||
chroot_name: str,
|
||||
command: list,
|
||||
capture_output: bool = True,
|
||||
preserve_env: bool = True) -> subprocess.CompletedProcess:
|
||||
preserve_env: bool = True,
|
||||
) -> subprocess.CompletedProcess:
|
||||
"""Execute a command in the chroot environment"""
|
||||
|
||||
if not self.chroot_exists(chroot_name):
|
||||
|
|
@ -344,8 +358,13 @@ preserve-environment=true
|
|||
|
||||
# Build schroot command
|
||||
schroot_cmd = [
|
||||
'schroot', '-c', chroot_name, '--', 'sh', '-c',
|
||||
' '.join(command)
|
||||
"schroot",
|
||||
"-c",
|
||||
chroot_name,
|
||||
"--",
|
||||
"sh",
|
||||
"-c",
|
||||
" ".join(command),
|
||||
]
|
||||
|
||||
try:
|
||||
|
|
@ -356,15 +375,10 @@ preserve-environment=true
|
|||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
check=False,
|
||||
)
|
||||
else:
|
||||
result = subprocess.run(
|
||||
schroot_cmd,
|
||||
cwd=chroot_path,
|
||||
env=env,
|
||||
check=False
|
||||
)
|
||||
result = subprocess.run(schroot_cmd, cwd=chroot_path, env=env, check=False)
|
||||
|
||||
return result
|
||||
|
||||
|
|
@ -386,7 +400,7 @@ preserve-environment=true
|
|||
filtered_env = {}
|
||||
|
||||
# Always preserve basic system variables
|
||||
basic_vars = ['PATH', 'HOME', 'USER', 'SHELL', 'TERM', 'LANG', 'LC_ALL']
|
||||
basic_vars = ["PATH", "HOME", "USER", "SHELL", "TERM", "LANG", "LC_ALL"]
|
||||
for var in basic_vars:
|
||||
if var in env:
|
||||
filtered_env[var] = env[var]
|
||||
|
|
@ -409,7 +423,7 @@ preserve-environment=true
|
|||
raise ChrootError(f"Chroot '{chroot_name}' does not exist")
|
||||
|
||||
chroot_path = os.path.join(self.config.chroot_dir, chroot_name)
|
||||
full_dest_path = os.path.join(chroot_path, dest_path.lstrip('/'))
|
||||
full_dest_path = os.path.join(chroot_path, dest_path.lstrip("/"))
|
||||
|
||||
try:
|
||||
# Create destination directory if it doesn't exist
|
||||
|
|
@ -430,7 +444,7 @@ preserve-environment=true
|
|||
raise ChrootError(f"Chroot '{chroot_name}' does not exist")
|
||||
|
||||
chroot_path = os.path.join(self.config.chroot_dir, chroot_name)
|
||||
full_source_path = os.path.join(chroot_path, source_path.lstrip('/'))
|
||||
full_source_path = os.path.join(chroot_path, source_path.lstrip("/"))
|
||||
|
||||
try:
|
||||
# Create destination directory if it doesn't exist
|
||||
|
|
@ -452,14 +466,14 @@ preserve-environment=true
|
|||
|
||||
try:
|
||||
# Clean package cache
|
||||
self.execute_in_chroot(chroot_name, ['apt-get', 'clean'])
|
||||
self.execute_in_chroot(chroot_name, ["apt-get", "clean"])
|
||||
|
||||
# Clean temporary files
|
||||
self.execute_in_chroot(chroot_name, ['rm', '-rf', '/tmp/*'])
|
||||
self.execute_in_chroot(chroot_name, ['rm', '-rf', '/var/tmp/*'])
|
||||
self.execute_in_chroot(chroot_name, ["rm", "-rf", "/tmp/*"])
|
||||
self.execute_in_chroot(chroot_name, ["rm", "-rf", "/var/tmp/*"])
|
||||
|
||||
# Clean build artifacts
|
||||
self.execute_in_chroot(chroot_name, ['rm', '-rf', '/build/*'])
|
||||
self.execute_in_chroot(chroot_name, ["rm", "-rf", "/build/*"])
|
||||
|
||||
except Exception as e:
|
||||
raise ChrootError(f"Failed to scrub chroot '{chroot_name}': {e}")
|
||||
|
|
|
|||
309
deb_mock/cli.py
309
deb_mock/cli.py
|
|
@ -3,28 +3,22 @@
|
|||
Command-line interface for deb-mock
|
||||
"""
|
||||
|
||||
import click
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
from .core import DebMock
|
||||
|
||||
import click
|
||||
|
||||
from .config import Config
|
||||
from .configs import get_available_configs, load_config
|
||||
from .exceptions import (
|
||||
DebMockError, ConfigurationError, ChrootError, SbuildError,
|
||||
BuildError, DependencyError, MetadataError, CacheError,
|
||||
PluginError, NetworkError, PermissionError, ValidationError,
|
||||
handle_exception, format_error_context
|
||||
)
|
||||
from .core import DebMock
|
||||
from .exceptions import ConfigurationError, ValidationError, handle_exception
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option()
|
||||
@click.option('--config', '-c', type=click.Path(exists=True),
|
||||
help='Configuration file path')
|
||||
@click.option('--chroot', '-r', help='Chroot configuration name (e.g., debian-bookworm-amd64)')
|
||||
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
|
||||
@click.option('--debug', is_flag=True, help='Enable debug output')
|
||||
@click.option("--config", "-c", type=click.Path(exists=True), help="Configuration file path")
|
||||
@click.option("--chroot", "-r", help="Chroot configuration name (e.g., debian-bookworm-amd64)")
|
||||
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
|
||||
@click.option("--debug", is_flag=True, help="Enable debug output")
|
||||
@click.pass_context
|
||||
def main(ctx, config, chroot, verbose, debug):
|
||||
"""
|
||||
|
|
@ -34,13 +28,13 @@ def main(ctx, config, chroot, verbose, debug):
|
|||
for Debian-based ecosystems.
|
||||
"""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj['verbose'] = verbose
|
||||
ctx.obj['debug'] = debug
|
||||
ctx.obj["verbose"] = verbose
|
||||
ctx.obj["debug"] = debug
|
||||
|
||||
# Load configuration
|
||||
if config:
|
||||
try:
|
||||
ctx.obj['config'] = Config.from_file(config)
|
||||
ctx.obj["config"] = Config.from_file(config)
|
||||
except ConfigurationError as e:
|
||||
e.print_error()
|
||||
sys.exit(e.get_exit_code())
|
||||
|
|
@ -48,93 +42,105 @@ def main(ctx, config, chroot, verbose, debug):
|
|||
# Load core config by name (similar to Mock's -r option)
|
||||
try:
|
||||
config_data = load_config(chroot)
|
||||
ctx.obj['config'] = Config(**config_data)
|
||||
ctx.obj["config"] = Config(**config_data)
|
||||
except ValueError as e:
|
||||
error = ValidationError(
|
||||
f"Invalid chroot configuration: {e}",
|
||||
field='chroot',
|
||||
field="chroot",
|
||||
value=chroot,
|
||||
expected_format='debian-suite-arch or ubuntu-suite-arch'
|
||||
expected_format="debian-suite-arch or ubuntu-suite-arch",
|
||||
)
|
||||
error.print_error()
|
||||
click.echo(f"Available configs: {', '.join(get_available_configs())}")
|
||||
sys.exit(error.get_exit_code())
|
||||
else:
|
||||
ctx.obj['config'] = Config.default()
|
||||
ctx.obj["config"] = Config.default()
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('source_package', type=click.Path(exists=True))
|
||||
@click.option('--chroot', help='Chroot environment to use')
|
||||
@click.option('--arch', help='Target architecture')
|
||||
@click.option('--output-dir', '-o', type=click.Path(),
|
||||
help='Output directory for build artifacts')
|
||||
@click.option('--keep-chroot', is_flag=True,
|
||||
help='Keep chroot after build (for debugging)')
|
||||
@click.option('--no-check', is_flag=True, help='Skip running tests during build')
|
||||
@click.option('--offline', is_flag=True, help='Build in offline mode (no network access)')
|
||||
@click.option('--build-timeout', type=int, help='Build timeout in seconds')
|
||||
@click.option('--force-arch', help='Force target architecture')
|
||||
@click.option('--unique-ext', help='Unique extension for buildroot directory')
|
||||
@click.option('--config-dir', help='Configuration directory')
|
||||
@click.option('--cleanup-after', is_flag=True, help='Clean chroot after build')
|
||||
@click.option('--no-cleanup-after', is_flag=True, help='Don\'t clean chroot after build')
|
||||
@click.argument("source_package", type=click.Path(exists=True))
|
||||
@click.option("--chroot", help="Chroot environment to use")
|
||||
@click.option("--arch", help="Target architecture")
|
||||
@click.option("--output-dir", "-o", type=click.Path(), help="Output directory for build artifacts")
|
||||
@click.option("--keep-chroot", is_flag=True, help="Keep chroot after build (for debugging)")
|
||||
@click.option("--no-check", is_flag=True, help="Skip running tests during build")
|
||||
@click.option("--offline", is_flag=True, help="Build in offline mode (no network access)")
|
||||
@click.option("--build-timeout", type=int, help="Build timeout in seconds")
|
||||
@click.option("--force-arch", help="Force target architecture")
|
||||
@click.option("--unique-ext", help="Unique extension for buildroot directory")
|
||||
@click.option("--config-dir", help="Configuration directory")
|
||||
@click.option("--cleanup-after", is_flag=True, help="Clean chroot after build")
|
||||
@click.option("--no-cleanup-after", is_flag=True, help="Don't clean chroot after build")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def build(ctx, source_package, chroot, arch, output_dir, keep_chroot,
|
||||
no_check, offline, build_timeout, force_arch, unique_ext,
|
||||
config_dir, cleanup_after, no_cleanup_after):
|
||||
def build(
|
||||
ctx,
|
||||
source_package,
|
||||
chroot,
|
||||
arch,
|
||||
output_dir,
|
||||
keep_chroot,
|
||||
no_check,
|
||||
offline,
|
||||
build_timeout,
|
||||
force_arch,
|
||||
unique_ext,
|
||||
config_dir,
|
||||
cleanup_after,
|
||||
no_cleanup_after,
|
||||
):
|
||||
"""
|
||||
Build a Debian source package in an isolated environment.
|
||||
|
||||
SOURCE_PACKAGE: Path to the .dsc file or source package directory
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
# Override config with command line options
|
||||
if chroot:
|
||||
ctx.obj['config'].chroot_name = chroot
|
||||
ctx.obj["config"].chroot_name = chroot
|
||||
if arch:
|
||||
ctx.obj['config'].architecture = arch
|
||||
ctx.obj["config"].architecture = arch
|
||||
if output_dir:
|
||||
ctx.obj['config'].output_dir = output_dir
|
||||
ctx.obj["config"].output_dir = output_dir
|
||||
if keep_chroot:
|
||||
ctx.obj['config'].keep_chroot = keep_chroot
|
||||
ctx.obj["config"].keep_chroot = keep_chroot
|
||||
if no_check:
|
||||
ctx.obj['config'].run_tests = False
|
||||
ctx.obj["config"].run_tests = False
|
||||
if offline:
|
||||
ctx.obj['config'].enable_network = False
|
||||
ctx.obj["config"].enable_network = False
|
||||
if build_timeout:
|
||||
ctx.obj['config'].build_timeout = build_timeout
|
||||
ctx.obj["config"].build_timeout = build_timeout
|
||||
if force_arch:
|
||||
ctx.obj['config'].force_architecture = force_arch
|
||||
ctx.obj["config"].force_architecture = force_arch
|
||||
if unique_ext:
|
||||
ctx.obj['config'].unique_extension = unique_ext
|
||||
ctx.obj["config"].unique_extension = unique_ext
|
||||
if config_dir:
|
||||
ctx.obj['config'].config_dir = config_dir
|
||||
ctx.obj["config"].config_dir = config_dir
|
||||
if cleanup_after is not None:
|
||||
ctx.obj['config'].cleanup_after = cleanup_after
|
||||
ctx.obj["config"].cleanup_after = cleanup_after
|
||||
if no_cleanup_after is not None:
|
||||
ctx.obj['config'].cleanup_after = not no_cleanup_after
|
||||
ctx.obj["config"].cleanup_after = not no_cleanup_after
|
||||
|
||||
result = deb_mock.build(source_package)
|
||||
|
||||
if ctx.obj['verbose']:
|
||||
if ctx.obj["verbose"]:
|
||||
click.echo(f"Build completed successfully: {result}")
|
||||
else:
|
||||
click.echo("Build completed successfully")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('source_packages', nargs=-1, type=click.Path(exists=True))
|
||||
@click.option('--chroot', help='Chroot environment to use')
|
||||
@click.option('--arch', help='Target architecture')
|
||||
@click.option('--output-dir', '-o', type=click.Path(),
|
||||
help='Output directory for build artifacts')
|
||||
@click.option('--keep-chroot', is_flag=True,
|
||||
help='Keep chroot after build (for debugging)')
|
||||
@click.option('--continue-on-failure', is_flag=True,
|
||||
help='Continue building remaining packages even if one fails')
|
||||
@click.argument("source_packages", nargs=-1, type=click.Path(exists=True))
|
||||
@click.option("--chroot", help="Chroot environment to use")
|
||||
@click.option("--arch", help="Target architecture")
|
||||
@click.option("--output-dir", "-o", type=click.Path(), help="Output directory for build artifacts")
|
||||
@click.option("--keep-chroot", is_flag=True, help="Keep chroot after build (for debugging)")
|
||||
@click.option(
|
||||
"--continue-on-failure",
|
||||
is_flag=True,
|
||||
help="Continue building remaining packages even if one fails",
|
||||
)
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def chain(ctx, source_packages, chroot, arch, output_dir, keep_chroot, continue_on_failure):
|
||||
|
|
@ -146,36 +152,33 @@ def chain(ctx, source_packages, chroot, arch, output_dir, keep_chroot, continue_
|
|||
if not source_packages:
|
||||
raise ValidationError(
|
||||
"No source packages specified",
|
||||
field='source_packages',
|
||||
expected_format='list of .dsc files or source directories'
|
||||
field="source_packages",
|
||||
expected_format="list of .dsc files or source directories",
|
||||
)
|
||||
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
# Override config with command line options
|
||||
if chroot:
|
||||
ctx.obj['config'].chroot_name = chroot
|
||||
ctx.obj["config"].chroot_name = chroot
|
||||
if arch:
|
||||
ctx.obj['config'].architecture = arch
|
||||
ctx.obj["config"].architecture = arch
|
||||
if output_dir:
|
||||
ctx.obj['config'].output_dir = output_dir
|
||||
ctx.obj["config"].output_dir = output_dir
|
||||
if keep_chroot:
|
||||
ctx.obj['config'].keep_chroot = keep_chroot
|
||||
ctx.obj["config"].keep_chroot = keep_chroot
|
||||
|
||||
results = deb_mock.build_chain(
|
||||
list(source_packages),
|
||||
continue_on_failure=continue_on_failure
|
||||
)
|
||||
results = deb_mock.build_chain(list(source_packages), continue_on_failure=continue_on_failure)
|
||||
|
||||
# Display results
|
||||
for result in results:
|
||||
if result['success']:
|
||||
if result["success"]:
|
||||
click.echo(f"✅ {result['package']} (step {result['order']})")
|
||||
else:
|
||||
click.echo(f"❌ {result['package']} (step {result['order']}): {result['error']}")
|
||||
|
||||
# Check if all builds succeeded
|
||||
failed_builds = [r for r in results if not r['success']]
|
||||
failed_builds = [r for r in results if not r["success"]]
|
||||
if failed_builds:
|
||||
sys.exit(1)
|
||||
else:
|
||||
|
|
@ -183,11 +186,15 @@ def chain(ctx, source_packages, chroot, arch, output_dir, keep_chroot, continue_
|
|||
|
||||
|
||||
@main.command()
|
||||
@click.argument('chroot_name')
|
||||
@click.option('--arch', help='Target architecture')
|
||||
@click.option('--suite', help='Debian suite (e.g., bookworm, sid)')
|
||||
@click.option('--bootstrap', is_flag=True, help='Use bootstrap chroot for cross-distribution builds')
|
||||
@click.option('--bootstrap-chroot', help='Name of bootstrap chroot to use')
|
||||
@click.argument("chroot_name")
|
||||
@click.option("--arch", help="Target architecture")
|
||||
@click.option("--suite", help="Debian suite (e.g., bookworm, sid)")
|
||||
@click.option(
|
||||
"--bootstrap",
|
||||
is_flag=True,
|
||||
help="Use bootstrap chroot for cross-distribution builds",
|
||||
)
|
||||
@click.option("--bootstrap-chroot", help="Name of bootstrap chroot to use")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def init_chroot(ctx, chroot_name, arch, suite, bootstrap, bootstrap_chroot):
|
||||
|
|
@ -199,17 +206,17 @@ def init_chroot(ctx, chroot_name, arch, suite, bootstrap, bootstrap_chroot):
|
|||
The --bootstrap option is useful for building packages for newer distributions
|
||||
on older systems (e.g., building Debian Sid packages on Debian Stable).
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
# Override config with command line options
|
||||
if arch:
|
||||
ctx.obj['config'].architecture = arch
|
||||
ctx.obj["config"].architecture = arch
|
||||
if suite:
|
||||
ctx.obj['config'].suite = suite
|
||||
ctx.obj["config"].suite = suite
|
||||
if bootstrap:
|
||||
ctx.obj['config'].use_bootstrap_chroot = True
|
||||
ctx.obj["config"].use_bootstrap_chroot = True
|
||||
if bootstrap_chroot:
|
||||
ctx.obj['config'].bootstrap_chroot_name = bootstrap_chroot
|
||||
ctx.obj["config"].bootstrap_chroot_name = bootstrap_chroot
|
||||
|
||||
deb_mock.init_chroot(chroot_name)
|
||||
click.echo(f"Chroot '{chroot_name}' initialized successfully")
|
||||
|
|
@ -219,7 +226,7 @@ def init_chroot(ctx, chroot_name, arch, suite, bootstrap, bootstrap_chroot):
|
|||
|
||||
|
||||
@main.command()
|
||||
@click.argument('chroot_name')
|
||||
@click.argument("chroot_name")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def clean_chroot(ctx, chroot_name):
|
||||
|
|
@ -228,13 +235,13 @@ def clean_chroot(ctx, chroot_name):
|
|||
|
||||
CHROOT_NAME: Name of the chroot environment to clean
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
deb_mock.clean_chroot(chroot_name)
|
||||
click.echo(f"Chroot '{chroot_name}' cleaned successfully")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('chroot_name')
|
||||
@click.argument("chroot_name")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def scrub_chroot(ctx, chroot_name):
|
||||
|
|
@ -243,7 +250,7 @@ def scrub_chroot(ctx, chroot_name):
|
|||
|
||||
CHROOT_NAME: Name of the chroot environment to scrub
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
deb_mock.chroot_manager.scrub_chroot(chroot_name)
|
||||
click.echo(f"Chroot '{chroot_name}' scrubbed successfully")
|
||||
|
||||
|
|
@ -255,15 +262,15 @@ def scrub_all_chroots(ctx):
|
|||
"""
|
||||
Clean up all chroot environments without removing them.
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
deb_mock.chroot_manager.scrub_all_chroots()
|
||||
click.echo("All chroots scrubbed successfully")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option('--chroot', help='Chroot environment to use')
|
||||
@click.option('--preserve-env', is_flag=True, help='Preserve environment variables in chroot')
|
||||
@click.option('--env-var', multiple=True, help='Specific environment variable to preserve')
|
||||
@click.option("--chroot", help="Chroot environment to use")
|
||||
@click.option("--preserve-env", is_flag=True, help="Preserve environment variables in chroot")
|
||||
@click.option("--env-var", multiple=True, help="Specific environment variable to preserve")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def shell(ctx, chroot, preserve_env, env_var):
|
||||
|
|
@ -273,23 +280,23 @@ def shell(ctx, chroot, preserve_env, env_var):
|
|||
Use --preserve-env to preserve environment variables (addresses common
|
||||
environment variable issues in chroot environments).
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
chroot_name = chroot or ctx.obj['config'].chroot_name
|
||||
chroot_name = chroot or ctx.obj["config"].chroot_name
|
||||
|
||||
# Configure environment preservation
|
||||
if preserve_env:
|
||||
ctx.obj['config'].environment_sanitization = False
|
||||
ctx.obj["config"].environment_sanitization = False
|
||||
if env_var:
|
||||
ctx.obj['config'].preserve_environment.extend(env_var)
|
||||
ctx.obj["config"].preserve_environment.extend(env_var)
|
||||
|
||||
deb_mock.shell(chroot_name)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('source_path')
|
||||
@click.argument('dest_path')
|
||||
@click.option('--chroot', help='Chroot environment to use')
|
||||
@click.argument("source_path")
|
||||
@click.argument("dest_path")
|
||||
@click.option("--chroot", help="Chroot environment to use")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def copyin(ctx, source_path, dest_path, chroot):
|
||||
|
|
@ -299,17 +306,17 @@ def copyin(ctx, source_path, dest_path, chroot):
|
|||
SOURCE_PATH: Path to file/directory on host
|
||||
DEST_PATH: Path in chroot where to copy
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
chroot_name = chroot or ctx.obj['config'].chroot_name
|
||||
chroot_name = chroot or ctx.obj["config"].chroot_name
|
||||
deb_mock.copyin(source_path, dest_path, chroot_name)
|
||||
click.echo(f"Copied {source_path} to {dest_path} in chroot '{chroot_name}'")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('source_path')
|
||||
@click.argument('dest_path')
|
||||
@click.option('--chroot', help='Chroot environment to use')
|
||||
@click.argument("source_path")
|
||||
@click.argument("dest_path")
|
||||
@click.option("--chroot", help="Chroot environment to use")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def copyout(ctx, source_path, dest_path, chroot):
|
||||
|
|
@ -319,9 +326,9 @@ def copyout(ctx, source_path, dest_path, chroot):
|
|||
SOURCE_PATH: Path to file/directory in chroot
|
||||
DEST_PATH: Path on host where to copy
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
chroot_name = chroot or ctx.obj['config'].chroot_name
|
||||
chroot_name = chroot or ctx.obj["config"].chroot_name
|
||||
deb_mock.copyout(source_path, dest_path, chroot_name)
|
||||
click.echo(f"Copied {source_path} from chroot '{chroot_name}' to {dest_path}")
|
||||
|
||||
|
|
@ -333,7 +340,7 @@ def list_chroots(ctx):
|
|||
"""
|
||||
List available chroot environments.
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
chroots = deb_mock.list_chroots()
|
||||
|
||||
if not chroots:
|
||||
|
|
@ -373,7 +380,7 @@ def cleanup_caches(ctx):
|
|||
"""
|
||||
Clean up old cache files (similar to Mock's cache management).
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
cleaned = deb_mock.cleanup_caches()
|
||||
|
||||
if not cleaned:
|
||||
|
|
@ -393,7 +400,7 @@ def cache_stats(ctx):
|
|||
"""
|
||||
Show cache statistics.
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
stats = deb_mock.get_cache_stats()
|
||||
|
||||
if not stats:
|
||||
|
|
@ -417,7 +424,7 @@ def config(ctx):
|
|||
"""
|
||||
Show current configuration.
|
||||
"""
|
||||
config = ctx.obj['config']
|
||||
config = ctx.obj["config"]
|
||||
click.echo("Current configuration:")
|
||||
click.echo(f" Chroot name: {config.chroot_name}")
|
||||
click.echo(f" Architecture: {config.architecture}")
|
||||
|
|
@ -430,9 +437,9 @@ def config(ctx):
|
|||
|
||||
|
||||
@main.command()
|
||||
@click.argument('source_package', type=click.Path(exists=True))
|
||||
@click.option('--chroot', help='Chroot environment to use')
|
||||
@click.option('--arch', help='Target architecture')
|
||||
@click.argument("source_package", type=click.Path(exists=True))
|
||||
@click.option("--chroot", help="Chroot environment to use")
|
||||
@click.option("--arch", help="Target architecture")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def install_deps(ctx, source_package, chroot, arch):
|
||||
|
|
@ -441,26 +448,26 @@ def install_deps(ctx, source_package, chroot, arch):
|
|||
|
||||
SOURCE_PACKAGE: Path to the .dsc file or source package directory
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
# Override config with command line options
|
||||
if chroot:
|
||||
ctx.obj['config'].chroot_name = chroot
|
||||
ctx.obj["config"].chroot_name = chroot
|
||||
if arch:
|
||||
ctx.obj['config'].architecture = arch
|
||||
ctx.obj["config"].architecture = arch
|
||||
|
||||
result = deb_mock.install_dependencies(source_package)
|
||||
|
||||
if ctx.obj['verbose']:
|
||||
if ctx.obj["verbose"]:
|
||||
click.echo(f"Dependencies installed successfully: {result}")
|
||||
else:
|
||||
click.echo("Dependencies installed successfully")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('packages', nargs=-1, required=True)
|
||||
@click.option('--chroot', help='Chroot environment to use')
|
||||
@click.option('--arch', help='Target architecture')
|
||||
@click.argument("packages", nargs=-1, required=True)
|
||||
@click.option("--chroot", help="Chroot environment to use")
|
||||
@click.option("--arch", help="Target architecture")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def install(ctx, packages, chroot, arch):
|
||||
|
|
@ -469,26 +476,26 @@ def install(ctx, packages, chroot, arch):
|
|||
|
||||
PACKAGES: List of packages to install
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
# Override config with command line options
|
||||
if chroot:
|
||||
ctx.obj['config'].chroot_name = chroot
|
||||
ctx.obj["config"].chroot_name = chroot
|
||||
if arch:
|
||||
ctx.obj['config'].architecture = arch
|
||||
ctx.obj["config"].architecture = arch
|
||||
|
||||
result = deb_mock.install_packages(packages)
|
||||
|
||||
if ctx.obj['verbose']:
|
||||
if ctx.obj["verbose"]:
|
||||
click.echo(f"Packages installed successfully: {result}")
|
||||
else:
|
||||
click.echo(f"Packages installed successfully: {', '.join(packages)}")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('packages', nargs=-1)
|
||||
@click.option('--chroot', help='Chroot environment to use')
|
||||
@click.option('--arch', help='Target architecture')
|
||||
@click.argument("packages", nargs=-1)
|
||||
@click.option("--chroot", help="Chroot environment to use")
|
||||
@click.option("--arch", help="Target architecture")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def update(ctx, packages, chroot, arch):
|
||||
|
|
@ -497,17 +504,17 @@ def update(ctx, packages, chroot, arch):
|
|||
|
||||
PACKAGES: List of packages to update (if empty, update all)
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
# Override config with command line options
|
||||
if chroot:
|
||||
ctx.obj['config'].chroot_name = chroot
|
||||
ctx.obj["config"].chroot_name = chroot
|
||||
if arch:
|
||||
ctx.obj['config'].architecture = arch
|
||||
ctx.obj["config"].architecture = arch
|
||||
|
||||
result = deb_mock.update_packages(packages)
|
||||
|
||||
if ctx.obj['verbose']:
|
||||
if ctx.obj["verbose"]:
|
||||
click.echo(f"Packages updated successfully: {result}")
|
||||
else:
|
||||
if packages:
|
||||
|
|
@ -517,9 +524,9 @@ def update(ctx, packages, chroot, arch):
|
|||
|
||||
|
||||
@main.command()
|
||||
@click.argument('packages', nargs=-1, required=True)
|
||||
@click.option('--chroot', help='Chroot environment to use')
|
||||
@click.option('--arch', help='Target architecture')
|
||||
@click.argument("packages", nargs=-1, required=True)
|
||||
@click.option("--chroot", help="Chroot environment to use")
|
||||
@click.option("--arch", help="Target architecture")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def remove(ctx, packages, chroot, arch):
|
||||
|
|
@ -528,26 +535,26 @@ def remove(ctx, packages, chroot, arch):
|
|||
|
||||
PACKAGES: List of packages to remove
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
# Override config with command line options
|
||||
if chroot:
|
||||
ctx.obj['config'].chroot_name = chroot
|
||||
ctx.obj["config"].chroot_name = chroot
|
||||
if arch:
|
||||
ctx.obj['config'].architecture = arch
|
||||
ctx.obj["config"].architecture = arch
|
||||
|
||||
result = deb_mock.remove_packages(packages)
|
||||
|
||||
if ctx.obj['verbose']:
|
||||
if ctx.obj["verbose"]:
|
||||
click.echo(f"Packages removed successfully: {result}")
|
||||
else:
|
||||
click.echo(f"Packages removed successfully: {', '.join(packages)}")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument('command')
|
||||
@click.option('--chroot', help='Chroot environment to use')
|
||||
@click.option('--arch', help='Target architecture')
|
||||
@click.argument("command")
|
||||
@click.option("--chroot", help="Chroot environment to use")
|
||||
@click.option("--arch", help="Target architecture")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def apt_cmd(ctx, command, chroot, arch):
|
||||
|
|
@ -556,31 +563,31 @@ def apt_cmd(ctx, command, chroot, arch):
|
|||
|
||||
COMMAND: APT command to execute (e.g., "update", "install package")
|
||||
"""
|
||||
deb_mock = DebMock(ctx.obj['config'])
|
||||
deb_mock = DebMock(ctx.obj["config"])
|
||||
|
||||
# Override config with command line options
|
||||
if chroot:
|
||||
ctx.obj['config'].chroot_name = chroot
|
||||
ctx.obj["config"].chroot_name = chroot
|
||||
if arch:
|
||||
ctx.obj['config'].architecture = arch
|
||||
ctx.obj["config"].architecture = arch
|
||||
|
||||
result = deb_mock.execute_apt_command(command)
|
||||
|
||||
if ctx.obj['verbose']:
|
||||
if ctx.obj["verbose"]:
|
||||
click.echo(f"APT command executed successfully: {result}")
|
||||
else:
|
||||
click.echo(f"APT command executed: {command}")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option('--expand', is_flag=True, help='Show expanded configuration values')
|
||||
@click.option("--expand", is_flag=True, help="Show expanded configuration values")
|
||||
@click.pass_context
|
||||
@handle_exception
|
||||
def debug_config(ctx, expand):
|
||||
"""
|
||||
Show detailed configuration information for debugging.
|
||||
"""
|
||||
config = ctx.obj['config']
|
||||
config = ctx.obj["config"]
|
||||
|
||||
if expand:
|
||||
# Show expanded configuration (with template values resolved)
|
||||
|
|
@ -601,11 +608,11 @@ def debug_config(ctx, expand):
|
|||
click.echo(f" chroot_home: {config.chroot_home}")
|
||||
|
||||
# Show plugin configuration
|
||||
if hasattr(config, 'plugins') and config.plugins:
|
||||
if hasattr(config, "plugins") and config.plugins:
|
||||
click.echo(" plugins:")
|
||||
for plugin_name, plugin_config in config.plugins.items():
|
||||
click.echo(f" {plugin_name}: {plugin_config}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -3,9 +3,11 @@ Configuration management for deb-mock
|
|||
"""
|
||||
|
||||
import os
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional
|
||||
from typing import Any, Dict
|
||||
|
||||
import yaml
|
||||
|
||||
from .exceptions import ConfigurationError
|
||||
|
||||
|
||||
|
|
@ -14,108 +16,123 @@ class Config:
|
|||
|
||||
def __init__(self, **kwargs):
|
||||
# Default configuration
|
||||
self.chroot_name = kwargs.get('chroot_name', 'bookworm-amd64')
|
||||
self.architecture = kwargs.get('architecture', 'amd64')
|
||||
self.suite = kwargs.get('suite', 'bookworm')
|
||||
self.output_dir = kwargs.get('output_dir', './output')
|
||||
self.keep_chroot = kwargs.get('keep_chroot', False)
|
||||
self.verbose = kwargs.get('verbose', False)
|
||||
self.debug = kwargs.get('debug', False)
|
||||
self.chroot_name = kwargs.get("chroot_name", "bookworm-amd64")
|
||||
self.architecture = kwargs.get("architecture", "amd64")
|
||||
self.suite = kwargs.get("suite", "bookworm")
|
||||
self.output_dir = kwargs.get("output_dir", "./output")
|
||||
self.keep_chroot = kwargs.get("keep_chroot", False)
|
||||
self.verbose = kwargs.get("verbose", False)
|
||||
self.debug = kwargs.get("debug", False)
|
||||
|
||||
# Chroot configuration
|
||||
self.basedir = kwargs.get('basedir', '/var/lib/deb-mock')
|
||||
self.chroot_dir = kwargs.get('chroot_dir', '/var/lib/deb-mock/chroots')
|
||||
self.chroot_config_dir = kwargs.get('chroot_config_dir', '/etc/schroot/chroot.d')
|
||||
self.chroot_home = kwargs.get('chroot_home', '/home/build')
|
||||
self.basedir = kwargs.get("basedir", "/var/lib/deb-mock")
|
||||
self.chroot_dir = kwargs.get("chroot_dir", "/var/lib/deb-mock/chroots")
|
||||
self.chroot_config_dir = kwargs.get("chroot_config_dir", "/etc/schroot/chroot.d")
|
||||
self.chroot_home = kwargs.get("chroot_home", "/home/build")
|
||||
|
||||
# sbuild configuration
|
||||
self.sbuild_config = kwargs.get('sbuild_config', '/etc/sbuild/sbuild.conf')
|
||||
self.sbuild_log_dir = kwargs.get('sbuild_log_dir', '/var/log/sbuild')
|
||||
self.sbuild_config = kwargs.get("sbuild_config", "/etc/sbuild/sbuild.conf")
|
||||
self.sbuild_log_dir = kwargs.get("sbuild_log_dir", "/var/log/sbuild")
|
||||
|
||||
# Build configuration
|
||||
self.build_deps = kwargs.get('build_deps', [])
|
||||
self.build_env = kwargs.get('build_env', {})
|
||||
self.build_options = kwargs.get('build_options', [])
|
||||
self.build_deps = kwargs.get("build_deps", [])
|
||||
self.build_env = kwargs.get("build_env", {})
|
||||
self.build_options = kwargs.get("build_options", [])
|
||||
|
||||
# Metadata configuration
|
||||
self.metadata_dir = kwargs.get('metadata_dir', './metadata')
|
||||
self.capture_logs = kwargs.get('capture_logs', True)
|
||||
self.capture_changes = kwargs.get('capture_changes', True)
|
||||
self.metadata_dir = kwargs.get("metadata_dir", "./metadata")
|
||||
self.capture_logs = kwargs.get("capture_logs", True)
|
||||
self.capture_changes = kwargs.get("capture_changes", True)
|
||||
|
||||
# Speed optimization (Mock-inspired features)
|
||||
self.cache_dir = kwargs.get('cache_dir', '/var/cache/deb-mock')
|
||||
self.use_root_cache = kwargs.get('use_root_cache', True)
|
||||
self.root_cache_dir = kwargs.get('root_cache_dir', '/var/cache/deb-mock/root-cache')
|
||||
self.root_cache_age = kwargs.get('root_cache_age', 7) # days
|
||||
self.use_package_cache = kwargs.get('use_package_cache', True)
|
||||
self.package_cache_dir = kwargs.get('package_cache_dir', '/var/cache/deb-mock/package-cache')
|
||||
self.use_ccache = kwargs.get('use_ccache', False)
|
||||
self.ccache_dir = kwargs.get('ccache_dir', '/var/cache/deb-mock/ccache')
|
||||
self.use_tmpfs = kwargs.get('use_tmpfs', False)
|
||||
self.tmpfs_size = kwargs.get('tmpfs_size', '2G')
|
||||
self.cache_dir = kwargs.get("cache_dir", "/var/cache/deb-mock")
|
||||
self.use_root_cache = kwargs.get("use_root_cache", True)
|
||||
self.root_cache_dir = kwargs.get("root_cache_dir", "/var/cache/deb-mock/root-cache")
|
||||
self.root_cache_age = kwargs.get("root_cache_age", 7) # days
|
||||
self.use_package_cache = kwargs.get("use_package_cache", True)
|
||||
self.package_cache_dir = kwargs.get("package_cache_dir", "/var/cache/deb-mock/package-cache")
|
||||
self.use_ccache = kwargs.get("use_ccache", False)
|
||||
self.ccache_dir = kwargs.get("ccache_dir", "/var/cache/deb-mock/ccache")
|
||||
self.use_tmpfs = kwargs.get("use_tmpfs", False)
|
||||
self.tmpfs_size = kwargs.get("tmpfs_size", "2G")
|
||||
|
||||
# Parallel builds
|
||||
self.parallel_jobs = kwargs.get('parallel_jobs', 4)
|
||||
self.parallel_compression = kwargs.get('parallel_compression', True)
|
||||
self.parallel_jobs = kwargs.get("parallel_jobs", 4)
|
||||
self.parallel_compression = kwargs.get("parallel_compression", True)
|
||||
|
||||
# Network and proxy
|
||||
self.use_host_resolv = kwargs.get('use_host_resolv', True)
|
||||
self.http_proxy = kwargs.get('http_proxy', None)
|
||||
self.https_proxy = kwargs.get('https_proxy', None)
|
||||
self.no_proxy = kwargs.get('no_proxy', None)
|
||||
self.use_host_resolv = kwargs.get("use_host_resolv", True)
|
||||
self.http_proxy = kwargs.get("http_proxy", None)
|
||||
self.https_proxy = kwargs.get("https_proxy", None)
|
||||
self.no_proxy = kwargs.get("no_proxy", None)
|
||||
|
||||
# Mirror configuration
|
||||
self.mirror = kwargs.get('mirror', 'http://deb.debian.org/debian/')
|
||||
self.security_mirror = kwargs.get('security_mirror', None)
|
||||
self.backports_mirror = kwargs.get('backports_mirror', None)
|
||||
self.mirror = kwargs.get("mirror", "http://deb.debian.org/debian/")
|
||||
self.security_mirror = kwargs.get("security_mirror", None)
|
||||
self.backports_mirror = kwargs.get("backports_mirror", None)
|
||||
|
||||
# Isolation and security
|
||||
self.isolation = kwargs.get('isolation', 'schroot') # schroot, simple, nspawn
|
||||
self.enable_network = kwargs.get('enable_network', True)
|
||||
self.selinux_enabled = kwargs.get('selinux_enabled', False)
|
||||
self.isolation = kwargs.get("isolation", "schroot") # schroot, simple, nspawn
|
||||
self.enable_network = kwargs.get("enable_network", True)
|
||||
self.selinux_enabled = kwargs.get("selinux_enabled", False)
|
||||
|
||||
# Bootstrap chroot support (Mock FAQ #2 - Cross-distribution builds)
|
||||
self.use_bootstrap_chroot = kwargs.get('use_bootstrap_chroot', False)
|
||||
self.bootstrap_chroot_name = kwargs.get('bootstrap_chroot_name', None)
|
||||
self.bootstrap_arch = kwargs.get('bootstrap_arch', None)
|
||||
self.bootstrap_suite = kwargs.get('bootstrap_suite', None)
|
||||
self.use_bootstrap_chroot = kwargs.get("use_bootstrap_chroot", False)
|
||||
self.bootstrap_chroot_name = kwargs.get("bootstrap_chroot_name", None)
|
||||
self.bootstrap_arch = kwargs.get("bootstrap_arch", None)
|
||||
self.bootstrap_suite = kwargs.get("bootstrap_suite", None)
|
||||
|
||||
# Build environment customization
|
||||
self.chroot_setup_cmd = kwargs.get('chroot_setup_cmd', [])
|
||||
self.chroot_additional_packages = kwargs.get('chroot_additional_packages', [])
|
||||
self.chroot_setup_cmd = kwargs.get("chroot_setup_cmd", [])
|
||||
self.chroot_additional_packages = kwargs.get("chroot_additional_packages", [])
|
||||
|
||||
# Environment variable preservation (Mock FAQ #1)
|
||||
self.preserve_environment = kwargs.get('preserve_environment', [])
|
||||
self.environment_sanitization = kwargs.get('environment_sanitization', True)
|
||||
self.allowed_environment_vars = kwargs.get('allowed_environment_vars', [
|
||||
'DEB_BUILD_OPTIONS', 'DEB_BUILD_PROFILES', 'CC', 'CXX', 'CFLAGS', 'CXXFLAGS',
|
||||
'LDFLAGS', 'MAKEFLAGS', 'CCACHE_DIR', 'CCACHE_HASHDIR', 'http_proxy',
|
||||
'https_proxy', 'no_proxy', 'DISPLAY', 'XAUTHORITY'
|
||||
])
|
||||
self.preserve_environment = kwargs.get("preserve_environment", [])
|
||||
self.environment_sanitization = kwargs.get("environment_sanitization", True)
|
||||
self.allowed_environment_vars = kwargs.get(
|
||||
"allowed_environment_vars",
|
||||
[
|
||||
"DEB_BUILD_OPTIONS",
|
||||
"DEB_BUILD_PROFILES",
|
||||
"CC",
|
||||
"CXX",
|
||||
"CFLAGS",
|
||||
"CXXFLAGS",
|
||||
"LDFLAGS",
|
||||
"MAKEFLAGS",
|
||||
"CCACHE_DIR",
|
||||
"CCACHE_HASHDIR",
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
"no_proxy",
|
||||
"DISPLAY",
|
||||
"XAUTHORITY",
|
||||
],
|
||||
)
|
||||
|
||||
# Advanced build options (Mock-inspired)
|
||||
self.run_tests = kwargs.get('run_tests', True)
|
||||
self.build_timeout = kwargs.get('build_timeout', 0) # 0 = no timeout
|
||||
self.force_architecture = kwargs.get('force_architecture', None)
|
||||
self.unique_extension = kwargs.get('unique_extension', None)
|
||||
self.config_dir = kwargs.get('config_dir', None)
|
||||
self.cleanup_after = kwargs.get('cleanup_after', True)
|
||||
self.run_tests = kwargs.get("run_tests", True)
|
||||
self.build_timeout = kwargs.get("build_timeout", 0) # 0 = no timeout
|
||||
self.force_architecture = kwargs.get("force_architecture", None)
|
||||
self.unique_extension = kwargs.get("unique_extension", None)
|
||||
self.config_dir = kwargs.get("config_dir", None)
|
||||
self.cleanup_after = kwargs.get("cleanup_after", True)
|
||||
|
||||
# APT configuration
|
||||
self.apt_sources = kwargs.get('apt_sources', [])
|
||||
self.apt_preferences = kwargs.get('apt_preferences', [])
|
||||
self.apt_command = kwargs.get('apt_command', 'apt-get')
|
||||
self.apt_install_command = kwargs.get('apt_install_command', 'apt-get install -y')
|
||||
self.apt_sources = kwargs.get("apt_sources", [])
|
||||
self.apt_preferences = kwargs.get("apt_preferences", [])
|
||||
self.apt_command = kwargs.get("apt_command", "apt-get")
|
||||
self.apt_install_command = kwargs.get("apt_install_command", "apt-get install -y")
|
||||
|
||||
# Plugin configuration
|
||||
self.plugins = kwargs.get('plugins', {})
|
||||
self.plugin_dir = kwargs.get('plugin_dir', '/usr/lib/deb-mock/plugins')
|
||||
self.plugins = kwargs.get("plugins", {})
|
||||
self.plugin_dir = kwargs.get("plugin_dir", "/usr/lib/deb-mock/plugins")
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, config_path: str) -> 'Config':
|
||||
def from_file(cls, config_path: str) -> "Config":
|
||||
"""Load configuration from a YAML file"""
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
with open(config_path, "r") as f:
|
||||
config_data = yaml.safe_load(f)
|
||||
|
||||
return cls(**config_data)
|
||||
|
|
@ -127,60 +144,60 @@ class Config:
|
|||
raise ConfigurationError(f"Error loading configuration: {e}")
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> 'Config':
|
||||
def default(cls) -> "Config":
|
||||
"""Create default configuration"""
|
||||
return cls()
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert configuration to dictionary"""
|
||||
return {
|
||||
'chroot_name': self.chroot_name,
|
||||
'architecture': self.architecture,
|
||||
'suite': self.suite,
|
||||
'output_dir': self.output_dir,
|
||||
'keep_chroot': self.keep_chroot,
|
||||
'verbose': self.verbose,
|
||||
'debug': self.debug,
|
||||
'chroot_dir': self.chroot_dir,
|
||||
'chroot_config_dir': self.chroot_config_dir,
|
||||
'sbuild_config': self.sbuild_config,
|
||||
'sbuild_log_dir': self.sbuild_log_dir,
|
||||
'build_deps': self.build_deps,
|
||||
'build_env': self.build_env,
|
||||
'build_options': self.build_options,
|
||||
'metadata_dir': self.metadata_dir,
|
||||
'capture_logs': self.capture_logs,
|
||||
'capture_changes': self.capture_changes,
|
||||
'use_root_cache': self.use_root_cache,
|
||||
'root_cache_dir': self.root_cache_dir,
|
||||
'root_cache_age': self.root_cache_age,
|
||||
'use_package_cache': self.use_package_cache,
|
||||
'package_cache_dir': self.package_cache_dir,
|
||||
'use_ccache': self.use_ccache,
|
||||
'ccache_dir': self.ccache_dir,
|
||||
'use_tmpfs': self.use_tmpfs,
|
||||
'tmpfs_size': self.tmpfs_size,
|
||||
'parallel_jobs': self.parallel_jobs,
|
||||
'parallel_compression': self.parallel_compression,
|
||||
'use_host_resolv': self.use_host_resolv,
|
||||
'http_proxy': self.http_proxy,
|
||||
'https_proxy': self.https_proxy,
|
||||
'no_proxy': self.no_proxy,
|
||||
'mirror': self.mirror,
|
||||
'security_mirror': self.security_mirror,
|
||||
'backports_mirror': self.backports_mirror,
|
||||
'isolation': self.isolation,
|
||||
'enable_network': self.enable_network,
|
||||
'selinux_enabled': self.selinux_enabled,
|
||||
'use_bootstrap_chroot': self.use_bootstrap_chroot,
|
||||
'bootstrap_chroot_name': self.bootstrap_chroot_name,
|
||||
'bootstrap_arch': self.bootstrap_arch,
|
||||
'bootstrap_suite': self.bootstrap_suite,
|
||||
'chroot_setup_cmd': self.chroot_setup_cmd,
|
||||
'chroot_additional_packages': self.chroot_additional_packages,
|
||||
'preserve_environment': self.preserve_environment,
|
||||
'environment_sanitization': self.environment_sanitization,
|
||||
'allowed_environment_vars': self.allowed_environment_vars,
|
||||
"chroot_name": self.chroot_name,
|
||||
"architecture": self.architecture,
|
||||
"suite": self.suite,
|
||||
"output_dir": self.output_dir,
|
||||
"keep_chroot": self.keep_chroot,
|
||||
"verbose": self.verbose,
|
||||
"debug": self.debug,
|
||||
"chroot_dir": self.chroot_dir,
|
||||
"chroot_config_dir": self.chroot_config_dir,
|
||||
"sbuild_config": self.sbuild_config,
|
||||
"sbuild_log_dir": self.sbuild_log_dir,
|
||||
"build_deps": self.build_deps,
|
||||
"build_env": self.build_env,
|
||||
"build_options": self.build_options,
|
||||
"metadata_dir": self.metadata_dir,
|
||||
"capture_logs": self.capture_logs,
|
||||
"capture_changes": self.capture_changes,
|
||||
"use_root_cache": self.use_root_cache,
|
||||
"root_cache_dir": self.root_cache_dir,
|
||||
"root_cache_age": self.root_cache_age,
|
||||
"use_package_cache": self.use_package_cache,
|
||||
"package_cache_dir": self.package_cache_dir,
|
||||
"use_ccache": self.use_ccache,
|
||||
"ccache_dir": self.ccache_dir,
|
||||
"use_tmpfs": self.use_tmpfs,
|
||||
"tmpfs_size": self.tmpfs_size,
|
||||
"parallel_jobs": self.parallel_jobs,
|
||||
"parallel_compression": self.parallel_compression,
|
||||
"use_host_resolv": self.use_host_resolv,
|
||||
"http_proxy": self.http_proxy,
|
||||
"https_proxy": self.https_proxy,
|
||||
"no_proxy": self.no_proxy,
|
||||
"mirror": self.mirror,
|
||||
"security_mirror": self.security_mirror,
|
||||
"backports_mirror": self.backports_mirror,
|
||||
"isolation": self.isolation,
|
||||
"enable_network": self.enable_network,
|
||||
"selinux_enabled": self.selinux_enabled,
|
||||
"use_bootstrap_chroot": self.use_bootstrap_chroot,
|
||||
"bootstrap_chroot_name": self.bootstrap_chroot_name,
|
||||
"bootstrap_arch": self.bootstrap_arch,
|
||||
"bootstrap_suite": self.bootstrap_suite,
|
||||
"chroot_setup_cmd": self.chroot_setup_cmd,
|
||||
"chroot_additional_packages": self.chroot_additional_packages,
|
||||
"preserve_environment": self.preserve_environment,
|
||||
"environment_sanitization": self.environment_sanitization,
|
||||
"allowed_environment_vars": self.allowed_environment_vars,
|
||||
}
|
||||
|
||||
def save(self, config_path: str) -> None:
|
||||
|
|
@ -189,7 +206,7 @@ class Config:
|
|||
config_dir = Path(config_path).parent
|
||||
config_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(config_path, 'w') as f:
|
||||
with open(config_path, "w") as f:
|
||||
yaml.dump(self.to_dict(), f, default_flow_style=False)
|
||||
except Exception as e:
|
||||
raise ConfigurationError(f"Error saving configuration: {e}")
|
||||
|
|
@ -206,17 +223,25 @@ class Config:
|
|||
errors.append(f"sbuild config file does not exist: {self.sbuild_config}")
|
||||
|
||||
# Check architecture
|
||||
valid_architectures = ['amd64', 'i386', 'arm64', 'armhf', 'ppc64el', 's390x']
|
||||
valid_architectures = ["amd64", "i386", "arm64", "armhf", "ppc64el", "s390x"]
|
||||
if self.architecture not in valid_architectures:
|
||||
errors.append(f"Invalid architecture: {self.architecture}")
|
||||
|
||||
# Check suite
|
||||
valid_suites = ['bookworm', 'sid', 'bullseye', 'buster', 'jammy', 'noble', 'focal']
|
||||
valid_suites = [
|
||||
"bookworm",
|
||||
"sid",
|
||||
"bullseye",
|
||||
"buster",
|
||||
"jammy",
|
||||
"noble",
|
||||
"focal",
|
||||
]
|
||||
if self.suite not in valid_suites:
|
||||
errors.append(f"Invalid suite: {self.suite}")
|
||||
|
||||
# Check isolation method
|
||||
valid_isolation = ['schroot', 'simple', 'nspawn']
|
||||
valid_isolation = ["schroot", "simple", "nspawn"]
|
||||
if self.isolation not in valid_isolation:
|
||||
errors.append(f"Invalid isolation method: {self.isolation}")
|
||||
|
||||
|
|
@ -225,7 +250,7 @@ class Config:
|
|||
errors.append("Parallel jobs must be at least 1")
|
||||
|
||||
if errors:
|
||||
raise ConfigurationError(f"Configuration validation failed:\n" + "\n".join(errors))
|
||||
raise ConfigurationError("Configuration validation failed:\n" + "\n".join(errors))
|
||||
|
||||
def get_chroot_path(self) -> str:
|
||||
"""Get the full path to the chroot directory"""
|
||||
|
|
@ -257,21 +282,21 @@ class Config:
|
|||
|
||||
# Set parallel build options
|
||||
if self.parallel_jobs > 1:
|
||||
env['DEB_BUILD_OPTIONS'] = f"parallel={self.parallel_jobs},nocheck"
|
||||
env['MAKEFLAGS'] = f"-j{self.parallel_jobs}"
|
||||
env["DEB_BUILD_OPTIONS"] = f"parallel={self.parallel_jobs},nocheck"
|
||||
env["MAKEFLAGS"] = f"-j{self.parallel_jobs}"
|
||||
|
||||
# Set ccache if enabled
|
||||
if self.use_ccache:
|
||||
env['CCACHE_DIR'] = self.get_ccache_path()
|
||||
env['CCACHE_HASHDIR'] = '1'
|
||||
env["CCACHE_DIR"] = self.get_ccache_path()
|
||||
env["CCACHE_HASHDIR"] = "1"
|
||||
|
||||
# Set proxy if configured
|
||||
if self.http_proxy:
|
||||
env['http_proxy'] = self.http_proxy
|
||||
env["http_proxy"] = self.http_proxy
|
||||
if self.https_proxy:
|
||||
env['https_proxy'] = self.https_proxy
|
||||
env["https_proxy"] = self.https_proxy
|
||||
if self.no_proxy:
|
||||
env['no_proxy'] = self.no_proxy
|
||||
env["no_proxy"] = self.no_proxy
|
||||
|
||||
# Merge with user-defined build environment
|
||||
env.update(self.build_env)
|
||||
|
|
|
|||
|
|
@ -5,14 +5,15 @@ This package provides default configuration files for various Debian-based Linux
|
|||
similar to Mock's mock-core-configs package.
|
||||
"""
|
||||
|
||||
import os
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List
|
||||
|
||||
import yaml
|
||||
|
||||
# Base directory for config files
|
||||
CONFIGS_DIR = Path(__file__).parent
|
||||
|
||||
|
||||
def get_available_configs() -> List[str]:
|
||||
"""Get list of available configuration names"""
|
||||
configs = []
|
||||
|
|
@ -21,15 +22,17 @@ def get_available_configs() -> List[str]:
|
|||
configs.append(config_file.stem)
|
||||
return sorted(configs)
|
||||
|
||||
|
||||
def load_config(config_name: str) -> Dict:
|
||||
"""Load a configuration by name"""
|
||||
config_file = CONFIGS_DIR / f"{config_name}.yaml"
|
||||
if not config_file.exists():
|
||||
raise ValueError(f"Configuration '{config_name}' not found")
|
||||
|
||||
with open(config_file, 'r') as f:
|
||||
with open(config_file, "r") as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
|
||||
def list_configs() -> Dict[str, Dict]:
|
||||
"""List all available configurations with their details"""
|
||||
configs = {}
|
||||
|
|
@ -37,10 +40,10 @@ def list_configs() -> Dict[str, Dict]:
|
|||
try:
|
||||
config = load_config(config_name)
|
||||
configs[config_name] = {
|
||||
'description': config.get('description', ''),
|
||||
'suite': config.get('suite', ''),
|
||||
'architecture': config.get('architecture', ''),
|
||||
'mirror': config.get('mirror', '')
|
||||
"description": config.get("description", ""),
|
||||
"suite": config.get("suite", ""),
|
||||
"architecture": config.get("architecture", ""),
|
||||
"mirror": config.get("mirror", ""),
|
||||
}
|
||||
except Exception:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -33,3 +33,4 @@ metadata_dir: "./metadata"
|
|||
keep_chroot: false
|
||||
verbose: false
|
||||
debug: false
|
||||
|
||||
|
|
|
|||
|
|
@ -33,3 +33,4 @@ metadata_dir: "./metadata"
|
|||
keep_chroot: false
|
||||
verbose: false
|
||||
debug: false
|
||||
|
||||
|
|
|
|||
|
|
@ -33,3 +33,4 @@ metadata_dir: "./metadata"
|
|||
keep_chroot: false
|
||||
verbose: false
|
||||
debug: false
|
||||
|
||||
|
|
|
|||
|
|
@ -33,3 +33,4 @@ metadata_dir: "./metadata"
|
|||
keep_chroot: false
|
||||
verbose: false
|
||||
debug: false
|
||||
|
||||
|
|
|
|||
236
deb_mock/core.py
236
deb_mock/core.py
|
|
@ -3,16 +3,15 @@ Core DebMock class for orchestrating the build process
|
|||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional, List
|
||||
from .config import Config
|
||||
from .chroot import ChrootManager
|
||||
from .sbuild import SbuildWrapper
|
||||
from .metadata import MetadataManager
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .cache import CacheManager
|
||||
from .exceptions import DebMockError, BuildError, ChrootError, SbuildError
|
||||
from .chroot import ChrootManager
|
||||
from .config import Config
|
||||
from .exceptions import ChrootError
|
||||
from .metadata import MetadataManager
|
||||
from .sbuild import SbuildWrapper
|
||||
|
||||
|
||||
class DebMock:
|
||||
|
|
@ -45,7 +44,7 @@ class DebMock:
|
|||
"""Build a Debian source package in an isolated environment"""
|
||||
|
||||
# Ensure chroot exists
|
||||
chroot_name = kwargs.get('chroot_name', self.config.chroot_name)
|
||||
chroot_name = kwargs.get("chroot_name", self.config.chroot_name)
|
||||
chroot_path = self.config.get_chroot_path()
|
||||
|
||||
# Try to restore from cache first
|
||||
|
|
@ -55,24 +54,19 @@ class DebMock:
|
|||
|
||||
# Check build dependencies
|
||||
deps_check = self.sbuild_wrapper.check_dependencies(source_package, chroot_name)
|
||||
if not deps_check['satisfied']:
|
||||
if not deps_check["satisfied"]:
|
||||
# Try to install missing dependencies
|
||||
if deps_check['missing']:
|
||||
self.sbuild_wrapper.install_build_dependencies(deps_check['missing'], chroot_name)
|
||||
if deps_check["missing"]:
|
||||
self.sbuild_wrapper.install_build_dependencies(deps_check["missing"], chroot_name)
|
||||
|
||||
# Setup build environment
|
||||
build_env = self.config.setup_build_environment()
|
||||
|
||||
# Build the package
|
||||
build_result = self.sbuild_wrapper.build_package(
|
||||
source_package,
|
||||
chroot_name,
|
||||
build_env=build_env,
|
||||
**kwargs
|
||||
)
|
||||
build_result = self.sbuild_wrapper.build_package(source_package, chroot_name, build_env=build_env, **kwargs)
|
||||
|
||||
# Create cache after successful build
|
||||
if build_result.get('success', False):
|
||||
if build_result.get("success", False):
|
||||
self.cache_manager.create_root_cache(chroot_path)
|
||||
|
||||
# Capture and store metadata
|
||||
|
|
@ -80,7 +74,7 @@ class DebMock:
|
|||
self.metadata_manager.store_metadata(metadata)
|
||||
|
||||
# Clean up chroot if not keeping it
|
||||
if not kwargs.get('keep_chroot', self.config.keep_chroot):
|
||||
if not kwargs.get("keep_chroot", self.config.keep_chroot):
|
||||
self.chroot_manager.clean_chroot(chroot_name)
|
||||
|
||||
return build_result
|
||||
|
|
@ -89,7 +83,7 @@ class DebMock:
|
|||
"""Build a chain of packages that depend on each other (similar to Mock's --chain)"""
|
||||
|
||||
results = []
|
||||
chroot_name = kwargs.get('chroot_name', self.config.chroot_name)
|
||||
chroot_name = kwargs.get("chroot_name", self.config.chroot_name)
|
||||
chroot_path = self.config.get_chroot_path()
|
||||
|
||||
# Try to restore from cache first
|
||||
|
|
@ -103,37 +97,36 @@ class DebMock:
|
|||
for i, source_package in enumerate(source_packages):
|
||||
try:
|
||||
# Build the package
|
||||
result = self.sbuild_wrapper.build_package(
|
||||
source_package,
|
||||
chroot_name,
|
||||
build_env=build_env,
|
||||
**kwargs
|
||||
result = self.sbuild_wrapper.build_package(source_package, chroot_name, build_env=build_env, **kwargs)
|
||||
|
||||
results.append(
|
||||
{
|
||||
"package": source_package,
|
||||
"success": True,
|
||||
"result": result,
|
||||
"order": i + 1,
|
||||
}
|
||||
)
|
||||
|
||||
results.append({
|
||||
'package': source_package,
|
||||
'success': True,
|
||||
'result': result,
|
||||
'order': i + 1
|
||||
})
|
||||
|
||||
# Install the built package in the chroot for subsequent builds
|
||||
if result.get('artifacts'):
|
||||
self._install_built_package(result['artifacts'], chroot_name)
|
||||
if result.get("artifacts"):
|
||||
self._install_built_package(result["artifacts"], chroot_name)
|
||||
|
||||
except Exception as e:
|
||||
results.append({
|
||||
'package': source_package,
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'order': i + 1
|
||||
})
|
||||
results.append(
|
||||
{
|
||||
"package": source_package,
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"order": i + 1,
|
||||
}
|
||||
)
|
||||
# Stop chain on failure unless continue_on_failure is specified
|
||||
if not kwargs.get('continue_on_failure', False):
|
||||
if not kwargs.get("continue_on_failure", False):
|
||||
break
|
||||
|
||||
# Create cache after successful chain build
|
||||
if any(r['success'] for r in results):
|
||||
if any(r["success"] for r in results):
|
||||
self.cache_manager.create_root_cache(chroot_path)
|
||||
|
||||
return results
|
||||
|
|
@ -142,7 +135,7 @@ class DebMock:
|
|||
"""Install a built package in the chroot for chain building"""
|
||||
|
||||
# Find .deb files in artifacts
|
||||
deb_files = [art for art in artifacts if art.endswith('.deb')]
|
||||
deb_files = [art for art in artifacts if art.endswith(".deb")]
|
||||
|
||||
if not deb_files:
|
||||
return
|
||||
|
|
@ -156,17 +149,11 @@ class DebMock:
|
|||
|
||||
# Install in chroot
|
||||
self.chroot_manager.execute_in_chroot(
|
||||
chroot_name,
|
||||
['dpkg', '-i', chroot_deb_path],
|
||||
capture_output=False
|
||||
chroot_name, ["dpkg", "-i", chroot_deb_path], capture_output=False
|
||||
)
|
||||
|
||||
# Clean up
|
||||
self.chroot_manager.execute_in_chroot(
|
||||
chroot_name,
|
||||
['rm', '-f', chroot_deb_path],
|
||||
capture_output=False
|
||||
)
|
||||
self.chroot_manager.execute_in_chroot(chroot_name, ["rm", "-f", chroot_deb_path], capture_output=False)
|
||||
|
||||
except Exception as e:
|
||||
# Log warning but continue
|
||||
|
|
@ -209,11 +196,7 @@ class DebMock:
|
|||
raise ChrootError(f"Chroot '{chroot_name}' does not exist")
|
||||
|
||||
# Execute shell in chroot
|
||||
self.chroot_manager.execute_in_chroot(
|
||||
chroot_name,
|
||||
['/bin/bash'],
|
||||
capture_output=False
|
||||
)
|
||||
self.chroot_manager.execute_in_chroot(chroot_name, ["/bin/bash"], capture_output=False)
|
||||
|
||||
def copyout(self, source_path: str, dest_path: str, chroot_name: str = None) -> None:
|
||||
"""Copy files from chroot to host (similar to Mock's --copyout)"""
|
||||
|
|
@ -241,24 +224,25 @@ class DebMock:
|
|||
"""Capture comprehensive build metadata"""
|
||||
|
||||
metadata = {
|
||||
'source_package': source_package,
|
||||
'build_result': build_result,
|
||||
'config': self.config.to_dict(),
|
||||
'artifacts': build_result.get('artifacts', []),
|
||||
'build_metadata': build_result.get('metadata', {}),
|
||||
'timestamp': self._get_timestamp(),
|
||||
'build_success': build_result.get('success', False),
|
||||
'cache_info': self.get_cache_stats()
|
||||
"source_package": source_package,
|
||||
"build_result": build_result,
|
||||
"config": self.config.to_dict(),
|
||||
"artifacts": build_result.get("artifacts", []),
|
||||
"build_metadata": build_result.get("metadata", {}),
|
||||
"timestamp": self._get_timestamp(),
|
||||
"build_success": build_result.get("success", False),
|
||||
"cache_info": self.get_cache_stats(),
|
||||
}
|
||||
|
||||
# Add artifact details
|
||||
metadata['artifact_details'] = self._get_artifact_details(build_result.get('artifacts', []))
|
||||
metadata["artifact_details"] = self._get_artifact_details(build_result.get("artifacts", []))
|
||||
|
||||
return metadata
|
||||
|
||||
def _get_timestamp(self) -> str:
|
||||
"""Get current timestamp"""
|
||||
from datetime import datetime
|
||||
|
||||
return datetime.now().isoformat()
|
||||
|
||||
def _get_artifact_details(self, artifacts: list) -> list:
|
||||
|
|
@ -268,13 +252,15 @@ class DebMock:
|
|||
for artifact_path in artifacts:
|
||||
if os.path.exists(artifact_path):
|
||||
stat = os.stat(artifact_path)
|
||||
details.append({
|
||||
'path': artifact_path,
|
||||
'name': os.path.basename(artifact_path),
|
||||
'size': stat.st_size,
|
||||
'modified': stat.st_mtime,
|
||||
'type': self._get_artifact_type(artifact_path)
|
||||
})
|
||||
details.append(
|
||||
{
|
||||
"path": artifact_path,
|
||||
"name": os.path.basename(artifact_path),
|
||||
"size": stat.st_size,
|
||||
"modified": stat.st_mtime,
|
||||
"type": self._get_artifact_type(artifact_path),
|
||||
}
|
||||
)
|
||||
|
||||
return details
|
||||
|
||||
|
|
@ -282,16 +268,16 @@ class DebMock:
|
|||
"""Determine the type of build artifact"""
|
||||
ext = Path(artifact_path).suffix.lower()
|
||||
|
||||
if ext == '.deb':
|
||||
return 'deb_package'
|
||||
elif ext == '.changes':
|
||||
return 'changes_file'
|
||||
elif ext == '.buildinfo':
|
||||
return 'buildinfo_file'
|
||||
elif ext == '.dsc':
|
||||
return 'source_package'
|
||||
if ext == ".deb":
|
||||
return "deb_package"
|
||||
elif ext == ".changes":
|
||||
return "changes_file"
|
||||
elif ext == ".buildinfo":
|
||||
return "buildinfo_file"
|
||||
elif ext == ".dsc":
|
||||
return "source_package"
|
||||
else:
|
||||
return 'other'
|
||||
return "other"
|
||||
|
||||
def verify_reproducible_build(self, source_package: str, **kwargs) -> Dict[str, Any]:
|
||||
"""Verify that a build is reproducible by building twice and comparing results"""
|
||||
|
|
@ -300,7 +286,7 @@ class DebMock:
|
|||
result1 = self.build(source_package, **kwargs)
|
||||
|
||||
# Clean chroot for second build
|
||||
chroot_name = kwargs.get('chroot_name', self.config.chroot_name)
|
||||
chroot_name = kwargs.get("chroot_name", self.config.chroot_name)
|
||||
if self.chroot_manager.chroot_exists(chroot_name):
|
||||
self.chroot_manager.clean_chroot(chroot_name)
|
||||
|
||||
|
|
@ -311,28 +297,24 @@ class DebMock:
|
|||
comparison = self._compare_build_results(result1, result2)
|
||||
|
||||
return {
|
||||
'reproducible': comparison['identical'],
|
||||
'first_build': result1,
|
||||
'second_build': result2,
|
||||
'comparison': comparison
|
||||
"reproducible": comparison["identical"],
|
||||
"first_build": result1,
|
||||
"second_build": result2,
|
||||
"comparison": comparison,
|
||||
}
|
||||
|
||||
def _compare_build_results(self, result1: Dict[str, Any], result2: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Compare two build results for reproducibility"""
|
||||
|
||||
comparison = {
|
||||
'identical': True,
|
||||
'differences': [],
|
||||
'artifact_comparison': {}
|
||||
}
|
||||
comparison = {"identical": True, "differences": [], "artifact_comparison": {}}
|
||||
|
||||
# Compare artifacts
|
||||
artifacts1 = set(result1.get('artifacts', []))
|
||||
artifacts2 = set(result2.get('artifacts', []))
|
||||
artifacts1 = set(result1.get("artifacts", []))
|
||||
artifacts2 = set(result2.get("artifacts", []))
|
||||
|
||||
if artifacts1 != artifacts2:
|
||||
comparison['identical'] = False
|
||||
comparison['differences'].append('Different artifacts produced')
|
||||
comparison["identical"] = False
|
||||
comparison["differences"].append("Different artifacts produced")
|
||||
|
||||
# Compare individual artifacts
|
||||
common_artifacts = artifacts1.intersection(artifacts2)
|
||||
|
|
@ -342,15 +324,15 @@ class DebMock:
|
|||
hash1 = self._get_file_hash(artifact)
|
||||
hash2 = self._get_file_hash(artifact)
|
||||
|
||||
comparison['artifact_comparison'][artifact] = {
|
||||
'identical': hash1 == hash2,
|
||||
'hash1': hash1,
|
||||
'hash2': hash2
|
||||
comparison["artifact_comparison"][artifact] = {
|
||||
"identical": hash1 == hash2,
|
||||
"hash1": hash1,
|
||||
"hash2": hash2,
|
||||
}
|
||||
|
||||
if hash1 != hash2:
|
||||
comparison['identical'] = False
|
||||
comparison['differences'].append(f'Artifact {artifact} differs')
|
||||
comparison["identical"] = False
|
||||
comparison["differences"].append(f"Artifact {artifact} differs")
|
||||
|
||||
return comparison
|
||||
|
||||
|
|
@ -383,18 +365,18 @@ class DebMock:
|
|||
|
||||
# Check and install dependencies
|
||||
deps_check = self.sbuild_wrapper.check_dependencies(source_package, chroot_name)
|
||||
if deps_check['missing']:
|
||||
result = self.sbuild_wrapper.install_build_dependencies(deps_check['missing'], chroot_name)
|
||||
if deps_check["missing"]:
|
||||
result = self.sbuild_wrapper.install_build_dependencies(deps_check["missing"], chroot_name)
|
||||
return {
|
||||
'success': True,
|
||||
'installed': deps_check['missing'],
|
||||
'details': result
|
||||
"success": True,
|
||||
"installed": deps_check["missing"],
|
||||
"details": result,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'success': True,
|
||||
'installed': [],
|
||||
'message': 'All dependencies already satisfied'
|
||||
"success": True,
|
||||
"installed": [],
|
||||
"message": "All dependencies already satisfied",
|
||||
}
|
||||
|
||||
def install_packages(self, packages: List[str]) -> Dict[str, Any]:
|
||||
|
|
@ -409,14 +391,14 @@ class DebMock:
|
|||
result = self.chroot_manager.execute_in_chroot(
|
||||
chroot_name,
|
||||
f"{self.config.apt_install_command} {' '.join(packages)}",
|
||||
as_root=True
|
||||
as_root=True,
|
||||
)
|
||||
|
||||
return {
|
||||
'success': result['returncode'] == 0,
|
||||
'installed': packages,
|
||||
'output': result['stdout'],
|
||||
'error': result['stderr'] if result['returncode'] != 0 else None
|
||||
"success": result["returncode"] == 0,
|
||||
"installed": packages,
|
||||
"output": result["stdout"],
|
||||
"error": result["stderr"] if result["returncode"] != 0 else None,
|
||||
}
|
||||
|
||||
def update_packages(self, packages: List[str] = None) -> Dict[str, Any]:
|
||||
|
|
@ -437,10 +419,10 @@ class DebMock:
|
|||
result = self.chroot_manager.execute_in_chroot(chroot_name, cmd, as_root=True)
|
||||
|
||||
return {
|
||||
'success': result['returncode'] == 0,
|
||||
'updated': packages if packages else 'all',
|
||||
'output': result['stdout'],
|
||||
'error': result['stderr'] if result['returncode'] != 0 else None
|
||||
"success": result["returncode"] == 0,
|
||||
"updated": packages if packages else "all",
|
||||
"output": result["stdout"],
|
||||
"error": result["stderr"] if result["returncode"] != 0 else None,
|
||||
}
|
||||
|
||||
def remove_packages(self, packages: List[str]) -> Dict[str, Any]:
|
||||
|
|
@ -456,10 +438,10 @@ class DebMock:
|
|||
result = self.chroot_manager.execute_in_chroot(chroot_name, cmd, as_root=True)
|
||||
|
||||
return {
|
||||
'success': result['returncode'] == 0,
|
||||
'removed': packages,
|
||||
'output': result['stdout'],
|
||||
'error': result['stderr'] if result['returncode'] != 0 else None
|
||||
"success": result["returncode"] == 0,
|
||||
"removed": packages,
|
||||
"output": result["stdout"],
|
||||
"error": result["stderr"] if result["returncode"] != 0 else None,
|
||||
}
|
||||
|
||||
def execute_apt_command(self, command: str) -> Dict[str, Any]:
|
||||
|
|
@ -475,8 +457,8 @@ class DebMock:
|
|||
result = self.chroot_manager.execute_in_chroot(chroot_name, cmd, as_root=True)
|
||||
|
||||
return {
|
||||
'success': result['returncode'] == 0,
|
||||
'command': command,
|
||||
'output': result['stdout'],
|
||||
'error': result['stderr'] if result['returncode'] != 0 else None
|
||||
"success": result["returncode"] == 0,
|
||||
"command": command,
|
||||
"output": result["stdout"],
|
||||
"error": result["stderr"] if result["returncode"] != 0 else None,
|
||||
}
|
||||
|
|
@ -5,10 +5,9 @@ This module provides a comprehensive exception hierarchy inspired by Mock's
|
|||
exception handling system, adapted for Debian-based build environments.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import functools
|
||||
from typing import Optional, Dict, Any, List
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
class DebMockError(Exception):
|
||||
|
|
@ -20,10 +19,13 @@ class DebMockError(Exception):
|
|||
and recovery suggestions.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str,
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
exit_code: int = 1,
|
||||
context: Optional[Dict[str, Any]] = None,
|
||||
suggestions: Optional[List[str]] = None):
|
||||
suggestions: Optional[List[str]] = None,
|
||||
):
|
||||
"""
|
||||
Initialize the exception with message and optional context.
|
||||
|
||||
|
|
@ -74,18 +76,22 @@ class ConfigurationError(DebMockError):
|
|||
missing required options, or contain conflicting settings.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, config_file: Optional[str] = None,
|
||||
config_section: Optional[str] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
config_file: Optional[str] = None,
|
||||
config_section: Optional[str] = None,
|
||||
):
|
||||
context = {}
|
||||
if config_file:
|
||||
context['config_file'] = config_file
|
||||
context["config_file"] = config_file
|
||||
if config_section:
|
||||
context['config_section'] = config_section
|
||||
context["config_section"] = config_section
|
||||
|
||||
suggestions = [
|
||||
"Check the configuration file syntax",
|
||||
"Verify all required options are set",
|
||||
"Ensure configuration values are valid for your system"
|
||||
"Ensure configuration values are valid for your system",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=2, context=context, suggestions=suggestions)
|
||||
|
|
@ -98,21 +104,26 @@ class ChrootError(DebMockError):
|
|||
This exception covers chroot creation, management, and cleanup errors.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, chroot_name: Optional[str] = None,
|
||||
operation: Optional[str] = None, chroot_path: Optional[str] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
chroot_name: Optional[str] = None,
|
||||
operation: Optional[str] = None,
|
||||
chroot_path: Optional[str] = None,
|
||||
):
|
||||
context = {}
|
||||
if chroot_name:
|
||||
context['chroot_name'] = chroot_name
|
||||
context["chroot_name"] = chroot_name
|
||||
if operation:
|
||||
context['operation'] = operation
|
||||
context["operation"] = operation
|
||||
if chroot_path:
|
||||
context['chroot_path'] = chroot_path
|
||||
context["chroot_path"] = chroot_path
|
||||
|
||||
suggestions = [
|
||||
"Ensure you have sufficient disk space",
|
||||
"Check that you have root privileges for chroot operations",
|
||||
"Verify the chroot name is valid",
|
||||
"Try cleaning up existing chroots with 'deb-mock clean-chroot'"
|
||||
"Try cleaning up existing chroots with 'deb-mock clean-chroot'",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=3, context=context, suggestions=suggestions)
|
||||
|
|
@ -125,21 +136,26 @@ class SbuildError(DebMockError):
|
|||
This exception covers sbuild execution, configuration, and result processing.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, sbuild_config: Optional[str] = None,
|
||||
build_log: Optional[str] = None, return_code: Optional[int] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
sbuild_config: Optional[str] = None,
|
||||
build_log: Optional[str] = None,
|
||||
return_code: Optional[int] = None,
|
||||
):
|
||||
context = {}
|
||||
if sbuild_config:
|
||||
context['sbuild_config'] = sbuild_config
|
||||
context["sbuild_config"] = sbuild_config
|
||||
if build_log:
|
||||
context['build_log'] = build_log
|
||||
context["build_log"] = build_log
|
||||
if return_code is not None:
|
||||
context['return_code'] = return_code
|
||||
context["return_code"] = return_code
|
||||
|
||||
suggestions = [
|
||||
"Check the build log for detailed error information",
|
||||
"Verify that sbuild is properly configured",
|
||||
"Ensure all build dependencies are available",
|
||||
"Try updating the chroot with 'deb-mock update-chroot'"
|
||||
"Try updating the chroot with 'deb-mock update-chroot'",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=4, context=context, suggestions=suggestions)
|
||||
|
|
@ -153,21 +169,26 @@ class BuildError(DebMockError):
|
|||
compilation errors, missing dependencies, or other build issues.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, source_package: Optional[str] = None,
|
||||
build_log: Optional[str] = None, artifacts: Optional[List[str]] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
source_package: Optional[str] = None,
|
||||
build_log: Optional[str] = None,
|
||||
artifacts: Optional[List[str]] = None,
|
||||
):
|
||||
context = {}
|
||||
if source_package:
|
||||
context['source_package'] = source_package
|
||||
context["source_package"] = source_package
|
||||
if build_log:
|
||||
context['build_log'] = build_log
|
||||
context["build_log"] = build_log
|
||||
if artifacts:
|
||||
context['artifacts'] = artifacts
|
||||
context["artifacts"] = artifacts
|
||||
|
||||
suggestions = [
|
||||
"Review the build log for specific error messages",
|
||||
"Check that all build dependencies are installed",
|
||||
"Verify the source package is valid and complete",
|
||||
"Try building with verbose output: 'deb-mock --verbose build'"
|
||||
"Try building with verbose output: 'deb-mock --verbose build'",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=5, context=context, suggestions=suggestions)
|
||||
|
|
@ -181,19 +202,23 @@ class DependencyError(DebMockError):
|
|||
and other dependency-related problems.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, missing_packages: Optional[List[str]] = None,
|
||||
conflicting_packages: Optional[List[str]] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
missing_packages: Optional[List[str]] = None,
|
||||
conflicting_packages: Optional[List[str]] = None,
|
||||
):
|
||||
context = {}
|
||||
if missing_packages:
|
||||
context['missing_packages'] = missing_packages
|
||||
context["missing_packages"] = missing_packages
|
||||
if conflicting_packages:
|
||||
context['conflicting_packages'] = conflicting_packages
|
||||
context["conflicting_packages"] = conflicting_packages
|
||||
|
||||
suggestions = [
|
||||
"Install missing build dependencies",
|
||||
"Resolve package conflicts by updating or removing conflicting packages",
|
||||
"Check that your chroot has access to the required repositories",
|
||||
"Try updating the chroot: 'deb-mock update-chroot'"
|
||||
"Try updating the chroot: 'deb-mock update-chroot'",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=6, context=context, suggestions=suggestions)
|
||||
|
|
@ -206,18 +231,22 @@ class MetadataError(DebMockError):
|
|||
This exception covers metadata capture, storage, and retrieval errors.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, metadata_file: Optional[str] = None,
|
||||
operation: Optional[str] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
metadata_file: Optional[str] = None,
|
||||
operation: Optional[str] = None,
|
||||
):
|
||||
context = {}
|
||||
if metadata_file:
|
||||
context['metadata_file'] = metadata_file
|
||||
context["metadata_file"] = metadata_file
|
||||
if operation:
|
||||
context['operation'] = operation
|
||||
context["operation"] = operation
|
||||
|
||||
suggestions = [
|
||||
"Check that the metadata directory is writable",
|
||||
"Verify that the metadata file format is valid",
|
||||
"Ensure sufficient disk space for metadata storage"
|
||||
"Ensure sufficient disk space for metadata storage",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=7, context=context, suggestions=suggestions)
|
||||
|
|
@ -230,21 +259,26 @@ class CacheError(DebMockError):
|
|||
This exception covers root cache, package cache, and ccache errors.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, cache_type: Optional[str] = None,
|
||||
cache_path: Optional[str] = None, operation: Optional[str] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
cache_type: Optional[str] = None,
|
||||
cache_path: Optional[str] = None,
|
||||
operation: Optional[str] = None,
|
||||
):
|
||||
context = {}
|
||||
if cache_type:
|
||||
context['cache_type'] = cache_type
|
||||
context["cache_type"] = cache_type
|
||||
if cache_path:
|
||||
context['cache_path'] = cache_path
|
||||
context["cache_path"] = cache_path
|
||||
if operation:
|
||||
context['operation'] = operation
|
||||
context["operation"] = operation
|
||||
|
||||
suggestions = [
|
||||
"Check that cache directories are writable",
|
||||
"Ensure sufficient disk space for cache operations",
|
||||
"Try cleaning up old caches: 'deb-mock cleanup-caches'",
|
||||
"Verify cache configuration settings"
|
||||
"Verify cache configuration settings",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=8, context=context, suggestions=suggestions)
|
||||
|
|
@ -257,19 +291,23 @@ class PluginError(DebMockError):
|
|||
This exception covers plugin loading, configuration, and execution errors.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, plugin_name: Optional[str] = None,
|
||||
plugin_config: Optional[Dict[str, Any]] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
plugin_name: Optional[str] = None,
|
||||
plugin_config: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
context = {}
|
||||
if plugin_name:
|
||||
context['plugin_name'] = plugin_name
|
||||
context["plugin_name"] = plugin_name
|
||||
if plugin_config:
|
||||
context['plugin_config'] = plugin_config
|
||||
context["plugin_config"] = plugin_config
|
||||
|
||||
suggestions = [
|
||||
"Check that the plugin is properly installed",
|
||||
"Verify plugin configuration is valid",
|
||||
"Ensure plugin dependencies are satisfied",
|
||||
"Try disabling the plugin if it's causing issues"
|
||||
"Try disabling the plugin if it's causing issues",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=9, context=context, suggestions=suggestions)
|
||||
|
|
@ -283,21 +321,26 @@ class NetworkError(DebMockError):
|
|||
other network operations.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, url: Optional[str] = None,
|
||||
proxy: Optional[str] = None, timeout: Optional[int] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
url: Optional[str] = None,
|
||||
proxy: Optional[str] = None,
|
||||
timeout: Optional[int] = None,
|
||||
):
|
||||
context = {}
|
||||
if url:
|
||||
context['url'] = url
|
||||
context["url"] = url
|
||||
if proxy:
|
||||
context['proxy'] = proxy
|
||||
context["proxy"] = proxy
|
||||
if timeout:
|
||||
context['timeout'] = timeout
|
||||
context["timeout"] = timeout
|
||||
|
||||
suggestions = [
|
||||
"Check your internet connection",
|
||||
"Verify repository URLs are accessible",
|
||||
"Configure proxy settings if behind a firewall",
|
||||
"Try using a different mirror or repository"
|
||||
"Try using a different mirror or repository",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=10, context=context, suggestions=suggestions)
|
||||
|
|
@ -311,21 +354,26 @@ class PermissionError(DebMockError):
|
|||
file access, and other permission issues.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, operation: Optional[str] = None,
|
||||
path: Optional[str] = None, required_privileges: Optional[str] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
operation: Optional[str] = None,
|
||||
path: Optional[str] = None,
|
||||
required_privileges: Optional[str] = None,
|
||||
):
|
||||
context = {}
|
||||
if operation:
|
||||
context['operation'] = operation
|
||||
context["operation"] = operation
|
||||
if path:
|
||||
context['path'] = path
|
||||
context["path"] = path
|
||||
if required_privileges:
|
||||
context['required_privileges'] = required_privileges
|
||||
context["required_privileges"] = required_privileges
|
||||
|
||||
suggestions = [
|
||||
"Run the command with appropriate privileges (sudo)",
|
||||
"Check file and directory permissions",
|
||||
"Verify your user is in the required groups",
|
||||
"Ensure the target paths are writable"
|
||||
"Ensure the target paths are writable",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=11, context=context, suggestions=suggestions)
|
||||
|
|
@ -339,21 +387,26 @@ class ValidationError(DebMockError):
|
|||
and other input data.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, field: Optional[str] = None,
|
||||
value: Optional[str] = None, expected_format: Optional[str] = None):
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
field: Optional[str] = None,
|
||||
value: Optional[str] = None,
|
||||
expected_format: Optional[str] = None,
|
||||
):
|
||||
context = {}
|
||||
if field:
|
||||
context['field'] = field
|
||||
context["field"] = field
|
||||
if value:
|
||||
context['value'] = value
|
||||
context["value"] = value
|
||||
if expected_format:
|
||||
context['expected_format'] = expected_format
|
||||
context["expected_format"] = expected_format
|
||||
|
||||
suggestions = [
|
||||
"Check the input format and syntax",
|
||||
"Verify that required fields are provided",
|
||||
"Ensure values are within acceptable ranges",
|
||||
"Review the documentation for correct usage"
|
||||
"Review the documentation for correct usage",
|
||||
]
|
||||
|
||||
super().__init__(message, exit_code=12, context=context, suggestions=suggestions)
|
||||
|
|
@ -367,6 +420,7 @@ def handle_exception(func):
|
|||
This decorator catches DebMockError exceptions and provides
|
||||
formatted error output with suggestions for resolution.
|
||||
"""
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
|
|
@ -378,15 +432,16 @@ def handle_exception(func):
|
|||
# Convert unexpected exceptions to DebMockError
|
||||
error = DebMockError(
|
||||
f"Unexpected error: {str(e)}",
|
||||
context={'exception_type': type(e).__name__},
|
||||
context={"exception_type": type(e).__name__},
|
||||
suggestions=[
|
||||
"This may be a bug in deb-mock",
|
||||
"Check the logs for more details",
|
||||
"Report the issue with full error context"
|
||||
]
|
||||
"Report the issue with full error context",
|
||||
],
|
||||
)
|
||||
error.print_error()
|
||||
sys.exit(1)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
Metadata management for deb-mock
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .exceptions import MetadataError
|
||||
|
||||
|
||||
|
|
@ -26,14 +26,14 @@ class MetadataManager:
|
|||
build_id = self._generate_build_id()
|
||||
|
||||
# Add build ID to metadata
|
||||
metadata['build_id'] = build_id
|
||||
metadata['stored_at'] = datetime.now().isoformat()
|
||||
metadata["build_id"] = build_id
|
||||
metadata["stored_at"] = datetime.now().isoformat()
|
||||
|
||||
# Create metadata file
|
||||
metadata_file = self.metadata_dir / f"{build_id}.json"
|
||||
|
||||
try:
|
||||
with open(metadata_file, 'w') as f:
|
||||
with open(metadata_file, "w") as f:
|
||||
json.dump(metadata, f, indent=2, default=str)
|
||||
except Exception as e:
|
||||
raise MetadataError(f"Failed to store metadata: {e}")
|
||||
|
|
@ -52,7 +52,7 @@ class MetadataManager:
|
|||
return None
|
||||
|
||||
try:
|
||||
with open(metadata_file, 'r') as f:
|
||||
with open(metadata_file, "r") as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
raise MetadataError(f"Failed to load metadata for build {build_id}: {e}")
|
||||
|
|
@ -68,17 +68,13 @@ class MetadataManager:
|
|||
return builds
|
||||
|
||||
try:
|
||||
with open(index_file, 'r') as f:
|
||||
with open(index_file, "r") as f:
|
||||
build_index = json.load(f)
|
||||
except Exception as e:
|
||||
raise MetadataError(f"Failed to load build index: {e}")
|
||||
|
||||
# Sort builds by timestamp (newest first)
|
||||
sorted_builds = sorted(
|
||||
build_index.values(),
|
||||
key=lambda x: x.get('timestamp', ''),
|
||||
reverse=True
|
||||
)
|
||||
sorted_builds = sorted(build_index.values(), key=lambda x: x.get("timestamp", ""), reverse=True)
|
||||
|
||||
# Apply limit if specified
|
||||
if limit:
|
||||
|
|
@ -86,7 +82,7 @@ class MetadataManager:
|
|||
|
||||
# Load full metadata for each build
|
||||
for build_info in sorted_builds:
|
||||
build_id = build_info.get('build_id')
|
||||
build_id = build_info.get("build_id")
|
||||
if build_id:
|
||||
full_metadata = self.get_build_info(build_id)
|
||||
if full_metadata:
|
||||
|
|
@ -130,8 +126,8 @@ class MetadataManager:
|
|||
all_builds = self.get_build_history()
|
||||
|
||||
for build in all_builds:
|
||||
build_id = build.get('build_id')
|
||||
timestamp = build.get('timestamp')
|
||||
build_id = build.get("build_id")
|
||||
timestamp = build.get("timestamp")
|
||||
|
||||
if timestamp:
|
||||
try:
|
||||
|
|
@ -145,17 +141,18 @@ class MetadataManager:
|
|||
|
||||
return deleted_count
|
||||
|
||||
def export_metadata(self, build_id: str, format: str = 'json') -> str:
|
||||
def export_metadata(self, build_id: str, format: str = "json") -> str:
|
||||
"""Export build metadata in specified format"""
|
||||
|
||||
metadata = self.get_build_info(build_id)
|
||||
if not metadata:
|
||||
raise MetadataError(f"Build {build_id} not found")
|
||||
|
||||
if format.lower() == 'json':
|
||||
if format.lower() == "json":
|
||||
return json.dumps(metadata, indent=2, default=str)
|
||||
elif format.lower() == 'yaml':
|
||||
elif format.lower() == "yaml":
|
||||
import yaml
|
||||
|
||||
return yaml.dump(metadata, default_flow_style=False)
|
||||
else:
|
||||
raise MetadataError(f"Unsupported export format: {format}")
|
||||
|
|
@ -173,26 +170,26 @@ class MetadataManager:
|
|||
build_index = {}
|
||||
if index_file.exists():
|
||||
try:
|
||||
with open(index_file, 'r') as f:
|
||||
with open(index_file, "r") as f:
|
||||
build_index = json.load(f)
|
||||
except Exception:
|
||||
build_index = {}
|
||||
|
||||
# Add new build to index
|
||||
build_index[build_id] = {
|
||||
'build_id': build_id,
|
||||
'source_package': metadata.get('source_package', ''),
|
||||
'timestamp': metadata.get('timestamp', ''),
|
||||
'build_success': metadata.get('build_success', False),
|
||||
'package_name': metadata.get('build_metadata', {}).get('package_name', ''),
|
||||
'package_version': metadata.get('build_metadata', {}).get('package_version', ''),
|
||||
'architecture': metadata.get('build_metadata', {}).get('architecture', ''),
|
||||
'suite': metadata.get('build_metadata', {}).get('suite', '')
|
||||
"build_id": build_id,
|
||||
"source_package": metadata.get("source_package", ""),
|
||||
"timestamp": metadata.get("timestamp", ""),
|
||||
"build_success": metadata.get("build_success", False),
|
||||
"package_name": metadata.get("build_metadata", {}).get("package_name", ""),
|
||||
"package_version": metadata.get("build_metadata", {}).get("package_version", ""),
|
||||
"architecture": metadata.get("build_metadata", {}).get("architecture", ""),
|
||||
"suite": metadata.get("build_metadata", {}).get("suite", ""),
|
||||
}
|
||||
|
||||
# Save updated index
|
||||
try:
|
||||
with open(index_file, 'w') as f:
|
||||
with open(index_file, "w") as f:
|
||||
json.dump(build_index, f, indent=2, default=str)
|
||||
except Exception as e:
|
||||
raise MetadataError(f"Failed to update build index: {e}")
|
||||
|
|
@ -206,7 +203,7 @@ class MetadataManager:
|
|||
return
|
||||
|
||||
try:
|
||||
with open(index_file, 'r') as f:
|
||||
with open(index_file, "r") as f:
|
||||
build_index = json.load(f)
|
||||
except Exception:
|
||||
return
|
||||
|
|
@ -215,7 +212,7 @@ class MetadataManager:
|
|||
del build_index[build_id]
|
||||
|
||||
try:
|
||||
with open(index_file, 'w') as f:
|
||||
with open(index_file, "w") as f:
|
||||
json.dump(build_index, f, indent=2, default=str)
|
||||
except Exception as e:
|
||||
raise MetadataError(f"Failed to update build index: {e}")
|
||||
|
|
@ -224,24 +221,24 @@ class MetadataManager:
|
|||
"""Check if a build matches the given criteria"""
|
||||
|
||||
for key, value in criteria.items():
|
||||
if key == 'package_name':
|
||||
build_package = build.get('build_metadata', {}).get('package_name', '')
|
||||
if key == "package_name":
|
||||
build_package = build.get("build_metadata", {}).get("package_name", "")
|
||||
if value.lower() not in build_package.lower():
|
||||
return False
|
||||
elif key == 'architecture':
|
||||
build_arch = build.get('build_metadata', {}).get('architecture', '')
|
||||
elif key == "architecture":
|
||||
build_arch = build.get("build_metadata", {}).get("architecture", "")
|
||||
if value.lower() != build_arch.lower():
|
||||
return False
|
||||
elif key == 'suite':
|
||||
build_suite = build.get('build_metadata', {}).get('suite', '')
|
||||
elif key == "suite":
|
||||
build_suite = build.get("build_metadata", {}).get("suite", "")
|
||||
if value.lower() != build_suite.lower():
|
||||
return False
|
||||
elif key == 'success':
|
||||
build_success = build.get('build_success', False)
|
||||
elif key == "success":
|
||||
build_success = build.get("build_success", False)
|
||||
if value != build_success:
|
||||
return False
|
||||
elif key == 'date_after':
|
||||
build_timestamp = build.get('timestamp', '')
|
||||
elif key == "date_after":
|
||||
build_timestamp = build.get("timestamp", "")
|
||||
if build_timestamp:
|
||||
try:
|
||||
build_time = datetime.fromisoformat(build_timestamp)
|
||||
|
|
@ -250,8 +247,8 @@ class MetadataManager:
|
|||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
elif key == 'date_before':
|
||||
build_timestamp = build.get('timestamp', '')
|
||||
elif key == "date_before":
|
||||
build_timestamp = build.get("timestamp", "")
|
||||
if build_timestamp:
|
||||
try:
|
||||
build_time = datetime.fromisoformat(build_timestamp)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ inspired by Fedora's Mock plugin architecture but adapted for Debian-based syste
|
|||
"""
|
||||
|
||||
from .hook_manager import HookManager
|
||||
from .base import BasePlugin
|
||||
from .registry import PluginRegistry
|
||||
|
||||
# Global hook manager instance
|
||||
|
|
@ -15,6 +14,7 @@ hook_manager = HookManager()
|
|||
# Global plugin registry
|
||||
plugin_registry = PluginRegistry()
|
||||
|
||||
|
||||
# Convenience function for plugins to register hooks
|
||||
def add_hook(hook_name: str, callback):
|
||||
"""
|
||||
|
|
@ -29,6 +29,7 @@ def add_hook(hook_name: str, callback):
|
|||
"""
|
||||
hook_manager.add_hook(hook_name, callback)
|
||||
|
||||
|
||||
# Convenience function to call hooks
|
||||
def call_hook(hook_name: str, context: dict = None):
|
||||
"""
|
||||
|
|
@ -40,6 +41,7 @@ def call_hook(hook_name: str, context: dict = None):
|
|||
"""
|
||||
hook_manager.call_hook(hook_name, context)
|
||||
|
||||
|
||||
# Convenience function to get available hooks
|
||||
def get_hook_names() -> list:
|
||||
"""
|
||||
|
|
@ -50,6 +52,7 @@ def get_hook_names() -> list:
|
|||
"""
|
||||
return hook_manager.get_hook_names()
|
||||
|
||||
|
||||
# Convenience function to register plugins
|
||||
def register_plugin(plugin_name: str, plugin_class):
|
||||
"""
|
||||
|
|
@ -61,6 +64,7 @@ def register_plugin(plugin_name: str, plugin_class):
|
|||
"""
|
||||
plugin_registry.register(plugin_name, plugin_class)
|
||||
|
||||
|
||||
# Convenience function to get registered plugins
|
||||
def get_registered_plugins() -> dict:
|
||||
"""
|
||||
|
|
@ -71,6 +75,7 @@ def get_registered_plugins() -> dict:
|
|||
"""
|
||||
return plugin_registry.get_plugins()
|
||||
|
||||
|
||||
# Convenience function to create plugin instances
|
||||
def create_plugin(plugin_name: str, config):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ inspired by Fedora's Mock plugin architecture but adapted for Debian-based syste
|
|||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
from typing import Any, Dict
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -48,15 +48,15 @@ class BasePlugin:
|
|||
Returns:
|
||||
True if plugin is enabled, False otherwise
|
||||
"""
|
||||
plugin_config = getattr(self.config, 'plugins', {})
|
||||
plugin_config = getattr(self.config, "plugins", {})
|
||||
plugin_name = self.plugin_name
|
||||
|
||||
# Check if plugin is explicitly enabled
|
||||
if plugin_name in plugin_config:
|
||||
return plugin_config[plugin_name].get('enabled', False)
|
||||
return plugin_config[plugin_name].get("enabled", False)
|
||||
|
||||
# Check if plugin is enabled via global plugin settings
|
||||
return getattr(self.config, 'enable_plugins', {}).get(plugin_name, False)
|
||||
return getattr(self.config, "enable_plugins", {}).get(plugin_name, False)
|
||||
|
||||
def _register_hooks(self):
|
||||
"""
|
||||
|
|
@ -74,7 +74,7 @@ class BasePlugin:
|
|||
Returns:
|
||||
Plugin configuration dictionary
|
||||
"""
|
||||
plugin_config = getattr(self.config, 'plugins', {})
|
||||
plugin_config = getattr(self.config, "plugins", {})
|
||||
return plugin_config.get(self.plugin_name, {})
|
||||
|
||||
def _log_info(self, message: str):
|
||||
|
|
@ -407,8 +407,8 @@ class BasePlugin:
|
|||
Dictionary with plugin information
|
||||
"""
|
||||
return {
|
||||
'name': self.plugin_name,
|
||||
'class': self.__class__.__name__,
|
||||
'enabled': self.enabled,
|
||||
'docstring': self.__class__.__doc__ or 'No documentation available'
|
||||
"name": self.plugin_name,
|
||||
"class": self.__class__.__name__,
|
||||
"enabled": self.enabled,
|
||||
"docstring": self.__class__.__doc__ or "No documentation available",
|
||||
}
|
||||
|
|
@ -5,11 +5,11 @@ This plugin allows mounting host directories into chroot environments,
|
|||
inspired by Fedora's Mock bind_mount plugin but adapted for Debian-based systems.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List, Tuple
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from .base import BasePlugin
|
||||
|
||||
|
|
@ -49,11 +49,11 @@ class BindMountPlugin(BasePlugin):
|
|||
mounts = []
|
||||
|
||||
# Get mounts from configuration
|
||||
if 'mounts' in plugin_config:
|
||||
for mount_config in plugin_config['mounts']:
|
||||
if "mounts" in plugin_config:
|
||||
for mount_config in plugin_config["mounts"]:
|
||||
if isinstance(mount_config, dict):
|
||||
host_path = mount_config.get('host_path')
|
||||
chroot_path = mount_config.get('chroot_path')
|
||||
host_path = mount_config.get("host_path")
|
||||
chroot_path = mount_config.get("chroot_path")
|
||||
elif isinstance(mount_config, (list, tuple)) and len(mount_config) >= 2:
|
||||
host_path = mount_config[0]
|
||||
chroot_path = mount_config[1]
|
||||
|
|
@ -65,8 +65,8 @@ class BindMountPlugin(BasePlugin):
|
|||
mounts.append((host_path, chroot_path))
|
||||
|
||||
# Legacy support for 'dirs' configuration (Mock compatibility)
|
||||
if 'dirs' in plugin_config:
|
||||
for host_path, chroot_path in plugin_config['dirs']:
|
||||
if "dirs" in plugin_config:
|
||||
for host_path, chroot_path in plugin_config["dirs"]:
|
||||
mounts.append((host_path, chroot_path))
|
||||
|
||||
return mounts
|
||||
|
|
@ -81,7 +81,7 @@ class BindMountPlugin(BasePlugin):
|
|||
if not self.enabled or not self.mounts:
|
||||
return
|
||||
|
||||
chroot_path = context.get('chroot_path')
|
||||
chroot_path = context.get("chroot_path")
|
||||
if not chroot_path:
|
||||
self._log_warning("No chroot_path in context, skipping bind mounts")
|
||||
return
|
||||
|
|
@ -104,7 +104,7 @@ class BindMountPlugin(BasePlugin):
|
|||
if not self.enabled or not self.mounts:
|
||||
return
|
||||
|
||||
chroot_path = context.get('chroot_path')
|
||||
chroot_path = context.get("chroot_path")
|
||||
if not chroot_path:
|
||||
self._log_warning("No chroot_path in context, skipping bind mount cleanup")
|
||||
return
|
||||
|
|
@ -132,7 +132,7 @@ class BindMountPlugin(BasePlugin):
|
|||
return
|
||||
|
||||
# Create full chroot mount path
|
||||
full_chroot_path = os.path.join(chroot_path, chroot_mount_path.lstrip('/'))
|
||||
full_chroot_path = os.path.join(chroot_path, chroot_mount_path.lstrip("/"))
|
||||
|
||||
# Create mount point directory if it doesn't exist
|
||||
mount_point_dir = os.path.dirname(full_chroot_path)
|
||||
|
|
@ -147,8 +147,8 @@ class BindMountPlugin(BasePlugin):
|
|||
|
||||
# Perform the bind mount
|
||||
try:
|
||||
cmd = ['mount', '--bind', host_path, full_chroot_path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
cmd = ["mount", "--bind", host_path, full_chroot_path]
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
self._log_debug(f"Successfully mounted {host_path} -> {full_chroot_path}")
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
|
|
@ -166,18 +166,18 @@ class BindMountPlugin(BasePlugin):
|
|||
chroot_mount_path: Path in the chroot that was mounted
|
||||
chroot_path: Base chroot path
|
||||
"""
|
||||
full_chroot_path = os.path.join(chroot_path, chroot_mount_path.lstrip('/'))
|
||||
full_chroot_path = os.path.join(chroot_path, chroot_mount_path.lstrip("/"))
|
||||
|
||||
try:
|
||||
cmd = ['umount', full_chroot_path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
cmd = ["umount", full_chroot_path]
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
self._log_debug(f"Successfully unmounted: {full_chroot_path}")
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
except subprocess.CalledProcessError:
|
||||
# Try force unmount if regular unmount fails
|
||||
try:
|
||||
cmd = ['umount', '-f', full_chroot_path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
cmd = ["umount", "-f", full_chroot_path]
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
self._log_debug(f"Successfully force unmounted: {full_chroot_path}")
|
||||
except subprocess.CalledProcessError as e2:
|
||||
self._log_warning(f"Failed to unmount {full_chroot_path}: {e2.stderr}")
|
||||
|
|
@ -194,13 +194,13 @@ class BindMountPlugin(BasePlugin):
|
|||
Returns:
|
||||
True if configuration is valid, False otherwise
|
||||
"""
|
||||
plugin_config = getattr(config, 'plugins', {}).get('bind_mount', {})
|
||||
plugin_config = getattr(config, "plugins", {}).get("bind_mount", {})
|
||||
|
||||
# Check mounts configuration
|
||||
if 'mounts' in plugin_config:
|
||||
for mount_config in plugin_config['mounts']:
|
||||
if "mounts" in plugin_config:
|
||||
for mount_config in plugin_config["mounts"]:
|
||||
if isinstance(mount_config, dict):
|
||||
if not all(key in mount_config for key in ['host_path', 'chroot_path']):
|
||||
if not all(key in mount_config for key in ["host_path", "chroot_path"]):
|
||||
self._log_error("Mount configuration missing required keys: host_path, chroot_path")
|
||||
return False
|
||||
elif isinstance(mount_config, (list, tuple)):
|
||||
|
|
@ -212,8 +212,8 @@ class BindMountPlugin(BasePlugin):
|
|||
return False
|
||||
|
||||
# Check dirs configuration (legacy)
|
||||
if 'dirs' in plugin_config:
|
||||
for host_path, chroot_path in plugin_config['dirs']:
|
||||
if "dirs" in plugin_config:
|
||||
for host_path, chroot_path in plugin_config["dirs"]:
|
||||
if not host_path or not chroot_path:
|
||||
self._log_error("Invalid dirs configuration: host_path and chroot_path must be non-empty")
|
||||
return False
|
||||
|
|
@ -228,9 +228,11 @@ class BindMountPlugin(BasePlugin):
|
|||
Dictionary with plugin information
|
||||
"""
|
||||
info = super().get_plugin_info()
|
||||
info.update({
|
||||
'mounts': self.mounts,
|
||||
'mount_count': len(self.mounts),
|
||||
'hooks': ['mount_root', 'postumount']
|
||||
})
|
||||
info.update(
|
||||
{
|
||||
"mounts": self.mounts,
|
||||
"mount_count": len(self.mounts),
|
||||
"hooks": ["mount_root", "postumount"],
|
||||
}
|
||||
)
|
||||
return info
|
||||
|
|
@ -5,11 +5,11 @@ This plugin compresses build logs to save disk space,
|
|||
inspired by Fedora's Mock compress_logs plugin but adapted for Debian-based systems.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from .base import BasePlugin
|
||||
|
||||
|
|
@ -45,12 +45,12 @@ class CompressLogsPlugin(BasePlugin):
|
|||
plugin_config = self._get_plugin_config()
|
||||
|
||||
return {
|
||||
'method': plugin_config.get('compression', 'gzip'),
|
||||
'level': plugin_config.get('level', 9),
|
||||
'extensions': plugin_config.get('extensions', ['.log']),
|
||||
'exclude_patterns': plugin_config.get('exclude_patterns', []),
|
||||
'min_size': plugin_config.get('min_size', 0), # Minimum file size to compress
|
||||
'command': plugin_config.get('command', None) # Custom compression command
|
||||
"method": plugin_config.get("compression", "gzip"),
|
||||
"level": plugin_config.get("level", 9),
|
||||
"extensions": plugin_config.get("extensions", [".log"]),
|
||||
"exclude_patterns": plugin_config.get("exclude_patterns", []),
|
||||
"min_size": plugin_config.get("min_size", 0), # Minimum file size to compress
|
||||
"command": plugin_config.get("command", None), # Custom compression command
|
||||
}
|
||||
|
||||
def process_logs(self, context: Dict[str, Any]) -> None:
|
||||
|
|
@ -63,7 +63,7 @@ class CompressLogsPlugin(BasePlugin):
|
|||
if not self.enabled:
|
||||
return
|
||||
|
||||
log_dir = context.get('log_dir')
|
||||
log_dir = context.get("log_dir")
|
||||
if not log_dir:
|
||||
self._log_warning("No log_dir in context, skipping log compression")
|
||||
return
|
||||
|
|
@ -82,7 +82,7 @@ class CompressLogsPlugin(BasePlugin):
|
|||
original_size = os.path.getsize(log_file)
|
||||
|
||||
# Check minimum size requirement
|
||||
if original_size < self.compression['min_size']:
|
||||
if original_size < self.compression["min_size"]:
|
||||
self._log_debug(f"Skipping {log_file} (size {original_size} < {self.compression['min_size']})")
|
||||
continue
|
||||
|
||||
|
|
@ -97,7 +97,9 @@ class CompressLogsPlugin(BasePlugin):
|
|||
compressed_count += 1
|
||||
size_saved = original_size - compressed_size
|
||||
total_size_saved += size_saved
|
||||
self._log_debug(f"Compressed {log_file}: {original_size} -> {compressed_size} bytes (saved {size_saved})")
|
||||
self._log_debug(
|
||||
f"Compressed {log_file}: {original_size} -> {compressed_size} bytes (saved {size_saved})"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self._log_error(f"Failed to compress {log_file}: {e}")
|
||||
|
|
@ -116,7 +118,7 @@ class CompressLogsPlugin(BasePlugin):
|
|||
"""
|
||||
log_files = []
|
||||
|
||||
for extension in self.compression['extensions']:
|
||||
for extension in self.compression["extensions"]:
|
||||
pattern = f"*{extension}"
|
||||
log_files.extend(Path(log_dir).glob(pattern))
|
||||
|
||||
|
|
@ -138,7 +140,7 @@ class CompressLogsPlugin(BasePlugin):
|
|||
Returns:
|
||||
True if file should be excluded, False otherwise
|
||||
"""
|
||||
for pattern in self.compression['exclude_patterns']:
|
||||
for pattern in self.compression["exclude_patterns"]:
|
||||
if pattern in filename:
|
||||
return True
|
||||
return False
|
||||
|
|
@ -153,7 +155,7 @@ class CompressLogsPlugin(BasePlugin):
|
|||
Returns:
|
||||
True if file is already compressed, False otherwise
|
||||
"""
|
||||
compressed_extensions = ['.gz', '.bz2', '.xz', '.lzma', '.zst']
|
||||
compressed_extensions = [".gz", ".bz2", ".xz", ".lzma", ".zst"]
|
||||
return any(file_path.endswith(ext) for ext in compressed_extensions)
|
||||
|
||||
def _compress_file(self, file_path: str) -> int:
|
||||
|
|
@ -166,21 +168,21 @@ class CompressLogsPlugin(BasePlugin):
|
|||
Returns:
|
||||
Size of the compressed file, or None if compression failed
|
||||
"""
|
||||
method = self.compression['method']
|
||||
level = self.compression['level']
|
||||
method = self.compression["method"]
|
||||
level = self.compression["level"]
|
||||
|
||||
# Use custom command if specified
|
||||
if self.compression['command']:
|
||||
if self.compression["command"]:
|
||||
return self._compress_with_custom_command(file_path)
|
||||
|
||||
# Use standard compression methods
|
||||
if method == 'gzip':
|
||||
if method == "gzip":
|
||||
return self._compress_gzip(file_path, level)
|
||||
elif method == 'bzip2':
|
||||
elif method == "bzip2":
|
||||
return self._compress_bzip2(file_path, level)
|
||||
elif method == 'xz':
|
||||
elif method == "xz":
|
||||
return self._compress_xz(file_path, level)
|
||||
elif method == 'zstd':
|
||||
elif method == "zstd":
|
||||
return self._compress_zstd(file_path, level)
|
||||
else:
|
||||
self._log_error(f"Unsupported compression method: {method}")
|
||||
|
|
@ -189,8 +191,8 @@ class CompressLogsPlugin(BasePlugin):
|
|||
def _compress_gzip(self, file_path: str, level: int) -> int:
|
||||
"""Compress file using gzip."""
|
||||
try:
|
||||
cmd = ['gzip', f'-{level}', file_path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
cmd = ["gzip", f"-{level}", file_path]
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
compressed_path = f"{file_path}.gz"
|
||||
return os.path.getsize(compressed_path) if os.path.exists(compressed_path) else None
|
||||
except subprocess.CalledProcessError as e:
|
||||
|
|
@ -200,8 +202,8 @@ class CompressLogsPlugin(BasePlugin):
|
|||
def _compress_bzip2(self, file_path: str, level: int) -> int:
|
||||
"""Compress file using bzip2."""
|
||||
try:
|
||||
cmd = ['bzip2', f'-{level}', file_path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
cmd = ["bzip2", f"-{level}", file_path]
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
compressed_path = f"{file_path}.bz2"
|
||||
return os.path.getsize(compressed_path) if os.path.exists(compressed_path) else None
|
||||
except subprocess.CalledProcessError as e:
|
||||
|
|
@ -211,8 +213,8 @@ class CompressLogsPlugin(BasePlugin):
|
|||
def _compress_xz(self, file_path: str, level: int) -> int:
|
||||
"""Compress file using xz."""
|
||||
try:
|
||||
cmd = ['xz', f'-{level}', file_path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
cmd = ["xz", f"-{level}", file_path]
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
compressed_path = f"{file_path}.xz"
|
||||
return os.path.getsize(compressed_path) if os.path.exists(compressed_path) else None
|
||||
except subprocess.CalledProcessError as e:
|
||||
|
|
@ -222,8 +224,8 @@ class CompressLogsPlugin(BasePlugin):
|
|||
def _compress_zstd(self, file_path: str, level: int) -> int:
|
||||
"""Compress file using zstd."""
|
||||
try:
|
||||
cmd = ['zstd', f'-{level}', file_path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
cmd = ["zstd", f"-{level}", file_path]
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
compressed_path = f"{file_path}.zst"
|
||||
return os.path.getsize(compressed_path) if os.path.exists(compressed_path) else None
|
||||
except subprocess.CalledProcessError as e:
|
||||
|
|
@ -233,12 +235,12 @@ class CompressLogsPlugin(BasePlugin):
|
|||
def _compress_with_custom_command(self, file_path: str) -> int:
|
||||
"""Compress file using custom command."""
|
||||
try:
|
||||
command = self.compression['command'].format(file=file_path)
|
||||
result = subprocess.run(command, shell=True, capture_output=True, text=True, check=True)
|
||||
command = self.compression["command"].format(file=file_path)
|
||||
subprocess.run(command, shell=True, capture_output=True, text=True, check=True)
|
||||
|
||||
# Try to determine compressed file size
|
||||
# This is a best-effort approach since custom commands may vary
|
||||
for ext in ['.gz', '.bz2', '.xz', '.zst', '.lzma']:
|
||||
for ext in [".gz", ".bz2", ".xz", ".zst", ".lzma"]:
|
||||
compressed_path = f"{file_path}{ext}"
|
||||
if os.path.exists(compressed_path):
|
||||
return os.path.getsize(compressed_path)
|
||||
|
|
@ -258,29 +260,29 @@ class CompressLogsPlugin(BasePlugin):
|
|||
Returns:
|
||||
True if configuration is valid, False otherwise
|
||||
"""
|
||||
plugin_config = getattr(config, 'plugins', {}).get('compress_logs', {})
|
||||
plugin_config = getattr(config, "plugins", {}).get("compress_logs", {})
|
||||
|
||||
# Validate compression method
|
||||
valid_methods = ['gzip', 'bzip2', 'xz', 'zstd']
|
||||
method = plugin_config.get('compression', 'gzip')
|
||||
if method not in valid_methods and not plugin_config.get('command'):
|
||||
valid_methods = ["gzip", "bzip2", "xz", "zstd"]
|
||||
method = plugin_config.get("compression", "gzip")
|
||||
if method not in valid_methods and not plugin_config.get("command"):
|
||||
self._log_error(f"Invalid compression method: {method}. Valid methods: {valid_methods}")
|
||||
return False
|
||||
|
||||
# Validate compression level
|
||||
level = plugin_config.get('level', 9)
|
||||
level = plugin_config.get("level", 9)
|
||||
if not isinstance(level, int) or level < 1 or level > 9:
|
||||
self._log_error(f"Invalid compression level: {level}. Must be 1-9")
|
||||
return False
|
||||
|
||||
# Validate extensions
|
||||
extensions = plugin_config.get('extensions', ['.log'])
|
||||
extensions = plugin_config.get("extensions", [".log"])
|
||||
if not isinstance(extensions, list):
|
||||
self._log_error("Extensions must be a list")
|
||||
return False
|
||||
|
||||
# Validate min_size
|
||||
min_size = plugin_config.get('min_size', 0)
|
||||
min_size = plugin_config.get("min_size", 0)
|
||||
if not isinstance(min_size, int) or min_size < 0:
|
||||
self._log_error(f"Invalid min_size: {min_size}. Must be non-negative integer")
|
||||
return False
|
||||
|
|
@ -295,11 +297,13 @@ class CompressLogsPlugin(BasePlugin):
|
|||
Dictionary with plugin information
|
||||
"""
|
||||
info = super().get_plugin_info()
|
||||
info.update({
|
||||
'compression_method': self.compression['method'],
|
||||
'compression_level': self.compression['level'],
|
||||
'extensions': self.compression['extensions'],
|
||||
'min_size': self.compression['min_size'],
|
||||
'hooks': ['process_logs']
|
||||
})
|
||||
info.update(
|
||||
{
|
||||
"compression_method": self.compression["method"],
|
||||
"compression_level": self.compression["level"],
|
||||
"extensions": self.compression["extensions"],
|
||||
"min_size": self.compression["min_size"],
|
||||
"hooks": ["process_logs"],
|
||||
}
|
||||
)
|
||||
return info
|
||||
|
|
@ -6,7 +6,7 @@ inspired by Fedora's Mock plugin hooks but adapted for Debian-based workflows.
|
|||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Callable, Any, Optional
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -27,30 +27,30 @@ class HookManager:
|
|||
|
||||
# Define available hook points (based on Mock's hook system)
|
||||
self.available_hooks = {
|
||||
'clean': 'Clean up plugin resources',
|
||||
'earlyprebuild': 'Very early build stage',
|
||||
'initfailed': 'Chroot initialization failed',
|
||||
'list_snapshots': 'List available snapshots',
|
||||
'make_snapshot': 'Create a snapshot',
|
||||
'mount_root': 'Mount chroot directory',
|
||||
'postbuild': 'After build completion',
|
||||
'postchroot': 'After chroot command',
|
||||
'postclean': 'After chroot cleanup',
|
||||
'postdeps': 'After dependency installation',
|
||||
'postinit': 'After chroot initialization',
|
||||
'postshell': 'After shell exit',
|
||||
'postupdate': 'After package updates',
|
||||
'postumount': 'After unmounting',
|
||||
'postapt': 'After APT operations',
|
||||
'prebuild': 'Before build starts',
|
||||
'prechroot': 'Before chroot command',
|
||||
'preinit': 'Before chroot initialization',
|
||||
'preshell': 'Before shell prompt',
|
||||
'preapt': 'Before APT operations',
|
||||
'process_logs': 'Process build logs',
|
||||
'remove_snapshot': 'Remove snapshot',
|
||||
'rollback_to': 'Rollback to snapshot',
|
||||
'scrub': 'Scrub chroot'
|
||||
"clean": "Clean up plugin resources",
|
||||
"earlyprebuild": "Very early build stage",
|
||||
"initfailed": "Chroot initialization failed",
|
||||
"list_snapshots": "List available snapshots",
|
||||
"make_snapshot": "Create a snapshot",
|
||||
"mount_root": "Mount chroot directory",
|
||||
"postbuild": "After build completion",
|
||||
"postchroot": "After chroot command",
|
||||
"postclean": "After chroot cleanup",
|
||||
"postdeps": "After dependency installation",
|
||||
"postinit": "After chroot initialization",
|
||||
"postshell": "After shell exit",
|
||||
"postupdate": "After package updates",
|
||||
"postumount": "After unmounting",
|
||||
"postapt": "After APT operations",
|
||||
"prebuild": "Before build starts",
|
||||
"prechroot": "Before chroot command",
|
||||
"preinit": "Before chroot initialization",
|
||||
"preshell": "Before shell prompt",
|
||||
"preapt": "Before APT operations",
|
||||
"process_logs": "Process build logs",
|
||||
"remove_snapshot": "Remove snapshot",
|
||||
"rollback_to": "Rollback to snapshot",
|
||||
"scrub": "Scrub chroot",
|
||||
}
|
||||
|
||||
def add_hook(self, hook_name: str, callback: Callable) -> None:
|
||||
|
|
@ -157,21 +157,18 @@ class HookManager:
|
|||
Dictionary with hook information
|
||||
"""
|
||||
if hook_name not in self.available_hooks:
|
||||
return {'error': f'Hook "{hook_name}" not found'}
|
||||
return {"error": f'Hook "{hook_name}" not found'}
|
||||
|
||||
info = {
|
||||
'name': hook_name,
|
||||
'description': self.available_hooks[hook_name],
|
||||
'registered_callbacks': len(self.hooks.get(hook_name, [])),
|
||||
'callbacks': []
|
||||
"name": hook_name,
|
||||
"description": self.available_hooks[hook_name],
|
||||
"registered_callbacks": len(self.hooks.get(hook_name, [])),
|
||||
"callbacks": [],
|
||||
}
|
||||
|
||||
if hook_name in self.hooks:
|
||||
for callback in self.hooks[hook_name]:
|
||||
info['callbacks'].append({
|
||||
'name': callback.__name__,
|
||||
'module': callback.__module__
|
||||
})
|
||||
info["callbacks"].append({"name": callback.__name__, "module": callback.__module__})
|
||||
|
||||
return info
|
||||
|
||||
|
|
@ -218,18 +215,18 @@ class HookManager:
|
|||
Dictionary with hook statistics
|
||||
"""
|
||||
stats = {
|
||||
'total_hooks': len(self.hooks),
|
||||
'total_callbacks': sum(len(callbacks) for callbacks in self.hooks.values()),
|
||||
'hooks_with_callbacks': len([h for h in self.hooks.values() if h]),
|
||||
'available_hooks': len(self.available_hooks),
|
||||
'hook_details': {}
|
||||
"total_hooks": len(self.hooks),
|
||||
"total_callbacks": sum(len(callbacks) for callbacks in self.hooks.values()),
|
||||
"hooks_with_callbacks": len([h for h in self.hooks.values() if h]),
|
||||
"available_hooks": len(self.available_hooks),
|
||||
"hook_details": {},
|
||||
}
|
||||
|
||||
for hook_name in self.available_hooks:
|
||||
stats['hook_details'][hook_name] = {
|
||||
'description': self.available_hooks[hook_name],
|
||||
'registered': hook_name in self.hooks,
|
||||
'callback_count': len(self.hooks.get(hook_name, []))
|
||||
stats["hook_details"][hook_name] = {
|
||||
"description": self.available_hooks[hook_name],
|
||||
"registered": hook_name in self.hooks,
|
||||
"callback_count": len(self.hooks.get(hook_name, [])),
|
||||
}
|
||||
|
||||
return stats
|
||||
|
|
@ -256,5 +253,4 @@ class HookManager:
|
|||
Returns:
|
||||
List of matching hook names
|
||||
"""
|
||||
return [name for name in self.available_hooks.keys()
|
||||
if name.startswith(partial_name)]
|
||||
return [name for name in self.available_hooks.keys() if name.startswith(partial_name)]
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ This module provides the plugin registration and management functionality
|
|||
for the Deb-Mock plugin system, inspired by Fedora's Mock plugin architecture.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import importlib
|
||||
from typing import Dict, Type, Any, Optional
|
||||
import logging
|
||||
from typing import Any, Dict, Optional, Type
|
||||
|
||||
from .base import BasePlugin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -29,8 +30,12 @@ class PluginRegistry:
|
|||
# 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 register(
|
||||
self,
|
||||
plugin_name: str,
|
||||
plugin_class: Type[BasePlugin],
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Register a plugin class.
|
||||
|
||||
|
|
@ -44,7 +49,7 @@ class PluginRegistry:
|
|||
TypeError: If plugin_class is not a subclass of BasePlugin
|
||||
"""
|
||||
if not issubclass(plugin_class, BasePlugin):
|
||||
raise TypeError(f"Plugin class must inherit from BasePlugin")
|
||||
raise TypeError("Plugin class must inherit from BasePlugin")
|
||||
|
||||
if plugin_name in self.plugins:
|
||||
raise ValueError(f"Plugin '{plugin_name}' is already registered")
|
||||
|
|
@ -160,17 +165,17 @@ class PluginRegistry:
|
|||
Dictionary with plugin information
|
||||
"""
|
||||
if plugin_name not in self.plugins:
|
||||
return {'error': f'Plugin "{plugin_name}" not found'}
|
||||
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'
|
||||
"name": plugin_name,
|
||||
"class": plugin_class.__name__,
|
||||
"module": plugin_class.__module__,
|
||||
"metadata": metadata,
|
||||
"docstring": plugin_class.__doc__ or "No documentation available",
|
||||
}
|
||||
|
||||
return info
|
||||
|
|
@ -225,21 +230,21 @@ class PluginRegistry:
|
|||
"""
|
||||
loaded_plugins = {}
|
||||
|
||||
if not hasattr(config, 'plugins') or not config.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):
|
||||
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')
|
||||
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)
|
||||
|
|
@ -257,29 +262,51 @@ class PluginRegistry:
|
|||
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(
|
||||
"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(
|
||||
"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(
|
||||
"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
|
||||
})
|
||||
self.register(
|
||||
"tmpfs",
|
||||
TmpfsPlugin,
|
||||
{
|
||||
"description": "Use tmpfs for faster I/O operations",
|
||||
"hooks": ["mount_root", "postumount"],
|
||||
"builtin": True,
|
||||
},
|
||||
)
|
||||
|
||||
logger.debug("Registered built-in plugins")
|
||||
|
||||
|
|
@ -296,19 +323,19 @@ class PluginRegistry:
|
|||
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),
|
||||
"builtin_plugins": len([p for p in self.plugin_metadata.values() if p.get("builtin", False)]),
|
||||
"external_plugins": len([p for p in self.plugin_metadata.values() if not p.get("builtin", False)]),
|
||||
"plugins_by_hook": {},
|
||||
}
|
||||
|
||||
# Count plugins by hook usage
|
||||
for plugin_name, metadata in self.plugin_metadata.items():
|
||||
hooks = metadata.get('hooks', [])
|
||||
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)
|
||||
if hook not in stats["plugins_by_hook"]:
|
||||
stats["plugins_by_hook"][hook] = []
|
||||
stats["plugins_by_hook"][hook].append(plugin_name)
|
||||
|
||||
return stats
|
||||
|
||||
|
|
@ -328,7 +355,7 @@ class PluginRegistry:
|
|||
|
||||
# Basic validation - plugins can override this method
|
||||
plugin_class = self.plugins[plugin_name]
|
||||
if hasattr(plugin_class, 'validate_config'):
|
||||
if hasattr(plugin_class, "validate_config"):
|
||||
return plugin_class.validate_config(config)
|
||||
|
||||
return True
|
||||
|
|
@ -5,14 +5,11 @@ This plugin provides root cache management for faster builds,
|
|||
inspired by Fedora's Mock root_cache plugin but adapted for Debian-based systems.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tarfile
|
||||
import hashlib
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional
|
||||
from typing import Any, Dict
|
||||
|
||||
from .base import BasePlugin
|
||||
|
||||
|
|
@ -54,13 +51,13 @@ class RootCachePlugin(BasePlugin):
|
|||
plugin_config = self._get_plugin_config()
|
||||
|
||||
return {
|
||||
'cache_dir': plugin_config.get('cache_dir', '/var/cache/deb-mock/root-cache'),
|
||||
'max_age_days': plugin_config.get('max_age_days', 7),
|
||||
'compression': plugin_config.get('compression', 'gzip'),
|
||||
'exclude_dirs': plugin_config.get('exclude_dirs', ['/tmp', '/var/tmp', '/var/cache']),
|
||||
'exclude_patterns': plugin_config.get('exclude_patterns', ['*.log', '*.tmp']),
|
||||
'min_cache_size_mb': plugin_config.get('min_cache_size_mb', 100),
|
||||
'auto_cleanup': plugin_config.get('auto_cleanup', True)
|
||||
"cache_dir": plugin_config.get("cache_dir", "/var/cache/deb-mock/root-cache"),
|
||||
"max_age_days": plugin_config.get("max_age_days", 7),
|
||||
"compression": plugin_config.get("compression", "gzip"),
|
||||
"exclude_dirs": plugin_config.get("exclude_dirs", ["/tmp", "/var/tmp", "/var/cache"]),
|
||||
"exclude_patterns": plugin_config.get("exclude_patterns", ["*.log", "*.tmp"]),
|
||||
"min_cache_size_mb": plugin_config.get("min_cache_size_mb", 100),
|
||||
"auto_cleanup": plugin_config.get("auto_cleanup", True),
|
||||
}
|
||||
|
||||
def _get_cache_file_path(self) -> str:
|
||||
|
|
@ -70,20 +67,20 @@ class RootCachePlugin(BasePlugin):
|
|||
Returns:
|
||||
Path to the cache file
|
||||
"""
|
||||
cache_dir = self.cache_settings['cache_dir']
|
||||
compression = self.cache_settings['compression']
|
||||
cache_dir = self.cache_settings["cache_dir"]
|
||||
compression = self.cache_settings["compression"]
|
||||
|
||||
# Create cache directory if it doesn't exist
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
|
||||
# Determine file extension based on compression
|
||||
extensions = {
|
||||
'gzip': '.tar.gz',
|
||||
'bzip2': '.tar.bz2',
|
||||
'xz': '.tar.xz',
|
||||
'zstd': '.tar.zst'
|
||||
"gzip": ".tar.gz",
|
||||
"bzip2": ".tar.bz2",
|
||||
"xz": ".tar.xz",
|
||||
"zstd": ".tar.zst",
|
||||
}
|
||||
ext = extensions.get(compression, '.tar.gz')
|
||||
ext = extensions.get(compression, ".tar.gz")
|
||||
|
||||
return os.path.join(cache_dir, f"cache{ext}")
|
||||
|
||||
|
|
@ -97,7 +94,7 @@ class RootCachePlugin(BasePlugin):
|
|||
if not self.enabled:
|
||||
return
|
||||
|
||||
chroot_path = context.get('chroot_path')
|
||||
chroot_path = context.get("chroot_path")
|
||||
if not chroot_path:
|
||||
self._log_warning("No chroot_path in context, skipping cache restoration")
|
||||
return
|
||||
|
|
@ -128,7 +125,7 @@ class RootCachePlugin(BasePlugin):
|
|||
if not self.enabled:
|
||||
return
|
||||
|
||||
chroot_path = context.get('chroot_path')
|
||||
chroot_path = context.get("chroot_path")
|
||||
if not chroot_path:
|
||||
self._log_warning("No chroot_path in context, skipping cache creation")
|
||||
return
|
||||
|
|
@ -151,7 +148,7 @@ class RootCachePlugin(BasePlugin):
|
|||
if not self.enabled:
|
||||
return
|
||||
|
||||
chroot_path = context.get('chroot_path')
|
||||
chroot_path = context.get("chroot_path")
|
||||
if not chroot_path:
|
||||
return
|
||||
|
||||
|
|
@ -172,7 +169,7 @@ class RootCachePlugin(BasePlugin):
|
|||
if not self.enabled:
|
||||
return
|
||||
|
||||
chroot_path = context.get('chroot_path')
|
||||
chroot_path = context.get("chroot_path")
|
||||
if not chroot_path:
|
||||
return
|
||||
|
||||
|
|
@ -193,7 +190,7 @@ class RootCachePlugin(BasePlugin):
|
|||
if not self.enabled:
|
||||
return
|
||||
|
||||
if self.cache_settings['auto_cleanup']:
|
||||
if self.cache_settings["auto_cleanup"]:
|
||||
self._log_info("Cleaning up old caches")
|
||||
|
||||
try:
|
||||
|
|
@ -223,7 +220,7 @@ class RootCachePlugin(BasePlugin):
|
|||
|
||||
# Check file age
|
||||
file_age = time.time() - os.path.getmtime(self.cache_file)
|
||||
max_age_seconds = self.cache_settings['max_age_days'] * 24 * 3600
|
||||
max_age_seconds = self.cache_settings["max_age_days"] * 24 * 3600
|
||||
|
||||
if file_age > max_age_seconds:
|
||||
self._log_debug(f"Cache is {file_age / 3600:.1f} hours old, max age is {max_age_seconds / 3600:.1f} hours")
|
||||
|
|
@ -231,7 +228,7 @@ class RootCachePlugin(BasePlugin):
|
|||
|
||||
# Check file size
|
||||
file_size_mb = os.path.getsize(self.cache_file) / (1024 * 1024)
|
||||
min_size_mb = self.cache_settings['min_cache_size_mb']
|
||||
min_size_mb = self.cache_settings["min_cache_size_mb"]
|
||||
|
||||
if file_size_mb < min_size_mb:
|
||||
self._log_debug(f"Cache size {file_size_mb:.1f}MB is below minimum {min_size_mb}MB")
|
||||
|
|
@ -253,18 +250,18 @@ class RootCachePlugin(BasePlugin):
|
|||
os.makedirs(chroot_path, exist_ok=True)
|
||||
|
||||
# Extract cache
|
||||
compression = self.cache_settings['compression']
|
||||
compression = self.cache_settings["compression"]
|
||||
|
||||
if compression == 'gzip':
|
||||
mode = 'r:gz'
|
||||
elif compression == 'bzip2':
|
||||
mode = 'r:bz2'
|
||||
elif compression == 'xz':
|
||||
mode = 'r:xz'
|
||||
elif compression == 'zstd':
|
||||
mode = 'r:zstd'
|
||||
if compression == "gzip":
|
||||
mode = "r:gz"
|
||||
elif compression == "bzip2":
|
||||
mode = "r:bz2"
|
||||
elif compression == "xz":
|
||||
mode = "r:xz"
|
||||
elif compression == "zstd":
|
||||
mode = "r:zstd"
|
||||
else:
|
||||
mode = 'r:gz' # Default to gzip
|
||||
mode = "r:gz" # Default to gzip
|
||||
|
||||
try:
|
||||
with tarfile.open(self.cache_file, mode) as tar:
|
||||
|
|
@ -287,23 +284,23 @@ class RootCachePlugin(BasePlugin):
|
|||
raise FileNotFoundError(f"Chroot path does not exist: {chroot_path}")
|
||||
|
||||
# Determine compression mode
|
||||
compression = self.cache_settings['compression']
|
||||
compression = self.cache_settings["compression"]
|
||||
|
||||
if compression == 'gzip':
|
||||
mode = 'w:gz'
|
||||
elif compression == 'bzip2':
|
||||
mode = 'w:bz2'
|
||||
elif compression == 'xz':
|
||||
mode = 'w:xz'
|
||||
elif compression == 'zstd':
|
||||
mode = 'w:zstd'
|
||||
if compression == "gzip":
|
||||
mode = "w:gz"
|
||||
elif compression == "bzip2":
|
||||
mode = "w:bz2"
|
||||
elif compression == "xz":
|
||||
mode = "w:xz"
|
||||
elif compression == "zstd":
|
||||
mode = "w:zstd"
|
||||
else:
|
||||
mode = 'w:gz' # Default to gzip
|
||||
mode = "w:gz" # Default to gzip
|
||||
|
||||
try:
|
||||
with tarfile.open(self.cache_file, mode) as tar:
|
||||
# Add chroot contents to archive
|
||||
tar.add(chroot_path, arcname='', exclude=self._get_exclude_filter())
|
||||
tar.add(chroot_path, arcname="", exclude=self._get_exclude_filter())
|
||||
|
||||
self._log_debug(f"Successfully created cache: {self.cache_file}")
|
||||
|
||||
|
|
@ -329,8 +326,8 @@ class RootCachePlugin(BasePlugin):
|
|||
Returns:
|
||||
Number of cache files cleaned up
|
||||
"""
|
||||
cache_dir = self.cache_settings['cache_dir']
|
||||
max_age_seconds = self.cache_settings['max_age_days'] * 24 * 3600
|
||||
cache_dir = self.cache_settings["cache_dir"]
|
||||
max_age_seconds = self.cache_settings["max_age_days"] * 24 * 3600
|
||||
current_time = time.time()
|
||||
cleaned_count = 0
|
||||
|
||||
|
|
@ -338,7 +335,7 @@ class RootCachePlugin(BasePlugin):
|
|||
return 0
|
||||
|
||||
for cache_file in os.listdir(cache_dir):
|
||||
if not cache_file.startswith('cache'):
|
||||
if not cache_file.startswith("cache"):
|
||||
continue
|
||||
|
||||
cache_path = os.path.join(cache_dir, cache_file)
|
||||
|
|
@ -361,13 +358,13 @@ class RootCachePlugin(BasePlugin):
|
|||
Returns:
|
||||
Function to filter out excluded files/directories
|
||||
"""
|
||||
exclude_dirs = self.cache_settings['exclude_dirs']
|
||||
exclude_patterns = self.cache_settings['exclude_patterns']
|
||||
exclude_dirs = self.cache_settings["exclude_dirs"]
|
||||
exclude_patterns = self.cache_settings["exclude_patterns"]
|
||||
|
||||
def exclude_filter(tarinfo):
|
||||
# Check excluded directories
|
||||
for exclude_dir in exclude_dirs:
|
||||
if tarinfo.name.startswith(exclude_dir.lstrip('/')):
|
||||
if tarinfo.name.startswith(exclude_dir.lstrip("/")):
|
||||
return None
|
||||
|
||||
# Check excluded patterns
|
||||
|
|
@ -389,47 +386,47 @@ class RootCachePlugin(BasePlugin):
|
|||
Returns:
|
||||
True if configuration is valid, False otherwise
|
||||
"""
|
||||
plugin_config = getattr(config, 'plugins', {}).get('root_cache', {})
|
||||
plugin_config = getattr(config, "plugins", {}).get("root_cache", {})
|
||||
|
||||
# Validate cache_dir
|
||||
cache_dir = plugin_config.get('cache_dir', '/var/cache/deb-mock/root-cache')
|
||||
cache_dir = plugin_config.get("cache_dir", "/var/cache/deb-mock/root-cache")
|
||||
if not cache_dir:
|
||||
self._log_error("cache_dir cannot be empty")
|
||||
return False
|
||||
|
||||
# Validate max_age_days
|
||||
max_age_days = plugin_config.get('max_age_days', 7)
|
||||
max_age_days = plugin_config.get("max_age_days", 7)
|
||||
if not isinstance(max_age_days, int) or max_age_days <= 0:
|
||||
self._log_error(f"Invalid max_age_days: {max_age_days}. Must be positive integer")
|
||||
return False
|
||||
|
||||
# Validate compression
|
||||
valid_compressions = ['gzip', 'bzip2', 'xz', 'zstd']
|
||||
compression = plugin_config.get('compression', 'gzip')
|
||||
valid_compressions = ["gzip", "bzip2", "xz", "zstd"]
|
||||
compression = plugin_config.get("compression", "gzip")
|
||||
if compression not in valid_compressions:
|
||||
self._log_error(f"Invalid compression: {compression}. Valid options: {valid_compressions}")
|
||||
return False
|
||||
|
||||
# Validate exclude_dirs
|
||||
exclude_dirs = plugin_config.get('exclude_dirs', ['/tmp', '/var/tmp', '/var/cache'])
|
||||
exclude_dirs = plugin_config.get("exclude_dirs", ["/tmp", "/var/tmp", "/var/cache"])
|
||||
if not isinstance(exclude_dirs, list):
|
||||
self._log_error("exclude_dirs must be a list")
|
||||
return False
|
||||
|
||||
# Validate exclude_patterns
|
||||
exclude_patterns = plugin_config.get('exclude_patterns', ['*.log', '*.tmp'])
|
||||
exclude_patterns = plugin_config.get("exclude_patterns", ["*.log", "*.tmp"])
|
||||
if not isinstance(exclude_patterns, list):
|
||||
self._log_error("exclude_patterns must be a list")
|
||||
return False
|
||||
|
||||
# Validate min_cache_size_mb
|
||||
min_cache_size_mb = plugin_config.get('min_cache_size_mb', 100)
|
||||
min_cache_size_mb = plugin_config.get("min_cache_size_mb", 100)
|
||||
if not isinstance(min_cache_size_mb, (int, float)) or min_cache_size_mb < 0:
|
||||
self._log_error(f"Invalid min_cache_size_mb: {min_cache_size_mb}. Must be non-negative number")
|
||||
return False
|
||||
|
||||
# Validate auto_cleanup
|
||||
auto_cleanup = plugin_config.get('auto_cleanup', True)
|
||||
auto_cleanup = plugin_config.get("auto_cleanup", True)
|
||||
if not isinstance(auto_cleanup, bool):
|
||||
self._log_error(f"Invalid auto_cleanup: {auto_cleanup}. Must be boolean")
|
||||
return False
|
||||
|
|
@ -444,17 +441,19 @@ class RootCachePlugin(BasePlugin):
|
|||
Dictionary with plugin information
|
||||
"""
|
||||
info = super().get_plugin_info()
|
||||
info.update({
|
||||
'cache_dir': self.cache_settings['cache_dir'],
|
||||
'cache_file': self.cache_file,
|
||||
'max_age_days': self.cache_settings['max_age_days'],
|
||||
'compression': self.cache_settings['compression'],
|
||||
'exclude_dirs': self.cache_settings['exclude_dirs'],
|
||||
'exclude_patterns': self.cache_settings['exclude_patterns'],
|
||||
'min_cache_size_mb': self.cache_settings['min_cache_size_mb'],
|
||||
'auto_cleanup': self.cache_settings['auto_cleanup'],
|
||||
'cache_exists': self._cache_exists(),
|
||||
'cache_valid': self._is_cache_valid() if self._cache_exists() else False,
|
||||
'hooks': ['preinit', 'postinit', 'postchroot', 'postshell', 'clean']
|
||||
})
|
||||
info.update(
|
||||
{
|
||||
"cache_dir": self.cache_settings["cache_dir"],
|
||||
"cache_file": self.cache_file,
|
||||
"max_age_days": self.cache_settings["max_age_days"],
|
||||
"compression": self.cache_settings["compression"],
|
||||
"exclude_dirs": self.cache_settings["exclude_dirs"],
|
||||
"exclude_patterns": self.cache_settings["exclude_patterns"],
|
||||
"min_cache_size_mb": self.cache_settings["min_cache_size_mb"],
|
||||
"auto_cleanup": self.cache_settings["auto_cleanup"],
|
||||
"cache_exists": self._cache_exists(),
|
||||
"cache_valid": (self._is_cache_valid() if self._cache_exists() else False),
|
||||
"hooks": ["preinit", "postinit", "postchroot", "postshell", "clean"],
|
||||
}
|
||||
)
|
||||
return info
|
||||
|
|
@ -5,10 +5,9 @@ This plugin uses tmpfs for faster I/O operations in chroot,
|
|||
inspired by Fedora's Mock tmpfs plugin but adapted for Debian-based systems.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
import subprocess
|
||||
from typing import Any, Dict
|
||||
|
||||
from .base import BasePlugin
|
||||
|
||||
|
|
@ -47,12 +46,12 @@ class TmpfsPlugin(BasePlugin):
|
|||
plugin_config = self._get_plugin_config()
|
||||
|
||||
return {
|
||||
'size': plugin_config.get('size', '2G'),
|
||||
'mode': plugin_config.get('mode', '0755'),
|
||||
'mount_point': plugin_config.get('mount_point', '/tmp'),
|
||||
'keep_mounted': plugin_config.get('keep_mounted', False),
|
||||
'required_ram_mb': plugin_config.get('required_ram_mb', 2048), # 2GB default
|
||||
'max_fs_size': plugin_config.get('max_fs_size', None)
|
||||
"size": plugin_config.get("size", "2G"),
|
||||
"mode": plugin_config.get("mode", "0755"),
|
||||
"mount_point": plugin_config.get("mount_point", "/tmp"),
|
||||
"keep_mounted": plugin_config.get("keep_mounted", False),
|
||||
"required_ram_mb": plugin_config.get("required_ram_mb", 2048), # 2GB default
|
||||
"max_fs_size": plugin_config.get("max_fs_size", None),
|
||||
}
|
||||
|
||||
def mount_root(self, context: Dict[str, Any]) -> None:
|
||||
|
|
@ -65,7 +64,7 @@ class TmpfsPlugin(BasePlugin):
|
|||
if not self.enabled:
|
||||
return
|
||||
|
||||
chroot_path = context.get('chroot_path')
|
||||
chroot_path = context.get("chroot_path")
|
||||
if not chroot_path:
|
||||
self._log_warning("No chroot_path in context, skipping tmpfs mount")
|
||||
return
|
||||
|
|
@ -101,13 +100,13 @@ class TmpfsPlugin(BasePlugin):
|
|||
if not self.enabled or not self.mounted:
|
||||
return
|
||||
|
||||
chroot_path = context.get('chroot_path')
|
||||
chroot_path = context.get("chroot_path")
|
||||
if not chroot_path:
|
||||
self._log_warning("No chroot_path in context, skipping tmpfs unmount")
|
||||
return
|
||||
|
||||
# Check if we should keep mounted
|
||||
if self.tmpfs_settings['keep_mounted']:
|
||||
if self.tmpfs_settings["keep_mounted"]:
|
||||
self._log_info("Keeping tmpfs mounted as requested")
|
||||
return
|
||||
|
||||
|
|
@ -129,9 +128,9 @@ class TmpfsPlugin(BasePlugin):
|
|||
"""
|
||||
try:
|
||||
# Get system RAM in MB
|
||||
with open('/proc/meminfo', 'r') as f:
|
||||
with open("/proc/meminfo", "r") as f:
|
||||
for line in f:
|
||||
if line.startswith('MemTotal:'):
|
||||
if line.startswith("MemTotal:"):
|
||||
mem_total_kb = int(line.split()[1])
|
||||
mem_total_mb = mem_total_kb // 1024
|
||||
break
|
||||
|
|
@ -139,12 +138,10 @@ class TmpfsPlugin(BasePlugin):
|
|||
self._log_warning("Could not determine system RAM")
|
||||
return False
|
||||
|
||||
required_ram = self.tmpfs_settings['required_ram_mb']
|
||||
required_ram = self.tmpfs_settings["required_ram_mb"]
|
||||
|
||||
if mem_total_mb < required_ram:
|
||||
self._log_warning(
|
||||
f"System has {mem_total_mb}MB RAM, but {required_ram}MB is required for tmpfs"
|
||||
)
|
||||
self._log_warning(f"System has {mem_total_mb}MB RAM, but {required_ram}MB is required for tmpfs")
|
||||
return False
|
||||
|
||||
self._log_debug(f"System RAM: {mem_total_mb}MB, required: {required_ram}MB")
|
||||
|
|
@ -166,20 +163,16 @@ class TmpfsPlugin(BasePlugin):
|
|||
"""
|
||||
try:
|
||||
# Check if the path is a mount point
|
||||
result = subprocess.run(
|
||||
['mountpoint', '-q', chroot_path],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
result = subprocess.run(["mountpoint", "-q", chroot_path], capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
except FileNotFoundError:
|
||||
# mountpoint command not available, try alternative method
|
||||
try:
|
||||
with open('/proc/mounts', 'r') as f:
|
||||
with open("/proc/mounts", "r") as f:
|
||||
for line in f:
|
||||
parts = line.split()
|
||||
if len(parts) >= 2 and parts[1] == chroot_path:
|
||||
return parts[0] == 'tmpfs'
|
||||
return parts[0] == "tmpfs"
|
||||
return False
|
||||
except Exception:
|
||||
self._log_warning("Could not check mount status")
|
||||
|
|
@ -196,38 +189,38 @@ class TmpfsPlugin(BasePlugin):
|
|||
options = []
|
||||
|
||||
# Add mode option
|
||||
mode = self.tmpfs_settings['mode']
|
||||
options.append(f'mode={mode}')
|
||||
mode = self.tmpfs_settings["mode"]
|
||||
options.append(f"mode={mode}")
|
||||
|
||||
# Add size option
|
||||
size = self.tmpfs_settings['size']
|
||||
size = self.tmpfs_settings["size"]
|
||||
if size:
|
||||
options.append(f'size={size}')
|
||||
options.append(f"size={size}")
|
||||
|
||||
# Add max_fs_size if specified
|
||||
max_fs_size = self.tmpfs_settings['max_fs_size']
|
||||
max_fs_size = self.tmpfs_settings["max_fs_size"]
|
||||
if max_fs_size:
|
||||
options.append(f'size={max_fs_size}')
|
||||
options.append(f"size={max_fs_size}")
|
||||
|
||||
# Add noatime for better performance
|
||||
options.append('noatime')
|
||||
options.append("noatime")
|
||||
|
||||
# Build mount command
|
||||
mount_cmd = [
|
||||
'mount', '-n', '-t', 'tmpfs',
|
||||
'-o', ','.join(options),
|
||||
'deb_mock_tmpfs', chroot_path
|
||||
"mount",
|
||||
"-n",
|
||||
"-t",
|
||||
"tmpfs",
|
||||
"-o",
|
||||
",".join(options),
|
||||
"deb_mock_tmpfs",
|
||||
chroot_path,
|
||||
]
|
||||
|
||||
self._log_debug(f"Mount command: {' '.join(mount_cmd)}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
mount_cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
subprocess.run(mount_cmd, capture_output=True, text=True, check=True)
|
||||
self._log_debug("Tmpfs mount command executed successfully")
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
|
|
@ -246,8 +239,8 @@ class TmpfsPlugin(BasePlugin):
|
|||
"""
|
||||
# Try normal unmount first
|
||||
try:
|
||||
cmd = ['umount', '-n', chroot_path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
cmd = ["umount", "-n", chroot_path]
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
self._log_debug("Tmpfs unmounted successfully")
|
||||
return
|
||||
|
||||
|
|
@ -256,8 +249,8 @@ class TmpfsPlugin(BasePlugin):
|
|||
|
||||
# Try lazy unmount
|
||||
try:
|
||||
cmd = ['umount', '-n', '-l', chroot_path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
cmd = ["umount", "-n", "-l", chroot_path]
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
self._log_debug("Tmpfs lazy unmounted successfully")
|
||||
return
|
||||
|
||||
|
|
@ -266,8 +259,8 @@ class TmpfsPlugin(BasePlugin):
|
|||
|
||||
# Try force unmount as last resort
|
||||
try:
|
||||
cmd = ['umount', '-n', '-f', chroot_path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
cmd = ["umount", "-n", "-f", chroot_path]
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
self._log_debug("Tmpfs force unmounted successfully")
|
||||
return
|
||||
|
||||
|
|
@ -285,28 +278,28 @@ class TmpfsPlugin(BasePlugin):
|
|||
Returns:
|
||||
True if configuration is valid, False otherwise
|
||||
"""
|
||||
plugin_config = getattr(config, 'plugins', {}).get('tmpfs', {})
|
||||
plugin_config = getattr(config, "plugins", {}).get("tmpfs", {})
|
||||
|
||||
# Validate size format
|
||||
size = plugin_config.get('size', '2G')
|
||||
size = plugin_config.get("size", "2G")
|
||||
if not self._is_valid_size_format(size):
|
||||
self._log_error(f"Invalid size format: {size}. Use format like '2G', '512M', etc.")
|
||||
return False
|
||||
|
||||
# Validate mode format
|
||||
mode = plugin_config.get('mode', '0755')
|
||||
mode = plugin_config.get("mode", "0755")
|
||||
if not self._is_valid_mode_format(mode):
|
||||
self._log_error(f"Invalid mode format: {mode}. Use octal format like '0755'")
|
||||
return False
|
||||
|
||||
# Validate required_ram_mb
|
||||
required_ram = plugin_config.get('required_ram_mb', 2048)
|
||||
required_ram = plugin_config.get("required_ram_mb", 2048)
|
||||
if not isinstance(required_ram, int) or required_ram <= 0:
|
||||
self._log_error(f"Invalid required_ram_mb: {required_ram}. Must be positive integer")
|
||||
return False
|
||||
|
||||
# Validate keep_mounted
|
||||
keep_mounted = plugin_config.get('keep_mounted', False)
|
||||
keep_mounted = plugin_config.get("keep_mounted", False)
|
||||
if not isinstance(keep_mounted, bool):
|
||||
self._log_error(f"Invalid keep_mounted: {keep_mounted}. Must be boolean")
|
||||
return False
|
||||
|
|
@ -331,7 +324,7 @@ class TmpfsPlugin(BasePlugin):
|
|||
return True
|
||||
|
||||
# Check if it ends with a valid unit
|
||||
valid_units = ['K', 'M', 'G', 'T']
|
||||
valid_units = ["K", "M", "G", "T"]
|
||||
if size[-1] in valid_units and size[:-1].isdigit():
|
||||
return True
|
||||
|
||||
|
|
@ -365,13 +358,15 @@ class TmpfsPlugin(BasePlugin):
|
|||
Dictionary with plugin information
|
||||
"""
|
||||
info = super().get_plugin_info()
|
||||
info.update({
|
||||
'tmpfs_size': self.tmpfs_settings['size'],
|
||||
'tmpfs_mode': self.tmpfs_settings['mode'],
|
||||
'mount_point': self.tmpfs_settings['mount_point'],
|
||||
'keep_mounted': self.tmpfs_settings['keep_mounted'],
|
||||
'required_ram_mb': self.tmpfs_settings['required_ram_mb'],
|
||||
'mounted': self.mounted,
|
||||
'hooks': ['mount_root', 'postumount']
|
||||
})
|
||||
info.update(
|
||||
{
|
||||
"tmpfs_size": self.tmpfs_settings["size"],
|
||||
"tmpfs_mode": self.tmpfs_settings["mode"],
|
||||
"mount_point": self.tmpfs_settings["mount_point"],
|
||||
"keep_mounted": self.tmpfs_settings["keep_mounted"],
|
||||
"required_ram_mb": self.tmpfs_settings["required_ram_mb"],
|
||||
"mounted": self.mounted,
|
||||
"hooks": ["mount_root", "postumount"],
|
||||
}
|
||||
)
|
||||
return info
|
||||
|
|
@ -5,9 +5,9 @@ sbuild wrapper for deb-mock
|
|||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any, Optional
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from .exceptions import SbuildError
|
||||
|
||||
|
||||
|
|
@ -17,8 +17,13 @@ class SbuildWrapper:
|
|||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def build_package(self, source_package: str, chroot_name: str = None,
|
||||
output_dir: str = None, **kwargs) -> Dict[str, Any]:
|
||||
def build_package(
|
||||
self,
|
||||
source_package: str,
|
||||
chroot_name: str = None,
|
||||
output_dir: str = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
"""Build a Debian source package using sbuild"""
|
||||
|
||||
if chroot_name is None:
|
||||
|
|
@ -32,13 +37,19 @@ class SbuildWrapper:
|
|||
# Prepare sbuild command
|
||||
cmd = self._prepare_sbuild_command(source_package, chroot_name, output_dir, **kwargs)
|
||||
|
||||
# Prepare environment variables
|
||||
env = os.environ.copy()
|
||||
if kwargs.get("build_env"):
|
||||
env.update(kwargs["build_env"])
|
||||
env.update(self.config.build_env)
|
||||
|
||||
# Create temporary log file
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.log', delete=False) as log_file:
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as log_file:
|
||||
log_path = log_file.name
|
||||
|
||||
try:
|
||||
# Execute sbuild
|
||||
result = self._execute_sbuild(cmd, log_path)
|
||||
result = self._execute_sbuild(cmd, log_path, env)
|
||||
|
||||
# Parse build results
|
||||
build_info = self._parse_build_results(output_dir, log_path, result)
|
||||
|
|
@ -50,93 +61,85 @@ class SbuildWrapper:
|
|||
if os.path.exists(log_path):
|
||||
os.unlink(log_path)
|
||||
|
||||
def _prepare_sbuild_command(self, source_package: str, chroot_name: str,
|
||||
output_dir: str, **kwargs) -> List[str]:
|
||||
def _prepare_sbuild_command(self, source_package: str, chroot_name: str, output_dir: str, **kwargs) -> List[str]:
|
||||
"""Prepare the sbuild command with all necessary options"""
|
||||
|
||||
cmd = ['sbuild']
|
||||
cmd = ["sbuild"]
|
||||
|
||||
# Basic options
|
||||
cmd.extend(['--chroot', chroot_name])
|
||||
cmd.extend(['--dist', self.config.suite])
|
||||
cmd.extend(['--arch', self.config.architecture])
|
||||
cmd.extend(["--chroot", chroot_name])
|
||||
cmd.extend(["--dist", self.config.suite])
|
||||
cmd.extend(["--arch", self.config.architecture])
|
||||
|
||||
# Output options
|
||||
cmd.extend(['--build-dir', output_dir])
|
||||
|
||||
# Logging options
|
||||
cmd.extend(['--log-dir', self.config.sbuild_log_dir])
|
||||
cmd.extend(["--build-dir", output_dir])
|
||||
|
||||
# Build options
|
||||
if kwargs.get('verbose', self.config.verbose):
|
||||
cmd.append('--verbose')
|
||||
if kwargs.get("verbose", self.config.verbose):
|
||||
cmd.append("--verbose")
|
||||
|
||||
if kwargs.get('debug', self.config.debug):
|
||||
cmd.append('--debug')
|
||||
if kwargs.get("debug", self.config.debug):
|
||||
cmd.append("--debug")
|
||||
|
||||
# Additional build options from config
|
||||
for option in self.config.build_options:
|
||||
cmd.extend(option.split())
|
||||
|
||||
# Custom build options
|
||||
if kwargs.get('build_options'):
|
||||
for option in kwargs['build_options']:
|
||||
if kwargs.get("build_options"):
|
||||
for option in kwargs["build_options"]:
|
||||
cmd.extend(option.split())
|
||||
|
||||
# Environment variables
|
||||
for key, value in self.config.build_env.items():
|
||||
cmd.extend(['--env', f'{key}={value}'])
|
||||
|
||||
# Custom environment variables
|
||||
if kwargs.get('build_env'):
|
||||
for key, value in kwargs['build_env'].items():
|
||||
cmd.extend(['--env', f'{key}={value}'])
|
||||
# Environment variables will be passed to subprocess.run
|
||||
pass
|
||||
|
||||
# Source package
|
||||
cmd.append(source_package)
|
||||
|
||||
return cmd
|
||||
|
||||
def _execute_sbuild(self, cmd: List[str], log_path: str) -> subprocess.CompletedProcess:
|
||||
def _execute_sbuild(self, cmd: List[str], log_path: str, env: Dict[str, str] = None) -> subprocess.CompletedProcess:
|
||||
"""Execute sbuild command"""
|
||||
try:
|
||||
# Redirect output to log file
|
||||
with open(log_path, 'w') as log_file:
|
||||
with open(log_path, "w") as log_file:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
stdout=log_file,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
check=True
|
||||
check=True,
|
||||
env=env,
|
||||
)
|
||||
return result
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Read log file for error details
|
||||
with open(log_path, 'r') as log_file:
|
||||
with open(log_path, "r") as log_file:
|
||||
log_content = log_file.read()
|
||||
raise SbuildError(f"sbuild failed: {e}\nLog output:\n{log_content}")
|
||||
except FileNotFoundError:
|
||||
raise SbuildError("sbuild not found. Please install sbuild package.")
|
||||
|
||||
def _parse_build_results(self, output_dir: str, log_path: str,
|
||||
result: subprocess.CompletedProcess) -> Dict[str, Any]:
|
||||
def _parse_build_results(
|
||||
self, output_dir: str, log_path: str, result: subprocess.CompletedProcess
|
||||
) -> Dict[str, Any]:
|
||||
"""Parse build results and collect artifacts"""
|
||||
|
||||
build_info = {
|
||||
'success': True,
|
||||
'output_dir': output_dir,
|
||||
'log_file': log_path,
|
||||
'artifacts': [],
|
||||
'metadata': {}
|
||||
"success": True,
|
||||
"output_dir": output_dir,
|
||||
"log_file": log_path,
|
||||
"artifacts": [],
|
||||
"metadata": {},
|
||||
}
|
||||
|
||||
# Collect build artifacts
|
||||
artifacts = self._collect_artifacts(output_dir)
|
||||
build_info['artifacts'] = artifacts
|
||||
build_info["artifacts"] = artifacts
|
||||
|
||||
# Parse build metadata
|
||||
metadata = self._parse_build_metadata(log_path, output_dir)
|
||||
build_info['metadata'] = metadata
|
||||
build_info["metadata"] = metadata
|
||||
|
||||
return build_info
|
||||
|
||||
|
|
@ -164,19 +167,19 @@ class SbuildWrapper:
|
|||
def _parse_build_metadata(self, log_path: str, output_dir: str) -> Dict[str, Any]:
|
||||
"""Parse build metadata from log and artifacts"""
|
||||
metadata = {
|
||||
'build_time': None,
|
||||
'package_name': None,
|
||||
'package_version': None,
|
||||
'architecture': self.config.architecture,
|
||||
'suite': self.config.suite,
|
||||
'chroot': self.config.chroot_name,
|
||||
'dependencies': [],
|
||||
'build_dependencies': []
|
||||
"build_time": None,
|
||||
"package_name": None,
|
||||
"package_version": None,
|
||||
"architecture": self.config.architecture,
|
||||
"suite": self.config.suite,
|
||||
"chroot": self.config.chroot_name,
|
||||
"dependencies": [],
|
||||
"build_dependencies": [],
|
||||
}
|
||||
|
||||
# Parse log file for metadata
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path, 'r') as log_file:
|
||||
with open(log_path, "r") as log_file:
|
||||
log_content = log_file.read()
|
||||
metadata.update(self._extract_metadata_from_log(log_content))
|
||||
|
||||
|
|
@ -193,15 +196,16 @@ class SbuildWrapper:
|
|||
|
||||
# Extract build time
|
||||
import re
|
||||
time_match = re.search(r'Build started at (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})', log_content)
|
||||
|
||||
time_match = re.search(r"Build started at (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})", log_content)
|
||||
if time_match:
|
||||
metadata['build_time'] = time_match.group(1)
|
||||
metadata["build_time"] = time_match.group(1)
|
||||
|
||||
# Extract package name and version
|
||||
package_match = re.search(r'Building (\S+) \((\S+)\)', log_content)
|
||||
package_match = re.search(r"Building (\S+) \((\S+)\)", log_content)
|
||||
if package_match:
|
||||
metadata['package_name'] = package_match.group(1)
|
||||
metadata['package_version'] = package_match.group(2)
|
||||
metadata["package_name"] = package_match.group(1)
|
||||
metadata["package_version"] = package_match.group(2)
|
||||
|
||||
return metadata
|
||||
|
||||
|
|
@ -210,17 +214,17 @@ class SbuildWrapper:
|
|||
metadata = {}
|
||||
|
||||
try:
|
||||
with open(changes_file, 'r') as f:
|
||||
with open(changes_file, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
lines = content.split('\n')
|
||||
lines = content.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith('Source:'):
|
||||
metadata['source_package'] = line.split(':', 1)[1].strip()
|
||||
elif line.startswith('Version:'):
|
||||
metadata['source_version'] = line.split(':', 1)[1].strip()
|
||||
elif line.startswith('Architecture:'):
|
||||
metadata['architectures'] = line.split(':', 1)[1].strip().split()
|
||||
if line.startswith("Source:"):
|
||||
metadata["source_package"] = line.split(":", 1)[1].strip()
|
||||
elif line.startswith("Version:"):
|
||||
metadata["source_version"] = line.split(":", 1)[1].strip()
|
||||
elif line.startswith("Architecture:"):
|
||||
metadata["architectures"] = line.split(":", 1)[1].strip().split()
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -233,33 +237,26 @@ class SbuildWrapper:
|
|||
chroot_name = self.config.chroot_name
|
||||
|
||||
# Use dpkg-checkbuilddeps to check dependencies
|
||||
cmd = ['schroot', '-c', chroot_name, '--', 'dpkg-checkbuilddeps']
|
||||
cmd = ["schroot", "-c", chroot_name, "--", "dpkg-checkbuilddeps"]
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
return {
|
||||
'satisfied': True,
|
||||
'missing': [],
|
||||
'conflicts': []
|
||||
}
|
||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||
return {"satisfied": True, "missing": [], "conflicts": []}
|
||||
except subprocess.CalledProcessError as e:
|
||||
# Parse missing dependencies from error output
|
||||
missing = self._parse_missing_dependencies(e.stderr)
|
||||
return {
|
||||
'satisfied': False,
|
||||
'missing': missing,
|
||||
'conflicts': []
|
||||
}
|
||||
return {"satisfied": False, "missing": missing, "conflicts": []}
|
||||
|
||||
def _parse_missing_dependencies(self, stderr: str) -> List[str]:
|
||||
"""Parse missing dependencies from dpkg-checkbuilddeps output"""
|
||||
missing = []
|
||||
|
||||
for line in stderr.split('\n'):
|
||||
if 'Unmet build dependencies:' in line:
|
||||
for line in stderr.split("\n"):
|
||||
if "Unmet build dependencies:" in line:
|
||||
# Extract package names from the line
|
||||
import re
|
||||
packages = re.findall(r'\b[a-zA-Z0-9][a-zA-Z0-9+\-\.]*\b', line)
|
||||
|
||||
packages = re.findall(r"\b[a-zA-Z0-9][a-zA-Z0-9+\-\.]*\b", line)
|
||||
missing.extend(packages)
|
||||
|
||||
return missing
|
||||
|
|
@ -272,7 +269,15 @@ class SbuildWrapper:
|
|||
if not dependencies:
|
||||
return
|
||||
|
||||
cmd = ['schroot', '-c', chroot_name, '--', 'apt-get', 'install', '-y'] + dependencies
|
||||
cmd = [
|
||||
"schroot",
|
||||
"-c",
|
||||
chroot_name,
|
||||
"--",
|
||||
"apt-get",
|
||||
"install",
|
||||
"-y",
|
||||
] + dependencies
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, check=True)
|
||||
|
|
|
|||
0
debian/rules
vendored
Normal file → Executable file
0
debian/rules
vendored
Normal file → Executable file
|
|
@ -1,5 +0,0 @@
|
|||
# This is a placeholder for the hello_1.0-1.debian.tar.gz file
|
||||
# In a real scenario, this would contain the debian/ directory
|
||||
# with packaging files like debian/control, debian/rules, etc.
|
||||
# For testing purposes, you would need to create an actual tarball
|
||||
# containing the Debian packaging files.
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Format: 3.0 (quilt)
|
||||
Source: hello
|
||||
Binary: hello
|
||||
Architecture: any
|
||||
Version: 1.0-1
|
||||
Maintainer: Deb-Mock Team <team@deb-mock.org>
|
||||
Homepage: https://github.com/deb-mock/deb-mock
|
||||
Standards-Version: 4.6.2
|
||||
Vcs-Browser: https://github.com/deb-mock/deb-mock
|
||||
Vcs-Git: https://github.com/deb-mock/deb-mock.git
|
||||
Build-Depends: debhelper-compat (= 13)
|
||||
|
||||
Package: hello
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}
|
||||
Description: Example package for Deb-Mock testing
|
||||
This is a simple example package used to test the Deb-Mock
|
||||
build environment. It demonstrates basic package building
|
||||
functionality.
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# This is a placeholder for the hello_1.0.orig.tar.gz file
|
||||
# In a real scenario, this would be the upstream source tarball
|
||||
# For testing purposes, you would need to create an actual tarball
|
||||
# containing the source code for the hello package.
|
||||
|
|
@ -20,3 +20,4 @@ parallel_jobs: 2
|
|||
keep_chroot: false
|
||||
verbose: true
|
||||
debug: false
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
Tests for configuration management
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import tempfile
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from deb_mock.config import Config
|
||||
from deb_mock.exceptions import ConfigurationError
|
||||
|
||||
|
|
@ -17,10 +17,10 @@ class TestConfig(unittest.TestCase):
|
|||
"""Test default configuration creation"""
|
||||
config = Config.default()
|
||||
|
||||
self.assertEqual(config.chroot_name, 'bookworm-amd64')
|
||||
self.assertEqual(config.architecture, 'amd64')
|
||||
self.assertEqual(config.suite, 'bookworm')
|
||||
self.assertEqual(config.output_dir, './output')
|
||||
self.assertEqual(config.chroot_name, "bookworm-amd64")
|
||||
self.assertEqual(config.architecture, "amd64")
|
||||
self.assertEqual(config.suite, "bookworm")
|
||||
self.assertEqual(config.output_dir, "./output")
|
||||
self.assertFalse(config.keep_chroot)
|
||||
self.assertFalse(config.verbose)
|
||||
self.assertFalse(config.debug)
|
||||
|
|
@ -28,18 +28,18 @@ class TestConfig(unittest.TestCase):
|
|||
def test_custom_config(self):
|
||||
"""Test custom configuration creation"""
|
||||
config = Config(
|
||||
chroot_name='sid-amd64',
|
||||
architecture='arm64',
|
||||
suite='sid',
|
||||
output_dir='/tmp/build',
|
||||
chroot_name="sid-amd64",
|
||||
architecture="arm64",
|
||||
suite="sid",
|
||||
output_dir="/tmp/build",
|
||||
keep_chroot=True,
|
||||
verbose=True
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
self.assertEqual(config.chroot_name, 'sid-amd64')
|
||||
self.assertEqual(config.architecture, 'arm64')
|
||||
self.assertEqual(config.suite, 'sid')
|
||||
self.assertEqual(config.output_dir, '/tmp/build')
|
||||
self.assertEqual(config.chroot_name, "sid-amd64")
|
||||
self.assertEqual(config.architecture, "arm64")
|
||||
self.assertEqual(config.suite, "sid")
|
||||
self.assertEqual(config.output_dir, "/tmp/build")
|
||||
self.assertTrue(config.keep_chroot)
|
||||
self.assertTrue(config.verbose)
|
||||
|
||||
|
|
@ -54,17 +54,17 @@ keep_chroot: true
|
|||
verbose: true
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
||||
f.write(config_data)
|
||||
config_file = f.name
|
||||
|
||||
try:
|
||||
config = Config.from_file(config_file)
|
||||
|
||||
self.assertEqual(config.chroot_name, 'sid-amd64')
|
||||
self.assertEqual(config.architecture, 'arm64')
|
||||
self.assertEqual(config.suite, 'sid')
|
||||
self.assertEqual(config.output_dir, '/tmp/build')
|
||||
self.assertEqual(config.chroot_name, "sid-amd64")
|
||||
self.assertEqual(config.architecture, "arm64")
|
||||
self.assertEqual(config.suite, "sid")
|
||||
self.assertEqual(config.output_dir, "/tmp/build")
|
||||
self.assertTrue(config.keep_chroot)
|
||||
self.assertTrue(config.verbose)
|
||||
finally:
|
||||
|
|
@ -72,29 +72,21 @@ verbose: true
|
|||
|
||||
def test_config_to_dict(self):
|
||||
"""Test converting configuration to dictionary"""
|
||||
config = Config(
|
||||
chroot_name='test-chroot',
|
||||
architecture='amd64',
|
||||
suite='bookworm'
|
||||
)
|
||||
config = Config(chroot_name="test-chroot", architecture="amd64", suite="bookworm")
|
||||
|
||||
config_dict = config.to_dict()
|
||||
|
||||
self.assertEqual(config_dict['chroot_name'], 'test-chroot')
|
||||
self.assertEqual(config_dict['architecture'], 'amd64')
|
||||
self.assertEqual(config_dict['suite'], 'bookworm')
|
||||
self.assertIn('output_dir', config_dict)
|
||||
self.assertIn('keep_chroot', config_dict)
|
||||
self.assertEqual(config_dict["chroot_name"], "test-chroot")
|
||||
self.assertEqual(config_dict["architecture"], "amd64")
|
||||
self.assertEqual(config_dict["suite"], "bookworm")
|
||||
self.assertIn("output_dir", config_dict)
|
||||
self.assertIn("keep_chroot", config_dict)
|
||||
|
||||
def test_config_save(self):
|
||||
"""Test saving configuration to file"""
|
||||
config = Config(
|
||||
chroot_name='test-chroot',
|
||||
architecture='amd64',
|
||||
suite='bookworm'
|
||||
)
|
||||
config = Config(chroot_name="test-chroot", architecture="amd64", suite="bookworm")
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
||||
config_file = f.name
|
||||
|
||||
try:
|
||||
|
|
@ -112,14 +104,14 @@ verbose: true
|
|||
|
||||
def test_invalid_architecture(self):
|
||||
"""Test validation of invalid architecture"""
|
||||
config = Config(architecture='invalid-arch')
|
||||
config = Config(architecture="invalid-arch")
|
||||
|
||||
with self.assertRaises(ConfigurationError):
|
||||
config.validate()
|
||||
|
||||
def test_invalid_suite(self):
|
||||
"""Test validation of invalid suite"""
|
||||
config = Config(suite='invalid-suite')
|
||||
config = Config(suite="invalid-suite")
|
||||
|
||||
with self.assertRaises(ConfigurationError):
|
||||
config.validate()
|
||||
|
|
@ -127,15 +119,15 @@ verbose: true
|
|||
def test_get_paths(self):
|
||||
"""Test path generation methods"""
|
||||
config = Config(
|
||||
chroot_dir='/var/lib/chroots',
|
||||
output_dir='./output',
|
||||
metadata_dir='./metadata'
|
||||
chroot_dir="/var/lib/chroots",
|
||||
output_dir="./output",
|
||||
metadata_dir="./metadata",
|
||||
)
|
||||
|
||||
self.assertEqual(config.get_chroot_path(), '/var/lib/chroots/bookworm-amd64')
|
||||
self.assertEqual(config.get_output_path(), os.path.abspath('./output'))
|
||||
self.assertEqual(config.get_metadata_path(), os.path.abspath('./metadata'))
|
||||
self.assertEqual(config.get_chroot_path(), "/var/lib/chroots/bookworm-amd64")
|
||||
self.assertEqual(config.get_output_path(), os.path.abspath("./output"))
|
||||
self.assertEqual(config.get_metadata_path(), os.path.abspath("./metadata"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -3,13 +3,20 @@ Tests for the enhanced exception handling system
|
|||
"""
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
from deb_mock.exceptions import (
|
||||
DebMockError, ConfigurationError, ChrootError, SbuildError,
|
||||
BuildError, DependencyError, MetadataError, CacheError,
|
||||
PluginError, NetworkError, PermissionError, ValidationError,
|
||||
handle_exception, format_error_context
|
||||
BuildError,
|
||||
CacheError,
|
||||
ChrootError,
|
||||
ConfigurationError,
|
||||
DebMockError,
|
||||
DependencyError,
|
||||
NetworkError,
|
||||
PermissionError,
|
||||
SbuildError,
|
||||
ValidationError,
|
||||
format_error_context,
|
||||
handle_exception,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -26,7 +33,7 @@ class TestDebMockError:
|
|||
|
||||
def test_error_with_context(self):
|
||||
"""Test error with context information"""
|
||||
context = {'file': '/path/to/file', 'operation': 'read'}
|
||||
context = {"file": "/path/to/file", "operation": "read"}
|
||||
error = DebMockError("File operation failed", context=context)
|
||||
|
||||
expected = """Error: File operation failed
|
||||
|
|
@ -51,10 +58,9 @@ Suggestions:
|
|||
|
||||
def test_error_with_context_and_suggestions(self):
|
||||
"""Test error with both context and suggestions"""
|
||||
context = {'config_file': '/etc/deb-mock.conf'}
|
||||
context = {"config_file": "/etc/deb-mock.conf"}
|
||||
suggestions = ["Check config syntax", "Verify file exists"]
|
||||
error = DebMockError("Invalid configuration",
|
||||
context=context, suggestions=suggestions)
|
||||
error = DebMockError("Invalid configuration", context=context, suggestions=suggestions)
|
||||
|
||||
expected = """Error: Invalid configuration
|
||||
|
||||
|
|
@ -88,7 +94,7 @@ class TestSpecificExceptions:
|
|||
error = ConfigurationError(
|
||||
"Invalid configuration",
|
||||
config_file="/etc/deb-mock.conf",
|
||||
config_section="chroot"
|
||||
config_section="chroot",
|
||||
)
|
||||
|
||||
assert "config_file: /etc/deb-mock.conf" in str(error)
|
||||
|
|
@ -102,7 +108,7 @@ class TestSpecificExceptions:
|
|||
"Failed to create chroot",
|
||||
chroot_name="bookworm-amd64",
|
||||
operation="create",
|
||||
chroot_path="/var/lib/deb-mock/chroots/bookworm-amd64"
|
||||
chroot_path="/var/lib/deb-mock/chroots/bookworm-amd64",
|
||||
)
|
||||
|
||||
assert "chroot_name: bookworm-amd64" in str(error)
|
||||
|
|
@ -116,7 +122,7 @@ class TestSpecificExceptions:
|
|||
"Build failed",
|
||||
sbuild_config="/etc/sbuild/sbuild.conf",
|
||||
build_log="/var/log/sbuild.log",
|
||||
return_code=1
|
||||
return_code=1,
|
||||
)
|
||||
|
||||
assert "sbuild_config: /etc/sbuild/sbuild.conf" in str(error)
|
||||
|
|
@ -130,7 +136,7 @@ class TestSpecificExceptions:
|
|||
"Package build failed",
|
||||
source_package="hello_1.0.dsc",
|
||||
build_log="/tmp/build.log",
|
||||
artifacts=["hello_1.0-1_amd64.deb"]
|
||||
artifacts=["hello_1.0-1_amd64.deb"],
|
||||
)
|
||||
|
||||
assert "source_package: hello_1.0.dsc" in str(error)
|
||||
|
|
@ -143,7 +149,7 @@ class TestSpecificExceptions:
|
|||
error = DependencyError(
|
||||
"Missing build dependencies",
|
||||
missing_packages=["build-essential", "devscripts"],
|
||||
conflicting_packages=["old-package"]
|
||||
conflicting_packages=["old-package"],
|
||||
)
|
||||
|
||||
assert "missing_packages: ['build-essential', 'devscripts']" in str(error)
|
||||
|
|
@ -156,7 +162,7 @@ class TestSpecificExceptions:
|
|||
"Cache operation failed",
|
||||
cache_type="root_cache",
|
||||
cache_path="/var/cache/deb-mock/root-cache",
|
||||
operation="restore"
|
||||
operation="restore",
|
||||
)
|
||||
|
||||
assert "cache_type: root_cache" in str(error)
|
||||
|
|
@ -170,7 +176,7 @@ class TestSpecificExceptions:
|
|||
"Repository access failed",
|
||||
url="http://deb.debian.org/debian/",
|
||||
proxy="http://proxy.example.com:3128",
|
||||
timeout=30
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
assert "url: http://deb.debian.org/debian/" in str(error)
|
||||
|
|
@ -184,7 +190,7 @@ class TestSpecificExceptions:
|
|||
"Insufficient privileges",
|
||||
operation="create_chroot",
|
||||
path="/var/lib/deb-mock",
|
||||
required_privileges="root"
|
||||
required_privileges="root",
|
||||
)
|
||||
|
||||
assert "operation: create_chroot" in str(error)
|
||||
|
|
@ -198,7 +204,7 @@ class TestSpecificExceptions:
|
|||
"Invalid architecture",
|
||||
field="architecture",
|
||||
value="invalid-arch",
|
||||
expected_format="amd64, i386, arm64, etc."
|
||||
expected_format="amd64, i386, arm64, etc.",
|
||||
)
|
||||
|
||||
assert "field: architecture" in str(error)
|
||||
|
|
@ -212,23 +218,15 @@ class TestHelperFunctions:
|
|||
|
||||
def test_format_error_context(self):
|
||||
"""Test format_error_context helper"""
|
||||
context = format_error_context(
|
||||
file="/path/to/file",
|
||||
operation="read",
|
||||
user="testuser",
|
||||
none_value=None
|
||||
)
|
||||
context = format_error_context(file="/path/to/file", operation="read", user="testuser", none_value=None)
|
||||
|
||||
expected = {
|
||||
'file': '/path/to/file',
|
||||
'operation': 'read',
|
||||
'user': 'testuser'
|
||||
}
|
||||
expected = {"file": "/path/to/file", "operation": "read", "user": "testuser"}
|
||||
assert context == expected
|
||||
assert 'none_value' not in context
|
||||
assert "none_value" not in context
|
||||
|
||||
def test_handle_exception_decorator_success(self):
|
||||
"""Test handle_exception decorator with successful function"""
|
||||
|
||||
@handle_exception
|
||||
def successful_function():
|
||||
return "success"
|
||||
|
|
@ -238,6 +236,7 @@ class TestHelperFunctions:
|
|||
|
||||
def test_handle_exception_decorator_debmock_error(self, capsys):
|
||||
"""Test handle_exception decorator with DebMockError"""
|
||||
|
||||
@handle_exception
|
||||
def failing_function():
|
||||
raise ConfigurationError("Config error", config_file="/etc/config")
|
||||
|
|
@ -253,6 +252,7 @@ class TestHelperFunctions:
|
|||
|
||||
def test_handle_exception_decorator_unexpected_error(self, capsys):
|
||||
"""Test handle_exception decorator with unexpected error"""
|
||||
|
||||
@handle_exception
|
||||
def unexpected_error_function():
|
||||
raise ValueError("Unexpected value error")
|
||||
|
|
@ -276,7 +276,7 @@ class TestExceptionIntegration:
|
|||
"Failed to create chroot environment",
|
||||
chroot_name="bookworm-amd64",
|
||||
operation="debootstrap",
|
||||
chroot_path="/var/lib/deb-mock/chroots/bookworm-amd64"
|
||||
chroot_path="/var/lib/deb-mock/chroots/bookworm-amd64",
|
||||
)
|
||||
|
||||
error_str = str(error)
|
||||
|
|
@ -300,7 +300,7 @@ class TestExceptionIntegration:
|
|||
"Package build failed due to compilation errors",
|
||||
source_package="myapp_1.0.dsc",
|
||||
build_log="/tmp/build_myapp.log",
|
||||
artifacts=[]
|
||||
artifacts=[],
|
||||
)
|
||||
|
||||
error_str = str(error)
|
||||
|
|
@ -322,7 +322,7 @@ class TestExceptionIntegration:
|
|||
error = DependencyError(
|
||||
"Unable to resolve build dependencies",
|
||||
missing_packages=["libssl-dev", "libcurl4-openssl-dev"],
|
||||
conflicting_packages=["libssl1.0-dev"]
|
||||
conflicting_packages=["libssl1.0-dev"],
|
||||
)
|
||||
|
||||
error_str = str(error)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue