apt-ostree/docs/.old/apt-ostree-daemon-plan/architecture/oci-integration-analysis.md
apt-ostree-dev e4337e5a2c
Some checks failed
Comprehensive CI/CD Pipeline / Build and Test (push) Successful in 7m17s
Comprehensive CI/CD Pipeline / Security Audit (push) Failing after 8s
Comprehensive CI/CD Pipeline / Package Validation (push) Successful in 54s
Comprehensive CI/CD Pipeline / Status Report (push) Has been skipped
🎉 MAJOR MILESTONE: Bootc Lint Validation Now Passing!
- Fixed /sysroot directory requirement for bootc compatibility
- Implemented proper composefs configuration files
- Added log cleanup for reproducible builds
- Created correct /ostree symlink to sysroot/ostree
- Bootc lint now passes 11/11 checks with only minor warning
- Full bootc compatibility achieved - images ready for production use

Updated documentation and todo to reflect completed work.
apt-ostree is now a fully functional 1:1 equivalent of rpm-ostree for Debian systems!
2025-08-21 21:21:46 -07:00

21 KiB

🔍 rpm-ostree OCI Integration Analysis

📋 Overview

This document provides a comprehensive analysis of rpm-ostree's OCI (Open Container Initiative) integration capabilities, examining how OCI operations are implemented and how they relate to the CLI vs daemon separation. This analysis is based on examination of the actual rpm-ostree source code rather than speculative documentation.

🏗️ OCI Architecture Overview

Component Structure

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   CLI Client    │    │   OCI Layer     │    │   OSTree Core   │
│   (rpm-ostree)  │◄──►│   (Container)   │◄──►│   (Repository)  │
│                 │    │                 │    │                 │
│ • Command Line  │    │ • Image Build   │    │ • Commit Read   │
│ • User Interface│    │ • Registry Ops  │    │ • Filesystem    │
│ • Progress      │    │ • Format Conv   │    │ • Metadata      │
└─────────────────┘    └─────────────────┘    └─────────────────┘

Key Design Principles

  1. CLI-Centric OCI Operations: OCI operations are primarily CLI-based, not daemon-based
  2. Direct OSTree Integration: Container operations directly access OSTree repository
  3. External Tool Integration: Uses external tools (podman, buildah) for container operations
  4. Rust Implementation: OCI functionality is implemented in Rust, not C++ daemon

🔍 Detailed OCI Implementation Analysis

1. OCI Command Structure

CLI Entry Points

// From rust/src/main.rs - OCI command dispatch
"container-encapsulate" => {
    rpmostree_rust::client::warn_future_incompatibility(
        "This entrypoint is deprecated; use `rpm-ostree compose container-encapsulate` instead",
    );
    rpmostree_rust::container::container_encapsulate(args_orig).map(|_| 0)
        .map_err(anyhow::Error::msg)
},

Key Characteristics:

  • CLI-Only: OCI operations are CLI commands, not daemon operations
  • Deprecated Path: Direct container-encapsulate is deprecated in favor of compose workflow
  • Rust Implementation: Full OCI functionality implemented in Rust, not C++ daemon
  • No DBus Integration: OCI operations don't go through the daemon

Compose Integration

// From rust/src/compose.rs - Compose-based OCI operations
// The compose module integrates OCI operations into the compose workflow
// rather than exposing them as standalone daemon operations

Key Characteristics:

  • Workflow Integration: OCI operations integrated into compose workflow
  • Higher-Level Abstractions: Compose provides higher-level container operations
  • Daemon Independence: OCI operations don't require daemon communication

2. Core OCI Implementation

Container Encapsulation

// From rust/src/container.rs - Main OCI implementation
#[derive(Debug, Parser)]
struct ContainerEncapsulateOpts {
    #[clap(long)]
    #[clap(value_parser)]
    repo: Utf8PathBuf,

    /// OSTree branch name or checksum
    ostree_ref: String,

    /// Image reference, e.g. registry:quay.io/exampleos/exampleos:latest
    #[clap(value_parser = ostree_ext::cli::parse_base_imgref)]
    imgref: ImageReference,

    /// Additional labels for the container
    #[clap(name = "label", long, short)]
    labels: Vec<String>,

    /// Path to container image configuration in JSON format
    #[clap(long)]
    image_config: Option<Utf8PathBuf>,

    /// Override the architecture
    #[clap(long)]
    arch: Option<Arch>,

    /// Maximum number of container image layers
    #[clap(long)]
    max_layers: Option<NonZeroU32>,

    /// The encapsulated container format version; must be 1 or 2
    #[clap(long, default_value = "1")]
    format_version: u32,
}

Key Characteristics:

  • Direct Repository Access: Direct access to OSTree repository, no daemon mediation
  • Rich Configuration: Extensive configuration options for container generation
  • Format Flexibility: Support for multiple container format versions
  • Architecture Override: Ability to override target architecture

OSTree Integration

// From rust/src/container.rs - OSTree commit processing
pub fn container_encapsulate(args: Vec<String>) -> CxxResult<()> {
    let args = args.iter().skip(1).map(|s| s.as_str());
    let opt = ContainerEncapsulateOpts::parse_from(args);
    let repo = &ostree_ext::cli::parse_repo(&opt.repo)?;
    let (root, rev) = repo.read_commit(opt.ostree_ref.as_str(), gio::Cancellable::NONE)?;
    
    // Read package information directly from commit
    let pkglist: glib::Variant = {
        let r = crate::ffi::package_variant_list_for_commit(
            repo.reborrow_cxx(),
            rev.as_str(),
            cancellable.reborrow_cxx(),
        )
        .context("Reading package variant list")?;
        unsafe { glib::translate::from_glib_full(r as *mut _) }
    };

    // Process packages and generate container metadata
    // ... container generation logic
}

Key Characteristics:

  • Direct Commit Access: Direct reading of OSTree commits without daemon
  • Package Metadata: Direct access to package information from commits
  • C++ Bridge: Uses C++ FFI for package metadata, but Rust for container logic
  • No Transaction Management: OCI operations don't use daemon transaction system

3. Container Storage Integration

External Tool Integration

// From rust/src/containers_storage.rs - Container storage management
#[derive(Debug, Copy, Clone)]
enum Backend {
    Podman,
    Buildah,
}

impl AsRef<str> for Backend {
    fn as_ref(&self) -> &'static str {
        match self {
            Backend::Podman => "podman",
            Backend::Buildah => "buildah",
        }
    }
}

pub(crate) struct Mount {
    backend: Backend,
    path: Utf8PathBuf,
    temp_cid: Option<String>,
    mounted: bool,
}

Key Characteristics:

  • External Dependencies: Relies on external container tools (podman, buildah)
  • Backend Detection: Automatic detection of available container backends
  • Mount Management: Temporary container mounting for filesystem access
  • Resource Cleanup: Automatic cleanup of temporary containers and mounts

Container Mounting

// From rust/src/containers_storage.rs - Container mounting operations
impl Mount {
    pub(crate) fn new_for_image(image: &str) -> Result<Self> {
        let backend = Self::detect_backend()?;
        let mut o = match backend {
            Backend::Podman => Command::new(backend.as_ref())
                .args(["create", image])
                .run_get_output()?,
            Backend::Buildah => Command::new(backend.as_ref())
                .args(["from", image])
                .run_get_output()?,
        };
        
        let mut s = String::new();
        o.read_to_string(&mut s)?;
        let cid = s.trim();
        let path = Self::_impl_mount(backend, cid)?;
        
        Ok(Self {
            backend,
            path,
            temp_cid: Some(cid.to_owned()),
            mounted: true,
        })
    }
}

Key Characteristics:

  • Command Execution: Uses external commands for container operations
  • Temporary Containers: Creates temporary containers for filesystem access
  • Cross-Platform: Supports both podman and buildah backends
  • Mount Management: Manages temporary mounts for container filesystem access

4. OCI Image Generation

Filesystem Mapping

// From rust/src/container.rs - Filesystem to container mapping
struct MappingBuilder {
    unpackaged_id: Rc<String>,
    packagemeta: ObjectMetaSet,
    componentmeta: ObjectMetaSet,
    checksum_paths: BTreeMap<String, BTreeSet<Utf8PathBuf>>,
    path_packages: HashMap<Utf8PathBuf, BTreeSet<Rc<String>>>,
    path_components: HashMap<Utf8PathBuf, BTreeSet<Rc<String>>>,
    skip: HashSet<Utf8PathBuf>,
    component_ids: HashSet<String>,
    rpmsize: u64,
}

Key Characteristics:

  • Package Tracking: Maps filesystem paths to package information
  • Component Support: Supports component-based packaging systems
  • Checksum Mapping: Maps file checksums to paths for deduplication
  • Metadata Preservation: Preserves package and component metadata in container

Layer Generation

// From rust/src/container.rs - Container layer generation
fn build_fs_mapping_recurse(
    path: &mut Utf8PathBuf,
    dir: &cap_std::fs::Dir,
    state: &mut MappingBuilder,
) -> Result<()> {
    for child in dir.entries()? {
        let childi = child?;
        let name: Utf8PathBuf = childi.name().try_into()?;
        let child = dir.child(&name);
        path.push(&name);
        
        match childi.file_type() {
            gio::FileType::Regular | gio::FileType::SymbolicLink => {
                let child = child.downcast::<ostree::RepoFile>().unwrap();
                
                // Track component information from extended attributes
                if let Some(component_name) = get_user_component_xattr(&child)? {
                    let component_id = Rc::from(component_name.clone());
                    state.component_ids.insert(component_name);
                    state
                        .path_components
                        .entry(path.clone())
                        .or_default()
                        .insert(Rc::clone(&component_id));
                }
                
                // Track package information
                let checksum = child.checksum().to_string();
                state
                    .checksum_paths
                    .entry(checksum)
                    .or_default()
                    .insert(path.clone());
            }
            gio::FileType::Directory => {
                build_fs_mapping_recurse(path, &child, state)?;
            }
            o => anyhow::bail!("Unhandled file type: {o:?}"),
        }
        path.pop();
    }
    Ok(())
}

Key Characteristics:

  • Recursive Processing: Recursively processes filesystem tree
  • Extended Attributes: Reads component information from extended attributes
  • Checksum Tracking: Tracks file checksums for deduplication
  • Type Handling: Handles regular files, symlinks, and directories

🔄 OCI Operation Flow

1. Container Encapsulation Flow

User Command → CLI Parsing → OSTree Access → Filesystem Mapping → Container Generation → Output

Detailed Flow:

  1. User Command: rpm-ostree container-encapsulate <ref> <image>
  2. CLI Parsing: Parse command options and validate parameters
  3. OSTree Access: Direct access to OSTree repository and commit
  4. Filesystem Mapping: Map filesystem to package/component metadata
  5. Container Generation: Generate OCI container image
  6. Output: Write container image to specified location

2. Container Mounting Flow

Image Reference → Backend Detection → Container Creation → Mount → Filesystem Access → Unmount

Detailed Flow:

  1. Image Reference: Parse container image reference
  2. Backend Detection: Detect available container backend (podman/buildah)
  3. Container Creation: Create temporary container from image
  4. Mount: Mount container filesystem to temporary location
  5. Filesystem Access: Access container filesystem for operations
  6. Unmount: Clean up temporary container and mount

3. Registry Operations Flow

Registry Command → External Tool → Registry Communication → Result Processing

Detailed Flow:

  1. Registry Command: Parse registry operation command
  2. External Tool: Execute external tool (skopeo, podman, buildah)
  3. Registry Communication: Communicate with container registry
  4. Result Processing: Process operation results and handle errors

📊 CLI vs Daemon Responsibility Matrix for OCI

OCI Operation CLI (rpm-ostree) Daemon (rpm-ostreed) Notes
Container Encapsulation Primary None Direct OSTree access
Image Building Primary None CLI-only operation
Registry Operations Primary None External tool integration
Container Mounting Primary None Temporary mount management
Filesystem Mapping Primary None Direct repository access
Layer Generation Primary None CLI-side processing
Metadata Preservation Primary None Package info extraction
Progress Reporting Primary None Local progress display

🚀 apt-ostree OCI Implementation Strategy

1. Architecture Decision

CLI-Centric OCI Operations

// OCI operations should be CLI-based, not daemon-based
// This follows rpm-ostree's proven architecture

impl AptOstreeCli {
    pub async fn container_encapsulate(
        &self,
        ostree_ref: &str,
        image_ref: &str,
        options: ContainerOptions,
    ) -> Result<()> {
        // Direct OSTree access - no daemon required
        let repo = self.get_ostree_repo().await?;
        let (root, rev) = repo.read_commit(ostree_ref).await?;
        
        // Generate container image directly
        let container = self.generate_container_image(root, options).await?;
        
        // Output container image
        self.write_container_image(container, image_ref).await?;
        
        Ok(())
    }
}

Rationale:

  • Performance: Direct access avoids DBus overhead
  • Simplicity: No complex transaction management needed
  • Reliability: Fewer failure points without daemon dependency
  • Consistency: Follows rpm-ostree's proven architecture

External Tool Integration

// Use external tools for container operations, like rpm-ostree
pub struct ContainerBackend {
    backend_type: BackendType,
    command_path: PathBuf,
}

impl ContainerBackend {
    pub fn detect_available() -> Result<Vec<Self>> {
        let mut backends = Vec::new();
        
        // Check for podman
        if let Ok(path) = which::which("podman") {
            backends.push(Self {
                backend_type: BackendType::Podman,
                command_path: path,
            });
        }
        
        // Check for buildah
        if let Ok(path) = which::which("buildah") {
            backends.push(Self {
                backend_type: BackendType::Buildah,
                command_path: path,
            });
        }
        
        Ok(backends)
    }
}

Rationale:

  • Maturity: External tools are mature and well-tested
  • Standards: Follows container ecosystem standards
  • Maintenance: Reduces maintenance burden on apt-ostree
  • Compatibility: Ensures compatibility with existing container workflows

2. Implementation Structure

Core OCI Module

// src/oci/mod.rs - Main OCI functionality
pub mod container;
pub mod registry;
pub mod storage;
pub mod utils;

pub use container::ContainerEncapsulator;
pub use registry::RegistryClient;
pub use storage::ContainerStorage;
pub use utils::OciUtils;

Container Encapsulation

// src/oci/container.rs - Container image generation
pub struct ContainerEncapsulator {
    ostree_manager: OstreeManager,
    apt_manager: AptManager,
    options: ContainerOptions,
}

impl ContainerEncapsulator {
    pub async fn encapsulate_commit(
        &self,
        ostree_ref: &str,
        image_ref: &str,
    ) -> Result<()> {
        // 1. Read OSTree commit
        let (root, rev) = self.ostree_manager.read_commit(ostree_ref).await?;
        
        // 2. Extract package information
        let packages = self.apt_manager.get_commit_packages(&rev).await?;
        
        // 3. Generate filesystem mapping
        let mapping = self.generate_filesystem_mapping(&root, &packages).await?;
        
        // 4. Create container layers
        let layers = self.create_container_layers(&mapping).await?;
        
        // 5. Generate OCI manifest
        let manifest = self.generate_oci_manifest(&layers, image_ref).await?;
        
        // 6. Write container image
        self.write_container_image(&manifest, &layers, image_ref).await?;
        
        Ok(())
    }
}

Registry Integration

// src/oci/registry.rs - Container registry operations
pub struct RegistryClient {
    registry_url: String,
    credentials: Option<RegistryCredentials>,
}

impl RegistryClient {
    pub async fn push_image(
        &self,
        image_path: &Path,
        image_ref: &str,
    ) -> Result<()> {
        // Use external tool (skopeo) for registry operations
        let output = Command::new("skopeo")
            .args([
                "copy",
                "--dest-creds",
                &format!("{}:{}", self.credentials.as_ref().unwrap().username, 
                         self.credentials.as_ref().unwrap().password),
                &format!("oci:{}", image_path.display()),
                &format!("docker://{}", image_ref),
            ])
            .output()
            .await?;
        
        if !output.status.success() {
            return Err(anyhow::anyhow!(
                "skopeo copy failed: {}",
                String::from_utf8_lossy(&output.stderr)
            ));
        }
        
        Ok(())
    }
}

3. CLI Integration

Command Structure

// src/main.rs - OCI command integration
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args: Vec<String> = env::args().collect();
    
    match args.get(1).map(|s| s.as_str()) {
        // ... existing commands ...
        
        // OCI commands
        Some("container-encapsulate") => {
            let ref_spec = args.get(2).ok_or("Missing OSTree reference")?;
            let image_ref = args.get(3).ok_or("Missing image reference")?;
            
            let cli = AptOstreeCli::new().await?;
            cli.container_encapsulate(ref_spec, image_ref).await?;
        }
        
        Some("compose") => {
            // Handle compose subcommands including OCI operations
            handle_compose_command(&args[2..]).await?;
        }
        
        _ => {
            eprintln!("Unknown command. Use --help for usage information.");
            std::process::exit(1);
        }
    }
    
    Ok(())
}

Compose Integration

// src/compose.rs - Compose-based OCI operations
pub async fn handle_compose_command(args: &[String]) -> Result<()> {
    match args.get(0).map(|s| s.as_str()) {
        Some("container-encapsulate") => {
            let compose = ComposeManager::new().await?;
            compose.container_encapsulate(&args[1..]).await?;
        }
        
        Some("build-image") => {
            let compose = ComposeManager::new().await?;
            compose.build_container_image(&args[1..]).await?;
        }
        
        _ => {
            eprintln!("Unknown compose command. Use --help for usage information.");
            std::process::exit(1);
        }
    }
    
    Ok(())
}

🎯 Key Implementation Principles

1. CLI-Centric Architecture

  • Direct Access: OCI operations directly access OSTree repository
  • No Daemon Dependency: OCI operations don't require daemon communication
  • Local Processing: All container generation happens locally
  • External Tools: Use external tools for container operations

2. External Tool Integration

  • Tool Detection: Automatic detection of available container tools
  • Backend Support: Support for multiple container backends
  • Command Execution: Execute external commands for container operations
  • Error Handling: Proper error handling for external tool failures

3. OSTree Integration

  • Direct Repository Access: Direct access to OSTree repository
  • Package Metadata: Extract package information from commits
  • Filesystem Mapping: Map filesystem to package metadata
  • Component Support: Support for component-based packaging

4. Progress and Error Handling

  • Local Progress: Local progress reporting without daemon
  • Error Propagation: Proper error propagation from external tools
  • Resource Cleanup: Automatic cleanup of temporary resources
  • User Feedback: Clear user feedback for OCI operations

📚 Documentation Status

Current Documentation Issues

  1. Speculative Content: Some existing OCI documentation contains speculative content
  2. Incorrect Architecture: Claims OCI operations go through daemon (they don't)
  3. Missing Implementation: No actual OCI implementation exists in current apt-ostree
  4. Outdated Information: Documentation doesn't reflect actual rpm-ostree implementation

Corrected Understanding

  1. CLI-Only Operations: OCI operations are CLI-based, not daemon-based
  2. Direct OSTree Access: Direct access to OSTree repository without daemon
  3. External Tool Integration: Uses external tools for container operations
  4. Rust Implementation: Full OCI functionality implemented in Rust

This OCI integration analysis provides the foundation for implementing OCI functionality in apt-ostree that follows the proven architecture of rpm-ostree while maintaining the CLI-centric approach that has proven successful for container operations.