* [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(¶m);
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