Skip to main content

harmont_cli/commands/cache/
restore.rs

1use std::path::Path;
2
3use anyhow::{Context, Result};
4use tracing::{info, warn};
5
6use super::manifest;
7use crate::orchestrator::docker_client::DockerClient;
8
9/// Restore cached Docker images from tar files in the given directory.
10///
11/// Each `.tar` file is mapped back to its `harmont-local/*` tag via
12/// [`manifest::tag_from_tar_name`]. Images that already exist in the
13/// local Docker daemon are skipped.
14///
15/// # Errors
16///
17/// Returns an error if the Docker daemon is unreachable or a filesystem
18/// operation on `dir` fails.
19#[allow(clippy::print_stderr)]
20pub async fn handle_restore(dir: &Path) -> Result<i32> {
21    let docker = DockerClient::connect()?;
22    docker.ping().await?;
23
24    if !dir.exists() {
25        info!("cache dir does not exist, nothing to restore");
26        eprintln!("restored 0/0 images (cache dir missing)");
27        return Ok(0);
28    }
29
30    let mut tars = Vec::new();
31    let mut entries = tokio::fs::read_dir(dir)
32        .await
33        .with_context(|| format!("read cache dir {}", dir.display()))?;
34    while let Some(entry) = entries.next_entry().await? {
35        let name = entry.file_name();
36        let name_str = name.to_string_lossy().to_string();
37        if std::path::Path::new(&name_str)
38            .extension()
39            .is_some_and(|ext| ext.eq_ignore_ascii_case("tar"))
40        {
41            tars.push((name_str, entry.path()));
42        }
43    }
44
45    let total = tars.len();
46    let mut restored = 0u32;
47    let mut skipped = 0u32;
48
49    for (filename, tar_path) in &tars {
50        let Some(tag) = manifest::tag_from_tar_name(filename) else {
51            warn!("skip unrecognized tar: {filename}");
52            continue;
53        };
54
55        if docker.image_exists(&tag).await? {
56            info!("skip (present): {tag}");
57            skipped += 1;
58            continue;
59        }
60
61        info!("restore: {filename} → {tag}");
62        match docker.import_image(tar_path).await {
63            Ok(()) => restored += 1,
64            Err(e) => {
65                warn!("failed to load {filename}: {e}");
66            }
67        }
68    }
69
70    eprintln!("restored {restored}/{total} images ({skipped} already present)");
71    Ok(0)
72}