zlayer-builder 0.13.0

Dockerfile parsing and buildah-based container image building
Documentation
//! Windows → WSL2 buildah build backend.
//!
//! Builds LINUX images on a Windows host by running `buildah` inside the
//! `zlayer` WSL2 distro (`wsl.exe -d <distro> -- buildah …`), delegating to
//! [`BuildahBackend`] driven by a [`BuildahExecutor`] whose transport is
//! [`BuildahTransport::Wsl`]. The executor rewrites Windows drive-rooted argv
//! (`C:\…` → `/mnt/c/…`) so the in-distro buildah reads the Windows-side build
//! context + rendered Dockerfile through `/mnt`.

use std::path::Path;
use std::sync::mpsc;

use crate::buildah::{BuildahExecutor, BuildahTransport};
use crate::builder::{BuildOptions, BuiltImage, RegistryAuth};
use crate::dockerfile::Dockerfile;
use crate::error::{BuildError, Result};
use crate::tui::BuildEvent;

use super::buildah::BuildahBackend;
use super::BuildBackend;

/// Runs buildah inside the `zlayer` WSL2 distro to build Linux images on Windows.
pub struct Wsl2BuildBackend {
    inner: BuildahBackend,
    distro: String,
}

impl Wsl2BuildBackend {
    /// Construct a WSL2 buildah backend targeting `distro`.
    ///
    /// # Errors
    /// [`BuildError::NotSupported`] if the distro is missing;
    /// [`BuildError::BuildahNotFound`] if buildah is not callable in-distro.
    pub async fn new(distro: &str) -> Result<Self> {
        if !zlayer_wsl::distro::distro_exists().await {
            return Err(BuildError::NotSupported {
                operation: format!(
                    "WSL2 distro '{distro}' not found — run `zlayer daemon install` to \
                     provision it before building Linux images on this host"
                ),
            });
        }
        let probe = tokio::process::Command::new("wsl.exe")
            .args(["-d", distro, "--", "buildah", "--version"])
            .output()
            .await
            .map_err(|e| BuildError::BuildahNotFound {
                message: format!("failed to invoke wsl.exe for distro '{distro}': {e}"),
            })?;
        if !probe.status.success() {
            return Err(BuildError::BuildahNotFound {
                message: format!(
                    "buildah is not available inside WSL2 distro '{distro}': {}",
                    String::from_utf8_lossy(&probe.stderr).trim()
                ),
            });
        }
        let executor =
            BuildahExecutor::with_path("buildah").with_transport(BuildahTransport::Wsl {
                distro: distro.to_string(),
            });
        Ok(Self {
            inner: BuildahBackend::with_executor(executor),
            distro: distro.to_string(),
        })
    }
}

#[async_trait::async_trait]
impl BuildBackend for Wsl2BuildBackend {
    async fn build_image(
        &self,
        context: &Path,
        dockerfile: &Dockerfile,
        options: &BuildOptions,
        event_tx: Option<mpsc::Sender<BuildEvent>>,
    ) -> Result<BuiltImage> {
        self.inner
            .build_image(context, dockerfile, options, event_tx)
            .await
    }
    async fn push_image(&self, tag: &str, auth: Option<&RegistryAuth>) -> Result<()> {
        self.inner.push_image(tag, auth).await
    }
    async fn tag_image(&self, image: &str, new_tag: &str) -> Result<()> {
        self.inner.tag_image(image, new_tag).await
    }
    async fn manifest_create(&self, name: &str) -> Result<()> {
        self.inner.manifest_create(name).await
    }
    async fn manifest_add(&self, manifest: &str, image: &str) -> Result<()> {
        self.inner.manifest_add(manifest, image).await
    }
    async fn manifest_push(
        &self,
        name: &str,
        destination: &str,
        auth: Option<&RegistryAuth>,
    ) -> Result<()> {
        self.inner.manifest_push(name, destination, auth).await
    }
    async fn is_available(&self) -> bool {
        if !zlayer_wsl::distro::distro_exists().await {
            return false;
        }
        tokio::process::Command::new("wsl.exe")
            .args(["-d", self.distro.as_str(), "--", "buildah", "--version"])
            .output()
            .await
            .is_ok_and(|o| o.status.success())
    }
    fn name(&self) -> &'static str {
        "wsl2-buildah"
    }
}