use std::collections::HashMap;
use std::path::Path;
use cuenv_core::manifest::{ContainerImage, Project};
use cuenv_events::emit_stdout;
use super::{CommandExecutor, relative_path_from_root};
pub struct BuildOptions {
pub path: String,
pub package: String,
pub names: Vec<String>,
pub labels: Vec<String>,
}
pub fn execute_build(options: &BuildOptions, executor: &CommandExecutor) -> cuenv_core::Result<()> {
let target_path =
Path::new(&options.path)
.canonicalize()
.map_err(|e| cuenv_core::Error::Io {
source: e,
path: Some(Path::new(&options.path).to_path_buf().into_boxed_path()),
operation: "canonicalize path".to_string(),
})?;
let module = executor.get_module(&target_path)?;
let relative_path = relative_path_from_root(&module.root, &target_path);
let instance = module.get(&relative_path).ok_or_else(|| {
cuenv_core::Error::configuration(format!(
"No CUE instance found at path: {} (relative: {})",
target_path.display(),
relative_path.display()
))
})?;
let project: Project = instance.deserialize()?;
if project.images.is_empty() {
emit_stdout!("cuenv build: no images defined in configuration");
return Ok(());
}
if options.names.is_empty() && options.labels.is_empty() {
emit_stdout!("Available images:\n");
for (name, image) in &project.images {
let desc = image
.description
.as_deref()
.map_or(String::new(), |d| format!(" {d}"));
let tags = if image.tags.is_empty() {
String::new()
} else {
format!(" [{}]", image.tags.join(", "))
};
emit_stdout!(format!(" {name}{tags}{desc}"));
}
return Ok(());
}
let filtered = filter_images(&project.images, &options.names, &options.labels);
if filtered.is_empty() {
emit_stdout!("cuenv build: no images match the specified filters");
return Ok(());
}
for (name, image) in &filtered {
let registry = image
.registry
.as_deref()
.map_or(String::from("local"), |r| r.to_string());
let platforms = if image.platform.is_empty() {
String::from("native")
} else {
image.platform.join(", ")
};
emit_stdout!(format!(
"cuenv build: {name} (context: {}, dockerfile: {}, registry: {registry}, platform: {platforms})",
image.context, image.dockerfile
));
}
emit_stdout!(
"\ncuenv build: execution backends not yet implemented — schema validated successfully"
);
Ok(())
}
fn filter_images(
all_images: &HashMap<String, ContainerImage>,
names: &[String],
labels: &[String],
) -> HashMap<String, ContainerImage> {
all_images
.iter()
.filter(|(name, image)| {
let name_match = names.is_empty() || names.contains(name);
let label_match = labels.is_empty() || labels.iter().any(|l| image.labels.contains(l));
name_match && label_match
})
.map(|(name, image)| (name.clone(), image.clone()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use cuenv_core::manifest::ImageOutputRef;
fn test_image(tags: Vec<&str>, labels: Vec<&str>) -> ContainerImage {
ContainerImage {
image_type: "image".to_string(),
ref_output: ImageOutputRef {
cuenv_output_ref: true,
cuenv_image: "test".to_string(),
cuenv_output: "ref".to_string(),
},
digest: ImageOutputRef {
cuenv_output_ref: true,
cuenv_image: "test".to_string(),
cuenv_output: "digest".to_string(),
},
context: ".".to_string(),
dockerfile: "Dockerfile".to_string(),
build_args: HashMap::new(),
target: None,
tags: tags.into_iter().map(String::from).collect(),
registry: None,
repository: None,
platform: vec![],
depends_on: vec![],
labels: labels.into_iter().map(String::from).collect(),
inputs: vec![],
description: None,
}
}
#[test]
fn test_filter_images_no_filters() {
let mut images = HashMap::new();
images.insert("api".to_string(), test_image(vec!["latest"], vec![]));
images.insert("worker".to_string(), test_image(vec!["latest"], vec![]));
let result = filter_images(&images, &[], &[]);
assert_eq!(result.len(), 2);
}
#[test]
fn test_filter_images_by_name() {
let mut images = HashMap::new();
images.insert("api".to_string(), test_image(vec!["latest"], vec![]));
images.insert("worker".to_string(), test_image(vec!["latest"], vec![]));
let result = filter_images(&images, &["api".to_string()], &[]);
assert_eq!(result.len(), 1);
assert!(result.contains_key("api"));
}
#[test]
fn test_filter_images_by_label() {
let mut images = HashMap::new();
images.insert("api".to_string(), test_image(vec!["latest"], vec!["ci"]));
images.insert("worker".to_string(), test_image(vec!["latest"], vec![]));
let result = filter_images(&images, &[], &["ci".to_string()]);
assert_eq!(result.len(), 1);
assert!(result.contains_key("api"));
}
}