use new api updater features
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
		
				
					committed by
					
						 Wolfgang Bumiller
						Wolfgang Bumiller
					
				
			
			
				
	
			
			
			
						parent
						
							be5b468975
						
					
				
				
					commit
					a8a20e9210
				
			| @ -30,7 +30,7 @@ use lazy_static::lazy_static; | |||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
|  |  | ||||||
| use proxmox::api::api; | use proxmox::api::api; | ||||||
| use proxmox::api::schema::{ApiStringFormat, Schema, StringSchema}; | use proxmox::api::schema::{ApiStringFormat, Schema, StringSchema, Updatable}; | ||||||
| use proxmox::const_regex; | use proxmox::const_regex; | ||||||
|  |  | ||||||
| // we only allow a limited set of characters | // we only allow a limited set of characters | ||||||
| @ -403,6 +403,12 @@ pub struct Userid { | |||||||
|     name_len: usize, |     name_len: usize, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl Updatable for Userid { | ||||||
|  |     type Updater = Option<Userid>; | ||||||
|  |  | ||||||
|  |     const UPDATER_IS_OPTION: bool = true; | ||||||
|  | } | ||||||
|  |  | ||||||
| impl Userid { | impl Userid { | ||||||
|     pub const API_SCHEMA: Schema = StringSchema::new("User ID") |     pub const API_SCHEMA: Schema = StringSchema::new("User ID") | ||||||
|         .format(&PROXMOX_USER_ID_FORMAT) |         .format(&PROXMOX_USER_ID_FORMAT) | ||||||
|  | |||||||
| @ -5,7 +5,6 @@ use anyhow::Error; | |||||||
|  |  | ||||||
| use crate::api2::types::PROXMOX_CONFIG_DIGEST_SCHEMA; | use crate::api2::types::PROXMOX_CONFIG_DIGEST_SCHEMA; | ||||||
| use proxmox::api::{api, Permission, Router, RpcEnvironment, SubdirMap}; | use proxmox::api::{api, Permission, Router, RpcEnvironment, SubdirMap}; | ||||||
| use proxmox::api::schema::Updatable; |  | ||||||
| use proxmox::list_subdirs_api_method; | use proxmox::list_subdirs_api_method; | ||||||
|  |  | ||||||
| use crate::config::tfa::{self, WebauthnConfig, WebauthnConfigUpdater}; | use crate::config::tfa::{self, WebauthnConfig, WebauthnConfigUpdater}; | ||||||
| @ -74,9 +73,14 @@ pub fn update_webauthn_config( | |||||||
|             let digest = proxmox::tools::hex_to_digest(digest)?; |             let digest = proxmox::tools::hex_to_digest(digest)?; | ||||||
|             crate::tools::detect_modified_configuration_file(&digest, &wa.digest()?)?; |             crate::tools::detect_modified_configuration_file(&digest, &wa.digest()?)?; | ||||||
|         } |         } | ||||||
|         wa.update_from::<&str>(webauthn, &[])?; |         if let Some(ref rp) = webauthn.rp { wa.rp = rp.clone(); } | ||||||
|  |         if let Some(ref origin) = webauthn.rp { wa.origin = origin.clone(); } | ||||||
|  |         if let Some(ref id) = webauthn.id { wa.id = id.clone(); } | ||||||
|     } else { |     } else { | ||||||
|         tfa.webauthn = Some(WebauthnConfig::try_build_from(webauthn)?); |         let rp = webauthn.rp.unwrap(); | ||||||
|  |         let origin = webauthn.origin.unwrap(); | ||||||
|  |         let id = webauthn.id.unwrap(); | ||||||
|  |         tfa.webauthn = Some(WebauthnConfig { rp, origin, id }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     tfa::write(&tfa)?; |     tfa::write(&tfa)?; | ||||||
|  | |||||||
| @ -9,7 +9,6 @@ use serde::{Deserialize, Serialize}; | |||||||
| use serde_json::{json, Value}; | use serde_json::{json, Value}; | ||||||
|  |  | ||||||
| use proxmox::api::router::SubdirMap; | use proxmox::api::router::SubdirMap; | ||||||
| use proxmox::api::schema::Updatable; |  | ||||||
| use proxmox::api::{api, Permission, Router, RpcEnvironment}; | use proxmox::api::{api, Permission, Router, RpcEnvironment}; | ||||||
| use proxmox::http_bail; | use proxmox::http_bail; | ||||||
| use proxmox::list_subdirs_api_method; | use proxmox::list_subdirs_api_method; | ||||||
| @ -21,7 +20,7 @@ use crate::acme::AcmeClient; | |||||||
| use crate::api2::types::{AcmeAccountName, AcmeChallengeSchema, Authid, KnownAcmeDirectory}; | use crate::api2::types::{AcmeAccountName, AcmeChallengeSchema, Authid, KnownAcmeDirectory}; | ||||||
| use crate::config::acl::PRIV_SYS_MODIFY; | use crate::config::acl::PRIV_SYS_MODIFY; | ||||||
| use crate::config::acme::plugin::{ | use crate::config::acme::plugin::{ | ||||||
|     DnsPlugin, DnsPluginCore, DnsPluginCoreUpdater, PLUGIN_ID_SCHEMA, |     self, DnsPlugin, DnsPluginCore, DnsPluginCoreUpdater, PLUGIN_ID_SCHEMA, | ||||||
| }; | }; | ||||||
| use crate::server::WorkerTask; | use crate::server::WorkerTask; | ||||||
| use crate::tools::ControlFlow; | use crate::tools::ControlFlow; | ||||||
| @ -464,7 +463,7 @@ pub struct PluginConfig { | |||||||
|     /// |     /// | ||||||
|     /// Allows to cope with long TTL of DNS records. |     /// Allows to cope with long TTL of DNS records. | ||||||
|     #[serde(skip_serializing_if = "Option::is_none", default)] |     #[serde(skip_serializing_if = "Option::is_none", default)] | ||||||
|     validation_delay: Option<u32>, |     alidation_delay: Option<u32>, | ||||||
|  |  | ||||||
|     /// Flag to disable the config. |     /// Flag to disable the config. | ||||||
|     #[serde(skip_serializing_if = "Option::is_none", default)] |     #[serde(skip_serializing_if = "Option::is_none", default)] | ||||||
| @ -515,8 +514,6 @@ fn modify_cfg_for_api(id: &str, ty: &str, data: &Value) -> PluginConfig { | |||||||
| )] | )] | ||||||
| /// List ACME challenge plugins. | /// List ACME challenge plugins. | ||||||
| pub fn list_plugins(mut rpcenv: &mut dyn RpcEnvironment) -> Result<Vec<PluginConfig>, Error> { | pub fn list_plugins(mut rpcenv: &mut dyn RpcEnvironment) -> Result<Vec<PluginConfig>, Error> { | ||||||
|     use crate::config::acme::plugin; |  | ||||||
|  |  | ||||||
|     let (plugins, digest) = plugin::config()?; |     let (plugins, digest) = plugin::config()?; | ||||||
|     rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); |     rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); | ||||||
|     Ok(plugins |     Ok(plugins | ||||||
| @ -539,8 +536,6 @@ pub fn list_plugins(mut rpcenv: &mut dyn RpcEnvironment) -> Result<Vec<PluginCon | |||||||
| )] | )] | ||||||
| /// List ACME challenge plugins. | /// List ACME challenge plugins. | ||||||
| pub fn get_plugin(id: String, mut rpcenv: &mut dyn RpcEnvironment) -> Result<PluginConfig, Error> { | pub fn get_plugin(id: String, mut rpcenv: &mut dyn RpcEnvironment) -> Result<PluginConfig, Error> { | ||||||
|     use crate::config::acme::plugin; |  | ||||||
|  |  | ||||||
|     let (plugins, digest) = plugin::config()?; |     let (plugins, digest) = plugin::config()?; | ||||||
|     rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); |     rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); | ||||||
|  |  | ||||||
| @ -562,7 +557,7 @@ pub fn get_plugin(id: String, mut rpcenv: &mut dyn RpcEnvironment) -> Result<Plu | |||||||
|                 description: "The ACME challenge plugin type.", |                 description: "The ACME challenge plugin type.", | ||||||
|             }, |             }, | ||||||
|             core: { |             core: { | ||||||
|                 type: DnsPluginCoreUpdater, |                 type: DnsPluginCore, | ||||||
|                 flatten: true, |                 flatten: true, | ||||||
|             }, |             }, | ||||||
|             data: { |             data: { | ||||||
| @ -578,9 +573,7 @@ pub fn get_plugin(id: String, mut rpcenv: &mut dyn RpcEnvironment) -> Result<Plu | |||||||
|     protected: true, |     protected: true, | ||||||
| )] | )] | ||||||
| /// Add ACME plugin configuration. | /// Add ACME plugin configuration. | ||||||
| pub fn add_plugin(r#type: String, core: DnsPluginCoreUpdater, data: String) -> Result<(), Error> { | pub fn add_plugin(r#type: String, core: DnsPluginCore, data: String) -> Result<(), Error> { | ||||||
|     use crate::config::acme::plugin; |  | ||||||
|  |  | ||||||
|     // Currently we only support DNS plugins and the standalone plugin is "fixed": |     // Currently we only support DNS plugins and the standalone plugin is "fixed": | ||||||
|     if r#type != "dns" { |     if r#type != "dns" { | ||||||
|         bail!("invalid ACME plugin type: {:?}", r#type); |         bail!("invalid ACME plugin type: {:?}", r#type); | ||||||
| @ -588,13 +581,8 @@ pub fn add_plugin(r#type: String, core: DnsPluginCoreUpdater, data: String) -> R | |||||||
|  |  | ||||||
|     let data = String::from_utf8(base64::decode(&data)?) |     let data = String::from_utf8(base64::decode(&data)?) | ||||||
|         .map_err(|_| format_err!("data must be valid UTF-8"))?; |         .map_err(|_| format_err!("data must be valid UTF-8"))?; | ||||||
|     //core.api_fixup()?; |  | ||||||
|  |  | ||||||
|     // FIXME: Solve the Updater with non-optional fields thing... |     let id = core.id.clone(); | ||||||
|     let id = core |  | ||||||
|         .id |  | ||||||
|         .clone() |  | ||||||
|         .ok_or_else(|| format_err!("missing required 'id' parameter"))?; |  | ||||||
|  |  | ||||||
|     let _lock = plugin::lock()?; |     let _lock = plugin::lock()?; | ||||||
|  |  | ||||||
| @ -603,10 +591,7 @@ pub fn add_plugin(r#type: String, core: DnsPluginCoreUpdater, data: String) -> R | |||||||
|         bail!("ACME plugin ID {:?} already exists", id); |         bail!("ACME plugin ID {:?} already exists", id); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let plugin = serde_json::to_value(DnsPlugin { |     let plugin = serde_json::to_value(DnsPlugin { core, data })?; | ||||||
|         core: DnsPluginCore::try_build_from(core)?, |  | ||||||
|         data, |  | ||||||
|     })?; |  | ||||||
|  |  | ||||||
|     plugins.insert(id, r#type, plugin); |     plugins.insert(id, r#type, plugin); | ||||||
|  |  | ||||||
| @ -628,8 +613,6 @@ pub fn add_plugin(r#type: String, core: DnsPluginCoreUpdater, data: String) -> R | |||||||
| )] | )] | ||||||
| /// Delete an ACME plugin configuration. | /// Delete an ACME plugin configuration. | ||||||
| pub fn delete_plugin(id: String) -> Result<(), Error> { | pub fn delete_plugin(id: String) -> Result<(), Error> { | ||||||
|     use crate::config::acme::plugin; |  | ||||||
|  |  | ||||||
|     let _lock = plugin::lock()?; |     let _lock = plugin::lock()?; | ||||||
|  |  | ||||||
|     let (mut plugins, _digest) = plugin::config()?; |     let (mut plugins, _digest) = plugin::config()?; | ||||||
| @ -641,10 +624,23 @@ pub fn delete_plugin(id: String) -> Result<(), Error> { | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[api()] | ||||||
|  | #[derive(Serialize, Deserialize)] | ||||||
|  | #[serde(rename_all="kebab-case")] | ||||||
|  | #[allow(non_camel_case_types)] | ||||||
|  | /// Deletable property name | ||||||
|  | pub enum DeletableProperty { | ||||||
|  |     /// Delete the disable property | ||||||
|  |     disable, | ||||||
|  |     /// Delete the validation-delay property | ||||||
|  |     validation_delay, | ||||||
|  | } | ||||||
|  |  | ||||||
| #[api( | #[api( | ||||||
|     input: { |     input: { | ||||||
|         properties: { |         properties: { | ||||||
|             core_update: { |             id: { schema: PLUGIN_ID_SCHEMA }, | ||||||
|  |             update: { | ||||||
|                 type: DnsPluginCoreUpdater, |                 type: DnsPluginCoreUpdater, | ||||||
|                 flatten: true, |                 flatten: true, | ||||||
|             }, |             }, | ||||||
| @ -654,14 +650,18 @@ pub fn delete_plugin(id: String) -> Result<(), Error> { | |||||||
|                 // This is different in the API! |                 // This is different in the API! | ||||||
|                 description: "DNS plugin data (base64 encoded with padding).", |                 description: "DNS plugin data (base64 encoded with padding).", | ||||||
|             }, |             }, | ||||||
|  |             delete: { | ||||||
|  |                 description: "List of properties to delete.", | ||||||
|  |                 type: Array, | ||||||
|  |                 optional: true, | ||||||
|  |                 items: { | ||||||
|  |                     type: DeletableProperty, | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|             digest: { |             digest: { | ||||||
|                 description: "Digest to protect against concurrent updates", |                 description: "Digest to protect against concurrent updates", | ||||||
|                 optional: true, |                 optional: true, | ||||||
|             }, |             }, | ||||||
|             delete: { |  | ||||||
|                 description: "Options to remove from the configuration", |  | ||||||
|                 optional: true, |  | ||||||
|             }, |  | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
|     access: { |     access: { | ||||||
| @ -671,13 +671,12 @@ pub fn delete_plugin(id: String) -> Result<(), Error> { | |||||||
| )] | )] | ||||||
| /// Update an ACME plugin configuration. | /// Update an ACME plugin configuration. | ||||||
| pub fn update_plugin( | pub fn update_plugin( | ||||||
|     core_update: DnsPluginCoreUpdater, |     id: String, | ||||||
|  |     update: DnsPluginCoreUpdater, | ||||||
|     data: Option<String>, |     data: Option<String>, | ||||||
|     delete: Option<String>, |     delete: Option<Vec<DeletableProperty>>, | ||||||
|     digest: Option<String>, |     digest: Option<String>, | ||||||
| ) -> Result<(), Error> { | ) -> Result<(), Error> { | ||||||
|     use crate::config::acme::plugin; |  | ||||||
|  |  | ||||||
|     let data = data |     let data = data | ||||||
|         .as_deref() |         .as_deref() | ||||||
|         .map(base64::decode) |         .map(base64::decode) | ||||||
| @ -685,16 +684,6 @@ pub fn update_plugin( | |||||||
|         .map(String::from_utf8) |         .map(String::from_utf8) | ||||||
|         .transpose() |         .transpose() | ||||||
|         .map_err(|_| format_err!("data must be valid UTF-8"))?; |         .map_err(|_| format_err!("data must be valid UTF-8"))?; | ||||||
|     //core_update.api_fixup()?; |  | ||||||
|  |  | ||||||
|     // unwrap: the id is matched by this method's API path |  | ||||||
|     let id = core_update.id.clone().unwrap(); |  | ||||||
|  |  | ||||||
|     let delete: Vec<&str> = delete |  | ||||||
|         .as_deref() |  | ||||||
|         .unwrap_or("") |  | ||||||
|         .split(&[' ', ',', ';', '\0'][..]) |  | ||||||
|         .collect(); |  | ||||||
|  |  | ||||||
|     let _lock = plugin::lock()?; |     let _lock = plugin::lock()?; | ||||||
|  |  | ||||||
| @ -712,10 +701,21 @@ pub fn update_plugin( | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             let mut plugin: DnsPlugin = serde_json::from_value(entry.clone())?; |             let mut plugin: DnsPlugin = serde_json::from_value(entry.clone())?; | ||||||
|             plugin.core.update_from(core_update, &delete)?; |  | ||||||
|             if let Some(data) = data { |             if let Some(delete) = delete { | ||||||
|                 plugin.data = data; |                 for delete_prop in delete { | ||||||
|  |                     match delete_prop { | ||||||
|  |                         DeletableProperty::validation_delay => { plugin.core.validation_delay = None; }, | ||||||
|  |                         DeletableProperty::disable => { plugin.core.disable = None; }, | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |             if let Some(data) = data { plugin.data = data; } | ||||||
|  |             if let Some(api) = update.api { plugin.core.api = api; } | ||||||
|  |             if update.validation_delay.is_some() { plugin.core.validation_delay = update.validation_delay; } | ||||||
|  |             if update.disable.is_some() { plugin.core.disable = update.disable; } | ||||||
|  |  | ||||||
|  |  | ||||||
|             *entry = serde_json::to_value(plugin)?; |             *entry = serde_json::to_value(plugin)?; | ||||||
|         } |         } | ||||||
|         None => http_bail!(NOT_FOUND, "no such plugin"), |         None => http_bail!(NOT_FOUND, "no such plugin"), | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ use crate::api2::admin::{ | |||||||
| use crate::api2::types::*; | use crate::api2::types::*; | ||||||
| use crate::backup::*; | use crate::backup::*; | ||||||
| use crate::config::cached_user_info::CachedUserInfo; | use crate::config::cached_user_info::CachedUserInfo; | ||||||
| use crate::config::datastore::{self, DataStoreConfig}; | use crate::config::datastore::{self, DataStoreConfig, DataStoreConfigUpdater}; | ||||||
| use crate::config::acl::{PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY}; | use crate::config::acl::{PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY}; | ||||||
| use crate::server::{jobstate, WorkerTask}; | use crate::server::{jobstate, WorkerTask}; | ||||||
|  |  | ||||||
| @ -183,55 +183,9 @@ pub enum DeletableProperty { | |||||||
|             name: { |             name: { | ||||||
|                 schema: DATASTORE_SCHEMA, |                 schema: DATASTORE_SCHEMA, | ||||||
|             }, |             }, | ||||||
|             comment: { |             update: { | ||||||
|                 optional: true, |                 type: DataStoreConfigUpdater, | ||||||
|                 schema: SINGLE_LINE_COMMENT_SCHEMA, |                 flatten: true, | ||||||
|             }, |  | ||||||
|             "notify-user": { |  | ||||||
|                 optional: true, |  | ||||||
|                 type: Userid, |  | ||||||
|             }, |  | ||||||
|             "notify": { |  | ||||||
|                 optional: true, |  | ||||||
|                 schema: DATASTORE_NOTIFY_STRING_SCHEMA, |  | ||||||
|             }, |  | ||||||
|             "gc-schedule": { |  | ||||||
|                 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, |  | ||||||
|             }, |  | ||||||
|             "verify-new": { |  | ||||||
|                 description: "If enabled, all new backups will be verified right after completion.", |  | ||||||
|                 type: bool, |  | ||||||
|                 optional: true, |  | ||||||
|                 default: false, |  | ||||||
|             }, |             }, | ||||||
|             delete: { |             delete: { | ||||||
|                 description: "List of properties to delete.", |                 description: "List of properties to delete.", | ||||||
| @ -252,21 +206,9 @@ pub enum DeletableProperty { | |||||||
|     }, |     }, | ||||||
| )] | )] | ||||||
| /// Update datastore config. | /// Update datastore config. | ||||||
| #[allow(clippy::too_many_arguments)] |  | ||||||
| pub fn update_datastore( | pub fn update_datastore( | ||||||
|  |     update: DataStoreConfigUpdater, | ||||||
|     name: String, |     name: String, | ||||||
|     comment: Option<String>, |  | ||||||
|     gc_schedule: Option<String>, |  | ||||||
|     prune_schedule: Option<String>, |  | ||||||
|     keep_last: Option<u64>, |  | ||||||
|     keep_hourly: Option<u64>, |  | ||||||
|     keep_daily: Option<u64>, |  | ||||||
|     keep_weekly: Option<u64>, |  | ||||||
|     keep_monthly: Option<u64>, |  | ||||||
|     keep_yearly: Option<u64>, |  | ||||||
|     verify_new: Option<bool>, |  | ||||||
|     notify: Option<String>, |  | ||||||
|     notify_user: Option<Userid>, |  | ||||||
|     delete: Option<Vec<DeletableProperty>>, |     delete: Option<Vec<DeletableProperty>>, | ||||||
|     digest: Option<String>, |     digest: Option<String>, | ||||||
| ) -> Result<(), Error> { | ) -> Result<(), Error> { | ||||||
| @ -302,7 +244,7 @@ pub fn update_datastore( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if let Some(comment) = comment { |     if let Some(comment) = update.comment { | ||||||
|         let comment = comment.trim().to_string(); |         let comment = comment.trim().to_string(); | ||||||
|         if comment.is_empty() { |         if comment.is_empty() { | ||||||
|             data.comment = None; |             data.comment = None; | ||||||
| @ -312,25 +254,25 @@ pub fn update_datastore( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     let mut gc_schedule_changed = false; |     let mut gc_schedule_changed = false; | ||||||
|     if gc_schedule.is_some() { |     if update.gc_schedule.is_some() { | ||||||
|         gc_schedule_changed = data.gc_schedule != gc_schedule; |         gc_schedule_changed = data.gc_schedule != update.gc_schedule; | ||||||
|         data.gc_schedule = gc_schedule; |         data.gc_schedule = update.gc_schedule; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let mut prune_schedule_changed = false; |     let mut prune_schedule_changed = false; | ||||||
|     if prune_schedule.is_some() { |     if update.prune_schedule.is_some() { | ||||||
|         prune_schedule_changed = data.prune_schedule != prune_schedule; |         prune_schedule_changed = data.prune_schedule != update.prune_schedule; | ||||||
|         data.prune_schedule = prune_schedule; |         data.prune_schedule = update.prune_schedule; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if keep_last.is_some() { data.keep_last = keep_last; } |     if update.keep_last.is_some() { data.keep_last = update.keep_last; } | ||||||
|     if keep_hourly.is_some() { data.keep_hourly = keep_hourly; } |     if update.keep_hourly.is_some() { data.keep_hourly = update.keep_hourly; } | ||||||
|     if keep_daily.is_some() { data.keep_daily = keep_daily; } |     if update.keep_daily.is_some() { data.keep_daily = update.keep_daily; } | ||||||
|     if keep_weekly.is_some() { data.keep_weekly = keep_weekly; } |     if update.keep_weekly.is_some() { data.keep_weekly = update.keep_weekly; } | ||||||
|     if keep_monthly.is_some() { data.keep_monthly = keep_monthly; } |     if update.keep_monthly.is_some() { data.keep_monthly = update.keep_monthly; } | ||||||
|     if keep_yearly.is_some() { data.keep_yearly = keep_yearly; } |     if update.keep_yearly.is_some() { data.keep_yearly = update.keep_yearly; } | ||||||
|  |  | ||||||
|     if let Some(notify_str) = notify { |     if let Some(notify_str) = update.notify { | ||||||
|         let value = parse_property_string(¬ify_str, &DatastoreNotify::API_SCHEMA)?; |         let value = parse_property_string(¬ify_str, &DatastoreNotify::API_SCHEMA)?; | ||||||
|         let notify: DatastoreNotify = serde_json::from_value(value)?; |         let notify: DatastoreNotify = serde_json::from_value(value)?; | ||||||
|         if let  DatastoreNotify { gc: None, verify: None, sync: None } = notify { |         if let  DatastoreNotify { gc: None, verify: None, sync: None } = notify { | ||||||
| @ -339,9 +281,9 @@ pub fn update_datastore( | |||||||
|             data.notify = Some(notify_str); |             data.notify = Some(notify_str); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     if verify_new.is_some() { data.verify_new = verify_new; } |     if update.verify_new.is_some() { data.verify_new = update.verify_new; } | ||||||
|  |  | ||||||
|     if notify_user.is_some() { data.notify_user = notify_user; } |     if update.notify_user.is_some() { data.notify_user = update.notify_user; } | ||||||
|  |  | ||||||
|     config.set_data(&name, "datastore", &data)?; |     config.set_data(&name, "datastore", &data)?; | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| use anyhow::Error; | use anyhow::Error; | ||||||
|  | use ::serde::{Deserialize, Serialize}; | ||||||
|  |  | ||||||
| use proxmox::api::schema::Updatable; |  | ||||||
| use proxmox::api::{api, Permission, Router, RpcEnvironment}; | use proxmox::api::{api, Permission, Router, RpcEnvironment}; | ||||||
|  |  | ||||||
| use crate::api2::types::NODE_SCHEMA; | use crate::api2::types::NODE_SCHEMA; | ||||||
| @ -32,6 +32,28 @@ pub fn get_node_config(mut rpcenv: &mut dyn RpcEnvironment) -> Result<NodeConfig | |||||||
|     Ok(config) |     Ok(config) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[api()] | ||||||
|  | #[derive(Serialize, Deserialize)] | ||||||
|  | #[serde(rename_all="kebab-case")] | ||||||
|  | #[allow(non_camel_case_types)] | ||||||
|  | /// Deletable property name | ||||||
|  | pub enum DeletableProperty { | ||||||
|  |     /// Delete the acme property. | ||||||
|  |     acme, | ||||||
|  |     /// Delete the acmedomain0 property. | ||||||
|  |     acmedomain0, | ||||||
|  |     /// Delete the acmedomain1 property. | ||||||
|  |     acmedomain1, | ||||||
|  |     /// Delete the acmedomain2 property. | ||||||
|  |     acmedomain2, | ||||||
|  |     /// Delete the acmedomain3 property. | ||||||
|  |     acmedomain3, | ||||||
|  |     /// Delete the acmedomain4 property. | ||||||
|  |     acmedomain4, | ||||||
|  |     /// Delete the http-proxy property. | ||||||
|  |     http_proxy, | ||||||
|  | } | ||||||
|  |  | ||||||
| #[api( | #[api( | ||||||
|     input: { |     input: { | ||||||
|         properties: { |         properties: { | ||||||
| @ -40,13 +62,17 @@ pub fn get_node_config(mut rpcenv: &mut dyn RpcEnvironment) -> Result<NodeConfig | |||||||
|                 description: "Digest to protect against concurrent updates", |                 description: "Digest to protect against concurrent updates", | ||||||
|                 optional: true, |                 optional: true, | ||||||
|             }, |             }, | ||||||
|             updater: { |             update: { | ||||||
|                 type: NodeConfigUpdater, |                 type: NodeConfigUpdater, | ||||||
|                 flatten: true, |                 flatten: true, | ||||||
|             }, |             }, | ||||||
|             delete: { |             delete: { | ||||||
|                 description: "Options to remove from the configuration", |                 description: "List of properties to delete.", | ||||||
|  |                 type: Array, | ||||||
|                 optional: true, |                 optional: true, | ||||||
|  |                 items: { | ||||||
|  |                     type: DeletableProperty, | ||||||
|  |                 } | ||||||
|             }, |             }, | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
| @ -57,8 +83,9 @@ pub fn get_node_config(mut rpcenv: &mut dyn RpcEnvironment) -> Result<NodeConfig | |||||||
| )] | )] | ||||||
| /// Update the node configuration | /// Update the node configuration | ||||||
| pub fn update_node_config( | pub fn update_node_config( | ||||||
|     updater: NodeConfigUpdater, |     // node: String, // not used | ||||||
|     delete: Option<String>, |     update: NodeConfigUpdater, | ||||||
|  |     delete: Option<Vec<DeletableProperty>>, | ||||||
|     digest: Option<String>, |     digest: Option<String>, | ||||||
| ) -> Result<(), Error> { | ) -> Result<(), Error> { | ||||||
|     let _lock = crate::config::node::lock()?; |     let _lock = crate::config::node::lock()?; | ||||||
| @ -71,13 +98,27 @@ pub fn update_node_config( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let delete: Vec<&str> = delete |     if let Some(delete) = delete { | ||||||
|         .as_deref() |         for delete_prop in delete { | ||||||
|         .unwrap_or("") |             match delete_prop { | ||||||
|         .split(&[' ', ',', ';', '\0'][..]) |                 DeletableProperty::acme => { config.acme = None; }, | ||||||
|         .collect(); |                 DeletableProperty::acmedomain0 => { config.acmedomain0 = None; }, | ||||||
|  |                 DeletableProperty::acmedomain1 => { config.acmedomain1 = None; }, | ||||||
|  |                 DeletableProperty::acmedomain2 => { config.acmedomain2 = None; }, | ||||||
|  |                 DeletableProperty::acmedomain3 => { config.acmedomain3 = None; }, | ||||||
|  |                 DeletableProperty::acmedomain4 => { config.acmedomain4 = None; }, | ||||||
|  |                 DeletableProperty::http_proxy => { config.http_proxy = None; }, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     config.update_from(updater, &delete)?; |     if update.acme.is_some() { config.acme = update.acme; } | ||||||
|  |     if update.acmedomain0.is_some() { config.acmedomain0 = update.acmedomain0; } | ||||||
|  |     if update.acmedomain1.is_some() { config.acmedomain1 = update.acmedomain1; } | ||||||
|  |     if update.acmedomain2.is_some() { config.acmedomain2 = update.acmedomain2; } | ||||||
|  |     if update.acmedomain3.is_some() { config.acmedomain3 = update.acmedomain3; } | ||||||
|  |     if update.acmedomain4.is_some() { config.acmedomain4 = update.acmedomain4; } | ||||||
|  |     if update.http_proxy.is_some() { config.http_proxy = update.http_proxy; } | ||||||
|  |  | ||||||
|     crate::config::node::save_config(&config)?; |     crate::config::node::save_config(&config)?; | ||||||
|  |  | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ use proxmox::tools::fs::file_get_contents; | |||||||
| use proxmox_backup::acme::AcmeClient; | use proxmox_backup::acme::AcmeClient; | ||||||
| use proxmox_backup::api2; | use proxmox_backup::api2; | ||||||
| use proxmox_backup::api2::types::AcmeAccountName; | use proxmox_backup::api2::types::AcmeAccountName; | ||||||
| use proxmox_backup::config::acme::plugin::DnsPluginCoreUpdater; | use proxmox_backup::config::acme::plugin::DnsPluginCore; | ||||||
| use proxmox_backup::config::acme::KNOWN_ACME_DIRECTORIES; | use proxmox_backup::config::acme::KNOWN_ACME_DIRECTORIES; | ||||||
|  |  | ||||||
| pub fn acme_mgmt_cli() -> CommandLineInterface { | pub fn acme_mgmt_cli() -> CommandLineInterface { | ||||||
| @ -312,7 +312,7 @@ fn get_plugin(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error | |||||||
|                 description: "The ACME challenge plugin type.", |                 description: "The ACME challenge plugin type.", | ||||||
|             }, |             }, | ||||||
|             core: { |             core: { | ||||||
|                 type: DnsPluginCoreUpdater, |                 type: DnsPluginCore, | ||||||
|                 flatten: true, |                 flatten: true, | ||||||
|             }, |             }, | ||||||
|             data: { |             data: { | ||||||
| @ -323,7 +323,7 @@ fn get_plugin(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error | |||||||
|     } |     } | ||||||
| )] | )] | ||||||
| /// Show acme account information. | /// Show acme account information. | ||||||
| fn add_plugin(r#type: String, core: DnsPluginCoreUpdater, data: String) -> Result<(), Error> { | fn add_plugin(r#type: String, core: DnsPluginCore, data: String) -> Result<(), Error> { | ||||||
|     let data = base64::encode(&file_get_contents(&data)?); |     let data = base64::encode(&file_get_contents(&data)?); | ||||||
|     api2::config::acme::add_plugin(r#type, core, data)?; |     api2::config::acme::add_plugin(r#type, core, data)?; | ||||||
|     Ok(()) |     Ok(()) | ||||||
|  | |||||||
| @ -62,20 +62,21 @@ impl Default for StandalonePlugin { | |||||||
| #[serde(rename_all = "kebab-case")] | #[serde(rename_all = "kebab-case")] | ||||||
| pub struct DnsPluginCore { | pub struct DnsPluginCore { | ||||||
|     /// Plugin ID. |     /// Plugin ID. | ||||||
|     pub(crate) id: String, |     #[updater(skip)] | ||||||
|  |     pub id: String, | ||||||
|  |  | ||||||
|     /// DNS API Plugin Id. |     /// DNS API Plugin Id. | ||||||
|     pub(crate) api: String, |     pub api: String, | ||||||
|  |  | ||||||
|     /// Extra delay in seconds to wait before requesting validation. |     /// Extra delay in seconds to wait before requesting validation. | ||||||
|     /// |     /// | ||||||
|     /// Allows to cope with long TTL of DNS records. |     /// Allows to cope with long TTL of DNS records. | ||||||
|     #[serde(skip_serializing_if = "Option::is_none", default)] |     #[serde(skip_serializing_if = "Option::is_none", default)] | ||||||
|     pub(crate) validation_delay: Option<u32>, |     pub validation_delay: Option<u32>, | ||||||
|  |  | ||||||
|     /// Flag to disable the config. |     /// Flag to disable the config. | ||||||
|     #[serde(skip_serializing_if = "Option::is_none", default)] |     #[serde(skip_serializing_if = "Option::is_none", default)] | ||||||
|     disable: Option<bool>, |     pub disable: Option<bool>, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[api( | #[api( | ||||||
| @ -88,17 +89,12 @@ pub struct DnsPluginCore { | |||||||
| #[serde(rename_all = "kebab-case")] | #[serde(rename_all = "kebab-case")] | ||||||
| pub struct DnsPlugin { | pub struct DnsPlugin { | ||||||
|     #[serde(flatten)] |     #[serde(flatten)] | ||||||
|     pub(crate) core: DnsPluginCore, |     pub core: DnsPluginCore, | ||||||
|  |  | ||||||
|     // FIXME: The `Updater` should allow: |  | ||||||
|     //   * having different descriptions for this and the Updater version |  | ||||||
|     //   * having different `#[serde]` attributes for the Updater |  | ||||||
|     //   * or, well, leaving fields out completely in teh Updater but this means we may need to |  | ||||||
|     //     separate Updater and Builder deriving. |  | ||||||
|     // We handle this property separately in the API calls. |     // We handle this property separately in the API calls. | ||||||
|     /// DNS plugin data (base64url encoded without padding). |     /// DNS plugin data (base64url encoded without padding). | ||||||
|     #[serde(with = "proxmox::tools::serde::string_as_base64url_nopad")] |     #[serde(with = "proxmox::tools::serde::string_as_base64url_nopad")] | ||||||
|     pub(crate) data: String, |     pub data: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl DnsPlugin { | impl DnsPlugin { | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ use serde::{Serialize, Deserialize}; | |||||||
|  |  | ||||||
| use proxmox::api::{ | use proxmox::api::{ | ||||||
|     api, |     api, | ||||||
|     schema::{Schema, StringSchema}, |     schema::{Schema, StringSchema, Updater}, | ||||||
|     section_config::{ |     section_config::{ | ||||||
|         SectionConfig, |         SectionConfig, | ||||||
|         SectionConfigData, |         SectionConfigData, | ||||||
| @ -82,14 +82,16 @@ pub const DIR_NAME_SCHEMA: Schema = StringSchema::new("Directory name").schema() | |||||||
|         }, |         }, | ||||||
|     } |     } | ||||||
| )] | )] | ||||||
| #[derive(Serialize,Deserialize)] | #[derive(Serialize,Deserialize,Updater)] | ||||||
| #[serde(rename_all="kebab-case")] | #[serde(rename_all="kebab-case")] | ||||||
| /// Datastore configuration properties. | /// Datastore configuration properties. | ||||||
| pub struct DataStoreConfig { | pub struct DataStoreConfig { | ||||||
|  |     #[updater(skip)] | ||||||
|     pub name: String, |     pub name: String, | ||||||
|  |     #[updater(skip)] | ||||||
|  |     pub path: String, | ||||||
|     #[serde(skip_serializing_if="Option::is_none")] |     #[serde(skip_serializing_if="Option::is_none")] | ||||||
|     pub comment: Option<String>, |     pub comment: Option<String>, | ||||||
|     pub path: String, |  | ||||||
|     #[serde(skip_serializing_if="Option::is_none")] |     #[serde(skip_serializing_if="Option::is_none")] | ||||||
|     pub gc_schedule: Option<String>, |     pub gc_schedule: Option<String>, | ||||||
|     #[serde(skip_serializing_if="Option::is_none")] |     #[serde(skip_serializing_if="Option::is_none")] | ||||||
|  | |||||||
| @ -94,26 +94,26 @@ pub struct AcmeConfig { | |||||||
| /// Node specific configuration. | /// Node specific configuration. | ||||||
| pub struct NodeConfig { | pub struct NodeConfig { | ||||||
|     /// The acme account to use on this node. |     /// The acme account to use on this node. | ||||||
|     #[serde(skip_serializing_if = "Updater::is_empty")] |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|     acme: Option<String>, |     pub acme: Option<String>, | ||||||
|  |  | ||||||
|     #[serde(skip_serializing_if = "Updater::is_empty")] |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|     acmedomain0: Option<String>, |     pub acmedomain0: Option<String>, | ||||||
|  |  | ||||||
|     #[serde(skip_serializing_if = "Updater::is_empty")] |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|     acmedomain1: Option<String>, |     pub acmedomain1: Option<String>, | ||||||
|  |  | ||||||
|     #[serde(skip_serializing_if = "Updater::is_empty")] |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|     acmedomain2: Option<String>, |     pub acmedomain2: Option<String>, | ||||||
|  |  | ||||||
|     #[serde(skip_serializing_if = "Updater::is_empty")] |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|     acmedomain3: Option<String>, |     pub acmedomain3: Option<String>, | ||||||
|  |  | ||||||
|     #[serde(skip_serializing_if = "Updater::is_empty")] |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|     acmedomain4: Option<String>, |     pub acmedomain4: Option<String>, | ||||||
|  |  | ||||||
|     #[serde(skip_serializing_if = "Updater::is_empty")] |     #[serde(skip_serializing_if = "Option::is_none")] | ||||||
|     http_proxy: Option<String>, |     pub http_proxy: Option<String>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl NodeConfig { | impl NodeConfig { | ||||||
|  | |||||||
| @ -96,18 +96,18 @@ pub struct WebauthnConfig { | |||||||
|     /// Relying party name. Any text identifier. |     /// Relying party name. Any text identifier. | ||||||
|     /// |     /// | ||||||
|     /// Changing this *may* break existing credentials. |     /// Changing this *may* break existing credentials. | ||||||
|     rp: String, |     pub rp: String, | ||||||
|  |  | ||||||
|     /// Site origin. Must be a `https://` URL (or `http://localhost`). Should contain the address |     /// Site origin. Must be a `https://` URL (or `http://localhost`). Should contain the address | ||||||
|     /// users type in their browsers to access the web interface. |     /// users type in their browsers to access the web interface. | ||||||
|     /// |     /// | ||||||
|     /// Changing this *may* break existing credentials. |     /// Changing this *may* break existing credentials. | ||||||
|     origin: String, |     pub origin: String, | ||||||
|  |  | ||||||
|     /// Relying part ID. Must be the domain name without protocol, port or location. |     /// Relying part ID. Must be the domain name without protocol, port or location. | ||||||
|     /// |     /// | ||||||
|     /// Changing this *will* break existing credentials. |     /// Changing this *will* break existing credentials. | ||||||
|     id: String, |     pub id: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl WebauthnConfig { | impl WebauthnConfig { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user