harmont_cli/commands/cache/
save.rs1use std::path::Path;
2
3use anyhow::{Context, Result};
4use tracing::info;
5
6use super::manifest::{self, Manifest};
7use crate::orchestrator::docker_client::DockerClient;
8
9#[allow(clippy::print_stdout)]
21pub async fn handle_save(dir: &Path) -> Result<i32> {
22 let docker = DockerClient::connect()?;
23 docker.ping().await?;
24
25 tokio::fs::create_dir_all(dir)
26 .await
27 .with_context(|| format!("create cache dir {}", dir.display()))?;
28
29 let tags = docker.list_images_by_prefix("harmont-local/").await?;
30
31 let mut manifest = Manifest::new();
32
33 for tag in &tags {
34 let filename = manifest::tar_name_for_tag(tag);
35 let tar_path = dir.join(&filename);
36
37 if tar_path.exists() {
38 info!("skip (exists): {filename}");
39 } else {
40 info!("save: {tag} → {filename}");
41 docker.export_image(tag, &tar_path).await?;
42 }
43
44 manifest.images.insert(filename, tag.clone());
45 }
46
47 let manifest_json = serde_json::to_string_pretty(&manifest)?;
48 tokio::fs::write(dir.join("manifest.json"), &manifest_json)
49 .await
50 .context("write manifest.json")?;
51
52 let mut entries = tokio::fs::read_dir(dir).await?;
53 while let Some(entry) = entries.next_entry().await? {
54 let name = entry.file_name();
55 let name_str = name.to_string_lossy();
56 if name_str.ends_with(".tar") && !manifest.images.contains_key(name_str.as_ref()) {
57 info!("prune stale: {name_str}");
58 tokio::fs::remove_file(entry.path()).await.ok();
59 }
60 }
61
62 let hash = manifest.content_hash();
63 println!("{hash}");
64
65 Ok(0)
66}