1 use std
::path
::PathBuf
;
3 use anyhow
::{bail, Error}
;
5 use ::serde
::{Deserialize, Serialize}
;
7 use proxmox
::api
::{api, Router, RpcEnvironment, Permission}
;
8 use proxmox
::api
::section_config
::SectionConfigData
;
9 use proxmox
::api
::schema
::parse_property_string
;
11 use pbs_datastore
::task
::TaskState
;
13 use crate::api2
::types
::*;
15 use crate::config
::cached_user_info
::CachedUserInfo
;
16 use crate::config
::datastore
::{self, DataStoreConfig, DIR_NAME_SCHEMA}
;
17 use crate::config
::acl
::{PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY}
;
18 use crate::server
::{jobstate, WorkerTask}
;
25 description
: "List the configured datastores (with config digest).",
27 items
: { type: datastore::DataStoreConfig }
,
30 permission
: &Permission
::Anybody
,
33 /// List all datastores
34 pub fn list_datastores(
36 mut rpcenv
: &mut dyn RpcEnvironment
,
37 ) -> Result
<Vec
<DataStoreConfig
>, Error
> {
39 let (config
, digest
) = datastore
::config()?
;
41 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
42 let user_info
= CachedUserInfo
::new()?
;
44 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
46 let list
:Vec
<DataStoreConfig
> = config
.convert_to_typed_array("datastore")?
;
47 let filter_by_privs
= |store
: &DataStoreConfig
| {
48 let user_privs
= user_info
.lookup_privs(&auth_id
, &["datastore", &store
.name
]);
49 (user_privs
& PRIV_DATASTORE_AUDIT
) != 0
52 Ok(list
.into_iter().filter(filter_by_privs
).collect())
55 pub(crate) fn do_create_datastore(
57 mut config
: SectionConfigData
,
58 datastore
: DataStoreConfig
,
59 worker
: Option
<&dyn TaskState
>,
60 ) -> Result
<(), Error
> {
61 let path
: PathBuf
= datastore
.path
.clone().into();
63 let backup_user
= crate::backup
::backup_user()?
;
64 let _store
= ChunkStore
::create(&datastore
.name
, path
, backup_user
.uid
, backup_user
.gid
, worker
)?
;
66 config
.set_data(&datastore
.name
, "datastore", &datastore
)?
;
68 datastore
::save_config(&config
)?
;
70 jobstate
::create_state_file("prune", &datastore
.name
)?
;
71 jobstate
::create_state_file("garbage_collection", &datastore
.name
)?
;
76 // fixme: impl. const fn get_object_schema(datastore::DataStoreConfig::API_SCHEMA),
77 // but this need support for match inside const fn
78 // see: https://github.com/rust-lang/rust/issues/49146
85 schema
: DATASTORE_SCHEMA
,
88 schema
: DIR_NAME_SCHEMA
,
92 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
100 schema
: DATASTORE_NOTIFY_STRING_SCHEMA
,
104 schema
: GC_SCHEDULE_SCHEMA
,
108 schema
: PRUNE_SCHEDULE_SCHEMA
,
112 schema
: PRUNE_SCHEMA_KEEP_LAST
,
116 schema
: PRUNE_SCHEMA_KEEP_HOURLY
,
120 schema
: PRUNE_SCHEMA_KEEP_DAILY
,
124 schema
: PRUNE_SCHEMA_KEEP_WEEKLY
,
128 schema
: PRUNE_SCHEMA_KEEP_MONTHLY
,
132 schema
: PRUNE_SCHEMA_KEEP_YEARLY
,
137 permission
: &Permission
::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE
, false),
140 /// Create new datastore config.
141 pub fn create_datastore(
143 rpcenv
: &mut dyn RpcEnvironment
,
144 ) -> Result
<String
, Error
> {
146 let lock
= datastore
::lock_config()?
;
148 let datastore
: datastore
::DataStoreConfig
= serde_json
::from_value(param
)?
;
150 let (config
, _digest
) = datastore
::config()?
;
152 if config
.sections
.get(&datastore
.name
).is_some() {
153 bail
!("datastore '{}' already exists.", datastore
.name
);
156 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
158 WorkerTask
::new_thread(
160 Some(datastore
.name
.to_string()),
163 move |worker
| do_create_datastore(lock
, config
, datastore
, Some(&worker
)),
171 schema
: DATASTORE_SCHEMA
,
175 returns
: { type: datastore::DataStoreConfig }
,
177 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT
, false),
180 /// Read a datastore configuration.
181 pub fn read_datastore(
183 mut rpcenv
: &mut dyn RpcEnvironment
,
184 ) -> Result
<DataStoreConfig
, Error
> {
185 let (config
, digest
) = datastore
::config()?
;
187 let store_config
= config
.lookup("datastore", &name
)?
;
188 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
194 #[derive(Serialize, Deserialize)]
195 #[serde(rename_all="kebab-case")]
196 #[allow(non_camel_case_types)]
197 /// Deletable property name
198 pub enum DeletableProperty
{
199 /// Delete the comment property.
201 /// Delete the garbage collection schedule.
203 /// Delete the prune job schedule.
205 /// Delete the keep-last property
207 /// Delete the keep-hourly property
209 /// Delete the keep-daily property
211 /// Delete the keep-weekly property
213 /// Delete the keep-monthly property
215 /// Delete the keep-yearly property
217 /// Delete the verify-new property
219 /// Delete the notify-user property
221 /// Delete the notify property
230 schema
: DATASTORE_SCHEMA
,
234 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
242 schema
: DATASTORE_NOTIFY_STRING_SCHEMA
,
246 schema
: GC_SCHEDULE_SCHEMA
,
250 schema
: PRUNE_SCHEDULE_SCHEMA
,
254 schema
: PRUNE_SCHEMA_KEEP_LAST
,
258 schema
: PRUNE_SCHEMA_KEEP_HOURLY
,
262 schema
: PRUNE_SCHEMA_KEEP_DAILY
,
266 schema
: PRUNE_SCHEMA_KEEP_WEEKLY
,
270 schema
: PRUNE_SCHEMA_KEEP_MONTHLY
,
274 schema
: PRUNE_SCHEMA_KEEP_YEARLY
,
277 description
: "If enabled, all new backups will be verified right after completion.",
283 description
: "List of properties to delete.",
287 type: DeletableProperty
,
292 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
297 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY
, false),
300 /// Update datastore config.
301 #[allow(clippy::too_many_arguments)]
302 pub fn update_datastore(
304 comment
: Option
<String
>,
305 gc_schedule
: Option
<String
>,
306 prune_schedule
: Option
<String
>,
307 keep_last
: Option
<u64>,
308 keep_hourly
: Option
<u64>,
309 keep_daily
: Option
<u64>,
310 keep_weekly
: Option
<u64>,
311 keep_monthly
: Option
<u64>,
312 keep_yearly
: Option
<u64>,
313 verify_new
: Option
<bool
>,
314 notify
: Option
<String
>,
315 notify_user
: Option
<Userid
>,
316 delete
: Option
<Vec
<DeletableProperty
>>,
317 digest
: Option
<String
>,
318 ) -> Result
<(), Error
> {
320 let _lock
= datastore
::lock_config()?
;
322 // pass/compare digest
323 let (mut config
, expected_digest
) = datastore
::config()?
;
325 if let Some(ref digest
) = digest
{
326 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
327 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
330 let mut data
: datastore
::DataStoreConfig
= config
.lookup("datastore", &name
)?
;
332 if let Some(delete
) = delete
{
333 for delete_prop
in delete
{
335 DeletableProperty
::comment
=> { data.comment = None; }
,
336 DeletableProperty
::gc_schedule
=> { data.gc_schedule = None; }
,
337 DeletableProperty
::prune_schedule
=> { data.prune_schedule = None; }
,
338 DeletableProperty
::keep_last
=> { data.keep_last = None; }
,
339 DeletableProperty
::keep_hourly
=> { data.keep_hourly = None; }
,
340 DeletableProperty
::keep_daily
=> { data.keep_daily = None; }
,
341 DeletableProperty
::keep_weekly
=> { data.keep_weekly = None; }
,
342 DeletableProperty
::keep_monthly
=> { data.keep_monthly = None; }
,
343 DeletableProperty
::keep_yearly
=> { data.keep_yearly = None; }
,
344 DeletableProperty
::verify_new
=> { data.verify_new = None; }
,
345 DeletableProperty
::notify
=> { data.notify = None; }
,
346 DeletableProperty
::notify_user
=> { data.notify_user = None; }
,
351 if let Some(comment
) = comment
{
352 let comment
= comment
.trim().to_string();
353 if comment
.is_empty() {
356 data
.comment
= Some(comment
);
360 let mut gc_schedule_changed
= false;
361 if gc_schedule
.is_some() {
362 gc_schedule_changed
= data
.gc_schedule
!= gc_schedule
;
363 data
.gc_schedule
= gc_schedule
;
366 let mut prune_schedule_changed
= false;
367 if prune_schedule
.is_some() {
368 prune_schedule_changed
= data
.prune_schedule
!= prune_schedule
;
369 data
.prune_schedule
= prune_schedule
;
372 if keep_last
.is_some() { data.keep_last = keep_last; }
373 if keep_hourly
.is_some() { data.keep_hourly = keep_hourly; }
374 if keep_daily
.is_some() { data.keep_daily = keep_daily; }
375 if keep_weekly
.is_some() { data.keep_weekly = keep_weekly; }
376 if keep_monthly
.is_some() { data.keep_monthly = keep_monthly; }
377 if keep_yearly
.is_some() { data.keep_yearly = keep_yearly; }
379 if let Some(notify_str
) = notify
{
380 let value
= parse_property_string(¬ify_str
, &DatastoreNotify
::API_SCHEMA
)?
;
381 let notify
: DatastoreNotify
= serde_json
::from_value(value
)?
;
382 if let DatastoreNotify { gc: None, verify: None, sync: None }
= notify
{
385 data
.notify
= Some(notify_str
);
388 if verify_new
.is_some() { data.verify_new = verify_new; }
390 if notify_user
.is_some() { data.notify_user = notify_user; }
392 config
.set_data(&name
, "datastore", &data
)?
;
394 datastore
::save_config(&config
)?
;
396 // we want to reset the statefiles, to avoid an immediate action in some cases
397 // (e.g. going from monthly to weekly in the second week of the month)
398 if gc_schedule_changed
{
399 jobstate
::update_job_last_run_time("garbage_collection", &name
)?
;
402 if prune_schedule_changed
{
403 jobstate
::update_job_last_run_time("prune", &name
)?
;
414 schema
: DATASTORE_SCHEMA
,
418 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
423 permission
: &Permission
::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE
, false),
426 /// Remove a datastore configuration.
427 pub async
fn delete_datastore(name
: String
, digest
: Option
<String
>) -> Result
<(), Error
> {
429 let _lock
= datastore
::lock_config()?
;
431 let (mut config
, expected_digest
) = datastore
::config()?
;
433 if let Some(ref digest
) = digest
{
434 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
435 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
438 match config
.sections
.get(&name
) {
439 Some(_
) => { config.sections.remove(&name); }
,
440 None
=> bail
!("datastore '{}' does not exist.", name
),
443 datastore
::save_config(&config
)?
;
446 let _
= jobstate
::remove_state_file("prune", &name
);
447 let _
= jobstate
::remove_state_file("garbage_collection", &name
);
449 crate::server
::notify_datastore_removed().await?
;
454 const ITEM_ROUTER
: Router
= Router
::new()
455 .get(&API_METHOD_READ_DATASTORE
)
456 .put(&API_METHOD_UPDATE_DATASTORE
)
457 .delete(&API_METHOD_DELETE_DATASTORE
);
459 pub const ROUTER
: Router
= Router
::new()
460 .get(&API_METHOD_LIST_DATASTORES
)
461 .post(&API_METHOD_CREATE_DATASTORE
)
462 .match_all("name", &ITEM_ROUTER
);