use std::env;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use std::sync::Arc;
use sui_cache::config::BackendConfig;
use sui_cache::storage::{build_backend, StorageBackend};
use sui_dockerfile_node_cache_daemon::default_socket_path;
use sui_dockerfile_wrapper::{run_wrapper, DaemonAwareCacheClient, FilesystemDockerfileEnvironment, RealCommandRunner, WrapperConfig};
fn resolve_daemon_socket_path(env_override: Option<&str>, probe_exists: impl Fn(&Path) -> bool) -> Option<PathBuf> {
let candidate = env_override.map_or_else(default_socket_path, PathBuf::from);
if probe_exists(&candidate) {
Some(candidate)
} else {
None
}
}
#[tokio::main]
async fn main() -> ExitCode {
let Ok(dockerfile_path_raw) = env::var("SUI_DOCKERFILE_PATH") else {
eprintln!("SUI_DOCKERFILE_PATH is required");
return ExitCode::FAILURE;
};
let dockerfile_path = PathBuf::from(dockerfile_path_raw);
let context_dir = env::var("SUI_BUILD_CONTEXT").map_or_else(|_| PathBuf::from("."), PathBuf::from);
let Ok(image_tag) = env::var("SUI_IMAGE_TAG") else {
eprintln!("SUI_IMAGE_TAG is required");
return ExitCode::FAILURE;
};
let socket_path =
resolve_daemon_socket_path(env::var("SUI_NODE_CACHE_SOCKET_PATH").ok().as_deref(), Path::exists);
let Ok(backend_config_raw) = env::var("SUI_CACHE_BACKEND_CONFIG") else {
eprintln!("SUI_CACHE_BACKEND_CONFIG is required");
return ExitCode::FAILURE;
};
let backend_config = match serde_json::from_str::<BackendConfig>(&backend_config_raw) {
Ok(cfg) => cfg,
Err(e) => {
eprintln!("invalid SUI_CACHE_BACKEND_CONFIG: {e}");
return ExitCode::FAILURE;
}
};
let remote = match build_backend(&backend_config).await {
Ok(b) => b,
Err(e) => {
eprintln!("failed to build remote cache backend: {e}");
return ExitCode::FAILURE;
}
};
let cache: Arc<dyn StorageBackend> = match &socket_path {
Some(path) => Arc::new(DaemonAwareCacheClient::new(Some(path.clone()), remote)),
None => remote,
};
let config = WrapperConfig {
dockerfile_path: dockerfile_path.clone(),
context_dir,
build_args: std::collections::BTreeMap::new(),
image_tag,
daemon_socket_path: socket_path,
};
let env_impl = FilesystemDockerfileEnvironment { build_args: config.build_args.clone() };
let runner = RealCommandRunner;
match run_wrapper(&config, &env_impl, &cache, &runner).await {
Ok(receipt) => match receipt.to_json() {
Ok(json) => {
println!("{json}");
if matches!(receipt.outcome, sui_dockerfile_wrapper::WrapperOutcome::BuildFailed { .. }) {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}
Err(e) => {
eprintln!("failed to render receipt: {e}");
ExitCode::FAILURE
}
},
Err(e) => {
eprintln!("wrapper run failed: {e}");
ExitCode::FAILURE
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_env_override_and_socket_present_resolves_to_default_path() {
let resolved = resolve_daemon_socket_path(None, |p| p == default_socket_path());
assert_eq!(resolved, Some(default_socket_path()));
}
#[test]
fn no_env_override_and_socket_absent_resolves_to_none() {
let resolved = resolve_daemon_socket_path(None, |_| false);
assert_eq!(resolved, None, "no daemon on this node ⇒ direct-to-remote-tier, unchanged Phase 2 behavior");
}
#[test]
fn env_override_path_is_honored_when_present() {
let resolved =
resolve_daemon_socket_path(Some("/custom/socket.sock"), |p| p == Path::new("/custom/socket.sock"));
assert_eq!(resolved, Some(PathBuf::from("/custom/socket.sock")));
}
#[test]
fn env_override_path_absent_still_resolves_to_none() {
let resolved = resolve_daemon_socket_path(Some("/custom/socket.sock"), |_| false);
assert_eq!(resolved, None);
}
}