mod buildah;
#[cfg(target_os = "windows")]
pub mod hcs;
#[cfg(target_os = "macos")]
mod sandbox;
pub use buildah::BuildahBackend;
#[cfg(target_os = "windows")]
pub use hcs::HcsBackend;
#[cfg(target_os = "macos")]
pub use sandbox::SandboxBackend;
use std::path::Path;
use std::sync::Arc;
use crate::builder::{BuildOptions, BuiltImage, RegistryAuth};
use crate::dockerfile::Dockerfile;
use crate::error::{BuildError, Result};
use crate::tui::BuildEvent;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "lowercase")]
pub enum ImageOs {
#[default]
Linux,
Windows,
}
#[derive(thiserror::Error, Debug)]
#[error("unknown OS: {0} (expected linux or windows)")]
pub struct ImageOsParseError(pub String);
impl std::str::FromStr for ImageOs {
type Err = ImageOsParseError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let os_part = s.split('/').next().unwrap_or("").trim();
match os_part.to_ascii_lowercase().as_str() {
"linux" => Ok(ImageOs::Linux),
"windows" => Ok(ImageOs::Windows),
_ => Err(ImageOsParseError(s.to_string())),
}
}
}
#[async_trait::async_trait]
pub trait BuildBackend: Send + Sync {
async fn build_image(
&self,
context: &Path,
dockerfile: &Dockerfile,
options: &BuildOptions,
event_tx: Option<std::sync::mpsc::Sender<BuildEvent>>,
) -> Result<BuiltImage>;
async fn push_image(&self, tag: &str, auth: Option<&RegistryAuth>) -> Result<()>;
async fn tag_image(&self, image: &str, new_tag: &str) -> Result<()>;
async fn manifest_create(&self, name: &str) -> Result<()>;
async fn manifest_add(&self, manifest: &str, image: &str) -> Result<()>;
async fn manifest_push(&self, name: &str, destination: &str) -> Result<()>;
async fn is_available(&self) -> bool;
fn name(&self) -> &'static str;
}
pub async fn detect_backend(target_os: ImageOs) -> Result<Arc<dyn BuildBackend>> {
if let Ok(forced) = std::env::var("ZLAYER_BACKEND") {
match forced.to_lowercase().as_str() {
"buildah" => {
let backend = BuildahBackend::new().await?;
return Ok(Arc::new(backend));
}
#[cfg(target_os = "macos")]
"sandbox" => {
let backend = SandboxBackend::default();
return Ok(Arc::new(backend));
}
other => {
return Err(BuildError::BuildahNotFound {
message: format!("Unknown ZLAYER_BACKEND value: {other}"),
});
}
}
}
#[cfg(target_os = "windows")]
{
match target_os {
ImageOs::Linux => Err(BuildError::BuildahNotFound {
message: "Linux image building on Windows hosts requires a Linux peer \
(Phase L follow-up will add WSL2-buildah routing)"
.to_string(),
}),
ImageOs::Windows => {
let backend = HcsBackend::new().await?;
Ok(Arc::new(backend))
}
}
}
#[cfg(target_os = "macos")]
{
match target_os {
ImageOs::Linux => {
if let Ok(backend) = BuildahBackend::try_new().await {
Ok(Arc::new(backend))
} else {
tracing::info!(
"Buildah not available on macOS, falling back to sandbox backend"
);
Ok(Arc::new(SandboxBackend::default()))
}
}
ImageOs::Windows => Err(BuildError::BuildahNotFound {
message: "building Windows images requires a Windows host — run this build \
on a Windows node of the ZLayer cluster"
.to_string(),
}),
}
}
#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
{
match target_os {
ImageOs::Linux => {
let backend = BuildahBackend::new().await?;
Ok(Arc::new(backend))
}
ImageOs::Windows => Err(BuildError::BuildahNotFound {
message: "building Windows images requires a Windows host — run this build \
on a Windows node of the ZLayer cluster"
.to_string(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn image_os_parses_simple_and_slash_form() {
assert_eq!("linux".parse::<ImageOs>().unwrap(), ImageOs::Linux);
assert_eq!("Linux".parse::<ImageOs>().unwrap(), ImageOs::Linux);
assert_eq!("windows".parse::<ImageOs>().unwrap(), ImageOs::Windows);
assert_eq!("linux/amd64".parse::<ImageOs>().unwrap(), ImageOs::Linux);
assert_eq!(
"windows/amd64".parse::<ImageOs>().unwrap(),
ImageOs::Windows
);
assert!("darwin".parse::<ImageOs>().is_err());
}
}