debian-atomic-config/scripts/generate-variants.py
2025-08-26 10:16:43 -07:00

202 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""
Generate apt-ostree treefiles from variant definitions
"""
import os
import yaml
import argparse
from pathlib import Path
from typing import Dict, List, Any
class VariantGenerator:
"""Generate apt-ostree treefiles from variant definitions"""
def __init__(self, config_dir: str):
self.config_dir = Path(config_dir)
self.variants_dir = self.config_dir / "variants"
self.package_groups_dir = self.config_dir / "package-groups"
self.treefiles_dir = self.config_dir / "treefiles"
# Ensure output directory exists
self.treefiles_dir.mkdir(exist_ok=True)
def load_variant(self, variant_file: str) -> Dict[str, Any]:
"""Load a variant definition file"""
variant_path = self.variants_dir / variant_file
with open(variant_path, 'r') as f:
return yaml.safe_load(f)
def load_package_group(self, group_file: str) -> Dict[str, Any]:
"""Load a package group definition file"""
group_path = self.package_groups_dir / group_file
with open(group_path, 'r') as f:
return yaml.safe_load(f)
def flatten_packages(self, variant: Dict[str, Any]) -> List[str]:
"""Flatten all package lists into a single list"""
packages = []
# Add base packages
if 'base_packages' in variant:
packages.extend(variant['base_packages'])
# Add all other package categories
for key, value in variant.items():
if key.endswith('_packages') and key != 'base_packages':
if isinstance(value, list):
packages.extend(value)
# Remove duplicates while preserving order
seen = set()
unique_packages = []
for pkg in packages:
if pkg not in seen:
seen.add(pkg)
unique_packages.append(pkg)
return unique_packages
def generate_treefile(self, variant: Dict[str, Any]) -> Dict[str, Any]:
"""Generate an apt-ostree treefile from variant definition"""
# Get all packages
packages = self.flatten_packages(variant)
# Get excluded packages
excluded = variant.get('excluded_packages', [])
# Remove excluded packages
final_packages = [pkg for pkg in packages if pkg not in excluded]
# Build treefile structure
treefile = {
'ref': variant['ostree']['ref'],
'repos': variant['repositories'],
'packages': {
'include': final_packages
}
}
# Add system configuration if present
if 'system_config' in variant:
treefile['system'] = variant['system_config']
return treefile
def save_treefile(self, treefile: Dict[str, Any], variant_name: str) -> str:
"""Save treefile to disk"""
filename = f"{variant_name}.yaml"
output_path = self.treefiles_dir / filename
with open(output_path, 'w') as f:
yaml.dump(treefile, f, default_flow_style=False, indent=2)
return str(output_path)
def generate_all_variants(self) -> List[str]:
"""Generate treefiles for all variants"""
generated_files = []
# Find all variant files
variant_files = list(self.variants_dir.glob("*.yaml"))
for variant_file in variant_files:
print(f"Processing variant: {variant_file.name}")
try:
# Load variant
variant = self.load_variant(variant_file.name)
# Generate treefile
treefile = self.generate_treefile(variant)
# Save treefile
variant_name = variant['variant']['name']
output_path = self.save_treefile(treefile, variant_name)
print(f" Generated: {output_path}")
generated_files.append(output_path)
except Exception as e:
print(f" Error processing {variant_file.name}: {e}")
return generated_files
def validate_treefile(self, treefile_path: str) -> bool:
"""Validate a generated treefile"""
try:
with open(treefile_path, 'r') as f:
treefile = yaml.safe_load(f)
# Check required fields
required_fields = ['ref', 'repos', 'packages']
for field in required_fields:
if field not in treefile:
print(f" ❌ Missing required field: {field}")
return False
# Check packages structure
if 'include' not in treefile['packages']:
print(f" ❌ Missing packages.include field")
return False
# Check repositories structure
for repo in treefile['repos']:
if 'name' not in repo or 'url' not in repo:
print(f" ❌ Invalid repository structure: {repo}")
return False
print(f" ✅ Valid treefile: {treefile_path}")
return True
except Exception as e:
print(f" ❌ Error validating {treefile_path}: {e}")
return False
def main():
parser = argparse.ArgumentParser(description="Generate apt-ostree treefiles from variants")
parser.add_argument("--config-dir", default=".", help="Configuration directory")
parser.add_argument("--validate", action="store_true", help="Validate generated treefiles")
parser.add_argument("--variant", help="Generate treefile for specific variant only")
args = parser.parse_args()
generator = VariantGenerator(args.config_dir)
if args.variant:
# Generate single variant
variant_file = f"{args.variant}.yaml"
if not (generator.variants_dir / variant_file).exists():
print(f"Variant file not found: {variant_file}")
return 1
variant = generator.load_variant(variant_file)
treefile = generator.generate_treefile(variant)
variant_name = variant['variant']['name']
output_path = generator.save_treefile(treefile, variant_name)
print(f"Generated: {output_path}")
if args.validate:
generator.validate_treefile(output_path)
else:
# Generate all variants
print("Generating treefiles for all variants...")
generated_files = generator.generate_all_variants()
print(f"\nGenerated {len(generated_files)} treefiles:")
for file_path in generated_files:
print(f" {file_path}")
if args.validate:
print("\nValidating treefiles...")
valid_count = 0
for file_path in generated_files:
if generator.validate_treefile(file_path):
valid_count += 1
print(f"\nValidation complete: {valid_count}/{len(generated_files)} treefiles valid")
if __name__ == "__main__":
main()