zlayer_builder/backend/mod.rs
1//! Build backend abstraction.
2//!
3//! Provides a unified [`BuildBackend`] trait that decouples the build orchestration
4//! logic from the underlying container tooling. Platform-specific implementations
5//! are selected at runtime via [`detect_backend`].
6//!
7//! # Backends
8//!
9//! - [`BuildahBackend`] — wraps the `buildah` CLI (Linux + macOS with buildah installed).
10//! - [`SandboxBackend`] — macOS-only, uses the Seatbelt sandbox when buildah is unavailable.
11
12mod buildah;
13#[cfg(target_os = "macos")]
14mod sandbox;
15
16pub use buildah::BuildahBackend;
17#[cfg(target_os = "macos")]
18pub use sandbox::SandboxBackend;
19
20use std::path::Path;
21use std::sync::Arc;
22
23use crate::builder::{BuildOptions, BuiltImage, RegistryAuth};
24use crate::dockerfile::Dockerfile;
25use crate::error::{BuildError, Result};
26use crate::tui::BuildEvent;
27
28/// A pluggable build backend.
29///
30/// Implementations handle the low-level mechanics of building, pushing, tagging,
31/// and managing manifest lists for container images.
32#[async_trait::async_trait]
33pub trait BuildBackend: Send + Sync {
34 /// Build a container image from a parsed Dockerfile.
35 ///
36 /// # Arguments
37 ///
38 /// * `context` — path to the build context directory
39 /// * `dockerfile` — parsed Dockerfile IR
40 /// * `options` — build configuration (tags, args, caching, etc.)
41 /// * `event_tx` — optional channel for streaming progress events to a TUI
42 async fn build_image(
43 &self,
44 context: &Path,
45 dockerfile: &Dockerfile,
46 options: &BuildOptions,
47 event_tx: Option<std::sync::mpsc::Sender<BuildEvent>>,
48 ) -> Result<BuiltImage>;
49
50 /// Push an image to a container registry.
51 async fn push_image(&self, tag: &str, auth: Option<&RegistryAuth>) -> Result<()>;
52
53 /// Tag an existing image with a new name.
54 async fn tag_image(&self, image: &str, new_tag: &str) -> Result<()>;
55
56 /// Create a new (empty) manifest list.
57 async fn manifest_create(&self, name: &str) -> Result<()>;
58
59 /// Add an image to an existing manifest list.
60 async fn manifest_add(&self, manifest: &str, image: &str) -> Result<()>;
61
62 /// Push a manifest list (and all referenced images) to a registry.
63 async fn manifest_push(&self, name: &str, destination: &str) -> Result<()>;
64
65 /// Returns `true` if the backend tooling is installed and functional.
66 async fn is_available(&self) -> bool;
67
68 /// Human-readable name for this backend (e.g. `"buildah"`, `"sandbox"`).
69 fn name(&self) -> &'static str;
70}
71
72/// Auto-detect the best available build backend for the current platform.
73///
74/// Selection order:
75///
76/// 1. If the `ZLAYER_BACKEND` environment variable is set to `"buildah"` or
77/// `"sandbox"`, that backend is forced.
78/// 2. On macOS, buildah is tried first; if unavailable, the sandbox backend
79/// is returned as a fallback.
80/// 3. On Linux, only the buildah backend is available.
81///
82/// # Errors
83///
84/// Returns an error if no usable backend can be found.
85pub async fn detect_backend() -> Result<Arc<dyn BuildBackend>> {
86 // Check for explicit override
87 if let Ok(forced) = std::env::var("ZLAYER_BACKEND") {
88 match forced.to_lowercase().as_str() {
89 "buildah" => {
90 let backend = BuildahBackend::new().await?;
91 return Ok(Arc::new(backend));
92 }
93 #[cfg(target_os = "macos")]
94 "sandbox" => {
95 let backend = SandboxBackend::default();
96 return Ok(Arc::new(backend));
97 }
98 other => {
99 return Err(BuildError::BuildahNotFound {
100 message: format!("Unknown ZLAYER_BACKEND value: {other}"),
101 });
102 }
103 }
104 }
105
106 // Auto-detect
107 #[cfg(target_os = "macos")]
108 {
109 if let Ok(backend) = BuildahBackend::try_new().await {
110 Ok(Arc::new(backend))
111 } else {
112 tracing::info!("Buildah not available on macOS, falling back to sandbox backend");
113 Ok(Arc::new(SandboxBackend::default()))
114 }
115 }
116
117 #[cfg(not(target_os = "macos"))]
118 {
119 let backend = BuildahBackend::new().await?;
120 Ok(Arc::new(backend))
121 }
122}