use new api updater features
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
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 {
|
||||||
|
|
Loading…
Reference in New Issue