diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs index c39d0fa2..7ab36047 100644 --- a/src/api2/config/datastore.rs +++ b/src/api2/config/datastore.rs @@ -4,11 +4,11 @@ use anyhow::{bail, Error}; use serde_json::Value; use ::serde::{Deserialize, Serialize}; -use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; +use proxmox::api::{api, Router, RpcEnvironment, Permission}; use crate::api2::types::*; use crate::backup::*; -use crate::config::datastore; +use crate::config::datastore::{self, DataStoreConfig, DIR_NAME_SCHEMA}; use crate::config::acl::{PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY}; #[api( @@ -18,25 +18,7 @@ use crate::config::acl::{PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY}; returns: { description: "List the configured datastores (with config digest).", type: Array, - items: { - description: "Datastore configuration.", - properties: { - name: { - schema: DATASTORE_SCHEMA, - }, - path: { - schema: datastore::DIR_NAME_SCHEMA, - }, - "gc-schedule": { - optional: true, - schema: GC_SCHEDULE_SCHEMA, - }, - comment: { - optional: true, - schema: SINGLE_LINE_COMMENT_SCHEMA, - }, - }, - }, + items: { type: datastore::DataStoreConfig }, }, access: { permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_AUDIT, false), @@ -45,15 +27,23 @@ use crate::config::acl::{PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY}; /// List all datastores pub fn list_datastores( _param: Value, - _info: &ApiMethod, - _rpcenv: &mut dyn RpcEnvironment, -) -> Result { + mut rpcenv: &mut dyn RpcEnvironment, +) -> Result, Error> { let (config, digest) = datastore::config()?; - Ok(config.convert_to_array("name", Some(&digest), &[])) + let list = config.convert_to_typed_array("datastore")?; + + rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); + + Ok(list) } + +// fixme: impl. const fn get_object_schema(datastore::DataStoreConfig::API_SCHEMA), +// but this need support for match inside const fn +// see: https://github.com/rust-lang/rust/issues/49146 + #[api( protected: true, input: { @@ -61,6 +51,9 @@ pub fn list_datastores( name: { schema: DATASTORE_SCHEMA, }, + path: { + schema: DIR_NAME_SCHEMA, + }, comment: { optional: true, schema: SINGLE_LINE_COMMENT_SCHEMA, @@ -69,8 +62,33 @@ pub fn list_datastores( optional: true, schema: GC_SCHEDULE_SCHEMA, }, - path: { - schema: datastore::DIR_NAME_SCHEMA, + "prune-schedule": { + optional: true, + schema: PRUNE_SCHEDULE_SCHEMA, + }, + "keep-last": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_LAST, + }, + "keep-hourly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_HOURLY, + }, + "keep-daily": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_DAILY, + }, + "keep-weekly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_WEEKLY, + }, + "keep-monthly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_MONTHLY, + }, + "keep-yearly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_YEARLY, }, }, }, @@ -79,7 +97,7 @@ pub fn list_datastores( }, )] /// Create new datastore config. -pub fn create_datastore(name: String, param: Value) -> Result<(), Error> { +pub fn create_datastore(param: Value) -> Result<(), Error> { let _lock = crate::tools::open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0))?; @@ -87,16 +105,16 @@ pub fn create_datastore(name: String, param: Value) -> Result<(), Error> { let (mut config, _digest) = datastore::config()?; - if let Some(_) = config.sections.get(&name) { - bail!("datastore '{}' already exists.", name); + if let Some(_) = config.sections.get(&datastore.name) { + bail!("datastore '{}' already exists.", datastore.name); } let path: PathBuf = datastore.path.clone().into(); let backup_user = crate::backup::backup_user()?; - let _store = ChunkStore::create(&name, path, backup_user.uid, backup_user.gid)?; + let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid)?; - config.set_data(&name, "datastore", &datastore)?; + config.set_data(&datastore.name, "datastore", &datastore)?; datastore::save_config(&config)?; @@ -120,12 +138,16 @@ pub fn create_datastore(name: String, param: Value) -> Result<(), Error> { }, )] /// Read a datastore configuration. -pub fn read_datastore(name: String) -> Result { +pub fn read_datastore( + name: String, + mut rpcenv: &mut dyn RpcEnvironment, +) -> Result { let (config, digest) = datastore::config()?; - let mut data = config.lookup_json("datastore", &name)?; - data.as_object_mut().unwrap() - .insert("digest".into(), proxmox::tools::digest_to_hex(&digest).into()); - Ok(data) + + let store_config = config.lookup("datastore", &name)?; + rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); + + Ok(store_config) } #[api()] @@ -138,6 +160,20 @@ pub enum DeletableProperty { comment, /// Delete the garbage collection schedule. gc_schedule, + /// Delete the prune job schedule. + prune_schedule, + /// Delete the keep-last property + keep_last, + /// Delete the keep-hourly property + keep_hourly, + /// Delete the keep-daily property + keep_daily, + /// Delete the keep-weekly property + keep_weekly, + /// Delete the keep-monthly property + keep_monthly, + /// Delete the keep-yearly property + keep_yearly, } #[api( @@ -155,6 +191,34 @@ pub enum DeletableProperty { optional: true, schema: GC_SCHEDULE_SCHEMA, }, + "prune-schedule": { + optional: true, + schema: PRUNE_SCHEDULE_SCHEMA, + }, + "keep-last": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_LAST, + }, + "keep-hourly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_HOURLY, + }, + "keep-daily": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_DAILY, + }, + "keep-weekly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_WEEKLY, + }, + "keep-monthly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_MONTHLY, + }, + "keep-yearly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_YEARLY, + }, delete: { description: "List of properties to delete.", type: Array, @@ -178,6 +242,13 @@ pub fn update_datastore( name: String, comment: Option, gc_schedule: Option, + prune_schedule: Option, + keep_last: Option, + keep_hourly: Option, + keep_daily: Option, + keep_weekly: Option, + keep_monthly: Option, + keep_yearly: Option, delete: Option>, digest: Option, ) -> Result<(), Error> { @@ -199,6 +270,13 @@ pub fn update_datastore( match delete_prop { DeletableProperty::comment => { data.comment = None; }, DeletableProperty::gc_schedule => { data.gc_schedule = None; }, + DeletableProperty::prune_schedule => { data.prune_schedule = None; }, + DeletableProperty::keep_last => { data.keep_last = None; }, + DeletableProperty::keep_hourly => { data.keep_hourly = None; }, + DeletableProperty::keep_daily => { data.keep_daily = None; }, + DeletableProperty::keep_weekly => { data.keep_weekly = None; }, + DeletableProperty::keep_monthly => { data.keep_monthly = None; }, + DeletableProperty::keep_yearly => { data.keep_yearly = None; }, } } } @@ -213,6 +291,14 @@ pub fn update_datastore( } if gc_schedule.is_some() { data.gc_schedule = gc_schedule; } + if prune_schedule.is_some() { data.prune_schedule = prune_schedule; } + + if keep_last.is_some() { data.keep_last = keep_last; } + if keep_hourly.is_some() { data.keep_hourly = keep_hourly; } + if keep_daily.is_some() { data.keep_daily = keep_daily; } + if keep_weekly.is_some() { data.keep_weekly = keep_weekly; } + if keep_monthly.is_some() { data.keep_monthly = keep_monthly; } + if keep_yearly.is_some() { data.keep_yearly = keep_yearly; } config.set_data(&name, "datastore", &data)?; diff --git a/src/api2/types.rs b/src/api2/types.rs index 1ec49593..4ec95ef1 100644 --- a/src/api2/types.rs +++ b/src/api2/types.rs @@ -292,6 +292,11 @@ pub const GC_SCHEDULE_SCHEMA: Schema = StringSchema::new( .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event)) .schema(); +pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new( + "Run prune job at specified schedule.") + .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event)) + .schema(); + pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.") .format(&PROXMOX_SAFE_ID_FORMAT) .min_length(3) diff --git a/src/config/datastore.rs b/src/config/datastore.rs index 52d7273b..5c46e07f 100644 --- a/src/config/datastore.rs +++ b/src/config/datastore.rs @@ -26,16 +26,47 @@ pub const DIR_NAME_SCHEMA: Schema = StringSchema::new("Directory name").schema() #[api( properties: { + name: { + schema: DATASTORE_SCHEMA, + }, + path: { + schema: DIR_NAME_SCHEMA, + }, comment: { optional: true, schema: SINGLE_LINE_COMMENT_SCHEMA, }, "gc-schedule": { - schema: GC_SCHEDULE_SCHEMA, optional: true, + schema: GC_SCHEDULE_SCHEMA, }, - path: { - schema: DIR_NAME_SCHEMA, + "prune-schedule": { + optional: true, + schema: PRUNE_SCHEDULE_SCHEMA, + }, + "keep-last": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_LAST, + }, + "keep-hourly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_HOURLY, + }, + "keep-daily": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_DAILY, + }, + "keep-weekly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_WEEKLY, + }, + "keep-monthly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_MONTHLY, + }, + "keep-yearly": { + optional: true, + schema: PRUNE_SCHEMA_KEEP_YEARLY, }, } )] @@ -43,12 +74,27 @@ pub const DIR_NAME_SCHEMA: Schema = StringSchema::new("Directory name").schema() #[derive(Serialize,Deserialize)] /// Datastore configuration properties. pub struct DataStoreConfig { + pub name: String, #[serde(skip_serializing_if="Option::is_none")] pub comment: Option, pub path: String, #[serde(skip_serializing_if="Option::is_none")] pub gc_schedule: Option, - } + #[serde(skip_serializing_if="Option::is_none")] + pub prune_schedule: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub keep_last: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub keep_hourly: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub keep_daily: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub keep_weekly: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub keep_monthly: Option, + #[serde(skip_serializing_if="Option::is_none")] + pub keep_yearly: Option, +} fn init() -> SectionConfig { let obj_schema = match DataStoreConfig::API_SCHEMA { @@ -56,7 +102,7 @@ fn init() -> SectionConfig { _ => unreachable!(), }; - let plugin = SectionConfigPlugin::new("datastore".to_string(), None, obj_schema); + let plugin = SectionConfigPlugin::new("datastore".to_string(), Some(String::from("name")), obj_schema); let mut config = SectionConfig::new(&DATASTORE_SCHEMA); config.register_plugin(plugin);