Преглед на файлове

feat: --build-node flag to auto-build image on a cluster node

Alexander Korolev преди 3 месеца
родител
ревизия
9391712331
променени са 3 файла, в които са добавени 66 реда и са изтрити 0 реда
  1. 4 0
      src/cli.rs
  2. 60 0
      src/docker/mod.rs
  3. 2 0
      src/main.rs

+ 4 - 0
src/cli.rs

@@ -50,6 +50,10 @@ pub enum Commands {
         /// Explicit source node for image sync (default: auto-detect).
         #[arg(long)]
         sync_image_source: Option<String>,
+
+        /// Build image on this node if not found (requires docker-images/<recipe>/).
+        #[arg(long)]
+        build_node: Option<String>,
     },
 
     /// Show container logs

+ 60 - 0
src/docker/mod.rs

@@ -23,6 +23,7 @@ pub struct ServeOpts {
     pub auto_power_on: bool,
     pub sync_image: bool,
     pub sync_image_source: Option<String>,
+    pub build_node: Option<String>,
 }
 pub(crate) fn shq(s: &str) -> String {
     let mut out = String::with_capacity(s.len() + 2);
@@ -80,6 +81,65 @@ pub async fn serve(
     )
     .await?;
     use self::sync::sync_image_to_nodes;
+
+    // Build image on specified node if not found on any cluster node.
+    if let Some(ref build_node_name) = opts.build_node {
+        if !opts.dry_run && !cluster.nodes.iter().any(|n| n.name == *build_node_name) {
+            anyhow::bail!("build-node '{}' not in cluster", build_node_name);
+        }
+        if !opts.dry_run {
+            let build_dir = format!("docker-images/{}", recipe_name);
+            let local_dir = std::path::Path::new(&build_dir);
+            if !local_dir.join("build.sh").exists() {
+                anyhow::bail!(
+                    "No build system at '{}' (missing build.sh)",
+                    local_dir.display()
+                );
+            }
+            let build_node = cluster
+                .nodes
+                .iter()
+                .find(|n| n.name == *build_node_name)
+                .expect("build-node already validated");
+
+            // Check image presence on the build node.
+            let check = format!(
+                "docker image ls --format '{{{{.ID}}}}' {} | head -1",
+                recipe.docker_image
+            );
+            let present = run_ssh(config, build_node, check, false).await?;
+            if present.trim().is_empty() {
+                info!(
+                    "Building image '{}' on '{}'...",
+                    recipe.docker_image, build_node.name
+                );
+                // Upload build directory.
+                crate::ssh::upload_dir(
+                    config,
+                    build_node,
+                    local_dir,
+                    &format!("/tmp/{}-build", recipe_name),
+                    false,
+                )
+                .await?;
+                let build_cmd = format!(
+                    "cd /tmp/{}-build && TAG={} bash build.sh 2>&1",
+                    recipe_name, recipe.docker_image
+                );
+                crate::ssh::run_ssh_long_running(config, build_node, build_cmd, false).await?;
+                info!(
+                    "Image '{}' built on '{}'",
+                    recipe.docker_image, build_node.name
+                );
+            } else {
+                info!(
+                    "Image '{}' already present on '{}'",
+                    recipe.docker_image, build_node.name
+                );
+            }
+        }
+    }
+
     let mods_remote_path = mods::upload_mods_to_cluster(
         config,
         &cluster,

+ 2 - 0
src/main.rs

@@ -90,6 +90,7 @@ async fn main() -> Result<()> {
             auto_power_on,
             sync_image,
             sync_image_source,
+            build_node,
         } => {
             let cluster = docker::serve(
                 &config,
@@ -103,6 +104,7 @@ async fn main() -> Result<()> {
                     auto_power_on,
                     sync_image,
                     sync_image_source,
+                    build_node,
                 },
             )
             .await?;