- ✅ Real package installation (replaced mock installation) - ✅ Real OSTree commit creation from installed packages - ✅ OCI image creation from both commits and rootfs - ✅ Full bootc compatibility with proper labels - ✅ Comprehensive test suite (test-bootc-apt-ostree.sh) - ✅ Container tool validation (skopeo, podman) - ✅ Updated compatibility reports for Ubuntu Questing - ✅ Fixed OCI schema version and field naming issues - ✅ Temporary directory lifecycle fixes - ✅ Serde rename attributes for OCI JSON compliance Ready for Aurora-style workflow deployment!
14 KiB
14 KiB
apt-ostree OCI Container Integration
Overview
apt-ostree integrates with OCI (Open Container Initiative) containers to provide container-native deployment capabilities for Debian/Ubuntu systems. This document explains how apt-ostree implements OCI container integration.
Core OCI Integration
Container Image Generation
apt-ostree can generate OCI container images from OSTree commits:
// OCI integration in src/container.rs
use std::fs;
use std::path::Path;
use serde_json::{json, Value};
pub struct OciManager;
impl OciManager {
// Generate OCI container image from OSTree commit
pub fn generate_oci_image(
commit_checksum: &str,
image_name: &str,
image_tag: &str,
) -> Result<String, Box<dyn std::error::Error>> {
// 1. Extract OSTree commit to filesystem
let ostree_manager = OstreeManager::new()?;
let commit_tree = ostree_manager.read_commit(commit_checksum)?;
// 2. Create OCI image layers
let layers = Self::create_oci_layers(&commit_tree)?;
// 3. Generate OCI manifest
let manifest = Self::generate_oci_manifest(&layers, image_name, image_tag)?;
// 4. Create OCI image archive
Self::create_oci_archive(&manifest, &layers, image_name, image_tag)
}
// Create OCI layers from filesystem
fn create_oci_layers(
filesystem_tree: &OstreeRepoFile,
) -> Result<Vec<Value>, Box<dyn std::error::Error>> {
let mut layers = Vec::new();
// Create layer from filesystem
let layer_path = Self::create_filesystem_layer(filesystem_tree)?;
// Calculate layer digest
let layer_digest = Self::calculate_layer_digest(&layer_path)?;
// Create layer descriptor
let layer_desc = json!({
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": layer_digest,
"size": fs::metadata(&layer_path)?.len()
});
layers.push(layer_desc);
Ok(layers)
}
// Generate OCI manifest
fn generate_oci_manifest(
layers: &[Value],
image_name: &str,
image_tag: &str,
) -> Result<Value, Box<dyn std::error::Error>> {
let manifest = json!({
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:config",
"size": 0
},
"layers": layers
});
Ok(manifest)
}
// Create filesystem layer
fn create_filesystem_layer(
filesystem_tree: &OstreeRepoFile,
) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
// Create temporary directory for layer
let layer_path = tempfile::tempdir()?.path().to_path_buf();
// Extract filesystem tree to layer
let ostree_manager = OstreeManager::new()?;
ostree_manager.extract_tree_to_path(filesystem_tree, &layer_path)?;
// Create tar.gz archive
let archive_path = layer_path.with_extension("tar.gz");
Self::create_tar_gz(&layer_path, &archive_path)?;
Ok(archive_path)
}
// Calculate layer digest
fn calculate_layer_digest(layer_path: &Path) -> Result<String, Box<dyn std::error::Error>> {
use std::io::Read;
use sha2::{Sha256, Digest};
let mut file = fs::File::open(layer_path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
let mut hasher = Sha256::new();
hasher.update(&buffer);
let result = hasher.finalize();
Ok(format!("sha256:{:x}", result))
}
// Create tar.gz archive
fn create_tar_gz(
source_path: &Path,
archive_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
use std::process::Command;
let output = Command::new("tar")
.args(&["-czf", archive_path.to_str().unwrap(), "-C", source_path.to_str().unwrap(), "."])
.output()?;
if !output.status.success() {
return Err(format!(
"Failed to create tar.gz archive: {}",
String::from_utf8_lossy(&output.stderr)
).into());
}
Ok(())
}
}
Container Registry Integration
apt-ostree integrates with OCI container registries:
// Registry integration in src/container.rs
use std::process::Command;
impl OciManager {
// Push OCI image to registry
pub fn push_oci_image(
image_path: &str,
registry_url: &str,
image_name: &str,
image_tag: &str,
username: Option<&str>,
password: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::new("skopeo");
cmd.arg("copy");
// Add credentials if provided
if let (Some(user), Some(pass)) = (username, password) {
cmd.args(&["--dest-creds", &format!("{}:{}", user, pass)]);
}
cmd.args(&[
&format!("oci:{}", image_path),
&format!("docker://{}/{}/{}:{}", registry_url, image_name, image_tag)
]);
let output = cmd.output()?;
if !output.status.success() {
return Err(format!(
"Failed to push OCI image: {}",
String::from_utf8_lossy(&output.stderr)
).into());
}
Ok(())
}
// Pull OCI image from registry
pub fn pull_oci_image(
registry_url: &str,
image_name: &str,
image_tag: &str,
local_path: &str,
username: Option<&str>,
password: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = Command::new("skopeo");
cmd.arg("copy");
// Add credentials if provided
if let (Some(user), Some(pass)) = (username, password) {
cmd.args(&["--src-creds", &format!("{}:{}", user, pass)]);
}
cmd.args(&[
&format!("docker://{}/{}/{}:{}", registry_url, image_name, image_tag),
&format!("oci:{}", local_path)
]);
let output = cmd.output()?;
if !output.status.success() {
return Err(format!(
"Failed to pull OCI image: {}",
String::from_utf8_lossy(&output.stderr)
).into());
}
Ok(())
}
// List available images in registry
pub fn list_registry_images(
registry_url: &str,
username: Option<&str>,
password: Option<&str>,
) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let mut cmd = Command::new("skopeo");
cmd.args(&["list-tags"]);
// Add credentials if provided
if let (Some(user), Some(pass)) = (username, password) {
cmd.args(&["--creds", &format!("{}:{}", user, pass)]);
}
cmd.arg(&format!("docker://{}", registry_url));
let output = cmd.output()?;
if !output.status.success() {
return Err(format!(
"Failed to list registry images: {}",
String::from_utf8_lossy(&output.stderr)
).into());
}
// Parse output to extract image names
let output_str = String::from_utf8(output.stdout)?;
let images: Vec<String> = output_str
.lines()
.filter(|line| !line.trim().is_empty())
.map(|line| line.trim().to_string())
.collect();
Ok(images)
}
}
Bootc Compatibility
Bootc Image Generation
apt-ostree can generate bootc-compatible images:
// Bootc integration in src/container.rs
impl OciManager {
// Generate bootc-compatible image
pub fn generate_bootc_image(
commit_checksum: &str,
image_name: &str,
image_tag: &str,
) -> Result<String, Box<dyn std::error::Error>> {
// 1. Generate OCI image
let oci_image_path = Self::generate_oci_image(commit_checksum, image_name, image_tag)?;
// 2. Add bootc-specific metadata
Self::add_bootc_metadata(&oci_image_path, image_name, image_tag)?;
Ok(oci_image_path)
}
// Add bootc-specific metadata
fn add_bootc_metadata(
image_path: &str,
image_name: &str,
image_tag: &str,
) -> Result<(), Box<dyn std::error::Error>> {
// Create bootc metadata
let bootc_metadata = json!({
"bootc": {},
"deployment": {
"type": "ostree",
"ref": "ubuntu/24.04/x86_64/desktop"
}
});
// Write metadata to image
Self::write_image_metadata(image_path, &bootc_metadata)
}
// Write metadata to OCI image
fn write_image_metadata(
image_path: &str,
metadata: &Value,
) -> Result<(), Box<dyn std::error::Error>> {
// Create metadata directory
let metadata_path = format!("{}/blobs/sha256/metadata", image_path);
fs::create_dir_all(&metadata_path)?;
// Write metadata file
let metadata_file = format!("{}/bootc.json", metadata_path);
fs::write(metadata_file, serde_json::to_string_pretty(metadata)?)?;
Ok(())
}
}
mmdebstrap Integration
Base Image Creation
apt-ostree uses mmdebstrap for efficient base image creation:
// mmdebstrap integration in src/container.rs
impl OciManager {
// Create base system image with mmdebstrap
pub fn create_base_image(
release: &str,
arch: &str,
packages: &[String],
) -> Result<String, Box<dyn std::error::Error>> {
use std::process::Command;
// Create temporary directory for base system
let base_path = tempfile::tempdir()?.path().to_path_buf();
let mut cmd = Command::new("mmdebstrap");
cmd.args(&["--arch", arch, "--variant", "minbase", release, base_path.to_str().unwrap()]);
// Add additional packages if specified
if !packages.is_empty() {
cmd.arg("--include");
cmd.arg(&packages.join(","));
}
let output = cmd.output()?;
if !output.status.success() {
return Err(format!(
"Failed to create base system: {}",
String::from_utf8_lossy(&output.stderr)
).into());
}
// Create OCI image from base system
let image_path = Self::create_oci_from_filesystem(&base_path, "base", "latest")?;
Ok(image_path)
}
// Create OCI image from filesystem
fn create_oci_from_filesystem(
filesystem_path: &Path,
image_name: &str,
image_tag: &str,
) -> Result<String, Box<dyn std::error::Error>> {
// Create OCI image structure
let image_path = tempfile::tempdir()?.path().to_path_buf();
let blobs_path = image_path.join("blobs").join("sha256");
fs::create_dir_all(&blobs_path)?;
// Create layer from filesystem
let layer_path = Self::create_filesystem_layer_from_path(filesystem_path)?;
let layer_digest = Self::calculate_layer_digest(&layer_path)?;
// Copy layer to blobs directory
let blob_path = blobs_path.join(&layer_digest[7..]); // Remove "sha256:" prefix
fs::copy(&layer_path, &blob_path)?;
// Create manifest
let manifest = Self::generate_oci_manifest(&[json!({
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": layer_digest,
"size": fs::metadata(&blob_path)?.len()
})], image_name, image_tag)?;
// Write manifest
let manifest_path = image_path.join("manifest.json");
fs::write(manifest_path, serde_json::to_string_pretty(&manifest)?)?;
Ok(image_path.to_string_lossy().to_string())
}
// Create filesystem layer from path
fn create_filesystem_layer_from_path(
filesystem_path: &Path,
) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
// Create temporary directory for layer
let layer_path = tempfile::tempdir()?.path().to_path_buf();
// Create tar.gz archive of filesystem
let archive_path = layer_path.with_extension("tar.gz");
Self::create_tar_gz(filesystem_path, &archive_path)?;
Ok(archive_path)
}
}
Future Enhancements
Planned Features
- Enhanced OCI Support: Full OCI specification compliance
- Registry Authentication: Advanced registry authentication methods
- Image Optimization: Layer optimization and compression
- Multi-Architecture Support: Support for multiple architectures
Integration Roadmap
- Phase 1: Basic OCI integration (🔄 In Progress)
- Phase 2: Registry integration (📋 Planned)
- Phase 3: Bootc compatibility (📋 Planned)
- Phase 4: Advanced features (📋 Planned)
Manual OCI Build Process
Prerequisites
oci-image-tool(from Ubuntu packages)skopeofor image validation- OSTree repository with test commit
Build Steps
- Checkout OSTree Commit: Extract filesystem content from OSTree commit
- Create OCI Image Structure: Set up OCI directory structure with blobs
- Create Filesystem Layer: Tar and compress filesystem content
- Update Configuration: Add layer digest to OCI config
- Create OCI Manifest: Generate manifest with layer and config references
- Create OCI Index: Generate index for multi-platform support
- Validate Image: Use skopeo to validate OCI compliance
CLI Commands
# Build OCI image from OSTree commit
apt-ostree oci build --source test/oci/demo --output my-image.oci --format oci --repo /path/to/ostree/repo
# Build Docker image from OSTree commit
apt-ostree oci build --source test/oci/demo --output my-image.tar --format docker --repo /path/to/ostree/repo
# Push to registry
apt-ostree oci push /tmp/demo-image.oci localhost:5000 demo:latest
Image Features
- OCI Specification Compliance: Schema Version 2, SHA256 digests, Gzip compression
- Supported Formats: OCI directory structure, Docker tar archive
- Advanced Configuration: Custom labels, environment variables, exposed ports, volume mounts