From: Christian Ebner <c.ebner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [RFC proxmox-backup 3/8] api: config: sanity check jobs api endpoints
Date: Wed, 13 Dec 2023 16:38:14 +0100 [thread overview]
Message-ID: <20231213153819.391392-4-c.ebner@proxmox.com> (raw)
In-Reply-To: <20231213153819.391392-1-c.ebner@proxmox.com>
Adds the api endpoints to create, read/list, update and delete sanity
check configurations.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
src/api2/config/mod.rs | 2 +
src/api2/config/sanity_check.rs | 296 ++++++++++++++++++++++++++++++++
2 files changed, 298 insertions(+)
create mode 100644 src/api2/config/sanity_check.rs
diff --git a/src/api2/config/mod.rs b/src/api2/config/mod.rs
index 6cfeaea1..8409ad80 100644
--- a/src/api2/config/mod.rs
+++ b/src/api2/config/mod.rs
@@ -13,6 +13,7 @@ pub mod media_pool;
pub mod metrics;
pub mod prune;
pub mod remote;
+pub mod sanity_check;
pub mod sync;
pub mod tape_backup_job;
pub mod tape_encryption_keys;
@@ -30,6 +31,7 @@ const SUBDIRS: SubdirMap = &sorted!([
("metrics", &metrics::ROUTER),
("prune", &prune::ROUTER),
("remote", &remote::ROUTER),
+ ("sanity-check", &sync::ROUTER),
("sync", &sync::ROUTER),
("tape-backup-job", &tape_backup_job::ROUTER),
("tape-encryption-keys", &tape_encryption_keys::ROUTER),
diff --git a/src/api2/config/sanity_check.rs b/src/api2/config/sanity_check.rs
new file mode 100644
index 00000000..c708b952
--- /dev/null
+++ b/src/api2/config/sanity_check.rs
@@ -0,0 +1,296 @@
+use anyhow::Error;
+use hex::FromHex;
+use proxmox_sys::task_log;
+use proxmox_sys::WorkerTaskContext;
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+
+use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
+use proxmox_schema::{api, param_bail};
+
+use pbs_api_types::{
+ Authid, SanityCheckJobConfig, SanityCheckJobConfigUpdater, JOB_ID_SCHEMA, PRIV_SYS_AUDIT,
+ PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
+};
+use pbs_config::{sanity_check, CachedUserInfo};
+
+#[api(
+ input: {
+ properties: {},
+ },
+ returns: {
+ description: "List configured sanity checks schedules.",
+ type: Array,
+ items: { type: SanityCheckJobConfig },
+ },
+ access: {
+ permission: &Permission::Anybody,
+ description: "Requires Sys.Audit.",
+ },
+)]
+/// List all scheduled sanity check jobs.
+pub fn list_sanity_check_jobs(
+ _param: Value,
+ rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<SanityCheckJobConfig>, Error> {
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+ let user_info = CachedUserInfo::new()?;
+ user_info.check_privs(&auth_id, &["/"], PRIV_SYS_AUDIT | PRIV_SYS_MODIFY, true)?;
+
+ let (config, digest) = sanity_check::config()?;
+ let list = config.convert_to_typed_array("sanity-check")?;
+
+ rpcenv["digest"] = hex::encode(digest).into();
+
+ Ok(list)
+}
+
+pub fn do_create_sanity_check_job(
+ config: SanityCheckJobConfig,
+ worker: Option<&dyn WorkerTaskContext>,
+) -> Result<(), Error> {
+ let _lock = sanity_check::lock_config()?;
+
+ let (mut section_config, _digest) = sanity_check::config()?;
+
+ if section_config.sections.get(&config.id).is_some() {
+ param_bail!("id", "job '{}' already exists.", config.id);
+ }
+
+ section_config.set_data(&config.id, "sanity-check", &config)?;
+
+ sanity_check::save_config(§ion_config)?;
+
+ crate::server::jobstate::create_state_file("sanitycheckjob", &config.id)?;
+
+ if let Some(worker) = worker {
+ task_log!(worker, "Sanity check job created: {}", config.id);
+ }
+
+ Ok(())
+}
+
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ config: {
+ type: SanityCheckJobConfig,
+ flatten: true,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Anybody,
+ description: "Requires Sys.Modify.",
+ },
+)]
+/// Create a new sanity check job.
+pub fn create_sanity_check_job(
+ config: SanityCheckJobConfig,
+ rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+ let user_info = CachedUserInfo::new()?;
+ user_info.check_privs(&auth_id, &["/"], PRIV_SYS_MODIFY, true)?;
+
+ do_create_sanity_check_job(config, None)
+}
+
+#[api(
+ input: {
+ properties: {
+ id: {
+ schema: JOB_ID_SCHEMA,
+ },
+ },
+ },
+ returns: { type: SanityCheckJobConfig },
+ access: {
+ permission: &Permission::Anybody,
+ description: "Requires Sys.Audit.",
+ },
+)]
+/// Read a sanity check job configuration.
+pub fn read_sanity_check_job(
+ id: String,
+ rpcenv: &mut dyn RpcEnvironment,
+) -> Result<SanityCheckJobConfig, Error> {
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+ let user_info = CachedUserInfo::new()?;
+ user_info.check_privs(&auth_id, &["/"], PRIV_SYS_AUDIT, true)?;
+
+ let (config, digest) = sanity_check::config()?;
+ let sanity_check_job: SanityCheckJobConfig = config.lookup("sanity-check", &id)?;
+
+ rpcenv["digest"] = hex::encode(digest).into();
+
+ Ok(sanity_check_job)
+}
+
+#[api]
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+/// Deletable property name
+pub enum DeletableProperty {
+ /// Delete the comment.
+ Comment,
+ /// Unset the disable flag.
+ Disable,
+ /// Unset the notify-user.
+ NotifyUser,
+}
+
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ id: {
+ schema: JOB_ID_SCHEMA,
+ },
+ update: {
+ type: SanityCheckJobConfigUpdater,
+ flatten: true,
+ },
+ delete: {
+ description: "List of properties to delete.",
+ type: Array,
+ optional: true,
+ items: {
+ type: DeletableProperty,
+ }
+ },
+ digest: {
+ optional: true,
+ schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Anybody,
+ description: "Requires Sys.Modify.",
+ },
+)]
+/// Update sanity check job config.
+#[allow(clippy::too_many_arguments)]
+pub fn update_sanity_check_job(
+ id: String,
+ update: SanityCheckJobConfigUpdater,
+ delete: Option<Vec<DeletableProperty>>,
+ digest: Option<String>,
+ rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+ let user_info = CachedUserInfo::new()?;
+ user_info.check_privs(&auth_id, &["/"], PRIV_SYS_MODIFY, true)?;
+
+ let _lock = sanity_check::lock_config()?;
+
+ // pass/compare digest
+ let (mut config, expected_digest) = sanity_check::config()?;
+ if let Some(ref digest) = digest {
+ let digest = <[u8; 32]>::from_hex(digest)?;
+ crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
+ }
+
+ let mut data: SanityCheckJobConfig = config.lookup("sanity-check", &id)?;
+
+ if let Some(delete) = delete {
+ for delete_prop in delete {
+ match delete_prop {
+ DeletableProperty::Comment => {
+ data.comment = None;
+ }
+ DeletableProperty::Disable => {
+ data.disable = false;
+ }
+ DeletableProperty::NotifyUser => {
+ data.options.notify_user = None;
+ }
+ }
+ }
+ }
+
+ let mut schedule_changed = false;
+ if let Some(schedule) = update.schedule {
+ schedule_changed = data.schedule != schedule;
+ data.schedule = schedule;
+ }
+
+ if let Some(value) = update.comment {
+ data.comment = Some(value);
+ }
+ if let Some(value) = update.disable {
+ data.disable = value;
+ }
+ if let Some(value) = update.options.notify_user {
+ data.options.notify_user = Some(value);
+ }
+
+ config.set_data(&id, "sanity-check", &data)?;
+
+ sanity_check::save_config(&config)?;
+
+ if schedule_changed {
+ crate::server::jobstate::update_job_last_run_time("sanitycheckjob", &id)?;
+ }
+
+ Ok(())
+}
+
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ id: {
+ schema: JOB_ID_SCHEMA,
+ },
+ digest: {
+ optional: true,
+ schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Anybody,
+ description: "Requires Sys.Modify.",
+ },
+)]
+/// Remove a sanity check job configuration
+pub fn delete_sanity_check_job(
+ id: String,
+ digest: Option<String>,
+ rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+ let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
+ let user_info = CachedUserInfo::new()?;
+ user_info.check_privs(&auth_id, &["/"], PRIV_SYS_MODIFY, true)?;
+
+ let _lock = sanity_check::lock_config()?;
+
+ let (mut config, expected_digest) = sanity_check::config()?;
+ if let Some(ref digest) = digest {
+ let digest = <[u8; 32]>::from_hex(digest)?;
+ crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
+ }
+
+ if config.sections.remove(&id).is_none() {
+ http_bail!(NOT_FOUND, "job '{}' does not exist.", id);
+ }
+
+ sanity_check::save_config(&config)?;
+
+ crate::server::jobstate::remove_state_file("sanitycheckjob", &id)?;
+
+ Ok(())
+}
+
+const ITEM_ROUTER: Router = Router::new()
+ .get(&API_METHOD_READ_SANITY_CHECK_JOB)
+ .put(&API_METHOD_UPDATE_SANITY_CHECK_JOB)
+ .delete(&API_METHOD_DELETE_SANITY_CHECK_JOB);
+
+pub const ROUTER: Router = Router::new()
+ .get(&API_METHOD_LIST_SANITY_CHECK_JOBS)
+ .post(&API_METHOD_CREATE_SANITY_CHECK_JOB)
+ .match_all("id", &ITEM_ROUTER);
--
2.39.2
next prev parent reply other threads:[~2023-12-13 15:39 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-12-13 15:38 [pbs-devel] [RFC proxmox-backup 0/8] implement sanity check jobs Christian Ebner
2023-12-13 15:38 ` [pbs-devel] [RFC proxmox-backup 1/8] api-types: jobs: add sanity checks job types Christian Ebner
2023-12-13 15:38 ` [pbs-devel] [RFC proxmox-backup 2/8] config: implement sanity check job configuration Christian Ebner
2023-12-13 15:38 ` Christian Ebner [this message]
2023-12-13 15:38 ` [pbs-devel] [RFC proxmox-backup 4/8] server: add sanity check job email notifications Christian Ebner
2023-12-13 15:38 ` [pbs-devel] [RFC proxmox-backup 5/8] server: implement sanity check job Christian Ebner
2023-12-13 15:38 ` [pbs-devel] [RFC proxmox-backup 6/8] api: admin: add sanity check job api endpoints Christian Ebner
2023-12-13 15:38 ` [pbs-devel] [RFC proxmox-backup 7/8] manager: add sanity check jobs management cli commands Christian Ebner
2023-12-13 15:38 ` [pbs-devel] [RFC proxmox-backup 8/8] proxy: add sanity check task to scheduler Christian Ebner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20231213153819.391392-4-c.ebner@proxmox.com \
--to=c.ebner@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.