api: add support for notes on backup groups
Stored in atomically-updated 'notes' file in backup group directory. Available via dedicated GET/PUT API calls, as well as the first line being included in list_groups (similar to list_snapshots). Signed-off-by: Stefan Reiter <s.reiter@proxmox.com> Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
7d3482f5bf
commit
d6688884f6
|
@ -380,6 +380,9 @@ pub struct GroupListItem {
|
||||||
/// The owner of group
|
/// The owner of group
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub owner: Option<Authid>,
|
pub owner: Option<Authid>,
|
||||||
|
/// The first line from group "notes"
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub comment: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Error};
|
use anyhow::{bail, format_err, Error};
|
||||||
use futures::*;
|
use futures::*;
|
||||||
|
@ -17,7 +18,9 @@ use proxmox::api::{
|
||||||
};
|
};
|
||||||
use proxmox::api::router::{ReturnType, SubdirMap};
|
use proxmox::api::router::{ReturnType, SubdirMap};
|
||||||
use proxmox::api::schema::*;
|
use proxmox::api::schema::*;
|
||||||
use proxmox::tools::fs::{replace_file, CreateOptions};
|
use proxmox::tools::fs::{
|
||||||
|
file_read_firstline, file_read_optional_string, replace_file, CreateOptions,
|
||||||
|
};
|
||||||
use proxmox::{http_err, identity, list_subdirs_api_method, sortable};
|
use proxmox::{http_err, identity, list_subdirs_api_method, sortable};
|
||||||
|
|
||||||
use pxar::accessor::aio::Accessor;
|
use pxar::accessor::aio::Accessor;
|
||||||
|
@ -46,6 +49,15 @@ use crate::config::acl::{
|
||||||
PRIV_DATASTORE_VERIFY,
|
PRIV_DATASTORE_VERIFY,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const GROUP_NOTES_FILE_NAME: &str = "notes";
|
||||||
|
|
||||||
|
fn get_group_note_path(store: &DataStore, group: &BackupGroup) -> PathBuf {
|
||||||
|
let mut note_path = store.base_path();
|
||||||
|
note_path.push(group.group_path());
|
||||||
|
note_path.push(GROUP_NOTES_FILE_NAME);
|
||||||
|
note_path
|
||||||
|
}
|
||||||
|
|
||||||
fn check_priv_or_backup_owner(
|
fn check_priv_or_backup_owner(
|
||||||
store: &DataStore,
|
store: &DataStore,
|
||||||
group: &BackupGroup,
|
group: &BackupGroup,
|
||||||
|
@ -204,6 +216,9 @@ pub fn list_groups(
|
||||||
})
|
})
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
|
let note_path = get_group_note_path(&datastore, &group);
|
||||||
|
let comment = file_read_firstline(¬e_path).ok();
|
||||||
|
|
||||||
group_info.push(GroupListItem {
|
group_info.push(GroupListItem {
|
||||||
backup_type: group.backup_type().to_string(),
|
backup_type: group.backup_type().to_string(),
|
||||||
backup_id: group.backup_id().to_string(),
|
backup_id: group.backup_id().to_string(),
|
||||||
|
@ -211,6 +226,7 @@ pub fn list_groups(
|
||||||
owner: Some(owner),
|
owner: Some(owner),
|
||||||
backup_count,
|
backup_count,
|
||||||
files: last_backup.files,
|
files: last_backup.files,
|
||||||
|
comment,
|
||||||
});
|
});
|
||||||
|
|
||||||
group_info
|
group_info
|
||||||
|
@ -1558,6 +1574,86 @@ pub fn get_rrd_stats(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
store: {
|
||||||
|
schema: DATASTORE_SCHEMA,
|
||||||
|
},
|
||||||
|
"backup-type": {
|
||||||
|
schema: BACKUP_TYPE_SCHEMA,
|
||||||
|
},
|
||||||
|
"backup-id": {
|
||||||
|
schema: BACKUP_ID_SCHEMA,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: {
|
||||||
|
permission: &Permission::Privilege(&["datastore", "{store}"], PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_BACKUP, true),
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Get "notes" for a backup group
|
||||||
|
pub fn get_group_notes(
|
||||||
|
store: String,
|
||||||
|
backup_type: String,
|
||||||
|
backup_id: String,
|
||||||
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
|
) -> Result<String, Error> {
|
||||||
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||||
|
let backup_group = BackupGroup::new(backup_type, backup_id);
|
||||||
|
|
||||||
|
check_priv_or_backup_owner(&datastore, &backup_group, &auth_id, PRIV_DATASTORE_AUDIT)?;
|
||||||
|
|
||||||
|
let note_path = get_group_note_path(&datastore, &backup_group);
|
||||||
|
Ok(file_read_optional_string(note_path)?.unwrap_or_else(|| "".to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
store: {
|
||||||
|
schema: DATASTORE_SCHEMA,
|
||||||
|
},
|
||||||
|
"backup-type": {
|
||||||
|
schema: BACKUP_TYPE_SCHEMA,
|
||||||
|
},
|
||||||
|
"backup-id": {
|
||||||
|
schema: BACKUP_ID_SCHEMA,
|
||||||
|
},
|
||||||
|
notes: {
|
||||||
|
description: "A multiline text.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
access: {
|
||||||
|
permission: &Permission::Privilege(&["datastore", "{store}"],
|
||||||
|
PRIV_DATASTORE_MODIFY | PRIV_DATASTORE_BACKUP,
|
||||||
|
true),
|
||||||
|
},
|
||||||
|
)]
|
||||||
|
/// Set "notes" for a backup group
|
||||||
|
pub fn set_group_notes(
|
||||||
|
store: String,
|
||||||
|
backup_type: String,
|
||||||
|
backup_id: String,
|
||||||
|
notes: String,
|
||||||
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||||
|
let backup_group = BackupGroup::new(backup_type, backup_id);
|
||||||
|
|
||||||
|
check_priv_or_backup_owner(&datastore, &backup_group, &auth_id, PRIV_DATASTORE_MODIFY)?;
|
||||||
|
|
||||||
|
let note_path = get_group_note_path(&datastore, &backup_group);
|
||||||
|
replace_file(note_path, notes.as_bytes(), CreateOptions::new())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[api(
|
#[api(
|
||||||
input: {
|
input: {
|
||||||
properties: {
|
properties: {
|
||||||
|
@ -1782,6 +1878,12 @@ const DATASTORE_INFO_SUBDIRS: SubdirMap = &[
|
||||||
.get(&API_METHOD_GARBAGE_COLLECTION_STATUS)
|
.get(&API_METHOD_GARBAGE_COLLECTION_STATUS)
|
||||||
.post(&API_METHOD_START_GARBAGE_COLLECTION)
|
.post(&API_METHOD_START_GARBAGE_COLLECTION)
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"group-notes",
|
||||||
|
&Router::new()
|
||||||
|
.get(&API_METHOD_GET_GROUP_NOTES)
|
||||||
|
.put(&API_METHOD_SET_GROUP_NOTES)
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"groups",
|
"groups",
|
||||||
&Router::new()
|
&Router::new()
|
||||||
|
|
Loading…
Reference in New Issue