did stuff

This commit is contained in:
robojerk 2025-08-26 10:13:20 -07:00
parent 0ee5aa76d9
commit 7572de6f46
5 changed files with 974 additions and 0 deletions

142
.forgejo/workflows/ci.yml Normal file
View file

@ -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 <team@blue-build.org>
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 <team@blue-build.org> $(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"

Binary file not shown.

Binary file not shown.

View file

@ -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()

438
debian_variants_manager.py Normal file
View file

@ -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()