api: apt: implement support to send notification email on new updates
again, base idea copied off PVE, but, we safe the information about which pending version we send a mail out already in a separate object, to keep the api return type APTUpdateInfo clean. This also makes a few things a bit easier, as we can update the package status without saving/restoring the notify information. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
		| @ -85,6 +85,13 @@ fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> { | ||||
|             node: { | ||||
|                 schema: NODE_SCHEMA, | ||||
|             }, | ||||
|             notify: { | ||||
|                 type: bool, | ||||
|                 description: r#"Send notification mail about new package updates availanle to the | ||||
|                     email address configured for 'root@pam')."#, | ||||
|                 optional: true, | ||||
|                 default: false, | ||||
|             }, | ||||
|             quiet: { | ||||
|                 description: "Only produces output suitable for logging, omitting progress indicators.", | ||||
|                 type: bool, | ||||
| @ -102,16 +109,46 @@ fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> { | ||||
| )] | ||||
| /// Update the APT database | ||||
| pub fn apt_update_database( | ||||
|     notify: Option<bool>, | ||||
|     quiet: Option<bool>, | ||||
|     rpcenv: &mut dyn RpcEnvironment, | ||||
| ) -> Result<String, Error> { | ||||
|  | ||||
|     let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | ||||
|     let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false }; | ||||
|     // FIXME: change to non-option in signature and drop below once we have proxmox-api-macro 0.2.3 | ||||
|     let quiet = quiet.unwrap_or(API_METHOD_APT_UPDATE_DATABASE_PARAM_DEFAULT_QUIET); | ||||
|     let notify = notify.unwrap_or(API_METHOD_APT_UPDATE_DATABASE_PARAM_DEFAULT_NOTIFY); | ||||
|  | ||||
|     let upid_str = WorkerTask::new_thread("aptupdate", None, auth_id, to_stdout, move |worker| { | ||||
|         do_apt_update(&worker, quiet)?; | ||||
|  | ||||
|         let mut cache = apt::update_cache()?; | ||||
|  | ||||
|         if notify { | ||||
|             let mut notified = match cache.notified { | ||||
|                 Some(notified) => notified, | ||||
|                 None => std::collections::HashMap::new(), | ||||
|             }; | ||||
|             let mut to_notify: Vec<&APTUpdateInfo> = Vec::new(); | ||||
|  | ||||
|             for pkg in &cache.package_status { | ||||
|                 match notified.insert(pkg.package.to_owned(), pkg.version.to_owned()) { | ||||
|                     Some(notified_version) => { | ||||
|                         if notified_version != pkg.version { | ||||
|                             to_notify.push(pkg); | ||||
|                         } | ||||
|                     }, | ||||
|                     None => to_notify.push(pkg), | ||||
|                 } | ||||
|             } | ||||
|             if !to_notify.is_empty() { | ||||
|                 crate::server::send_updates_available(&to_notify)?; | ||||
|             } | ||||
|             cache.notified = Some(notified); | ||||
|             apt::write_pkg_cache(&cache)?; | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     })?; | ||||
|  | ||||
|  | ||||
| @ -9,8 +9,9 @@ use crate::{ | ||||
|     config::verify::VerificationJobConfig, | ||||
|     config::sync::SyncJobConfig, | ||||
|     api2::types::{ | ||||
|         Userid, | ||||
|         APTUpdateInfo, | ||||
|         GarbageCollectionStatus, | ||||
|         Userid, | ||||
|     }, | ||||
|     tools::format::HumanByte, | ||||
| }; | ||||
| @ -91,6 +92,16 @@ Synchronization failed: {{error}} | ||||
|  | ||||
| "###; | ||||
|  | ||||
| const PACKAGE_UPDATES_TEMPLATE: &str = r###" | ||||
| Proxmox Backup Server has the following updates available: | ||||
| {{#each updates }} | ||||
|   {{Package}}: {{OldVersion}} -> {{Version~}} | ||||
| {{/each }} | ||||
|  | ||||
| To upgrade visit the webinderface: <https://{{fqdn}}:{{port}}/#pbsServerAdministration:updates> | ||||
| "###; | ||||
|  | ||||
|  | ||||
| lazy_static::lazy_static!{ | ||||
|  | ||||
|     static ref HANDLEBARS: Handlebars<'static> = { | ||||
| @ -110,6 +121,8 @@ lazy_static::lazy_static!{ | ||||
|         hb.register_template_string("sync_ok_template", SYNC_OK_TEMPLATE).unwrap(); | ||||
|         hb.register_template_string("sync_err_template", SYNC_ERR_TEMPLATE).unwrap(); | ||||
|  | ||||
|         hb.register_template_string("package_update_template", PACKAGE_UPDATES_TEMPLATE).unwrap(); | ||||
|  | ||||
|         hb | ||||
|     }; | ||||
| } | ||||
| @ -261,6 +274,25 @@ pub fn send_sync_status( | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| pub fn send_updates_available( | ||||
|     updates: &Vec<&APTUpdateInfo>, | ||||
| ) -> Result<(), Error> { | ||||
|     // update mails always go to the root@pam configured email.. | ||||
|     if let Some(email) = lookup_user_email(Userid::root_userid()) { | ||||
|         let nodename = proxmox::tools::nodename(); | ||||
|         let subject = format!("New software packages available ({})", nodename); | ||||
|  | ||||
|         let text = HANDLEBARS.render("package_update_template", &json!({ | ||||
|             "fqdn": nix::sys::utsname::uname().nodename(), // FIXME: add get_fqdn helper like PVE? | ||||
|             "port": 8007, // user will surely request that they can change this | ||||
|             "updates": updates, | ||||
|         }))?; | ||||
|  | ||||
|         send_job_status_mail(&email, &subject, &text)?; | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| /// Lookup users email address | ||||
| /// | ||||
| /// For "backup@pam", this returns the address from "root@pam". | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| use std::collections::HashSet; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use anyhow::{Error, bail, format_err}; | ||||
| use apt_pkg_native::Cache; | ||||
| @ -11,8 +12,11 @@ use crate::api2::types::APTUpdateInfo; | ||||
| const APT_PKG_STATE_FN: &str = "/var/lib/proxmox-backup/pkg-state.json"; | ||||
|  | ||||
| #[derive(Debug, serde::Serialize, serde::Deserialize)] | ||||
| /// Some information we cache about the package (update) state | ||||
| /// Some information we cache about the package (update) state, like what pending update version | ||||
| /// we already notfied an user about | ||||
| pub struct PkgState { | ||||
|     /// simple map from package name to most recently notified (emailed) version | ||||
|     pub notified: Option<HashMap<String, String>>, | ||||
|     /// A list of pending updates | ||||
|     pub package_status: Vec<APTUpdateInfo>, | ||||
| } | ||||
| @ -64,6 +68,7 @@ pub fn update_cache() -> Result<PkgState, Error> { | ||||
|                 cache | ||||
|             }, | ||||
|             _ => PkgState { | ||||
|                 notified: None, | ||||
|                 package_status: all_upgradeable, | ||||
|             }, | ||||
|         }; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user