deb-bootc-image-builder/scripts/integration-scripts/integrate-with-apt-ostree.py
robojerk 126ee1a849
Some checks failed
particle-os CI / Test particle-os (push) Failing after 1s
particle-os CI / Integration Test (push) Has been skipped
particle-os CI / Security & Quality (push) Failing after 1s
Test particle-os Basic Functionality / test-basic (push) Failing after 1s
particle-os CI / Build and Release (push) Has been skipped
cleanup
2025-08-27 12:30:24 -07:00

266 lines
10 KiB
Python

#!/usr/bin/env python3
"""
Integration script between apt-ostree and deb-bootc-image-builder
This script demonstrates the complete Debian Atomic pipeline:
1. apt-ostree creates OSTree commits from treefiles
2. deb-bootc-image-builder creates bootable images from OSTree commits
This mimics the Fedora Atomic flow: treefiles → rpm-ostree → bootc-image-builder
"""
import os
import sys
import subprocess
import json
import tempfile
import shutil
from pathlib import Path
from typing import Dict, Any, Optional
class DebianAtomicPipeline:
"""Complete Debian Atomic pipeline integration"""
def __init__(self, workspace_path: str):
self.workspace_path = Path(workspace_path)
self.apt_ostree_path = self.workspace_path / "apt-ostree"
self.deb_bootc_path = self.workspace_path / "deb-bootc-image-builder"
self.atomic_configs_path = self.workspace_path / "debian-atomic-configs"
# Validate paths
self._validate_paths()
# Temporary working directory
self.temp_dir = Path(tempfile.mkdtemp(prefix="debian-atomic-"))
print(f"Working directory: {self.temp_dir}")
def _validate_paths(self):
"""Validate that all required components exist"""
required_paths = [
self.apt_ostree_path,
self.deb_bootc_path,
self.atomic_configs_path
]
for path in required_paths:
if not path.exists():
raise FileNotFoundError(f"Required path not found: {path}")
print("✅ All required components found")
def create_ostree_commit(self, treefile: str, variant: str) -> Dict[str, Any]:
"""Step 1: Use apt-ostree to create OSTree commit from treefile"""
print(f"\n🔧 Step 1: Creating OSTree commit from {treefile}")
# Build apt-ostree if needed
apt_ostree_bin = self.apt_ostree_path / "target" / "debug" / "apt-ostree"
if not apt_ostree_bin.exists():
print("Building apt-ostree...")
self._build_apt_ostree()
# Create OSTree commit
treefile_path = self.atomic_configs_path / "treefiles" / treefile
if not treefile_path.exists():
raise FileNotFoundError(f"Treefile not found: {treefile_path}")
# Run apt-ostree compose
result = subprocess.run([
str(apt_ostree_bin), "compose", str(treefile_path)
], capture_output=True, text=True, cwd=self.temp_dir)
if result.returncode != 0:
print(f"Error creating OSTree commit: {result.stderr}")
raise RuntimeError("Failed to create OSTree commit")
print("✅ OSTree commit created successfully")
# Extract commit information
commit_info = self._extract_commit_info()
return commit_info
def _build_apt_ostree(self):
"""Build apt-ostree binary"""
print("Building apt-ostree...")
result = subprocess.run([
"cargo", "build", "--bin", "apt-ostree"
], cwd=self.apt_ostree_path, capture_output=True, text=True)
if result.returncode != 0:
print(f"Build failed: {result.stderr}")
raise RuntimeError("Failed to build apt-ostree")
print("✅ apt-ostree built successfully")
def _extract_commit_info(self) -> Dict[str, Any]:
"""Extract commit information from apt-ostree output"""
# Look for the repository created by apt-ostree
repo_path = self.temp_dir / "apt-ostree-repo"
if not repo_path.exists():
raise RuntimeError("OSTree repository not found")
# Get commit information
commits_dir = repo_path / "commits"
if commits_dir.exists():
commits = list(commits_dir.iterdir())
if commits:
commit_id = commits[0].name
print(f"Found commit: {commit_id}")
# Read metadata
metadata_file = commits[0] / "metadata.json"
if metadata_file.exists():
with open(metadata_file) as f:
metadata = json.load(f)
return {
"commit_id": commit_id,
"repo_path": str(repo_path),
"metadata": metadata
}
raise RuntimeError("No commits found in repository")
def create_bootable_image(self, commit_info: Dict[str, Any], output_format: str = "raw") -> str:
"""Step 2: Use deb-bootc-image-builder to create bootable image from OSTree commit"""
print(f"\n🔧 Step 2: Creating bootable image from OSTree commit")
# Check if deb-bootc-image-builder binary exists
bib_bin = self.deb_bootc_path / "bib" / "bootc-image-builder"
if not bib_bin.exists():
print("Building deb-bootc-image-builder...")
self._build_deb_bootc()
# Create a recipe that uses the OSTree commit
recipe = self._create_ostree_recipe(commit_info)
recipe_path = self.temp_dir / "ostree-recipe.yml"
with open(recipe_path, 'w') as f:
json.dump(recipe, f, indent=2)
print(f"Created recipe: {recipe_path}")
# Run deb-bootc-image-builder
output_image = self.temp_dir / f"debian-atomic-{output_format}.img"
result = subprocess.run([
str(bib_bin), "build", str(recipe_path), "--output", str(output_image)
], capture_output=True, text=True, cwd=self.temp_dir)
if result.returncode != 0:
print(f"Error creating bootable image: {result.stderr}")
raise RuntimeError("Failed to create bootable image")
print(f"✅ Bootable image created: {output_image}")
return str(output_image)
def _build_deb_bootc(self):
"""Build deb-bootc-image-builder"""
print("Building deb-bootc-image-builder...")
result = subprocess.run([
"go", "build", "-o", "bib/bootc-image-builder", "./cmd/bootc-image-builder"
], cwd=self.deb_bootc_path, capture_output=True, text=True)
if result.returncode != 0:
print(f"Build failed: {result.stderr}")
raise RuntimeError("Failed to build deb-bootc-image-builder")
print("✅ deb-bootc-image-builder built successfully")
def _create_ostree_recipe(self, commit_info: Dict[str, Any]) -> Dict[str, Any]:
"""Create a recipe that uses the OSTree commit"""
return {
"name": "debian-atomic-ostree",
"description": "Debian Atomic system built from OSTree commit",
"base_image": "debian:trixie-slim",
"stages": [
{
"name": "ostree_integration",
"type": "ostree",
"config": {
"commit_id": commit_info["commit_id"],
"repo_path": commit_info["repo_path"],
"branch": commit_info["metadata"].get("branch", "debian-atomic")
}
},
{
"name": "system_config",
"type": "system",
"config": {
"hostname": "debian-atomic",
"timezone": "UTC",
"locale": "en_US.UTF-8"
}
}
],
"output": {
"format": "raw",
"size": "5G"
}
}
def run_complete_pipeline(self, treefile: str, variant: str, output_format: str = "raw") -> str:
"""Run the complete Debian Atomic pipeline"""
print(f"🚀 Starting Debian Atomic pipeline")
print(f"Treefile: {treefile}")
print(f"Variant: {variant}")
print(f"Output format: {output_format}")
try:
# Step 1: Create OSTree commit
commit_info = self.create_ostree_commit(treefile, variant)
# Step 2: Create bootable image
output_image = self.create_bootable_image(commit_info, output_format)
print(f"\n🎉 Pipeline completed successfully!")
print(f"Output image: {output_image}")
return output_image
except Exception as e:
print(f"❌ Pipeline failed: {e}")
raise
def cleanup(self):
"""Clean up temporary files"""
if self.temp_dir.exists():
shutil.rmtree(self.temp_dir)
print(f"Cleaned up: {self.temp_dir}")
def main():
"""Main function to demonstrate the integration"""
if len(sys.argv) < 2:
print("Usage: python3 integrate-with-apt-ostree.py <treefile> [variant] [output_format]")
print("Example: python3 integrate-with-apt-ostree.py minimal.yaml minimal raw")
sys.exit(1)
treefile = sys.argv[1]
variant = sys.argv[2] if len(sys.argv) > 2 else "minimal"
output_format = sys.argv[3] if len(sys.argv) > 3 else "raw"
# Get workspace path (assuming script is in deb-bootc-image-builder)
script_dir = Path(__file__).parent
workspace_path = script_dir.parent
try:
pipeline = DebianAtomicPipeline(str(workspace_path))
output_image = pipeline.run_complete_pipeline(treefile, variant, output_format)
print(f"\n📋 Summary:")
print(f"✅ OSTree commit created from {treefile}")
print(f"✅ Bootable image created: {output_image}")
print(f"✅ Debian Atomic pipeline completed successfully!")
print(f"\n🧪 To test the image:")
print(f"qemu-system-x86_64 -m 2G -drive file={output_image},format=raw -nographic")
except Exception as e:
print(f"❌ Pipeline failed: {e}")
sys.exit(1)
finally:
# Don't cleanup automatically for testing
# pipeline.cleanup()
pass
if __name__ == "__main__":
main()