use crate::config::resolve_volume_spec;
use crate::types::{RunMeta, ServiceConfig};
use crate::util::{die, run_cmd};
use std::process::Command;
pub fn ensure_container_available() {
let status = run_cmd(&["container".to_string(), "--version".to_string()], None, false);
if !status.success() {
die("Apple container CLI not found. Install from https://github.com/apple/container");
}
let system = run_cmd(&["container".to_string(), "system".to_string(), "start".to_string()], None, true);
if !system.success() {
die("failed to start container system service");
}
}
pub fn build_container_args(meta: &RunMeta, env_args: &[String], env_file: &Option<String>, read_only: bool) -> Vec<String> {
let mut args: Vec<String> = vec![
"container".to_string(),
"run".to_string(),
"--name".to_string(),
meta.container_name.clone(),
"--mount".to_string(),
format!(
"type=bind,source={},target={}{}",
meta.snapshot_path,
meta.workdir,
if read_only { ",readonly" } else { "" }
),
"-w".to_string(),
meta.workdir.clone(),
];
if let Some(network) = &meta.network {
args.push("--network".to_string());
args.push(network.clone());
}
if let Some(env_file) = env_file {
args.push("--env-file".to_string());
args.push(env_file.clone());
}
for (k, v) in &meta.env {
args.push("-e".to_string());
args.push(format!("{}={}", k, v));
}
for env in env_args {
args.push("-e".to_string());
args.push(env.clone());
}
for vol in &meta.volumes {
args.push("--mount".to_string());
args.push(format!(
"type=bind,source={},target={}{}",
vol.source,
vol.target,
if vol.readonly { ",readonly" } else { "" }
));
}
for p in &meta.ports {
args.push("--publish".to_string());
args.push(p.clone());
}
args.push(meta.image.clone());
for c in &meta.command {
args.push(c.clone());
}
args
}
pub fn build_service_args(run_id: &str, svc: &ServiceConfig, repo_root: &std::path::Path, network: &Option<String>) -> (String, Vec<String>) {
let name = format!("vvbox-svc-{}-{}", run_id, svc.name);
let mut args: Vec<String> = vec![
"container".to_string(),
"run".to_string(),
"--rm".to_string(),
"-d".to_string(),
"--name".to_string(),
name.clone(),
];
if let Some(net) = network {
args.push("--network".to_string());
args.push(net.clone());
}
if let Some(env) = &svc.env {
for (k, v) in env {
args.push("-e".to_string());
args.push(format!("{}={}", k, v));
}
}
if let Some(ports) = &svc.ports {
for p in ports {
args.push("--publish".to_string());
args.push(p.clone());
}
}
if let Some(vols) = &svc.volumes {
for v in vols {
let resolved = resolve_volume_spec(v, repo_root);
args.push("--mount".to_string());
args.push(format!(
"type=bind,source={},target={}{}",
resolved.source,
resolved.target,
if resolved.readonly { ",readonly" } else { "" }
));
}
}
if let Some(workdir) = &svc.workdir {
args.push("-w".to_string());
args.push(workdir.clone());
}
args.push(svc.image.clone());
if let Some(cmd) = &svc.command {
for c in cmd.to_vec() {
args.push(c);
}
}
(name, args)
}
pub fn build_named_service_args(prefix: &str, svc: &ServiceConfig, repo_root: &std::path::Path, network: &Option<String>) -> (String, Vec<String>) {
let name = format!("vvbox-svc-{}-{}", prefix, svc.name);
let mut args: Vec<String> = vec![
"container".to_string(),
"run".to_string(),
"--rm".to_string(),
"-d".to_string(),
"--name".to_string(),
name.clone(),
];
if let Some(net) = network {
args.push("--network".to_string());
args.push(net.clone());
}
if let Some(env) = &svc.env {
for (k, v) in env {
args.push("-e".to_string());
args.push(format!("{}={}", k, v));
}
}
if let Some(ports) = &svc.ports {
for p in ports {
args.push("--publish".to_string());
args.push(p.clone());
}
}
if let Some(vols) = &svc.volumes {
for v in vols {
let resolved = resolve_volume_spec(v, repo_root);
args.push("--mount".to_string());
args.push(format!(
"type=bind,source={},target={}{}",
resolved.source,
resolved.target,
if resolved.readonly { ",readonly" } else { "" }
));
}
}
if let Some(workdir) = &svc.workdir {
args.push("-w".to_string());
args.push(workdir.clone());
}
args.push(svc.image.clone());
if let Some(cmd) = &svc.command {
for c in cmd.to_vec() {
args.push(c);
}
}
(name, args)
}
pub fn stop_container(name: &str) {
let _ = run_cmd(&["container".to_string(), "stop".to_string(), name.to_string()], None, true);
}
pub fn tail_container_logs(name: &str, tail: Option<u32>, since: Option<String>, follow: bool) -> bool {
let mut args: Vec<String> = vec!["container".to_string(), "logs".to_string()];
if follow {
args.push("-f".to_string());
}
if let Some(t) = tail {
args.push("--tail".to_string());
args.push(t.to_string());
}
if let Some(s) = since {
args.push("--since".to_string());
args.push(s);
}
args.push(name.to_string());
let status = run_cmd(&args, None, true);
status.success()
}
pub fn container_list_entries() -> Vec<(String, String)> {
let output = Command::new("container")
.arg("list")
.arg("--all")
.arg("--format")
.arg("json")
.output();
if let Ok(out) = output {
if out.status.success() {
if let Ok(value) = serde_json::from_slice::<serde_json::Value>(&out.stdout) {
if let Some(arr) = value.as_array() {
return arr
.iter()
.filter_map(|item| {
let id = item
.get("configuration")
.and_then(|c| c.get("id"))
.and_then(|v| v.as_str())?;
let status = item.get("status").and_then(|v| v.as_str()).unwrap_or("unknown");
Some((id.to_string(), status.to_string()))
})
.collect();
}
}
}
}
vec![]
}
pub fn list_service_containers(prefix: &str) -> Vec<String> {
let name_prefix = format!("vvbox-svc-{}-", prefix);
container_list_entries()
.into_iter()
.filter(|(id, _)| id.starts_with(&name_prefix))
.map(|(id, _)| id)
.collect()
}
pub fn list_service_status(prefix: &str) -> Vec<(String, String)> {
let name_prefix = format!("vvbox-svc-{}-", prefix);
container_list_entries()
.into_iter()
.filter(|(id, _)| id.starts_with(&name_prefix))
.collect()
}