api-types: add namespace to BackupGroup

Make it easier by adding an helper accepting either group or
directory

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2022-04-21 15:04:59 +02:00 committed by Thomas Lamprecht
parent 686c4cd250
commit 0af9b69146
2 changed files with 120 additions and 25 deletions

View File

@ -1,5 +1,5 @@
use std::fmt; use std::fmt;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -16,19 +16,24 @@ use crate::{
}; };
const_regex! { const_regex! {
pub BACKUP_NAMESPACE_REGEX = concat!(r"^", BACKUP_NS_RE!(), r"$");
pub BACKUP_TYPE_REGEX = concat!(r"^(", BACKUP_TYPE_RE!(), r")$"); pub BACKUP_TYPE_REGEX = concat!(r"^(", BACKUP_TYPE_RE!(), r")$");
pub BACKUP_ID_REGEX = concat!(r"^", BACKUP_ID_RE!(), r"$"); pub BACKUP_ID_REGEX = concat!(r"^", BACKUP_ID_RE!(), r"$");
pub BACKUP_DATE_REGEX = concat!(r"^", BACKUP_TIME_RE!() ,r"$"); pub BACKUP_DATE_REGEX = concat!(r"^", BACKUP_TIME_RE!() ,r"$");
pub GROUP_PATH_REGEX = concat!(r"^(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), r")$"); pub GROUP_PATH_REGEX = concat!(
r"^(", BACKUP_NS_PATH_RE!(), r")?",
r"(", BACKUP_TYPE_RE!(), ")/",
r"(", BACKUP_ID_RE!(), r")$",
);
pub BACKUP_FILE_REGEX = r"^.*\.([fd]idx|blob)$"; pub BACKUP_FILE_REGEX = r"^.*\.([fd]idx|blob)$";
pub SNAPSHOT_PATH_REGEX = concat!(r"^", SNAPSHOT_PATH_REGEX_STR!(), r"$"); pub SNAPSHOT_PATH_REGEX = concat!(r"^", SNAPSHOT_PATH_REGEX_STR!(), r"$");
pub GROUP_OR_SNAPSHOT_PATH_REGEX = concat!(r"^", GROUP_OR_SNAPSHOT_PATH_REGEX_STR!(), r"$");
pub BACKUP_NAMESPACE_REGEX = concat!(r"^", BACKUP_NS_RE!(), r"$");
pub DATASTORE_MAP_REGEX = concat!(r"(:?", PROXMOX_SAFE_ID_REGEX_STR!(), r"=)?", PROXMOX_SAFE_ID_REGEX_STR!()); pub DATASTORE_MAP_REGEX = concat!(r"(:?", PROXMOX_SAFE_ID_REGEX_STR!(), r"=)?", PROXMOX_SAFE_ID_REGEX_STR!());
} }
@ -640,7 +645,7 @@ impl BackupNamespace {
/// Return an adapter which [`Display`]s as a path with `"ns/"` prefixes in front of every /// Return an adapter which [`Display`]s as a path with `"ns/"` prefixes in front of every
/// component. /// component.
fn display_as_path(&self) -> BackupNamespacePath { pub fn display_as_path(&self) -> BackupNamespacePath {
BackupNamespacePath(self) BackupNamespacePath(self)
} }
@ -775,6 +780,7 @@ impl std::cmp::PartialOrd for BackupType {
#[api( #[api(
properties: { properties: {
"backup-ns": { type: BackupNamespace },
"backup-type": { type: BackupType }, "backup-type": { type: BackupType },
"backup-id": { schema: BACKUP_ID_SCHEMA }, "backup-id": { schema: BACKUP_ID_SCHEMA },
}, },
@ -783,6 +789,14 @@ impl std::cmp::PartialOrd for BackupType {
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
/// A backup group (without a data store). /// A backup group (without a data store).
pub struct BackupGroup { pub struct BackupGroup {
/// An optional namespace this backup belongs to.
#[serde(
rename = "backup-ns",
skip_serializing_if = "BackupNamespace::is_root",
default
)]
pub ns: BackupNamespace,
/// Backup type. /// Backup type.
#[serde(rename = "backup-type")] #[serde(rename = "backup-type")]
pub ty: BackupType, pub ty: BackupType,
@ -793,8 +807,12 @@ pub struct BackupGroup {
} }
impl BackupGroup { impl BackupGroup {
pub fn new<T: Into<String>>(ty: BackupType, id: T) -> Self { pub fn new<T: Into<String>>(ns: BackupNamespace, ty: BackupType, id: T) -> Self {
Self { ty, id: id.into() } Self {
ns,
ty,
id: id.into(),
}
} }
pub fn matches(&self, filter: &crate::GroupFilter) -> bool { pub fn matches(&self, filter: &crate::GroupFilter) -> bool {
@ -820,21 +838,29 @@ impl AsRef<BackupGroup> for BackupGroup {
} }
} }
impl From<(BackupType, String)> for BackupGroup { impl From<(BackupNamespace, BackupType, String)> for BackupGroup {
fn from(data: (BackupType, String)) -> Self { #[inline]
fn from(data: (BackupNamespace, BackupType, String)) -> Self {
Self { Self {
ty: data.0, ns: data.0,
id: data.1, ty: data.1,
id: data.2,
} }
} }
} }
impl std::cmp::Ord for BackupGroup { impl std::cmp::Ord for BackupGroup {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let ns_order = self.ns.cmp(&other.ns);
if ns_order != std::cmp::Ordering::Equal {
return ns_order;
}
let type_order = self.ty.cmp(&other.ty); let type_order = self.ty.cmp(&other.ty);
if type_order != std::cmp::Ordering::Equal { if type_order != std::cmp::Ordering::Equal {
return type_order; return type_order;
} }
// try to compare IDs numerically // try to compare IDs numerically
let id_self = self.id.parse::<u64>(); let id_self = self.id.parse::<u64>();
let id_other = other.id.parse::<u64>(); let id_other = other.id.parse::<u64>();
@ -855,7 +881,11 @@ impl std::cmp::PartialOrd for BackupGroup {
impl fmt::Display for BackupGroup { impl fmt::Display for BackupGroup {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.ty, self.id) if self.ns.is_root() {
write!(f, "{}/{}", self.ty, self.id)
} else {
write!(f, "{}/{}/{}", self.ns.display_as_path(), self.ty, self.id)
}
} }
} }
@ -871,8 +901,9 @@ impl std::str::FromStr for BackupGroup {
.ok_or_else(|| format_err!("unable to parse backup group path '{}'", path))?; .ok_or_else(|| format_err!("unable to parse backup group path '{}'", path))?;
Ok(Self { Ok(Self {
ty: cap.get(1).unwrap().as_str().parse()?, ns: BackupNamespace::from_path(cap.get(1).unwrap().as_str())?,
id: cap.get(2).unwrap().as_str().to_owned(), ty: cap.get(2).unwrap().as_str().parse()?,
id: cap.get(3).unwrap().as_str().to_owned(),
}) })
} }
} }
@ -921,32 +952,44 @@ impl From<(BackupGroup, i64)> for BackupDir {
} }
} }
impl From<(BackupType, String, i64)> for BackupDir { impl From<(BackupNamespace, BackupType, String, i64)> for BackupDir {
fn from(data: (BackupType, String, i64)) -> Self { fn from(data: (BackupNamespace, BackupType, String, i64)) -> Self {
Self { Self {
group: (data.0, data.1).into(), group: (data.0, data.1, data.2).into(),
time: data.2, time: data.3,
} }
} }
} }
impl BackupDir { impl BackupDir {
pub fn with_rfc3339<T>(ty: BackupType, id: T, backup_time_string: &str) -> Result<Self, Error> pub fn with_rfc3339<T>(
ns: BackupNamespace,
ty: BackupType,
id: T,
backup_time_string: &str,
) -> Result<Self, Error>
where where
T: Into<String>, T: Into<String>,
{ {
let time = proxmox_time::parse_rfc3339(&backup_time_string)?; let time = proxmox_time::parse_rfc3339(&backup_time_string)?;
let group = BackupGroup::new(ty, id.into()); let group = BackupGroup::new(ns, ty, id.into());
Ok(Self { group, time }) Ok(Self { group, time })
} }
#[inline]
pub fn ty(&self) -> BackupType { pub fn ty(&self) -> BackupType {
self.group.ty self.group.ty
} }
#[inline]
pub fn id(&self) -> &str { pub fn id(&self) -> &str {
&self.group.id &self.group.id
} }
#[inline]
pub fn ns(&self) -> &BackupNamespace {
&self.group.ns
}
} }
impl std::str::FromStr for BackupDir { impl std::str::FromStr for BackupDir {
@ -960,22 +1003,56 @@ impl std::str::FromStr for BackupDir {
.captures(path) .captures(path)
.ok_or_else(|| format_err!("unable to parse backup snapshot path '{}'", path))?; .ok_or_else(|| format_err!("unable to parse backup snapshot path '{}'", path))?;
let ns = match cap.get(1) {
Some(cap) => BackupNamespace::from_path(cap.as_str())?,
None => BackupNamespace::root(),
};
BackupDir::with_rfc3339( BackupDir::with_rfc3339(
cap.get(1).unwrap().as_str().parse()?, ns,
cap.get(2).unwrap().as_str(), cap.get(2).unwrap().as_str().parse()?,
cap.get(3).unwrap().as_str(), cap.get(3).unwrap().as_str(),
cap.get(4).unwrap().as_str(),
) )
} }
} }
impl std::fmt::Display for BackupDir { impl fmt::Display for BackupDir {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// FIXME: log error? // FIXME: log error?
let time = proxmox_time::epoch_to_rfc3339_utc(self.time).map_err(|_| fmt::Error)?; let time = proxmox_time::epoch_to_rfc3339_utc(self.time).map_err(|_| fmt::Error)?;
write!(f, "{}/{}", self.group, time) write!(f, "{}/{}", self.group, time)
} }
} }
/// Used when both a backup group or a directory can be valid.
pub enum BackupPart {
Group(BackupGroup),
Dir(BackupDir),
}
impl std::str::FromStr for BackupPart {
type Err = Error;
/// Parse a path which can be either a backup group or a snapshot dir.
fn from_str(path: &str) -> Result<Self, Error> {
let cap = GROUP_OR_SNAPSHOT_PATH_REGEX
.captures(path)
.ok_or_else(|| format_err!("unable to parse backup snapshot path '{}'", path))?;
let ns = match cap.get(1) {
Some(cap) => BackupNamespace::from_path(cap.as_str())?,
None => BackupNamespace::root(),
};
let ty = cap.get(2).unwrap().as_str().parse()?;
let id = cap.get(3).unwrap().as_str().to_string();
Ok(match cap.get(4) {
Some(time) => BackupPart::Dir(BackupDir::with_rfc3339(ns, ty, id, time.as_str())?),
None => BackupPart::Group((ns, ty, id).into()),
})
}
}
#[api( #[api(
properties: { properties: {
"backup": { type: BackupDir }, "backup": { type: BackupDir },

View File

@ -34,14 +34,32 @@ macro_rules! BACKUP_NS_RE {
); );
} }
#[rustfmt::skip]
#[macro_export]
macro_rules! BACKUP_NS_PATH_RE {
() => (
concat!(r"(:?ns/", PROXMOX_SAFE_ID_REGEX_STR!(), r"/){0,7}ns/", PROXMOX_SAFE_ID_REGEX_STR!())
);
}
#[rustfmt::skip] #[rustfmt::skip]
#[macro_export] #[macro_export]
macro_rules! SNAPSHOT_PATH_REGEX_STR { macro_rules! SNAPSHOT_PATH_REGEX_STR {
() => ( () => (
concat!(r"(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), ")/(", BACKUP_TIME_RE!(), r")") concat!(
r"(", BACKUP_NS_PATH_RE!(), ")?",
r"(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), ")/(", BACKUP_TIME_RE!(), r")",
)
); );
} }
#[macro_export]
macro_rules! GROUP_OR_SNAPSHOT_PATH_REGEX_STR {
() => {
concat!(SNAPSHOT_PATH_REGEX_STR!(), "?")
};
}
mod acl; mod acl;
pub use acl::*; pub use acl::*;