api2/admin/datastore: add download-decoded endpoint
similar to 'download', but streams the decoded file when it is not encrypted Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
parent
eeaa2c212b
commit
6ef9bb59eb
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::{HashSet, HashMap};
|
use std::collections::{HashSet, HashMap};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, format_err, Error};
|
||||||
use futures::*;
|
use futures::*;
|
||||||
use hyper::http::request::Parts;
|
use hyper::http::request::Parts;
|
||||||
use hyper::{header, Body, Response, StatusCode};
|
use hyper::{header, Body, Response, StatusCode};
|
||||||
|
@ -23,7 +23,7 @@ use crate::config::datastore;
|
||||||
use crate::config::cached_user_info::CachedUserInfo;
|
use crate::config::cached_user_info::CachedUserInfo;
|
||||||
|
|
||||||
use crate::server::WorkerTask;
|
use crate::server::WorkerTask;
|
||||||
use crate::tools;
|
use crate::tools::{self, WrappedReaderStream};
|
||||||
use crate::config::acl::{
|
use crate::config::acl::{
|
||||||
PRIV_DATASTORE_AUDIT,
|
PRIV_DATASTORE_AUDIT,
|
||||||
PRIV_DATASTORE_MODIFY,
|
PRIV_DATASTORE_MODIFY,
|
||||||
|
@ -774,6 +774,118 @@ fn download_file(
|
||||||
}.boxed()
|
}.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[sortable]
|
||||||
|
pub const API_METHOD_DOWNLOAD_FILE_DECODED: ApiMethod = ApiMethod::new(
|
||||||
|
&ApiHandler::AsyncHttp(&download_file_decoded),
|
||||||
|
&ObjectSchema::new(
|
||||||
|
"Download single decoded file from backup snapshot. Only works if it's not encrypted.",
|
||||||
|
&sorted!([
|
||||||
|
("store", false, &DATASTORE_SCHEMA),
|
||||||
|
("backup-type", false, &BACKUP_TYPE_SCHEMA),
|
||||||
|
("backup-id", false, &BACKUP_ID_SCHEMA),
|
||||||
|
("backup-time", false, &BACKUP_TIME_SCHEMA),
|
||||||
|
("file-name", false, &BACKUP_ARCHIVE_NAME_SCHEMA),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
).access(None, &Permission::Privilege(
|
||||||
|
&["datastore", "{store}"],
|
||||||
|
PRIV_DATASTORE_READ | PRIV_DATASTORE_BACKUP,
|
||||||
|
true)
|
||||||
|
);
|
||||||
|
|
||||||
|
fn download_file_decoded(
|
||||||
|
_parts: Parts,
|
||||||
|
_req_body: Body,
|
||||||
|
param: Value,
|
||||||
|
_info: &ApiMethod,
|
||||||
|
rpcenv: Box<dyn RpcEnvironment>,
|
||||||
|
) -> ApiResponseFuture {
|
||||||
|
|
||||||
|
async move {
|
||||||
|
let store = tools::required_string_param(¶m, "store")?;
|
||||||
|
let datastore = DataStore::lookup_datastore(store)?;
|
||||||
|
|
||||||
|
let username = rpcenv.get_user().unwrap();
|
||||||
|
let user_info = CachedUserInfo::new()?;
|
||||||
|
let user_privs = user_info.lookup_privs(&username, &["datastore", &store]);
|
||||||
|
|
||||||
|
let file_name = tools::required_string_param(¶m, "file-name")?.to_owned();
|
||||||
|
|
||||||
|
let backup_type = tools::required_string_param(¶m, "backup-type")?;
|
||||||
|
let backup_id = tools::required_string_param(¶m, "backup-id")?;
|
||||||
|
let backup_time = tools::required_integer_param(¶m, "backup-time")?;
|
||||||
|
|
||||||
|
let backup_dir = BackupDir::new(backup_type, backup_id, backup_time);
|
||||||
|
|
||||||
|
let allowed = (user_privs & PRIV_DATASTORE_READ) != 0;
|
||||||
|
if !allowed { check_backup_owner(&datastore, backup_dir.group(), &username)?; }
|
||||||
|
|
||||||
|
let files = read_backup_index(&datastore, &backup_dir)?;
|
||||||
|
for file in files {
|
||||||
|
if file.filename == file_name && file.encrypted == Some(true) {
|
||||||
|
bail!("cannot decode '{}' - is encrypted", file_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Download {} from {} ({}/{})", file_name, store, backup_dir, file_name);
|
||||||
|
|
||||||
|
let mut path = datastore.base_path();
|
||||||
|
path.push(backup_dir.relative_path());
|
||||||
|
path.push(&file_name);
|
||||||
|
|
||||||
|
let extension = file_name.rsplitn(2, '.').next().unwrap();
|
||||||
|
|
||||||
|
let body = match extension {
|
||||||
|
"didx" => {
|
||||||
|
let index = DynamicIndexReader::open(&path)
|
||||||
|
.map_err(|err| format_err!("unable to read dynamic index '{:?}' - {}", &path, err))?;
|
||||||
|
|
||||||
|
let chunk_reader = LocalChunkReader::new(datastore, None);
|
||||||
|
let reader = AsyncIndexReader::new(index, chunk_reader);
|
||||||
|
Body::wrap_stream(reader
|
||||||
|
.map_err(move |err| {
|
||||||
|
eprintln!("error during streaming of '{:?}' - {}", path, err);
|
||||||
|
err
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
"fidx" => {
|
||||||
|
let index = FixedIndexReader::open(&path)
|
||||||
|
.map_err(|err| format_err!("unable to read fixed index '{:?}' - {}", &path, err))?;
|
||||||
|
|
||||||
|
let chunk_reader = LocalChunkReader::new(datastore, None);
|
||||||
|
let reader = AsyncIndexReader::new(index, chunk_reader);
|
||||||
|
Body::wrap_stream(reader
|
||||||
|
.map_err(move |err| {
|
||||||
|
eprintln!("error during streaming of '{:?}' - {}", path, err);
|
||||||
|
err
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
"blob" => {
|
||||||
|
let file = std::fs::File::open(&path)
|
||||||
|
.map_err(|err| http_err!(BAD_REQUEST, format!("File open failed: {}", err)))?;
|
||||||
|
|
||||||
|
Body::wrap_stream(
|
||||||
|
WrappedReaderStream::new(DataBlobReader::new(file, None)?)
|
||||||
|
.map_err(move |err| {
|
||||||
|
eprintln!("error during streaming of '{:?}' - {}", path, err);
|
||||||
|
err
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
extension => {
|
||||||
|
bail!("cannot download '{}' files", extension);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// fixme: set other headers ?
|
||||||
|
Ok(Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.header(header::CONTENT_TYPE, "application/octet-stream")
|
||||||
|
.body(body)
|
||||||
|
.unwrap())
|
||||||
|
}.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
#[sortable]
|
#[sortable]
|
||||||
pub const API_METHOD_UPLOAD_BACKUP_LOG: ApiMethod = ApiMethod::new(
|
pub const API_METHOD_UPLOAD_BACKUP_LOG: ApiMethod = ApiMethod::new(
|
||||||
&ApiHandler::AsyncHttp(&upload_backup_log),
|
&ApiHandler::AsyncHttp(&upload_backup_log),
|
||||||
|
@ -890,6 +1002,11 @@ const DATASTORE_INFO_SUBDIRS: SubdirMap = &[
|
||||||
&Router::new()
|
&Router::new()
|
||||||
.download(&API_METHOD_DOWNLOAD_FILE)
|
.download(&API_METHOD_DOWNLOAD_FILE)
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"download-decoded",
|
||||||
|
&Router::new()
|
||||||
|
.download(&API_METHOD_DOWNLOAD_FILE_DECODED)
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"files",
|
"files",
|
||||||
&Router::new()
|
&Router::new()
|
||||||
|
|
Loading…
Reference in New Issue