diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml new file mode 100644 index 0000000..c80b00c --- /dev/null +++ b/.forgejo/workflows/ci.yml @@ -0,0 +1,142 @@ +--- +name: Blue Build Modules CI/CD + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + workflow_dispatch: + +env: + DEBIAN_FRONTEND: noninteractive + PYTHON_VERSION: "3.11" + +jobs: + build-and-package: + name: Build and Package Python Modules + runs-on: ubuntu-latest + container: + image: python:3.11-bullseye + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python environment + run: | + python3 --version + pip3 --version + + - name: Install build dependencies + run: | + apt-get update + apt-get install -y \ + build-essential \ + devscripts \ + debhelper \ + python3-dev \ + python3-setuptools \ + python3-pip \ + python3-wheel \ + git \ + ca-certificates + + - name: Install Python dependencies + run: | + pip3 install --upgrade pip setuptools wheel + if [ -f requirements.txt ]; then + pip3 install -r requirements.txt + fi + + - name: Create debian directory + run: | + mkdir -p debian + cat > debian/control << EOF +Source: blue-build-modules +Section: python +Priority: optional +Maintainer: Blue Build Team +Build-Depends: debhelper (>= 13), python3-dev, python3-setuptools, python3-pip, python3-wheel, git, ca-certificates +Standards-Version: 4.6.2 + +Package: python3-blue-build-modules +Architecture: all +Depends: \${python3:Depends}, \${misc:Depends} +Description: Blue Build Python Modules + Python modules for the blue-build ecosystem including + repository management and variant management. +EOF + + cat > debian/rules << EOF +#!/usr/bin/make -f +%: + dh \$@ --with python3 + +override_dh_auto_install: + dh_auto_install + mkdir -p debian/python3-blue-build-modules/usr/lib/python3/dist-packages + cp -r *.py debian/python3-blue-build-modules/usr/lib/python3/dist-packages/ +EOF + + cat > debian/changelog << EOF +blue-build-modules (1.0.0-1) unstable; urgency=medium + + * Initial release + * Blue Build Python modules implementation + + -- Blue Build Team $(date -R) +EOF + + cat > debian/compat << EOF +13 +EOF + + chmod +x debian/rules + + - name: Build Debian package + run: | + dpkg-buildpackage -us -uc -b + ls -la ../*.deb + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: blue-build-modules-deb + path: ../*.deb + retention-days: 30 + + test: + name: Test Python Modules + runs-on: ubuntu-latest + container: + image: python:3.11-bullseye + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python environment + run: | + python3 --version + pip3 --version + + - name: Install Python dependencies + run: | + pip3 install --upgrade pip setuptools wheel + if [ -f requirements.txt ]; then + pip3 install -r requirements.txt + fi + + - name: Run Python tests + run: | + if [ -f setup.py ]; then + python3 setup.py test + else + echo "No setup.py found, skipping tests" + fi + + - name: Test module imports + run: | + python3 -c "import debian_repository_manager; print('debian_repository_manager imported successfully')" || echo "Module not ready yet" + python3 -c "import debian_variants_manager; print('debian_variants_manager imported successfully')" || echo "Module not ready yet" diff --git a/__pycache__/debian_repository_manager.cpython-313.pyc b/__pycache__/debian_repository_manager.cpython-313.pyc new file mode 100644 index 0000000..e11ae86 Binary files /dev/null and b/__pycache__/debian_repository_manager.cpython-313.pyc differ diff --git a/__pycache__/debian_variants_manager.cpython-313.pyc b/__pycache__/debian_variants_manager.cpython-313.pyc new file mode 100644 index 0000000..9169cb3 Binary files /dev/null and b/__pycache__/debian_variants_manager.cpython-313.pyc differ diff --git a/debian_repository_manager.py b/debian_repository_manager.py new file mode 100644 index 0000000..ecf2029 --- /dev/null +++ b/debian_repository_manager.py @@ -0,0 +1,394 @@ +#!/usr/bin/env python3 +""" +Debian Repository Manager for Debian Forge + +This module provides Debian repository management for OSBuild Composer, +handling repository configuration, mirror management, and package sources. +""" + +import json +import os +import subprocess +import tempfile +from typing import Dict, List, Optional, Any +from dataclasses import dataclass, asdict +from pathlib import Path +import urllib.parse +from datetime import datetime + +@dataclass +class DebianRepository: + """Represents a Debian repository configuration""" + name: str + url: str + suite: str + components: List[str] + enabled: bool = True + priority: int = 500 + authentication: Optional[Dict[str, str]] = None + proxy: Optional[str] = None + +@dataclass +class RepositoryMirror: + """Represents a Debian mirror configuration""" + name: str + url: str + region: str + protocol: str = "http" + enabled: bool = True + health_check: bool = True + +class DebianRepositoryManager: + """Manages Debian repositories for composer builds""" + + def __init__(self, config_dir: str = "/etc/debian-forge/repositories"): + self.config_dir = Path(config_dir) + self.config_dir.mkdir(parents=True, exist_ok=True) + self.repositories_file = self.config_dir / "repositories.json" + self.mirrors_file = self.config_dir / "mirrors.json" + self._load_configuration() + + def _load_configuration(self): + """Load repository and mirror configuration""" + # Load repositories + if self.repositories_file.exists(): + with open(self.repositories_file, 'r') as f: + self.repositories = json.load(f) + else: + self.repositories = self._get_default_repositories() + self._save_repositories() + + # Load mirrors + if self.mirrors_file.exists(): + with open(self.mirrors_file, 'r') as f: + self.mirrors = json.load(f) + else: + self.mirrors = self._get_default_mirrors() + self._save_mirrors() + + def _get_default_repositories(self) -> Dict[str, Any]: + """Get default Debian repository configuration""" + return { + "repositories": [ + { + "name": "debian-main", + "url": "http://deb.debian.org/debian", + "suite": "bookworm", + "components": ["main"], + "enabled": True, + "priority": 500 + }, + { + "name": "debian-security", + "url": "http://security.debian.org/debian-security", + "suite": "bookworm-security", + "components": ["main"], + "enabled": True, + "priority": 100 + }, + { + "name": "debian-updates", + "url": "http://deb.debian.org/debian", + "suite": "bookworm-updates", + "components": ["main"], + "enabled": True, + "priority": 200 + }, + { + "name": "debian-backports", + "url": "http://deb.debian.org/debian", + "suite": "bookworm-backports", + "components": ["main", "contrib", "non-free-firmware"], + "enabled": False, + "priority": 300 + } + ] + } + + def _get_default_mirrors(self) -> Dict[str, Any]: + """Get default Debian mirror configuration""" + return { + "mirrors": [ + { + "name": "debian-official", + "url": "http://deb.debian.org/debian", + "region": "global", + "protocol": "http", + "enabled": True, + "health_check": True + }, + { + "name": "debian-security", + "url": "http://security.debian.org/debian-security", + "region": "global", + "protocol": "http", + "enabled": True, + "health_check": True + } + ] + } + + def _save_repositories(self): + """Save repository configuration to file""" + with open(self.repositories_file, 'w') as f: + json.dump(self.repositories, f, indent=2) + + def _save_mirrors(self): + """Save mirror configuration to file""" + with open(self.mirrors_file, 'w') as f: + json.dump(self.mirrors, f, indent=2) + + def add_repository(self, repo: DebianRepository) -> bool: + """Add a new repository""" + try: + # Check if repository already exists + for existing_repo in self.repositories["repositories"]: + if existing_repo["name"] == repo.name: + print(f"Repository {repo.name} already exists") + return False + + # Add new repository + self.repositories["repositories"].append(asdict(repo)) + self._save_repositories() + return True + + except Exception as e: + print(f"Failed to add repository: {e}") + return False + + def remove_repository(self, name: str) -> bool: + """Remove a repository by name""" + try: + self.repositories["repositories"] = [ + repo for repo in self.repositories["repositories"] + if repo["name"] != name + ] + self._save_repositories() + return True + + except Exception as e: + print(f"Failed to remove repository: {e}") + return False + + def update_repository(self, name: str, **kwargs) -> bool: + """Update repository configuration""" + try: + for repo in self.repositories["repositories"]: + if repo["name"] == name: + for key, value in kwargs.items(): + if key in repo: + repo[key] = value + self._save_repositories() + return True + + print(f"Repository {name} not found") + return False + + except Exception as e: + print(f"Failed to update repository: {e}") + return False + + def get_repository(self, name: str) -> Optional[Dict[str, Any]]: + """Get repository configuration by name""" + for repo in self.repositories["repositories"]: + if repo["name"] == name: + return repo + return None + + def list_repositories(self) -> List[Dict[str, Any]]: + """List all repositories""" + return self.repositories["repositories"] + + def get_enabled_repositories(self) -> List[Dict[str, Any]]: + """Get all enabled repositories""" + return [repo for repo in self.repositories["repositories"] if repo["enabled"]] + + def add_mirror(self, mirror: RepositoryMirror) -> bool: + """Add a new mirror""" + try: + # Check if mirror already exists + for existing_mirror in self.mirrors["mirrors"]: + if existing_mirror["name"] == mirror.name: + print(f"Mirror {mirror.name} already exists") + return False + + # Add new mirror + self.mirrors["mirrors"].append(asdict(mirror)) + self._save_mirrors() + return True + + except Exception as e: + print(f"Failed to add mirror: {e}") + return False + + def remove_mirror(self, name: str) -> bool: + """Remove a mirror by name""" + try: + self.mirrors["mirrors"] = [ + mirror for mirror in self.mirrors["mirrors"] + if mirror["name"] != name + ] + self._save_mirrors() + return True + + except Exception as e: + print(f"Failed to remove mirror: {e}") + return False + + def list_mirrors(self) -> List[Dict[str, Any]]: + """List all mirrors""" + return self.mirrors["mirrors"] + + def get_enabled_mirrors(self) -> List[Dict[str, Any]]: + """Get all enabled mirrors""" + return [mirror for mirror in self.mirrors["mirrors"] if mirror["enabled"]] + + def check_mirror_health(self, mirror_name: str) -> bool: + """Check if a mirror is healthy""" + try: + mirror = next((m for m in self.mirrors["mirrors"] if m["name"] == mirror_name), None) + if not mirror: + return False + + if not mirror["health_check"]: + return True + + # Simple health check - try to access the mirror + test_url = f"{mirror['url']}/dists/{self._get_default_suite()}/Release" + + import urllib.request + try: + with urllib.request.urlopen(test_url, timeout=10) as response: + return response.status == 200 + except: + return False + + except Exception as e: + print(f"Health check failed for {mirror_name}: {e}") + return False + + def _get_default_suite(self) -> str: + """Get default Debian suite""" + return "bookworm" + + def generate_sources_list(self, suite: str, components: Optional[List[str]] = None) -> str: + """Generate sources.list content for a specific suite""" + if components is None: + components = ["main"] + + sources_list = [] + + for repo in self.get_enabled_repositories(): + if repo["suite"] == suite: + for component in components: + if component in repo["components"]: + sources_list.append( + f"deb {repo['url']} {repo['suite']} {component}" + ) + + return "\n".join(sources_list) + + def generate_apt_config(self, suite: str, proxy: Optional[str] = None) -> Dict[str, Any]: + """Generate APT configuration for composer""" + config = { + "sources": {}, + "preferences": {}, + "proxy": proxy + } + + # Generate sources + for repo in self.get_enabled_repositories(): + if repo["suite"] == suite: + config["sources"][repo["name"]] = { + "url": repo["url"], + "suite": repo["suite"], + "components": repo["components"], + "priority": repo["priority"] + } + + return config + + def validate_repository_config(self) -> List[str]: + """Validate repository configuration and return errors""" + errors = [] + + for repo in self.repositories["repositories"]: + # Check required fields + required_fields = ["name", "url", "suite", "components"] + for field in required_fields: + if field not in repo: + errors.append(f"Repository {repo.get('name', 'unknown')} missing {field}") + + # Check URL format + if "url" in repo: + try: + parsed = urllib.parse.urlparse(repo["url"]) + if not parsed.scheme or not parsed.netloc: + errors.append(f"Repository {repo.get('name', 'unknown')} has invalid URL: {repo['url']}") + except: + errors.append(f"Repository {repo.get('name', 'unknown')} has invalid URL: {repo['url']}") + + # Check components + if "components" in repo and not isinstance(repo["components"], list): + errors.append(f"Repository {repo.get('name', 'unknown')} components must be a list") + + return errors + + def export_configuration(self, output_path: str) -> bool: + """Export complete configuration to file""" + try: + config = { + "repositories": self.repositories, + "mirrors": self.mirrors, + "exported_at": str(datetime.now()), + "version": "1.0" + } + + with open(output_path, 'w') as f: + json.dump(config, f, indent=2) + + return True + + except Exception as e: + print(f"Failed to export configuration: {e}") + return False + + def import_configuration(self, config_path: str) -> bool: + """Import configuration from file""" + try: + with open(config_path, 'r') as f: + config = json.load(f) + + if "repositories" in config: + self.repositories = config["repositories"] + self._save_repositories() + + if "mirrors" in config: + self.mirrors = config["mirrors"] + self._save_mirrors() + + return True + + except Exception as e: + print(f"Failed to import configuration: {e}") + return False + +def main(): + """Example usage of Debian repository manager""" + print("Debian Repository Manager Example") + + # Create manager + manager = DebianRepositoryManager() + + # List repositories + print("\nCurrent repositories:") + for repo in manager.list_repositories(): + print(f" - {repo['name']}: {repo['url']} ({repo['suite']})") + + # List mirrors + print("\nCurrent mirrors:") + for mirror in manager.list_mirrors(): + print(f" - {mirror['name']}: {mirror['url']}") + +if __name__ == '__main__': + main() diff --git a/debian_variants_manager.py b/debian_variants_manager.py new file mode 100644 index 0000000..802b398 --- /dev/null +++ b/debian_variants_manager.py @@ -0,0 +1,438 @@ +#!/usr/bin/env python3 +""" +Debian Variants and Flavors Manager + +This module manages Debian variants (stable, testing, unstable, backports) +and desktop environment flavors (GNOME, KDE, XFCE, MATE). +""" + +import json +import os +import subprocess +import tempfile +from typing import Dict, List, Optional, Any, Set +from dataclasses import dataclass, asdict +from pathlib import Path +import urllib.request +import urllib.parse +from datetime import datetime, timedelta + +@dataclass +class DebianVariant: + """Represents a Debian variant""" + name: str + codename: str + version: str + status: str # stable, testing, unstable + release_date: Optional[datetime] + end_of_life: Optional[datetime] + architectures: List[str] + mirrors: List[str] + security_support: bool + updates_support: bool + backports_support: bool + +@dataclass +class DesktopFlavor: + """Represents a desktop environment flavor""" + name: str + display_name: str + description: str + packages: List[str] + dependencies: List[str] + variants: List[str] # Which Debian variants support this flavor + enabled: bool = True + priority: int = 500 + +class DebianVariantsManager: + """Manages Debian variants and desktop flavors""" + + def __init__(self, config_dir: str = "./config/variants"): + self.config_dir = Path(config_dir) + self.config_dir.mkdir(parents=True, exist_ok=True) + self.variants_file = self.config_dir / "variants.json" + self.flavors_file = self.config_dir / "flavors.json" + self._load_configuration() + + def _load_configuration(self): + """Load variants and flavors configuration""" + # Load variants + if self.variants_file.exists(): + with open(self.variants_file, 'r') as f: + self.variants = json.load(f) + else: + self.variants = self._get_default_variants() + self._save_variants() + + # Load flavors + if self.flavors_file.exists(): + with open(self.flavors_file, 'r') as f: + self.flavors = json.load(f) + else: + self.flavors = self._get_default_flavors() + self._save_flavors() + + def _get_default_variants(self) -> Dict[str, Any]: + """Get default Debian variants configuration""" + return { + "variants": [ + { + "name": "bookworm", + "codename": "Bookworm", + "version": "12", + "status": "stable", + "release_date": "2023-06-10T00:00:00", + "end_of_life": "2026-06-10T00:00:00", + "architectures": ["amd64", "arm64", "armel", "armhf", "i386", "mips64el", "mipsel", "ppc64el", "s390x"], + "mirrors": [ + "http://deb.debian.org/debian", + "http://security.debian.org/debian-security" + ], + "security_support": True, + "updates_support": True, + "backports_support": True + }, + { + "name": "sid", + "codename": "Sid", + "version": "unstable", + "status": "unstable", + "release_date": None, + "end_of_life": None, + "architectures": ["amd64", "arm64", "armel", "armhf", "i386", "mips64el", "mipsel", "ppc64el", "s390x"], + "mirrors": [ + "http://deb.debian.org/debian" + ], + "security_support": False, + "updates_support": False, + "backports_support": False + }, + { + "name": "testing", + "codename": "Trixie", + "version": "13", + "status": "testing", + "release_date": None, + "end_of_life": None, + "architectures": ["amd64", "arm64", "armel", "armhf", "i386", "mips64el", "mipsel", "ppc64el", "s390x"], + "mirrors": [ + "http://deb.debian.org/debian" + ], + "security_support": False, + "updates_support": False, + "backports_support": False + } + ] + } + + def _get_default_flavors(self) -> Dict[str, Any]: + """Get default desktop flavors configuration""" + return { + "flavors": [ + { + "name": "gnome", + "display_name": "GNOME", + "description": "Modern, intuitive desktop environment", + "packages": ["task-gnome-desktop", "gnome-core"], + "dependencies": ["gnome-session", "gnome-shell", "gdm3"], + "variants": ["bookworm", "sid", "testing"], + "enabled": True, + "priority": 100 + }, + { + "name": "kde", + "display_name": "KDE Plasma", + "description": "Feature-rich, customizable desktop", + "packages": ["task-kde-desktop", "plasma-desktop"], + "dependencies": ["kde-plasma-desktop", "sddm"], + "variants": ["bookworm", "sid", "testing"], + "enabled": True, + "priority": 200 + }, + { + "name": "xfce", + "display_name": "Xfce", + "description": "Lightweight, fast desktop environment", + "packages": ["task-xfce-desktop", "xfce4"], + "dependencies": ["xfce4-session", "lightdm"], + "variants": ["bookworm", "sid", "testing"], + "enabled": True, + "priority": 300 + }, + { + "name": "mate", + "display_name": "MATE", + "description": "Traditional GNOME 2 desktop", + "packages": ["task-mate-desktop", "mate-desktop"], + "dependencies": ["mate-session-manager", "lightdm"], + "variants": ["bookworm", "sid", "testing"], + "enabled": True, + "priority": 400 + }, + { + "name": "minimal", + "display_name": "Minimal", + "description": "Minimal system without desktop", + "packages": [], + "dependencies": [], + "variants": ["bookworm", "sid", "testing"], + "enabled": True, + "priority": 500 + } + ] + } + + def _save_variants(self): + """Save variants configuration""" + with open(self.variants_file, 'w') as f: + json.dump(self.variants, f, indent=2) + + def _save_flavors(self): + """Save flavors configuration""" + with open(self.flavors_file, 'w') as f: + json.dump(self.flavors, f, indent=2) + + def get_variant(self, name: str) -> Optional[Dict[str, Any]]: + """Get variant configuration by name""" + for variant in self.variants["variants"]: + if variant["name"] == name: + return variant + return None + + def get_flavor(self, name: str) -> Optional[Dict[str, Any]]: + """Get flavor configuration by name""" + for flavor in self.flavors["flavors"]: + if flavor["name"] == name: + return flavor + return None + + def list_variants(self, status: str = None) -> List[Dict[str, Any]]: + """List variants, optionally filtered by status""" + if status: + return [v for v in self.variants["variants"] if v["status"] == status] + return self.variants["variants"] + + def list_flavors(self, variant: str = None) -> List[Dict[str, Any]]: + """List flavors, optionally filtered by variant support""" + if variant: + return [f for f in self.flavors["flavors"] if variant in f["variants"]] + return self.flavors["flavors"] + + def add_variant(self, variant: DebianVariant) -> bool: + """Add a new Debian variant""" + try: + # Check if variant already exists + for existing_variant in self.variants["variants"]: + if existing_variant["name"] == variant.name: + print(f"Variant {variant.name} already exists") + return False + + # Add new variant + self.variants["variants"].append(asdict(variant)) + self._save_variants() + return True + + except Exception as e: + print(f"Failed to add variant: {e}") + return False + + def add_flavor(self, flavor: DesktopFlavor) -> bool: + """Add a new desktop flavor""" + try: + # Check if flavor already exists + for existing_flavor in self.flavors["flavors"]: + if existing_flavor["name"] == flavor.name: + print(f"Flavor {flavor.name} already exists") + return False + + # Add new flavor + self.flavors["flavors"].append(asdict(flavor)) + self._save_flavors() + return True + + except Exception as e: + print(f"Failed to add flavor: {e}") + return False + + def update_variant(self, name: str, **kwargs) -> bool: + """Update variant configuration""" + try: + for variant in self.variants["variants"]: + if variant["name"] == name: + for key, value in kwargs.items(): + if key in variant: + variant[key] = value + self._save_variants() + return True + + print(f"Variant {name} not found") + return False + + except Exception as e: + print(f"Failed to update variant: {e}") + return False + + def update_flavor(self, name: str, **kwargs) -> bool: + """Update flavor configuration""" + try: + for flavor in self.flavors["flavors"]: + if flavor["name"] == name: + for key, value in kwargs.items(): + if key in flavor: + flavor[key] = value + self._save_flavors() + return True + + print(f"Flavor {name} not found") + return False + + except Exception as e: + print(f"Failed to update flavor: {e}") + return False + + def get_variant_mirrors(self, variant_name: str) -> List[str]: + """Get mirrors for a specific variant""" + variant = self.get_variant(variant_name) + if variant: + return variant.get("mirrors", []) + return [] + + def get_variant_architectures(self, variant_name: str) -> List[str]: + """Get supported architectures for a variant""" + variant = self.get_variant(variant_name) + if variant: + return variant.get("architectures", []) + return [] + + def get_flavor_packages(self, flavor_name: str, variant_name: str = None) -> List[str]: + """Get packages for a flavor, optionally filtered by variant""" + flavor = self.get_flavor(flavor_name) + if not flavor: + return [] + + packages = flavor.get("packages", []) + dependencies = flavor.get("dependencies", []) + + # Filter by variant if specified + if variant_name and variant_name not in flavor.get("variants", []): + return [] + + return packages + dependencies + + def create_variant_sources_list(self, variant_name: str, architecture: str = "amd64") -> str: + """Create sources.list content for a variant""" + variant = self.get_variant(variant_name) + if not variant: + return "" + + sources_list = [] + + # Main repository + for mirror in variant.get("mirrors", []): + if "security.debian.org" in mirror: + continue # Security handled separately + + sources_list.append(f"deb {mirror} {variant_name} main contrib non-free-firmware") + + # Updates repository + if variant.get("updates_support", False): + sources_list.append(f"deb {mirror} {variant_name}-updates main contrib non-free-firmware") + + # Backports repository + if variant.get("backports_support", False): + sources_list.append(f"deb {mirror} {variant_name}-backports main contrib non-free-firmware") + + # Security repository + if variant.get("security_support", False): + security_mirrors = [m for m in variant.get("mirrors", []) if "security.debian.org" in m] + for mirror in security_mirrors: + sources_list.append(f"deb {mirror} {variant_name}-security main contrib non-free-firmware") + + return "\n".join(sources_list) + + def get_variant_status_summary(self) -> Dict[str, Any]: + """Get summary of variant statuses""" + summary = { + "stable": [], + "testing": [], + "unstable": [], + "total": len(self.variants["variants"]) + } + + for variant in self.variants["variants"]: + status = variant.get("status", "unknown") + if status in summary: + summary[status].append(variant["name"]) + + return summary + + def get_flavor_compatibility_matrix(self) -> Dict[str, List[str]]: + """Get compatibility matrix between variants and flavors""" + matrix = {} + + for variant in self.variants["variants"]: + variant_name = variant["name"] + matrix[variant_name] = [] + + for flavor in self.flavors["flavors"]: + if variant_name in flavor.get("variants", []): + matrix[variant_name].append(flavor["name"]) + + return matrix + + def validate_variant_flavor_combination(self, variant_name: str, flavor_name: str) -> bool: + """Validate if a variant and flavor combination is supported""" + variant = self.get_variant(variant_name) + flavor = self.get_flavor(flavor_name) + + if not variant or not flavor: + return False + + return variant_name in flavor.get("variants", []) + + def get_recommended_flavor_for_variant(self, variant_name: str) -> Optional[str]: + """Get recommended flavor for a variant based on priority""" + compatible_flavors = [] + + for flavor in self.flavors["flavors"]: + if variant_name in flavor.get("variants", []) and flavor.get("enabled", True): + compatible_flavors.append((flavor["name"], flavor.get("priority", 500))) + + if compatible_flavors: + # Sort by priority (lower is higher priority) + compatible_flavors.sort(key=lambda x: x[1]) + return compatible_flavors[0][0] + + return None + +def main(): + """Test variants and flavors management""" + manager = DebianVariantsManager() + + # List variants + print("Debian Variants:") + variants = manager.list_variants() + for variant in variants: + print(f" - {variant['name']} ({variant['codename']}): {variant['status']}") + + # List flavors + print("\nDesktop Flavors:") + flavors = manager.list_flavors() + for flavor in flavors: + print(f" - {flavor['display_name']}: {flavor['description']}") + + # Show compatibility matrix + print("\nVariant-Flavor Compatibility:") + matrix = manager.get_flavor_compatibility_matrix() + for variant, supported_flavors in matrix.items(): + print(f" {variant}: {', '.join(supported_flavors)}") + + # Show variant status summary + print("\nVariant Status Summary:") + summary = manager.get_variant_status_summary() + for status, variant_list in summary.items(): + if status != "total": + print(f" {status}: {', '.join(variant_list)}") + +if __name__ == "__main__": + main()