omni_forge/image_builder/
mod.rs1mod ensure;
3mod image_gen;
4
5use anyhow::Context;
6use anyhow::Result;
7use ensure::common;
8use ensure::ensure_devcontainers_cli;
9use ensure::ensure_docker;
10use ensure::ensure_npm;
11use serde::{ Deserialize, Serialize };
12use serde_json::Value;
13use std::collections::HashMap;
14use std::fs;
15use std::io;
16use std::path::Path;
17use std::process::Command;
18use anyhow::anyhow;
19#[derive(Debug, Serialize, Deserialize)]
20pub struct DevContainer {
21 pub name: String,
22 pub image: String,
23 pub features: HashMap<String, Option<FeatureData>>,
24}
25
26#[derive(Debug, Serialize, Deserialize, Clone)]
27pub struct FeatureData {
28 pub version: Option<String>,
29}
30
31const DOCKER_REGISTRY: &str = "localhost:5000";
32
33pub fn build_devcontainer(devcontainer_path: &Path) -> Result<String> {
34 println!("Final path: {}", devcontainer_path.display());
35
36 let content = fs
38 ::read_to_string(devcontainer_path)
39 .context("failed to read the path to the dev container")?;
40 let config: Value = serde_json5
41 ::from_str(&content)
42 .context("Failed to serialize 'App/.devcontainer/devcontainer.json'")?;
43
44 let image_name = generate_image_name(&config, devcontainer_path).context(
46 "Failed to get image name"
47 )?;
48
49 let workspace_folder = devcontainer_path
51 .parent() .and_then(|p| p.parent()) .ok_or_else(||
54 io::Error::new(io::ErrorKind::Other, "Failed to determine workspace folder")
55 )?;
56
57 println!("Path {}", workspace_folder.display());
58
59 let output = Command::new("devcontainer")
61 .args([
62 "build",
63 "--workspace-folder",
64 workspace_folder.to_str().unwrap(), "--image-name",
66 &image_name,
67 ])
68 .output()?;
69
70 if !output.status.success() {
71 return Err(
72 io::Error::new(io::ErrorKind::Other, String::from_utf8_lossy(&output.stderr)).into()
73 );
74 }
75
76 let tagged_image = format!("{}/{}", DOCKER_REGISTRY, image_name);
78 let tag_output = Command::new("docker").args(["tag", &image_name, &tagged_image]).output()?;
79
80 if !tag_output.status.success() {
81 return Err(
82 io::Error::new(io::ErrorKind::Other, String::from_utf8_lossy(&tag_output.stderr)).into()
83 );
84 }
85
86 let push_output = Command::new("docker").args(["push", &tagged_image]).output()?;
88
89 if !push_output.status.success() {
90 return Err(
91 io::Error
92 ::new(io::ErrorKind::Other, String::from_utf8_lossy(&push_output.stderr))
93 .into()
94 );
95 }
96
97 Ok(tagged_image)
98}
99
100fn generate_image_name(config: &Value, devcontainer_path: &Path) -> io::Result<String> {
101 let name = if let Some(name) = config.get("name").and_then(|n| n.as_str()) {
103 sanitize_docker_name(name)
105 } else {
106 let dir_name = devcontainer_path
108 .parent()
109 .and_then(|p| p.file_name())
110 .and_then(|n| n.to_str())
111 .ok_or_else(|| {
112 io::Error::new(io::ErrorKind::Other, "Failed to determine container name from path")
113 })?;
114 sanitize_docker_name(dir_name)
115 };
116
117 let version = config
119 .get("version")
120 .and_then(|v| v.as_str())
121 .unwrap_or("latest");
122
123 Ok(format!("{}-devcontainer:{}", name, version))
124}
125
126fn sanitize_docker_name(name: &str) -> String {
127 name.to_lowercase()
130 .chars()
131 .map(|c| {
132 match c {
133 'a'..='z' | '0'..='9' | '.' | '_' | '-' => c,
134 _ => '-',
135 }
136 })
137 .collect()
138}
139
140pub fn scan_and_build(path: &Path) -> Result<()> {
142 if !path.exists() {
143 return Err(anyhow!("Failed to locate application build path"));
144 }
145 image_gen::gen_devcontainer(path.to_str().unwrap());
146 let status = ensure::ensure_installations().context("Failed to enture installation")?;
147 println!("Installation status: {:?}", status);
148
149 println!("Building devcontainer image...");
150
151 let dev_ctr_json_str = format!("{}/.devcontainer/devcontainer.json", path.to_str().unwrap());
152 let dev_ctr_json_path: &Path = Path::new(&dev_ctr_json_str);
153
154 match build_devcontainer(dev_ctr_json_path) {
155 Ok(image) => println!("Built container image: {}", image),
156 Err(e) => eprintln!("Failed to build container: {}", e),
157 }
158
159 Ok(())
160}