mirror of
https://git.proxmox.com/git/proxmox
synced 2025-06-27 05:53:32 +00:00

Instead of passing the template strings for subject and body when constructing a notification, we pass only the name of a template. When rendering the template, the name of the template is used to find corresponding template files. For PVE, they are located at /usr/share/proxmox-ve/templates/default. The `default` part is the 'template namespace', which is a preparation for user-customizable and/or translatable notifications. Previously, the same template string was used to render HTML and plaintext notifications. This was achieved by providing some template helpers that 'abstract away' HTML/plaintext formatting. However, in hindsight this turned out to be pretty finicky. Since the current changes lay the foundations for user-customizable notification templates, I ripped these abstractions out. Now there are simply two templates, one for plaintext, one for HTML. Signed-off-by: Lukas Wagner <l.wagner@proxmox.com> Tested-by: Folke Gleumes <f.gleumes@proxmox.com> Reviewed-by: Fiona Ebner <f.ebner@proxmox.com>
163 lines
4.9 KiB
Rust
163 lines
4.9 KiB
Rust
use serde::Deserialize;
|
|
use std::path::Path;
|
|
|
|
use proxmox_schema::{ObjectSchema, Schema, StringSchema};
|
|
use proxmox_section_config::{SectionConfig, SectionConfigPlugin};
|
|
|
|
use crate::context::{common, Context};
|
|
use crate::Error;
|
|
|
|
const PBS_USER_CFG_FILENAME: &str = "/etc/proxmox-backup/user.cfg";
|
|
const PBS_NODE_CFG_FILENAME: &str = "/etc/proxmox-backup/node.cfg";
|
|
|
|
// FIXME: Switch to the actual schema when possible in terms of dependency.
|
|
// It's safe to assume that the config was written with the actual schema restrictions, so parsing
|
|
// it with the less restrictive schema should be enough for the purpose of getting the mail address.
|
|
const DUMMY_ID_SCHEMA: Schema = StringSchema::new("dummy ID").min_length(3).schema();
|
|
const DUMMY_EMAIL_SCHEMA: Schema = StringSchema::new("dummy email").schema();
|
|
const DUMMY_USER_SCHEMA: ObjectSchema = ObjectSchema {
|
|
description: "minimal PBS user",
|
|
properties: &[
|
|
("userid", false, &DUMMY_ID_SCHEMA),
|
|
("email", true, &DUMMY_EMAIL_SCHEMA),
|
|
],
|
|
additional_properties: true,
|
|
default_key: None,
|
|
};
|
|
|
|
#[derive(Deserialize)]
|
|
struct DummyPbsUser {
|
|
pub email: Option<String>,
|
|
}
|
|
|
|
/// Extract the root user's email address from the PBS user config.
|
|
fn lookup_mail_address(content: &str, username: &str) -> Option<String> {
|
|
let mut config = SectionConfig::new(&DUMMY_ID_SCHEMA).allow_unknown_sections(true);
|
|
let user_plugin = SectionConfigPlugin::new(
|
|
"user".to_string(),
|
|
Some("userid".to_string()),
|
|
&DUMMY_USER_SCHEMA,
|
|
);
|
|
config.register_plugin(user_plugin);
|
|
|
|
match config.parse(PBS_USER_CFG_FILENAME, content) {
|
|
Ok(parsed) => {
|
|
parsed.sections.get(username)?;
|
|
match parsed.lookup::<DummyPbsUser>("user", username) {
|
|
Ok(user) => common::normalize_for_return(user.email.as_deref()),
|
|
Err(err) => {
|
|
log::error!("unable to parse {PBS_USER_CFG_FILENAME}: {err}");
|
|
None
|
|
}
|
|
}
|
|
}
|
|
Err(err) => {
|
|
log::error!("unable to parse {PBS_USER_CFG_FILENAME}: {err}");
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
const DEFAULT_CONFIG: &str = "\
|
|
sendmail: mail-to-root
|
|
comment Send mails to root@pam's email address
|
|
mailto-user root@pam
|
|
|
|
|
|
matcher: default-matcher
|
|
mode all
|
|
target mail-to-root
|
|
comment Route all notifications to mail-to-root
|
|
";
|
|
|
|
#[derive(Debug)]
|
|
pub struct PBSContext;
|
|
|
|
pub static PBS_CONTEXT: PBSContext = PBSContext;
|
|
|
|
impl Context for PBSContext {
|
|
fn lookup_email_for_user(&self, user: &str) -> Option<String> {
|
|
let content = common::attempt_file_read(PBS_USER_CFG_FILENAME);
|
|
content.and_then(|content| lookup_mail_address(&content, user))
|
|
}
|
|
|
|
fn default_sendmail_author(&self) -> String {
|
|
"Proxmox Backup Server".into()
|
|
}
|
|
|
|
fn default_sendmail_from(&self) -> String {
|
|
let content = common::attempt_file_read(PBS_NODE_CFG_FILENAME);
|
|
content
|
|
.and_then(|content| common::lookup_datacenter_config_key(&content, "email-from"))
|
|
.unwrap_or_else(|| String::from("root"))
|
|
}
|
|
|
|
fn http_proxy_config(&self) -> Option<String> {
|
|
let content = common::attempt_file_read(PBS_NODE_CFG_FILENAME);
|
|
content.and_then(|content| common::lookup_datacenter_config_key(&content, "http-proxy"))
|
|
}
|
|
|
|
fn default_config(&self) -> &'static str {
|
|
return DEFAULT_CONFIG;
|
|
}
|
|
|
|
fn lookup_template(
|
|
&self,
|
|
filename: &str,
|
|
namespace: Option<&str>,
|
|
) -> Result<Option<String>, Error> {
|
|
let path = Path::new("/usr/share/proxmox-backup/templates")
|
|
.join(namespace.unwrap_or("default"))
|
|
.join(filename);
|
|
|
|
let template_string = proxmox_sys::fs::file_read_optional_string(path)
|
|
.map_err(|err| Error::Generic(format!("could not load template: {err}")))?;
|
|
Ok(template_string)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
const USER_CONFIG: &str = "
|
|
user: root@pam
|
|
email root@example.com
|
|
|
|
user: test@pbs
|
|
enable true
|
|
expire 0
|
|
";
|
|
|
|
#[test]
|
|
fn test_parse_mail() {
|
|
assert_eq!(
|
|
lookup_mail_address(USER_CONFIG, "root@pam"),
|
|
Some("root@example.com".to_string())
|
|
);
|
|
assert_eq!(lookup_mail_address(USER_CONFIG, "test@pbs"), None);
|
|
}
|
|
|
|
const NODE_CONFIG: &str = "
|
|
default-lang: de
|
|
email-from: root@example.com
|
|
http-proxy: http://localhost:1234
|
|
";
|
|
|
|
#[test]
|
|
fn test_parse_node_config() {
|
|
assert_eq!(
|
|
common::lookup_datacenter_config_key(NODE_CONFIG, "email-from"),
|
|
Some("root@example.com".to_string())
|
|
);
|
|
assert_eq!(
|
|
common::lookup_datacenter_config_key(NODE_CONFIG, "http-proxy"),
|
|
Some("http://localhost:1234".to_string())
|
|
);
|
|
assert_eq!(
|
|
common::lookup_datacenter_config_key(NODE_CONFIG, "foo"),
|
|
None
|
|
);
|
|
}
|
|
}
|