public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH proxmox-offline-mirror 0/4] extend/add commands
@ 2022-09-21  8:12 Fabian Grünbichler
  2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 1/4] pool: add diff and list helpers Fabian Grünbichler
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Fabian Grünbichler @ 2022-09-21  8:12 UTC (permalink / raw)
  To: pve-devel

this series adds diff commands for diffing two snapshots of a mirror
(`mirror snapshot diff`), a medium and its source mirrors (`medium
diff`) and extends `mirror snapshot list` to allow listing *all*
snapshots of *all* configured mirrors by leaving out the previously
mandatory mirror ID.

Fabian Grünbichler (4):
  pool: add diff and list helpers
  snapshots: add diff command
  medium: add diff command
  cli: allow listing snapshots of all mirrors

 src/bin/proxmox_offline_mirror_cmds/medium.rs | 107 ++++++++++-
 src/bin/proxmox_offline_mirror_cmds/mirror.rs | 128 ++++++++++++--
 src/medium.rs                                 | 103 ++++++++++-
 src/mirror.rs                                 |  15 +-
 src/pool.rs                                   | 167 +++++++++++++++++-
 src/types.rs                                  |  16 +-
 6 files changed, 520 insertions(+), 16 deletions(-)

-- 
2.30.2





^ permalink raw reply	[flat|nested] 6+ messages in thread

* [pve-devel] [PATCH proxmox-offline-mirror 1/4] pool: add diff and list helpers
  2022-09-21  8:12 [pve-devel] [PATCH proxmox-offline-mirror 0/4] extend/add commands Fabian Grünbichler
@ 2022-09-21  8:12 ` Fabian Grünbichler
  2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 2/4] snapshots: add diff command Fabian Grünbichler
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Fabian Grünbichler @ 2022-09-21  8:12 UTC (permalink / raw)
  To: pve-devel

one for diffing two relative paths within a pool (e.g., for comparing
snapshots), one for diffing two pools (e.g., for diffing mirror and
mirror on medium), and one for listing paths.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
we could extend Diff with a list of error paths and make most of the
errors here non-fatal, not sure whether that is nicer?

 src/pool.rs  | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/types.rs |  16 ++++-
 2 files changed, 181 insertions(+), 2 deletions(-)

diff --git a/src/pool.rs b/src/pool.rs
index b6047b8..5dba775 100644
--- a/src/pool.rs
+++ b/src/pool.rs
@@ -1,7 +1,7 @@
 use std::{
     cmp::max,
     collections::{hash_map::Entry, HashMap},
-    fs::{hard_link, remove_dir, File},
+    fs::{hard_link, remove_dir, File, Metadata},
     ops::Deref,
     os::linux::fs::MetadataExt,
     path::{Path, PathBuf},
@@ -15,6 +15,8 @@ use proxmox_sys::fs::{create_path, file_get_contents, replace_file, CreateOption
 use proxmox_time::epoch_i64;
 use walkdir::WalkDir;
 
+use crate::types::Diff;
+
 #[derive(Debug)]
 /// Pool consisting of two (possibly overlapping) directory trees:
 /// - pool_dir contains checksum files added by `add_file`
@@ -542,6 +544,169 @@ impl PoolLockGuard<'_> {
         std::fs::rename(&abs_from, &abs_to)
             .map_err(|err| format_err!("Failed to rename {abs_from:?} to {abs_to:?} - {err}"))
     }
+
+    /// Calculate diff between two pool dirs
+    pub(crate) fn diff_dirs(&self, path: &Path, other_path: &Path) -> Result<Diff, Error> {
+        let mut diff = Diff::default();
+
+        let handle_entry = |entry: Result<walkdir::DirEntry, walkdir::Error>,
+                            base: &Path,
+                            other_base: &Path,
+                            changed: Option<&mut Vec<(PathBuf, u64)>>,
+                            missing: &mut Vec<(PathBuf, u64)>|
+         -> Result<(), Error> {
+            let path = entry?.into_path();
+
+            let meta = path.metadata()?;
+            if !meta.is_file() {
+                return Ok(());
+            };
+
+            let relative = path.strip_prefix(base)?;
+            let mut absolute = other_base.to_path_buf();
+            absolute.push(relative);
+            if absolute.exists() {
+                if let Some(changed) = changed {
+                    let other_meta = absolute.metadata()?;
+                    if other_meta.st_ino() != meta.st_ino() {
+                        changed.push((
+                            relative.to_path_buf(),
+                            meta.st_size().abs_diff(other_meta.st_size()),
+                        ));
+                    }
+                }
+            } else {
+                missing.push((relative.to_path_buf(), meta.st_size()));
+            }
+
+            Ok(())
+        };
+
+        let path = self.get_path(path)?;
+        let other_path = self.get_path(other_path)?;
+
+        WalkDir::new(&path).into_iter().try_for_each(|entry| {
+            handle_entry(
+                entry,
+                &path,
+                &other_path,
+                Some(&mut diff.changed.paths),
+                &mut diff.removed.paths,
+            )
+        })?;
+        WalkDir::new(&other_path)
+            .into_iter()
+            .try_for_each(|entry| {
+                handle_entry(entry, &other_path, &path, None, &mut diff.added.paths)
+            })?;
+
+        Ok(diff)
+    }
+
+    /// Calculate diff between two pools
+    pub(crate) fn diff_pools(&self, other: &Pool) -> Result<Diff, Error> {
+        let mut diff = Diff::default();
+
+        let handle_entry = |entry: Result<walkdir::DirEntry, walkdir::Error>,
+                            pool: &Pool,
+                            pool_csums: &HashMap<u64, CheckSums>,
+                            other_pool: &Pool,
+                            other_csums: &HashMap<u64, CheckSums>,
+                            changed: Option<&mut Vec<(PathBuf, u64)>>,
+                            missing: &mut Vec<(PathBuf, u64)>|
+         -> Result<(), Error> {
+            let path = entry?.into_path();
+
+            let meta = path.metadata()?;
+            if !meta.is_file() {
+                return Ok(());
+            };
+
+            let base = &pool.link_dir;
+
+            let relative = path.strip_prefix(base)?;
+            let absolute = other_pool.get_path(relative)?;
+            if absolute.exists() {
+                if let Some(changed) = changed {
+                    let csum = match pool_csums.get(&meta.st_ino()) {
+                        Some(csum) => csum,
+                        None => {
+                            eprintln!("{path:?} path not registered with pool.");
+                            changed.push((relative.to_path_buf(), 0)); // TODO add warning/error field?
+                            return Ok(());
+                        }
+                    };
+                    let other_meta = absolute.metadata()?;
+                    let other_csum = match other_csums.get(&other_meta.st_ino()) {
+                        Some(csum) => csum,
+                        None => {
+                            eprintln!("{absolute:?} path not registered with pool.");
+                            changed.push((relative.to_path_buf(), 0)); // TODO add warning/error field?
+                            return Ok(());
+                        }
+                    };
+                    if csum != other_csum {
+                        changed.push((
+                            relative.to_path_buf(),
+                            meta.st_size().abs_diff(other_meta.st_size()),
+                        ));
+                    }
+                }
+            } else {
+                missing.push((relative.to_path_buf(), meta.st_size()));
+            }
+
+            Ok(())
+        };
+
+        let other = other.lock()?;
+        let (csums, _) = self.get_inode_csum_map()?;
+        let (other_csums, _) = other.get_inode_csum_map()?;
+
+        WalkDir::new(&self.link_dir)
+            .into_iter()
+            .try_for_each(|entry| {
+                handle_entry(
+                    entry,
+                    self,
+                    &csums,
+                    &other,
+                    &other_csums,
+                    Some(&mut diff.changed.paths),
+                    &mut diff.removed.paths,
+                )
+            })?;
+        WalkDir::new(&other.link_dir)
+            .into_iter()
+            .try_for_each(|entry| {
+                handle_entry(
+                    entry,
+                    &other,
+                    &other_csums,
+                    self,
+                    &csums,
+                    None,
+                    &mut diff.added.paths,
+                )
+            })?;
+
+        Ok(diff)
+    }
+
+    pub(crate) fn list_files(&self) -> Result<Vec<(PathBuf, Metadata)>, Error> {
+        let mut file_list = Vec::new();
+        WalkDir::new(&self.link_dir)
+            .into_iter()
+            .try_for_each(|entry| -> Result<(), Error> {
+                let path = entry?.into_path();
+                let meta = path.metadata()?;
+                let relative = path.strip_prefix(&self.link_dir)?;
+
+                file_list.push((relative.to_path_buf(), meta));
+                Ok(())
+            })?;
+        Ok(file_list)
+    }
 }
 
 fn link_file_do(source: &Path, target: &Path) -> Result<bool, Error> {
diff --git a/src/types.rs b/src/types.rs
index 7a1348a..3098a8d 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -1,4 +1,4 @@
-use std::{fmt::Display, str::FromStr};
+use std::{fmt::Display, path::PathBuf, str::FromStr};
 
 use anyhow::Error;
 use proxmox_schema::{api, const_regex, ApiStringFormat, Schema, StringSchema, Updater};
@@ -140,3 +140,17 @@ impl FromStr for ProductType {
         }
     }
 }
+
+/// Entries of Diff
+#[derive(Default)]
+pub struct DiffMember {
+    pub paths: Vec<(PathBuf, u64)>,
+}
+
+/// Differences between two pools or pool directories
+#[derive(Default)]
+pub struct Diff {
+    pub added: DiffMember,
+    pub changed: DiffMember,
+    pub removed: DiffMember,
+}
-- 
2.30.2





^ permalink raw reply	[flat|nested] 6+ messages in thread

* [pve-devel] [PATCH proxmox-offline-mirror 2/4] snapshots: add diff command
  2022-09-21  8:12 [pve-devel] [PATCH proxmox-offline-mirror 0/4] extend/add commands Fabian Grünbichler
  2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 1/4] pool: add diff and list helpers Fabian Grünbichler
@ 2022-09-21  8:12 ` Fabian Grünbichler
  2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 3/4] medium: " Fabian Grünbichler
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Fabian Grünbichler @ 2022-09-21  8:12 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
 src/bin/proxmox_offline_mirror_cmds/mirror.rs | 77 ++++++++++++++++++-
 src/mirror.rs                                 | 15 +++-
 2 files changed, 90 insertions(+), 2 deletions(-)

diff --git a/src/bin/proxmox_offline_mirror_cmds/mirror.rs b/src/bin/proxmox_offline_mirror_cmds/mirror.rs
index 3094146..348392b 100644
--- a/src/bin/proxmox_offline_mirror_cmds/mirror.rs
+++ b/src/bin/proxmox_offline_mirror_cmds/mirror.rs
@@ -3,7 +3,7 @@ use anyhow::{bail, format_err, Error};
 use proxmox_section_config::SectionConfigData;
 use proxmox_subscription::SubscriptionStatus;
 use serde_json::Value;
-use std::collections::HashMap;
+use std::{collections::HashMap, path::PathBuf};
 
 use proxmox_router::cli::{
     format_and_print_result, get_output_format, CliCommand, CliCommandMap, CommandLineInterface,
@@ -273,6 +273,73 @@ async fn garbage_collect(config: Option<String>, id: String, _param: Value) -> R
 
     Ok(())
 }
+
+#[api(
+    input: {
+        properties: {
+            config: {
+                type: String,
+                optional: true,
+                description: "Path to mirroring config file.",
+            },
+            id: {
+                schema: MIRROR_ID_SCHEMA,
+            },
+            snapshot: {
+                type: Snapshot,
+            },
+            other_snapshot: {
+                type: Snapshot,
+            },
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
+        }
+    },
+ )]
+/// Print differences between two snapshots.
+async fn diff_snapshots(
+    config: Option<String>,
+    id: String,
+    snapshot: Snapshot,
+    other_snapshot: Snapshot,
+    _param: Value,
+) -> Result<(), Error> {
+    let config = config.unwrap_or_else(get_config_path);
+
+    let (config, _digest) = proxmox_offline_mirror::config::config(&config)?;
+    let config: MirrorConfig = config.lookup("mirror", &id)?;
+    let mut diff = mirror::diff_snapshots(&config, &snapshot, &other_snapshot)?;
+    let sort = |(path, _): &(PathBuf, u64), (other_path, _): &(PathBuf, u64)| path.cmp(other_path);
+    diff.added.paths.sort_unstable_by(sort);
+    diff.changed.paths.sort_unstable_by(sort);
+    diff.removed.paths.sort_unstable_by(sort);
+
+    println!("{other_snapshot} added {} file(s)", diff.added.paths.len());
+    for (path, size) in diff.added.paths {
+        println!("\t{path:?}: +{size}b");
+    }
+
+    println!(
+        "\n{other_snapshot} removed {} file(s)",
+        diff.removed.paths.len()
+    );
+    for (path, size) in diff.removed.paths {
+        println!("\t{path:?}: -{size}b");
+    }
+
+    println!(
+        "\n {} file(s) diff between {snapshot} and {other_snapshot}",
+        diff.changed.paths.len()
+    );
+    for (path, size) in diff.changed.paths {
+        println!("\t{path:?}: +-{size}b");
+    }
+
+    Ok(())
+}
+
 pub fn mirror_commands() -> CommandLineInterface {
     let snapshot_cmds = CliCommandMap::new()
         .insert(
@@ -287,6 +354,14 @@ pub fn mirror_commands() -> CommandLineInterface {
         .insert(
             "remove",
             CliCommand::new(&API_METHOD_REMOVE_SNAPSHOT).arg_param(&["id", "snapshot"]),
+        )
+        .insert(
+            "diff",
+            CliCommand::new(&API_METHOD_DIFF_SNAPSHOTS).arg_param(&[
+                "id",
+                "snapshot",
+                "other_snapshot",
+            ]),
         );
 
     let cmd_def = CliCommandMap::new()
diff --git a/src/mirror.rs b/src/mirror.rs
index 5126393..5bf9219 100644
--- a/src/mirror.rs
+++ b/src/mirror.rs
@@ -15,7 +15,7 @@ use crate::{
     config::{MirrorConfig, SubscriptionKey},
     convert_repo_line,
     pool::Pool,
-    types::{Snapshot, SNAPSHOT_REGEX},
+    types::{Diff, Snapshot, SNAPSHOT_REGEX},
     FetchResult, Progress,
 };
 use proxmox_apt::{
@@ -745,3 +745,16 @@ pub fn gc(config: &MirrorConfig) -> Result<(usize, u64), Error> {
 
     pool.lock()?.gc()
 }
+
+/// Print differences between two snapshots
+pub fn diff_snapshots(
+    config: &MirrorConfig,
+    snapshot: &Snapshot,
+    other_snapshot: &Snapshot,
+) -> Result<Diff, Error> {
+    let pool = pool(config)?;
+    pool.lock()?.diff_dirs(
+        Path::new(&format!("{snapshot}")),
+        Path::new(&format!("{other_snapshot}")),
+    )
+}
-- 
2.30.2





^ permalink raw reply	[flat|nested] 6+ messages in thread

* [pve-devel] [PATCH proxmox-offline-mirror 3/4] medium: add diff command
  2022-09-21  8:12 [pve-devel] [PATCH proxmox-offline-mirror 0/4] extend/add commands Fabian Grünbichler
  2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 1/4] pool: add diff and list helpers Fabian Grünbichler
  2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 2/4] snapshots: add diff command Fabian Grünbichler
@ 2022-09-21  8:12 ` Fabian Grünbichler
  2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 4/4] cli: allow listing snapshots of all mirrors Fabian Grünbichler
  2022-09-26  7:51 ` [pve-devel] applied-series: [PATCH proxmox-offline-mirror 0/4] extend/add commands Wolfgang Bumiller
  4 siblings, 0 replies; 6+ messages in thread
From: Fabian Grünbichler @ 2022-09-21  8:12 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
 src/bin/proxmox_offline_mirror_cmds/medium.rs | 107 +++++++++++++++++-
 src/medium.rs                                 | 103 ++++++++++++++++-
 2 files changed, 207 insertions(+), 3 deletions(-)

diff --git a/src/bin/proxmox_offline_mirror_cmds/medium.rs b/src/bin/proxmox_offline_mirror_cmds/medium.rs
index b76e4e6..574f748 100644
--- a/src/bin/proxmox_offline_mirror_cmds/medium.rs
+++ b/src/bin/proxmox_offline_mirror_cmds/medium.rs
@@ -1,4 +1,4 @@
-use std::path::Path;
+use std::path::{Path, PathBuf};
 
 use anyhow::Error;
 use serde_json::Value;
@@ -220,6 +220,108 @@ async fn sync(
     Ok(Value::Null)
 }
 
+#[api(
+    input: {
+        properties: {
+            config: {
+                type: String,
+                optional: true,
+                description: "Path to mirroring config file.",
+            },
+            id: {
+                schema: MEDIA_ID_SCHEMA,
+            },
+            verbose: {
+                type: bool,
+                optional: true,
+                default: false,
+                description: "Verbose output (print paths in addition to summary)."
+            },
+        }
+    },
+ )]
+/// Diff a medium
+async fn diff(
+    config: Option<String>,
+    id: String,
+    verbose: bool,
+    _param: Value,
+) -> Result<Value, Error> {
+    let config = config.unwrap_or_else(get_config_path);
+
+    let (section_config, _digest) = proxmox_offline_mirror::config::config(&config)?;
+    let config: MediaConfig = section_config.lookup("medium", &id)?;
+    let mut mirrors = Vec::with_capacity(config.mirrors.len());
+    for mirror in &config.mirrors {
+        let mirror: MirrorConfig = section_config.lookup("mirror", mirror)?;
+        mirrors.push(mirror);
+    }
+
+    let mut diffs = medium::diff(&config, mirrors)?;
+    let mut mirrors: Vec<String> = diffs.keys().cloned().collect();
+    mirrors.sort_unstable();
+
+    let sort_paths =
+        |(path, _): &(PathBuf, u64), (other_path, _): &(PathBuf, u64)| path.cmp(other_path);
+
+    let mut first = true;
+    for mirror in mirrors {
+        if first {
+            first = false;
+        } else {
+            println!();
+        }
+
+        println!("Mirror '{mirror}'");
+        if let Some(Some(mut diff)) = diffs.remove(&mirror) {
+            let mut total_size = 0;
+            println!("\t{} file(s) only on medium:", diff.added.paths.len());
+            if verbose {
+                diff.added.paths.sort_unstable_by(sort_paths);
+                diff.changed.paths.sort_unstable_by(sort_paths);
+                diff.removed.paths.sort_unstable_by(sort_paths);
+            }
+            for (path, size) in diff.added.paths {
+                if verbose {
+                    println!("\t\t{path:?}: +{size}b");
+                }
+                total_size += size;
+            }
+            println!("\tTotal size: +{total_size}b");
+
+            total_size = 0;
+            println!(
+                "\n\t{} file(s) missing on medium:",
+                diff.removed.paths.len()
+            );
+            for (path, size) in diff.removed.paths {
+                if verbose {
+                    println!("\t\t{path:?}: -{size}b");
+                }
+                total_size += size;
+            }
+            println!("\tTotal size: -{total_size}b");
+
+            total_size = 0;
+            println!(
+                "\n\t{} file(s) diff between source and medium:",
+                diff.changed.paths.len()
+            );
+            for (path, size) in diff.changed.paths {
+                if verbose {
+                    println!("\t\t{path:?}: +-{size}b");
+                }
+            }
+            println!("\tSum of size differences: +-{total_size}b");
+        } else {
+            // TODO
+            println!("\tNot yet synced or no longer available on source side.");
+        }
+    }
+
+    Ok(Value::Null)
+}
+
 pub fn medium_commands() -> CommandLineInterface {
     let cmd_def = CliCommandMap::new()
         .insert(
@@ -230,7 +332,8 @@ pub fn medium_commands() -> CommandLineInterface {
             "status",
             CliCommand::new(&API_METHOD_STATUS).arg_param(&["id"]),
         )
-        .insert("sync", CliCommand::new(&API_METHOD_SYNC).arg_param(&["id"]));
+        .insert("sync", CliCommand::new(&API_METHOD_SYNC).arg_param(&["id"]))
+        .insert("diff", CliCommand::new(&API_METHOD_DIFF).arg_param(&["id"]));
 
     cmd_def.into()
 }
diff --git a/src/medium.rs b/src/medium.rs
index bfd35b4..4093fc1 100644
--- a/src/medium.rs
+++ b/src/medium.rs
@@ -1,5 +1,7 @@
 use std::{
     collections::{HashMap, HashSet},
+    fs::Metadata,
+    os::linux::fs::MetadataExt,
     path::{Path, PathBuf},
 };
 
@@ -16,7 +18,7 @@ use crate::{
     generate_repo_file_line,
     mirror::pool,
     pool::Pool,
-    types::{Snapshot, SNAPSHOT_REGEX},
+    types::{Diff, Snapshot, SNAPSHOT_REGEX},
 };
 #[derive(Clone, Debug, Serialize, Deserialize)]
 #[serde(rename_all = "kebab-case")]
@@ -439,3 +441,102 @@ pub fn sync(
 
     Ok(())
 }
+
+/// Sync medium's content according to config.
+pub fn diff(
+    medium: &crate::config::MediaConfig,
+    mirrors: Vec<MirrorConfig>,
+) -> Result<HashMap<String, Option<Diff>>, Error> {
+    let medium_base = Path::new(&medium.mountpoint);
+    if !medium_base.exists() {
+        bail!("Medium mountpoint doesn't exist.");
+    }
+
+    let _lock = lock(medium_base)?;
+
+    let state =
+        load_state(medium_base)?.ok_or_else(|| format_err!("Medium not yet initializes."))?;
+
+    let mirror_state = get_mirror_state(medium, &state);
+
+    let pools: HashMap<String, String> =
+        state
+            .mirrors
+            .iter()
+            .fold(HashMap::new(), |mut map, (id, info)| {
+                map.insert(id.clone(), info.pool.clone());
+                map
+            });
+
+    let mut diffs = HashMap::new();
+
+    let convert_file_list_to_diff = |files: Vec<(PathBuf, Metadata)>, added: bool| -> Diff {
+        files
+            .into_iter()
+            .fold(Diff::default(), |mut diff, (file, meta)| {
+                if !meta.is_file() {
+                    return diff;
+                }
+
+                let size = meta.st_size();
+                if added {
+                    diff.added.paths.push((file, size));
+                } else {
+                    diff.removed.paths.push((file, size));
+                }
+                diff
+            })
+    };
+
+    let get_target_pool =
+        |mirror_id: &str, mirror: Option<&MirrorConfig>| -> Result<Option<Pool>, Error> {
+            let mut mirror_base = medium_base.to_path_buf();
+            mirror_base.push(Path::new(mirror_id));
+
+            let mut mirror_pool = medium_base.to_path_buf();
+            let pool_dir = match pools.get(mirror_id) {
+                Some(pool_dir) => pool_dir.to_owned(),
+                None => {
+                    if let Some(mirror) = mirror {
+                        mirror_pool_dir(mirror)
+                    } else {
+                        return Ok(None);
+                    }
+                }
+            };
+            mirror_pool.push(pool_dir);
+
+            Ok(Some(Pool::open(&mirror_base, &mirror_pool)?))
+        };
+
+    for mirror in mirrors.into_iter() {
+        let source_pool: Pool = pool(&mirror)?;
+
+        if !mirror_state.synced.contains(&mirror.id) {
+            let files = source_pool.lock()?.list_files()?;
+            diffs.insert(mirror.id, Some(convert_file_list_to_diff(files, false)));
+            continue;
+        }
+
+        let target_pool = get_target_pool(mirror.id.as_str(), Some(&mirror))?
+            .ok_or_else(|| format_err!("Failed to open target pool."))?;
+        diffs.insert(
+            mirror.id,
+            Some(source_pool.lock()?.diff_pools(&target_pool)?),
+        );
+    }
+
+    for dropped in mirror_state.target_only {
+        match get_target_pool(&dropped, None)? {
+            Some(pool) => {
+                let files = pool.lock()?.list_files()?;
+                diffs.insert(dropped, Some(convert_file_list_to_diff(files, false)));
+            }
+            None => {
+                diffs.insert(dropped, None);
+            }
+        }
+    }
+
+    Ok(diffs)
+}
-- 
2.30.2





^ permalink raw reply	[flat|nested] 6+ messages in thread

* [pve-devel] [PATCH proxmox-offline-mirror 4/4] cli: allow listing snapshots of all mirrors
  2022-09-21  8:12 [pve-devel] [PATCH proxmox-offline-mirror 0/4] extend/add commands Fabian Grünbichler
                   ` (2 preceding siblings ...)
  2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 3/4] medium: " Fabian Grünbichler
@ 2022-09-21  8:12 ` Fabian Grünbichler
  2022-09-26  7:51 ` [pve-devel] applied-series: [PATCH proxmox-offline-mirror 0/4] extend/add commands Wolfgang Bumiller
  4 siblings, 0 replies; 6+ messages in thread
From: Fabian Grünbichler @ 2022-09-21  8:12 UTC (permalink / raw)
  To: pve-devel

and slightly change the output format accordingly.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
technically a breaking change -> the json is now a map mirror ID => list
of snapshots, instead of a plain list of snapshots..

 src/bin/proxmox_offline_mirror_cmds/mirror.rs | 53 +++++++++++++++----
 1 file changed, 43 insertions(+), 10 deletions(-)

diff --git a/src/bin/proxmox_offline_mirror_cmds/mirror.rs b/src/bin/proxmox_offline_mirror_cmds/mirror.rs
index 348392b..a3fb258 100644
--- a/src/bin/proxmox_offline_mirror_cmds/mirror.rs
+++ b/src/bin/proxmox_offline_mirror_cmds/mirror.rs
@@ -3,7 +3,10 @@ use anyhow::{bail, format_err, Error};
 use proxmox_section_config::SectionConfigData;
 use proxmox_subscription::SubscriptionStatus;
 use serde_json::Value;
-use std::{collections::HashMap, path::PathBuf};
+use std::{
+    collections::{BTreeMap, HashMap},
+    path::PathBuf,
+};
 
 use proxmox_router::cli::{
     format_and_print_result, get_output_format, CliCommand, CliCommandMap, CommandLineInterface,
@@ -174,6 +177,7 @@ async fn create_snapshots(
             },
             id: {
                 schema: MIRROR_ID_SCHEMA,
+                optional: true,
             },
             "output-format": {
                 schema: OUTPUT_FORMAT,
@@ -183,25 +187,54 @@ async fn create_snapshots(
     },
  )]
 /// List existing repository snapshots.
-async fn list_snapshots(config: Option<String>, id: String, param: Value) -> Result<(), Error> {
+async fn list_snapshots(
+    config: Option<String>,
+    id: Option<String>,
+    param: Value,
+) -> Result<(), Error> {
     let output_format = get_output_format(&param);
     let config = config.unwrap_or_else(get_config_path);
 
     let (config, _digest) = proxmox_offline_mirror::config::config(&config)?;
-    let config: MirrorConfig = config.lookup("mirror", &id)?;
+    let res = if let Some(id) = id {
+        let config: MirrorConfig = config.lookup("mirror", &id)?;
 
-    let list = mirror::list_snapshots(&config)?;
+        let list = mirror::list_snapshots(&config)?;
+        let mut map = BTreeMap::new();
+        map.insert(config.id, list);
+        map
+    } else {
+        let mirrors: Vec<MirrorConfig> = config.convert_to_typed_array("mirror")?;
+        mirrors
+            .into_iter()
+            .fold(BTreeMap::new(), |mut map, mirror| {
+                match mirror::list_snapshots(&mirror) {
+                    Ok(list) => {
+                        map.insert(mirror.id, list);
+                    }
+                    Err(err) => eprintln!("Failed to list snapshots for {} - {err}", mirror.id),
+                }
+                map
+            })
+    };
 
     if output_format == "text" {
-        println!("Found {} snapshots:", list.len());
-        for snap in &list {
-            println!("- {snap}");
+        let mut first = true;
+        for (mirror, list) in res {
+            if first {
+                first = false;
+            } else {
+                println!();
+            }
+            println!("{mirror} ({} snapshots):", list.len());
+            for snap in &list {
+                println!("- {snap}");
+            }
         }
     } else {
-        let list = serde_json::json!(list);
-        format_and_print_result(&list, &output_format);
+        let map = serde_json::json!(res);
+        format_and_print_result(&map, &output_format);
     }
-
     Ok(())
 }
 
-- 
2.30.2





^ permalink raw reply	[flat|nested] 6+ messages in thread

* [pve-devel] applied-series: [PATCH proxmox-offline-mirror 0/4] extend/add commands
  2022-09-21  8:12 [pve-devel] [PATCH proxmox-offline-mirror 0/4] extend/add commands Fabian Grünbichler
                   ` (3 preceding siblings ...)
  2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 4/4] cli: allow listing snapshots of all mirrors Fabian Grünbichler
@ 2022-09-26  7:51 ` Wolfgang Bumiller
  4 siblings, 0 replies; 6+ messages in thread
From: Wolfgang Bumiller @ 2022-09-26  7:51 UTC (permalink / raw)
  To: Fabian Grünbichler; +Cc: pve-devel

applied series, thanks

On Wed, Sep 21, 2022 at 10:12:38AM +0200, Fabian Grünbichler wrote:
> this series adds diff commands for diffing two snapshots of a mirror
> (`mirror snapshot diff`), a medium and its source mirrors (`medium
> diff`) and extends `mirror snapshot list` to allow listing *all*
> snapshots of *all* configured mirrors by leaving out the previously
> mandatory mirror ID.
> 
> Fabian Grünbichler (4):
>   pool: add diff and list helpers
>   snapshots: add diff command
>   medium: add diff command
>   cli: allow listing snapshots of all mirrors
> 
>  src/bin/proxmox_offline_mirror_cmds/medium.rs | 107 ++++++++++-
>  src/bin/proxmox_offline_mirror_cmds/mirror.rs | 128 ++++++++++++--
>  src/medium.rs                                 | 103 ++++++++++-
>  src/mirror.rs                                 |  15 +-
>  src/pool.rs                                   | 167 +++++++++++++++++-
>  src/types.rs                                  |  16 +-
>  6 files changed, 520 insertions(+), 16 deletions(-)
> 
> -- 
> 2.30.2




^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2022-09-26  7:51 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-09-21  8:12 [pve-devel] [PATCH proxmox-offline-mirror 0/4] extend/add commands Fabian Grünbichler
2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 1/4] pool: add diff and list helpers Fabian Grünbichler
2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 2/4] snapshots: add diff command Fabian Grünbichler
2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 3/4] medium: " Fabian Grünbichler
2022-09-21  8:12 ` [pve-devel] [PATCH proxmox-offline-mirror 4/4] cli: allow listing snapshots of all mirrors Fabian Grünbichler
2022-09-26  7:51 ` [pve-devel] applied-series: [PATCH proxmox-offline-mirror 0/4] extend/add commands Wolfgang Bumiller

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal