Initial commit
This commit is contained in:
commit
3326d796f0
87 changed files with 15792 additions and 0 deletions
4
osbuild-stages/apt-stage/__init__.py
Normal file
4
osbuild-stages/apt-stage/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# apt-stage package
|
||||
from .apt_stage import AptStage
|
||||
|
||||
__all__ = ['AptStage']
|
||||
BIN
osbuild-stages/apt-stage/__pycache__/apt_stage.cpython-313.pyc
Normal file
BIN
osbuild-stages/apt-stage/__pycache__/apt_stage.cpython-313.pyc
Normal file
Binary file not shown.
326
osbuild-stages/apt-stage/apt_stage.py
Normal file
326
osbuild-stages/apt-stage/apt_stage.py
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Debian APT Stage for osbuild
|
||||
|
||||
This stage handles Debian package installation using apt/dpkg within an osbuild chroot.
|
||||
It replaces the DNF stage used in Fedora/RHEL systems.
|
||||
|
||||
Author: Debian bootc-image-builder team
|
||||
License: Same as original bootc-image-builder
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import json
|
||||
from typing import Dict, List, Optional, Any
|
||||
import logging
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AptStage:
|
||||
"""
|
||||
osbuild stage for Debian package management using apt/dpkg.
|
||||
|
||||
This stage handles:
|
||||
- Repository configuration
|
||||
- Package dependency resolution
|
||||
- Package installation
|
||||
- Cache management
|
||||
- OSTree integration considerations
|
||||
"""
|
||||
|
||||
def __init__(self, options: Dict[str, Any]):
|
||||
"""
|
||||
Initialize the APT stage with configuration options.
|
||||
|
||||
Args:
|
||||
options: Dictionary containing stage configuration
|
||||
- packages: List of packages to install
|
||||
- repos: List of repository configurations
|
||||
- release: Debian release (e.g., 'trixie', 'bookworm')
|
||||
- arch: Target architecture (e.g., 'amd64')
|
||||
- exclude_packages: List of packages to exclude
|
||||
- install_weak_deps: Whether to install weak dependencies
|
||||
"""
|
||||
self.options = options
|
||||
self.packages = options.get('packages', [])
|
||||
self.repos = options.get('repos', [])
|
||||
self.release = options.get('release', 'trixie')
|
||||
self.arch = options.get('arch', 'amd64')
|
||||
self.exclude_packages = options.get('exclude_packages', [])
|
||||
self.install_weak_deps = options.get('install_weak_deps', True)
|
||||
|
||||
# Validate required options
|
||||
if not self.packages:
|
||||
raise ValueError("No packages specified for installation")
|
||||
|
||||
logger.info(f"APT Stage initialized for {self.release} ({self.arch})")
|
||||
logger.info(f"Packages to install: {self.packages}")
|
||||
|
||||
def run(self, context) -> None:
|
||||
"""
|
||||
Execute the APT stage within the osbuild context.
|
||||
|
||||
Args:
|
||||
context: osbuild context providing chroot access
|
||||
"""
|
||||
logger.info("Starting APT stage execution")
|
||||
|
||||
try:
|
||||
# Step 1: Set up APT configuration in chroot
|
||||
self._setup_apt_config(context)
|
||||
|
||||
# Step 2: Configure repositories
|
||||
self._configure_repositories(context)
|
||||
|
||||
# Step 3: Update package lists
|
||||
self._update_package_lists(context)
|
||||
|
||||
# Step 4: Install packages with dependency resolution
|
||||
self._install_packages(context)
|
||||
|
||||
# Step 5: Clean up APT cache
|
||||
self._cleanup_cache(context)
|
||||
|
||||
logger.info("APT stage completed successfully")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"APT stage failed: {e}")
|
||||
raise
|
||||
|
||||
def _setup_apt_config(self, context) -> None:
|
||||
"""
|
||||
Set up APT configuration in the chroot environment.
|
||||
|
||||
Args:
|
||||
context: osbuild context
|
||||
"""
|
||||
logger.info("Setting up APT configuration")
|
||||
|
||||
# Create /etc/apt/apt.conf.d/ directory if it doesn't exist
|
||||
apt_conf_dir = os.path.join(context.root, "etc", "apt", "apt.conf.d")
|
||||
os.makedirs(apt_conf_dir, exist_ok=True)
|
||||
|
||||
# Configure APT for chroot environment
|
||||
apt_config = [
|
||||
'Acquire::Check-Valid-Until "false";',
|
||||
'Acquire::Languages "none";',
|
||||
'Acquire::GzipIndexes "true";',
|
||||
'Acquire::CompressionTypes::Order:: "gz";',
|
||||
'Dpkg::Options::="--force-confdef";',
|
||||
'Dpkg::Options::="--force-confold";',
|
||||
'Dpkg::Use-Pty "false";',
|
||||
'Dpkg::Progress-Fancy "0";',
|
||||
]
|
||||
|
||||
# Write APT configuration
|
||||
config_file = os.path.join(apt_conf_dir, "99osbuild")
|
||||
with open(config_file, 'w') as f:
|
||||
f.write('\n'.join(apt_config))
|
||||
|
||||
# Set proper permissions
|
||||
os.chmod(config_file, 0o644)
|
||||
|
||||
logger.info("APT configuration set up successfully")
|
||||
|
||||
def _configure_repositories(self, context) -> None:
|
||||
"""
|
||||
Configure APT repositories in the chroot.
|
||||
|
||||
Args:
|
||||
context: osbuild context
|
||||
"""
|
||||
logger.info("Configuring APT repositories")
|
||||
|
||||
# Create /etc/apt/sources.list.d/ directory
|
||||
sources_dir = os.path.join(context.root, "etc", "apt", "sources.list.d")
|
||||
os.makedirs(sources_dir, exist_ok=True)
|
||||
|
||||
# Default Debian repositories if none specified
|
||||
if not self.repos:
|
||||
self.repos = [
|
||||
{
|
||||
"name": "debian",
|
||||
"url": f"http://deb.debian.org/debian",
|
||||
"suite": self.release,
|
||||
"components": ["main", "contrib", "non-free"]
|
||||
},
|
||||
{
|
||||
"name": "debian-security",
|
||||
"url": f"http://deb.debian.org/debian-security",
|
||||
"suite": f"{self.release}-security",
|
||||
"components": ["main", "contrib", "non-free"]
|
||||
},
|
||||
{
|
||||
"name": "debian-updates",
|
||||
"url": f"http://deb.debian.org/debian",
|
||||
"suite": f"{self.release}-updates",
|
||||
"components": ["main", "contrib", "non-free"]
|
||||
}
|
||||
]
|
||||
|
||||
# Write repository configurations
|
||||
for repo in self.repos:
|
||||
repo_name = repo.get("name", "debian")
|
||||
repo_url = repo.get("url", "http://deb.debian.org/debian")
|
||||
repo_suite = repo.get("suite", self.release)
|
||||
repo_components = repo.get("components", ["main"])
|
||||
|
||||
# Create sources.list entry
|
||||
sources_entry = f"deb {repo_url} {repo_suite} {' '.join(repo_components)}\n"
|
||||
|
||||
# Write to sources.list.d file
|
||||
sources_file = os.path.join(sources_dir, f"{repo_name}.list")
|
||||
with open(sources_file, 'w') as f:
|
||||
f.write(sources_entry)
|
||||
|
||||
# Set proper permissions
|
||||
os.chmod(sources_file, 0o644)
|
||||
|
||||
logger.info(f"Configured {len(self.repos)} repositories")
|
||||
|
||||
def _update_package_lists(self, context) -> None:
|
||||
"""
|
||||
Update package lists in the chroot.
|
||||
|
||||
Args:
|
||||
context: osbuild context
|
||||
"""
|
||||
logger.info("Updating package lists")
|
||||
|
||||
# Run apt-get update in chroot
|
||||
cmd = ["apt-get", "update"]
|
||||
result = context.run(cmd)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Failed to update package lists: {result.stderr}")
|
||||
|
||||
logger.info("Package lists updated successfully")
|
||||
|
||||
def _install_packages(self, context) -> None:
|
||||
"""
|
||||
Install packages with dependency resolution.
|
||||
|
||||
Args:
|
||||
context: osbuild context
|
||||
"""
|
||||
logger.info(f"Installing {len(self.packages)} packages")
|
||||
|
||||
# Build apt-get install command
|
||||
cmd = ["apt-get", "install", "-y", "--no-install-recommends"]
|
||||
|
||||
# Add architecture specification if needed
|
||||
if self.arch != 'amd64':
|
||||
cmd.extend(["-o", f"APT::Architecture={self.arch}"])
|
||||
|
||||
# Add package list
|
||||
cmd.extend(self.packages)
|
||||
|
||||
# Run package installation
|
||||
result = context.run(cmd)
|
||||
|
||||
if result.returncode != 0:
|
||||
# Log detailed error information
|
||||
logger.error(f"Package installation failed: {result.stderr}")
|
||||
logger.error(f"Command executed: {' '.join(cmd)}")
|
||||
|
||||
# Try to get more detailed error information
|
||||
self._log_apt_errors(context)
|
||||
|
||||
raise RuntimeError("Package installation failed")
|
||||
|
||||
logger.info("Package installation completed successfully")
|
||||
|
||||
def _cleanup_cache(self, context) -> None:
|
||||
"""
|
||||
Clean up APT cache to reduce image size.
|
||||
|
||||
Args:
|
||||
context: osbuild context
|
||||
"""
|
||||
logger.info("Cleaning up APT cache")
|
||||
|
||||
# Clean package cache
|
||||
cmd = ["apt-get", "clean"]
|
||||
result = context.run(cmd)
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.warning(f"Cache cleanup failed: {result.stderr}")
|
||||
else:
|
||||
logger.info("APT cache cleaned successfully")
|
||||
|
||||
# Remove package lists
|
||||
cmd = ["rm", "-rf", "/var/lib/apt/lists/*"]
|
||||
result = context.run(cmd)
|
||||
|
||||
if result.returncode != 0:
|
||||
logger.warning(f"Package list cleanup failed: {result.stderr}")
|
||||
else:
|
||||
logger.info("Package lists removed successfully")
|
||||
|
||||
def _log_apt_errors(self, context) -> None:
|
||||
"""
|
||||
Log detailed APT error information for debugging.
|
||||
|
||||
Args:
|
||||
context: osbuild context
|
||||
"""
|
||||
logger.info("Collecting detailed APT error information")
|
||||
|
||||
# Check APT status
|
||||
cmd = ["apt-get", "check"]
|
||||
result = context.run(cmd)
|
||||
logger.info(f"APT check result: {result.stdout}")
|
||||
|
||||
# Check for broken packages
|
||||
cmd = ["dpkg", "--audit"]
|
||||
result = context.run(cmd)
|
||||
if result.stdout:
|
||||
logger.error(f"Broken packages detected: {result.stdout}")
|
||||
|
||||
# Check package status
|
||||
cmd = ["dpkg", "-l"]
|
||||
result = context.run(cmd)
|
||||
logger.debug(f"Package status: {result.stdout}")
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for the APT stage.
|
||||
|
||||
This function is called by osbuild when executing the stage.
|
||||
"""
|
||||
import sys
|
||||
|
||||
# Read options from stdin (osbuild passes options as JSON)
|
||||
options = json.load(sys.stdin)
|
||||
|
||||
# Create and run the stage
|
||||
stage = AptStage(options)
|
||||
|
||||
# Note: In a real osbuild stage, the context would be provided by osbuild
|
||||
# For now, this is a placeholder for testing
|
||||
class MockContext:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
|
||||
def run(self, cmd):
|
||||
# Mock implementation for testing
|
||||
logger.info(f"Would run: {' '.join(cmd)}")
|
||||
return type('Result', (), {'returncode': 0, 'stdout': '', 'stderr': ''})()
|
||||
|
||||
# For testing purposes
|
||||
if len(sys.argv) > 1 and sys.argv[1] == '--test':
|
||||
context = MockContext('/tmp/test-chroot')
|
||||
stage.run(context)
|
||||
else:
|
||||
# In real osbuild environment, context would be provided
|
||||
raise NotImplementedError("This stage must be run within osbuild")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue