Some checks failed
Checks / Spelling (push) Has been cancelled
Checks / Python Linters (push) Has been cancelled
Checks / Shell Linters (push) Has been cancelled
Checks / 📦 Packit config lint (push) Has been cancelled
Checks / 🔍 Check for valid snapshot urls (push) Has been cancelled
Checks / 🔍 Check JSON files for formatting consistency (push) Has been cancelled
Generate / Documentation (push) Has been cancelled
Generate / Test Data (push) Has been cancelled
Tests / Unittest (push) Has been cancelled
Tests / Assembler test (legacy) (push) Has been cancelled
Tests / Smoke run: unittest as normal user on default runner (push) Has been cancelled
445 lines
16 KiB
Python
445 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Debian Atomic Blueprint Generator for Debian Forge
|
|
|
|
This module provides enhanced blueprint generation for Debian atomic images,
|
|
integrating with repository management and dependency resolution systems.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from typing import Dict, List, Optional, Any
|
|
from dataclasses import dataclass, asdict
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
try:
|
|
from debian_repository_manager import DebianRepositoryManager
|
|
from debian_package_resolver import DebianPackageResolver
|
|
except ImportError:
|
|
DebianRepositoryManager = None
|
|
DebianPackageResolver = None
|
|
|
|
@dataclass
|
|
class AtomicBlueprintConfig:
|
|
"""Configuration for atomic blueprint generation"""
|
|
name: str
|
|
description: str
|
|
version: str
|
|
base_packages: List[str]
|
|
additional_packages: List[str] = None
|
|
excluded_packages: List[str] = None
|
|
suite: str = "bookworm"
|
|
architecture: str = "amd64"
|
|
include_recommends: bool = False
|
|
ostree_ref: str = None
|
|
users: List[Dict[str, Any]] = None
|
|
services: Dict[str, List[str]] = None
|
|
filesystem_customizations: Dict[str, Any] = None
|
|
|
|
class DebianAtomicBlueprintGenerator:
|
|
"""Generates optimized Debian atomic blueprints"""
|
|
|
|
def __init__(self, config_dir: str = None):
|
|
if DebianRepositoryManager and config_dir:
|
|
self.repository_manager = DebianRepositoryManager(config_dir)
|
|
elif DebianRepositoryManager:
|
|
# Use temporary directory for testing
|
|
import tempfile
|
|
temp_dir = tempfile.mkdtemp(prefix="debian-forge-")
|
|
self.repository_manager = DebianRepositoryManager(temp_dir)
|
|
else:
|
|
self.repository_manager = None
|
|
|
|
self.package_resolver = DebianPackageResolver() if DebianPackageResolver else None
|
|
self.base_packages = [
|
|
"systemd",
|
|
"systemd-sysv",
|
|
"dbus",
|
|
"udev",
|
|
"ostree",
|
|
"linux-image-amd64"
|
|
]
|
|
|
|
def generate_base_blueprint(self, config: AtomicBlueprintConfig = None) -> Dict[str, Any]:
|
|
"""Generate base atomic blueprint"""
|
|
if config is None:
|
|
config = AtomicBlueprintConfig(
|
|
name="debian-atomic-base",
|
|
description="Debian Atomic Base System",
|
|
version="1.0.0",
|
|
base_packages=self.base_packages
|
|
)
|
|
|
|
# Resolve package dependencies
|
|
all_packages = config.base_packages + (config.additional_packages or [])
|
|
resolved_packages = self._resolve_packages(all_packages, config.suite, config.architecture)
|
|
|
|
# Generate blueprint
|
|
blueprint = {
|
|
"name": config.name,
|
|
"description": config.description,
|
|
"version": config.version,
|
|
"distro": f"debian-{config.suite}",
|
|
"arch": config.architecture,
|
|
"packages": [{"name": pkg} for pkg in resolved_packages],
|
|
"modules": [],
|
|
"groups": [],
|
|
"customizations": self._generate_base_customizations(config)
|
|
}
|
|
|
|
# Add OSTree configuration
|
|
if config.ostree_ref:
|
|
blueprint["ostree"] = {
|
|
"ref": config.ostree_ref,
|
|
"parent": f"debian/{config.suite}/base"
|
|
}
|
|
|
|
return blueprint
|
|
|
|
def generate_workstation_blueprint(self) -> Dict[str, Any]:
|
|
"""Generate workstation atomic blueprint"""
|
|
workstation_packages = [
|
|
"firefox-esr",
|
|
"libreoffice",
|
|
"gnome-core",
|
|
"gdm3",
|
|
"network-manager",
|
|
"pulseaudio",
|
|
"fonts-dejavu"
|
|
]
|
|
|
|
config = AtomicBlueprintConfig(
|
|
name="debian-atomic-workstation",
|
|
description="Debian Atomic Workstation",
|
|
version="1.0.0",
|
|
base_packages=self.base_packages,
|
|
additional_packages=workstation_packages,
|
|
ostree_ref="debian/bookworm/workstation"
|
|
)
|
|
|
|
blueprint = self.generate_base_blueprint(config)
|
|
blueprint["customizations"]["services"]["enabled"].extend([
|
|
"gdm3",
|
|
"NetworkManager",
|
|
"pulseaudio"
|
|
])
|
|
|
|
return blueprint
|
|
|
|
def generate_server_blueprint(self) -> Dict[str, Any]:
|
|
"""Generate server atomic blueprint"""
|
|
server_packages = [
|
|
"nginx",
|
|
"postgresql",
|
|
"redis",
|
|
"fail2ban",
|
|
"logrotate",
|
|
"rsyslog"
|
|
]
|
|
|
|
config = AtomicBlueprintConfig(
|
|
name="debian-atomic-server",
|
|
description="Debian Atomic Server",
|
|
version="1.0.0",
|
|
base_packages=self.base_packages,
|
|
additional_packages=server_packages,
|
|
ostree_ref="debian/bookworm/server"
|
|
)
|
|
|
|
blueprint = self.generate_base_blueprint(config)
|
|
blueprint["customizations"]["services"]["enabled"].extend([
|
|
"nginx",
|
|
"postgresql",
|
|
"redis-server",
|
|
"fail2ban"
|
|
])
|
|
|
|
return blueprint
|
|
|
|
def generate_container_blueprint(self) -> Dict[str, Any]:
|
|
"""Generate container atomic blueprint"""
|
|
container_packages = [
|
|
"podman",
|
|
"buildah",
|
|
"skopeo",
|
|
"containers-common",
|
|
"crun"
|
|
]
|
|
|
|
config = AtomicBlueprintConfig(
|
|
name="debian-atomic-container",
|
|
description="Debian Atomic Container Host",
|
|
version="1.0.0",
|
|
base_packages=self.base_packages,
|
|
additional_packages=container_packages,
|
|
ostree_ref="debian/bookworm/container"
|
|
)
|
|
|
|
blueprint = self.generate_base_blueprint(config)
|
|
blueprint["customizations"]["services"]["enabled"].extend([
|
|
"podman"
|
|
])
|
|
|
|
# Add container-specific configurations
|
|
blueprint["customizations"]["filesystem"] = {
|
|
"/var/lib/containers": {
|
|
"type": "directory",
|
|
"mode": "0755"
|
|
}
|
|
}
|
|
|
|
return blueprint
|
|
|
|
def generate_minimal_blueprint(self) -> Dict[str, Any]:
|
|
"""Generate minimal atomic blueprint"""
|
|
minimal_packages = [
|
|
"systemd",
|
|
"systemd-sysv",
|
|
"ostree",
|
|
"linux-image-amd64"
|
|
]
|
|
|
|
config = AtomicBlueprintConfig(
|
|
name="debian-atomic-minimal",
|
|
description="Debian Atomic Minimal System",
|
|
version="1.0.0",
|
|
base_packages=minimal_packages,
|
|
ostree_ref="debian/bookworm/minimal"
|
|
)
|
|
|
|
return self.generate_base_blueprint(config)
|
|
|
|
def _resolve_packages(self, packages: List[str], suite: str, architecture: str) -> List[str]:
|
|
"""Resolve package dependencies"""
|
|
if not self.package_resolver:
|
|
return packages
|
|
|
|
try:
|
|
resolution = self.package_resolver.resolve_package_dependencies(
|
|
packages, suite, architecture, include_recommends=False
|
|
)
|
|
|
|
if resolution.conflicts:
|
|
print(f"Warning: Package conflicts detected: {resolution.conflicts}")
|
|
|
|
if resolution.missing:
|
|
print(f"Warning: Missing packages: {resolution.missing}")
|
|
|
|
return resolution.install_order
|
|
|
|
except Exception as e:
|
|
print(f"Package resolution failed: {e}")
|
|
return packages
|
|
|
|
def _generate_base_customizations(self, config: AtomicBlueprintConfig) -> Dict[str, Any]:
|
|
"""Generate base customizations for blueprint"""
|
|
customizations = {
|
|
"user": config.users or [
|
|
{
|
|
"name": "debian",
|
|
"description": "Debian atomic user",
|
|
"password": "$6$rounds=656000$debian$atomic.system.user",
|
|
"home": "/home/debian",
|
|
"shell": "/bin/bash",
|
|
"groups": ["wheel", "sudo"],
|
|
"uid": 1000,
|
|
"gid": 1000
|
|
}
|
|
],
|
|
"services": config.services or {
|
|
"enabled": ["sshd", "systemd-networkd", "systemd-resolved"],
|
|
"disabled": ["systemd-timesyncd"]
|
|
},
|
|
"kernel": {
|
|
"append": "ostree=/ostree/boot.1/debian/bookworm/0"
|
|
}
|
|
}
|
|
|
|
if config.filesystem_customizations:
|
|
customizations["filesystem"] = config.filesystem_customizations
|
|
|
|
return customizations
|
|
|
|
def generate_osbuild_manifest(self, blueprint: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Generate OSBuild manifest from blueprint"""
|
|
manifest = {
|
|
"version": "2",
|
|
"pipelines": [
|
|
{
|
|
"name": "build",
|
|
"runner": "org.osbuild.linux",
|
|
"stages": []
|
|
}
|
|
]
|
|
}
|
|
|
|
# Add debootstrap stage
|
|
debootstrap_stage = {
|
|
"type": "org.osbuild.debootstrap",
|
|
"options": {
|
|
"suite": "bookworm",
|
|
"mirror": "http://deb.debian.org/debian",
|
|
"arch": blueprint.get("arch", "amd64"),
|
|
"variant": "minbase",
|
|
"apt_proxy": "http://192.168.1.101:3142"
|
|
}
|
|
}
|
|
manifest["pipelines"][0]["stages"].append(debootstrap_stage)
|
|
|
|
# Add APT configuration stage
|
|
apt_config_stage = {
|
|
"type": "org.osbuild.apt.config",
|
|
"options": {
|
|
"sources": self._get_apt_sources(),
|
|
"preferences": {},
|
|
"proxy": "http://192.168.1.101:3142"
|
|
}
|
|
}
|
|
manifest["pipelines"][0]["stages"].append(apt_config_stage)
|
|
|
|
# Add package installation stage
|
|
package_names = [pkg["name"] for pkg in blueprint["packages"]]
|
|
apt_stage = {
|
|
"type": "org.osbuild.apt",
|
|
"options": {
|
|
"packages": package_names,
|
|
"recommends": False,
|
|
"update": True,
|
|
"apt_proxy": "http://192.168.1.101:3142"
|
|
}
|
|
}
|
|
manifest["pipelines"][0]["stages"].append(apt_stage)
|
|
|
|
# Add OSTree commit stage
|
|
ostree_stage = {
|
|
"type": "org.osbuild.ostree.commit",
|
|
"options": {
|
|
"repo": blueprint.get("name", "debian-atomic"),
|
|
"branch": blueprint.get("ostree", {}).get("ref", f"debian/bookworm/{blueprint['name']}"),
|
|
"subject": f"Debian atomic {blueprint['name']} system",
|
|
"body": f"Built from blueprint: {blueprint['name']} v{blueprint['version']}"
|
|
}
|
|
}
|
|
manifest["pipelines"][0]["stages"].append(ostree_stage)
|
|
|
|
return manifest
|
|
|
|
def _get_apt_sources(self) -> Dict[str, Any]:
|
|
"""Get APT sources configuration"""
|
|
if not self.repository_manager:
|
|
return {
|
|
"main": "deb http://deb.debian.org/debian bookworm main",
|
|
"security": "deb http://security.debian.org/debian-security bookworm-security main",
|
|
"updates": "deb http://deb.debian.org/debian bookworm-updates main"
|
|
}
|
|
|
|
return self.repository_manager.generate_apt_config("bookworm", proxy="http://192.168.1.101:3142")
|
|
|
|
def save_blueprint(self, blueprint: Dict[str, Any], output_dir: str = "blueprints") -> str:
|
|
"""Save blueprint to file"""
|
|
output_path = Path(output_dir) / f"{blueprint['name']}.json"
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
with open(output_path, 'w') as f:
|
|
json.dump(blueprint, f, indent=2)
|
|
|
|
return str(output_path)
|
|
|
|
def validate_blueprint(self, blueprint: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Validate blueprint structure and content"""
|
|
validation = {
|
|
"valid": True,
|
|
"errors": [],
|
|
"warnings": [],
|
|
"suggestions": []
|
|
}
|
|
|
|
# Check required fields
|
|
required_fields = ["name", "description", "version", "packages"]
|
|
for field in required_fields:
|
|
if field not in blueprint:
|
|
validation["valid"] = False
|
|
validation["errors"].append(f"Missing required field: {field}")
|
|
|
|
# Validate packages
|
|
if "packages" in blueprint:
|
|
if not blueprint["packages"]:
|
|
validation["warnings"].append("No packages specified")
|
|
|
|
package_names = [pkg.get("name") if isinstance(pkg, dict) else pkg for pkg in blueprint["packages"]]
|
|
|
|
# Check for essential packages
|
|
essential_packages = ["systemd", "ostree"]
|
|
missing_essential = [pkg for pkg in essential_packages if pkg not in package_names]
|
|
if missing_essential:
|
|
validation["suggestions"].append(f"Consider adding essential packages: {missing_essential}")
|
|
|
|
# Validate customizations
|
|
if "customizations" in blueprint and "services" in blueprint["customizations"]:
|
|
services = blueprint["customizations"]["services"]
|
|
if "enabled" in services and "disabled" in services:
|
|
conflicts = set(services["enabled"]) & set(services["disabled"])
|
|
if conflicts:
|
|
validation["valid"] = False
|
|
validation["errors"].append(f"Services both enabled and disabled: {list(conflicts)}")
|
|
|
|
return validation
|
|
|
|
def generate_all_blueprints(self, output_dir: str = "blueprints") -> List[str]:
|
|
"""Generate all standard blueprints"""
|
|
blueprints = [
|
|
("base", self.generate_base_blueprint()),
|
|
("workstation", self.generate_workstation_blueprint()),
|
|
("server", self.generate_server_blueprint()),
|
|
("container", self.generate_container_blueprint()),
|
|
("minimal", self.generate_minimal_blueprint())
|
|
]
|
|
|
|
saved_files = []
|
|
for name, blueprint in blueprints:
|
|
try:
|
|
output_path = self.save_blueprint(blueprint, output_dir)
|
|
saved_files.append(output_path)
|
|
print(f"Generated {name} blueprint: {output_path}")
|
|
except Exception as e:
|
|
print(f"Failed to generate {name} blueprint: {e}")
|
|
|
|
return saved_files
|
|
|
|
def main():
|
|
"""Example usage of blueprint generator"""
|
|
print("Debian Atomic Blueprint Generator")
|
|
|
|
generator = DebianAtomicBlueprintGenerator()
|
|
|
|
# Generate all blueprints
|
|
print("\nGenerating all blueprints...")
|
|
saved_files = generator.generate_all_blueprints()
|
|
|
|
print(f"\nGenerated {len(saved_files)} blueprints:")
|
|
for file_path in saved_files:
|
|
print(f" - {file_path}")
|
|
|
|
# Example: Generate and validate a custom blueprint
|
|
print("\nGenerating custom blueprint...")
|
|
config = AtomicBlueprintConfig(
|
|
name="debian-atomic-custom",
|
|
description="Custom Debian Atomic System",
|
|
version="1.0.0",
|
|
base_packages=["systemd", "ostree"],
|
|
additional_packages=["vim", "curl", "wget"],
|
|
ostree_ref="debian/bookworm/custom"
|
|
)
|
|
|
|
custom_blueprint = generator.generate_base_blueprint(config)
|
|
validation = generator.validate_blueprint(custom_blueprint)
|
|
|
|
print(f"Custom blueprint validation: {'Valid' if validation['valid'] else 'Invalid'}")
|
|
if validation['errors']:
|
|
print(f"Errors: {validation['errors']}")
|
|
if validation['warnings']:
|
|
print(f"Warnings: {validation['warnings']}")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|