use anyhow::{bail, format_err, Error};
use futures::stream::{StreamExt, TryStreamExt};
+use serde::Deserialize;
use serde_json::{json, Value};
use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
use pxar::accessor::{MaybeReady, ReadAt, ReadAtOperation};
use pbs_api_types::{
- Authid, CryptMode, Fingerprint, GroupListItem, HumanByte, PruneListItem, PruneOptions,
- RateLimitConfig, SnapshotListItem, StorageStatus, BACKUP_ID_SCHEMA, BACKUP_TIME_SCHEMA,
+ Authid, BackupDir, BackupGroup, BackupNamespace, BackupPart, BackupType, CryptMode,
+ Fingerprint, GroupListItem, HumanByte, PruneListItem, PruneOptions, RateLimitConfig,
+ SnapshotListItem, StorageStatus, BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA, BACKUP_TIME_SCHEMA,
BACKUP_TYPE_SCHEMA, TRAFFIC_CONTROL_BURST_SCHEMA, TRAFFIC_CONTROL_RATE_SCHEMA,
};
use pbs_client::catalog_shell::Shell;
BACKUP_SOURCE_SCHEMA,
};
use pbs_config::key_config::{decrypt_key, rsa_encrypt_key_config, KeyConfig};
-use pbs_datastore::backup_info::{BackupDir, BackupGroup};
use pbs_datastore::catalog::{BackupCatalogWriter, CatalogReader, CatalogWriter};
use pbs_datastore::chunk_store::verify_chunk_size;
use pbs_datastore::dynamic_index::{BufferedDynamicReader, DynamicIndexReader};
let mut args = json!({});
if let Some(group) = group {
- args["backup-type"] = group.backup_type().into();
- args["backup-id"] = group.backup_id().into();
+ args["backup-type"] = group.ty.to_string().into();
+ args["backup-id"] = group.id.into();
}
let mut result = client.get(&path, Some(args)).await?;
client: &HttpClient,
store: &str,
group: BackupGroup,
-) -> Result<(String, String, i64), Error> {
+) -> Result<BackupDir, Error> {
let list = api_datastore_list_snapshots(client, store, Some(group.clone())).await?;
let mut list: Vec<SnapshotListItem> = serde_json::from_value(list)?;
if list.is_empty() {
- bail!(
- "backup group {:?} does not contain any snapshots.",
- group.group_path()
- );
+ bail!("backup group {} does not contain any snapshots.", group);
}
- list.sort_unstable_by(|a, b| b.backup_time.cmp(&a.backup_time));
+ list.sort_unstable_by(|a, b| b.backup.time.cmp(&a.backup.time));
- let backup_time = list[0].backup_time;
+ Ok((group, list[0].backup.time).into())
+}
- Ok((
- group.backup_type().to_owned(),
- group.backup_id().to_owned(),
- backup_time,
- ))
+pub async fn dir_or_last_from_group(
+ client: &HttpClient,
+ repo: &BackupRepository,
+ path: &str,
+) -> Result<BackupDir, Error> {
+ match path.parse::<BackupPart>()? {
+ BackupPart::Dir(dir) => Ok(dir),
+ BackupPart::Group(group) => {
+ api_datastore_latest_snapshot(&client, repo.store(), group).await
+ }
+ }
}
async fn backup_directory<P: AsRef<Path>>(
record_repository(&repo);
let render_group_path = |_v: &Value, record: &Value| -> Result<String, Error> {
- let item: GroupListItem = serde_json::from_value(record.to_owned())?;
- let group = BackupGroup::new(item.backup_type, item.backup_id);
- Ok(group.group_path().to_str().unwrap().to_owned())
+ let item = GroupListItem::deserialize(record)?;
+ Ok(item.backup.to_string())
};
let render_last_backup = |_v: &Value, record: &Value| -> Result<String, Error> {
- let item: GroupListItem = serde_json::from_value(record.to_owned())?;
- let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.last_backup)?;
- Ok(snapshot.relative_path().to_str().unwrap().to_owned())
+ let item = GroupListItem::deserialize(record)?;
+ let snapshot = BackupDir {
+ group: item.backup,
+ time: item.last_backup,
+ };
+ Ok(snapshot.to_string())
};
let render_files = |_v: &Value, record: &Value| -> Result<String, Error> {
- let item: GroupListItem = serde_json::from_value(record.to_owned())?;
+ let item = GroupListItem::deserialize(record)?;
Ok(pbs_tools::format::render_backup_file_list(&item.files))
};
let group: BackupGroup = group.parse()?;
- param["backup-type"] = group.backup_type().into();
- param["backup-id"] = group.backup_id().into();
+ param["backup-type"] = group.ty.to_string().into();
+ param["backup-id"] = group.id.into();
let path = format!("api2/json/admin/datastore/{}/change-owner", repo.store());
client.post(&path, Some(param)).await?;
optional: true,
default: false,
},
+ "backup-ns": {
+ schema: BACKUP_NAMESPACE_SCHEMA,
+ optional: true,
+ },
"backup-type": {
schema: BACKUP_TYPE_SCHEMA,
optional: true,
.as_str()
.unwrap_or(proxmox_sys::nodename());
- let backup_type = param["backup-type"].as_str().unwrap_or("host");
+ let backup_namespace: BackupNamespace = match param.get("backup-ns") {
+ Some(ns) => ns
+ .as_str()
+ .ok_or_else(|| format_err!("bad namespace {:?}", ns))?
+ .parse()?,
+ None => BackupNamespace::root(),
+ };
+
+ let backup_type: BackupType = param["backup-type"].as_str().unwrap_or("host").parse()?;
let include_dev = param["include-dev"].as_array();
let client = connect_rate_limited(&repo, rate_limit)?;
record_repository(&repo);
- println!(
- "Starting backup: {}/{}/{}",
+ let snapshot = BackupDir::from((
+ backup_namespace,
backup_type,
- backup_id,
- BackupDir::backup_time_to_string(backup_time)?
- );
+ backup_id.to_owned(),
+ backup_time,
+ ));
+ println!("Starting backup: {snapshot}");
println!("Client name: {}", proxmox_sys::nodename());
client,
crypt_config.clone(),
repo.store(),
- backup_type,
- backup_id,
- backup_time,
+ &snapshot,
verbose,
false,
)
None
};
- let snapshot = BackupDir::new(backup_type, backup_id, backup_time)?;
let mut manifest = BackupManifest::new(snapshot);
let mut catalog = None;
let path = json::required_string_param(¶m, "snapshot")?;
- let (backup_type, backup_id, backup_time) = if path.matches('/').count() == 1 {
- let group: BackupGroup = path.parse()?;
- api_datastore_latest_snapshot(&client, repo.store(), group).await?
- } else {
- let snapshot: BackupDir = path.parse()?;
- (
- snapshot.group().backup_type().to_owned(),
- snapshot.group().backup_id().to_owned(),
- snapshot.backup_time(),
- )
- };
+ let backup_dir = dir_or_last_from_group(&client, &repo, &path).await?;
let target = json::required_string_param(¶m, "target")?;
let target = if target == "-" { None } else { Some(target) };
client,
crypt_config.clone(),
repo.store(),
- &backup_type,
- &backup_id,
- backup_time,
+ &backup_dir,
true,
)
.await?;
if let Some(dry_run) = dry_run {
api_param["dry-run"] = dry_run.into();
}
- api_param["backup-type"] = group.backup_type().into();
- api_param["backup-id"] = group.backup_id().into();
+ api_param["backup-type"] = group.ty.to_string().into();
+ api_param["backup-id"] = group.id.into();
let mut result = client.post(&path, Some(api_param)).await?;
let render_snapshot_path = |_v: &Value, record: &Value| -> Result<String, Error> {
let item: PruneListItem = serde_json::from_value(record.to_owned())?;
- let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.backup_time)?;
- Ok(snapshot.relative_path().to_str().unwrap().to_owned())
+ Ok(item.backup.to_string())
};
let render_prune_action = |v: &Value, _record: &Value| -> Result<String, Error> {