* [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(¶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
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.