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
|
|
@ -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
|
||||
|
||||
|
|
@ -18,71 +17,71 @@ logger = logging.getLogger(__name__)
|
|||
class TmpfsPlugin(BasePlugin):
|
||||
"""
|
||||
Use tmpfs for faster I/O operations in chroot.
|
||||
|
||||
|
||||
This plugin mounts a tmpfs filesystem on the chroot directory,
|
||||
which can significantly improve build performance by using RAM
|
||||
instead of disk for temporary files and build artifacts.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, config, hook_manager):
|
||||
"""Initialize the Tmpfs plugin."""
|
||||
super().__init__(config, hook_manager)
|
||||
self.tmpfs_settings = self._get_tmpfs_settings()
|
||||
self.mounted = False
|
||||
self._log_info(f"Initialized with size: {self.tmpfs_settings['size']}")
|
||||
|
||||
|
||||
def _register_hooks(self):
|
||||
"""Register tmpfs hooks."""
|
||||
self.hook_manager.add_hook("mount_root", self.mount_root)
|
||||
self.hook_manager.add_hook("postumount", self.postumount)
|
||||
self._log_debug("Registered mount_root and postumount hooks")
|
||||
|
||||
|
||||
def _get_tmpfs_settings(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get tmpfs settings from configuration.
|
||||
|
||||
|
||||
Returns:
|
||||
Dictionary with tmpfs settings
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
Mount tmpfs when chroot is mounted.
|
||||
|
||||
|
||||
Args:
|
||||
context: Context dictionary with chroot information
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
# Check if we have enough RAM
|
||||
if not self._check_ram_requirements():
|
||||
self._log_warning("Insufficient RAM for tmpfs, skipping mount")
|
||||
return
|
||||
|
||||
|
||||
# Check if already mounted
|
||||
if self._is_mounted(chroot_path):
|
||||
self._log_info(f"Tmpfs already mounted at {chroot_path}")
|
||||
self.mounted = True
|
||||
return
|
||||
|
||||
|
||||
self._log_info(f"Mounting tmpfs at {chroot_path}")
|
||||
|
||||
|
||||
try:
|
||||
self._mount_tmpfs(chroot_path)
|
||||
self.mounted = True
|
||||
|
|
@ -90,288 +89,284 @@ class TmpfsPlugin(BasePlugin):
|
|||
except Exception as e:
|
||||
self._log_error(f"Failed to mount tmpfs: {e}")
|
||||
self.mounted = False
|
||||
|
||||
|
||||
def postumount(self, context: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Unmount tmpfs when chroot is unmounted.
|
||||
|
||||
|
||||
Args:
|
||||
context: Context dictionary with chroot information
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
self._log_info(f"Unmounting tmpfs from {chroot_path}")
|
||||
|
||||
|
||||
try:
|
||||
self._unmount_tmpfs(chroot_path)
|
||||
self.mounted = False
|
||||
self._log_info("Tmpfs unmounted successfully")
|
||||
except Exception as e:
|
||||
self._log_error(f"Failed to unmount tmpfs: {e}")
|
||||
|
||||
|
||||
def _check_ram_requirements(self) -> bool:
|
||||
"""
|
||||
Check if system has enough RAM for tmpfs.
|
||||
|
||||
|
||||
Returns:
|
||||
True if system has sufficient RAM, False otherwise
|
||||
"""
|
||||
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
|
||||
else:
|
||||
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")
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self._log_error(f"Failed to check RAM requirements: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _is_mounted(self, chroot_path: str) -> bool:
|
||||
"""
|
||||
Check if tmpfs is already mounted at the given path.
|
||||
|
||||
|
||||
Args:
|
||||
chroot_path: Path to check
|
||||
|
||||
|
||||
Returns:
|
||||
True if tmpfs is mounted, False otherwise
|
||||
"""
|
||||
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")
|
||||
return False
|
||||
|
||||
|
||||
def _mount_tmpfs(self, chroot_path: str) -> None:
|
||||
"""
|
||||
Mount tmpfs at the specified path.
|
||||
|
||||
|
||||
Args:
|
||||
chroot_path: Path where to mount tmpfs
|
||||
"""
|
||||
# Build mount options
|
||||
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:
|
||||
self._log_error(f"Tmpfs mount failed: {e.stderr}")
|
||||
raise
|
||||
except FileNotFoundError:
|
||||
self._log_error("mount command not found - ensure mount is available")
|
||||
raise
|
||||
|
||||
|
||||
def _unmount_tmpfs(self, chroot_path: str) -> None:
|
||||
"""
|
||||
Unmount tmpfs from the specified path.
|
||||
|
||||
|
||||
Args:
|
||||
chroot_path: Path where tmpfs is mounted
|
||||
"""
|
||||
# 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
|
||||
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
self._log_warning(f"Normal unmount failed: {e.stderr}")
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
self._log_warning(f"Lazy unmount failed: {e.stderr}")
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
self._log_error(f"Force unmount failed: {e.stderr}")
|
||||
raise
|
||||
|
||||
|
||||
def validate_config(self, config: Any) -> bool:
|
||||
"""
|
||||
Validate plugin configuration.
|
||||
|
||||
|
||||
Args:
|
||||
config: Configuration to validate
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _is_valid_size_format(self, size: str) -> bool:
|
||||
"""
|
||||
Check if size format is valid.
|
||||
|
||||
|
||||
Args:
|
||||
size: Size string to validate
|
||||
|
||||
|
||||
Returns:
|
||||
True if format is valid, False otherwise
|
||||
"""
|
||||
if not size:
|
||||
return False
|
||||
|
||||
|
||||
# Check if it's a number (bytes)
|
||||
if size.isdigit():
|
||||
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
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _is_valid_mode_format(self, mode: str) -> bool:
|
||||
"""
|
||||
Check if mode format is valid.
|
||||
|
||||
|
||||
Args:
|
||||
mode: Mode string to validate
|
||||
|
||||
|
||||
Returns:
|
||||
True if format is valid, False otherwise
|
||||
"""
|
||||
if not mode:
|
||||
return False
|
||||
|
||||
|
||||
# Check if it's a valid octal number
|
||||
try:
|
||||
int(mode, 8)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def get_plugin_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Get plugin information.
|
||||
|
||||
|
||||
Returns:
|
||||
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']
|
||||
})
|
||||
return 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"],
|
||||
}
|
||||
)
|
||||
return info
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue