notify: add sendmail plugin

This plugin uses the 'sendmail' command to send an email
to one or more recipients.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
This commit is contained in:
Lukas Wagner 2023-07-20 16:31:34 +02:00 committed by Wolfgang Bumiller
parent 714ef27786
commit 7c42752690
5 changed files with 128 additions and 2 deletions

View File

@ -8,12 +8,17 @@ repository.workspace = true
exclude.workspace = true
[dependencies]
handlebars = { workspace = true, optional = true }
lazy_static.workspace = true
log.workspace = true
openssl.workspace = true
proxmox-schema = { workspace = true, features = ["api-macro", "api-types"]}
proxmox-section-config = { workspace = true }
proxmox-sys.workspace = true
proxmox-sys = { workspace = true, optional = true }
regex.workspace = true
serde.workspace = true
serde = { workspace = true, features = ["derive"]}
serde_json.workspace = true
[features]
default = ["sendmail"]
sendmail = ["dep:handlebars", "dep:proxmox-sys"]

View File

@ -13,6 +13,18 @@ lazy_static! {
fn config_init() -> SectionConfig {
let mut config = SectionConfig::new(&BACKEND_NAME_SCHEMA);
#[cfg(feature = "sendmail")]
{
use crate::endpoints::sendmail::{SendmailConfig, SENDMAIL_TYPENAME};
const SENDMAIL_SCHEMA: &ObjectSchema = SendmailConfig::API_SCHEMA.unwrap_object_schema();
config.register_plugin(SectionConfigPlugin::new(
SENDMAIL_TYPENAME.to_string(),
Some(String::from("name")),
SENDMAIL_SCHEMA,
));
}
config
}

View File

@ -0,0 +1,2 @@
#[cfg(feature = "sendmail")]
pub mod sendmail;

View File

@ -0,0 +1,89 @@
use crate::schema::{EMAIL_SCHEMA, ENTITY_NAME_SCHEMA, USER_SCHEMA};
use crate::{Endpoint, Error, Notification};
use proxmox_schema::api_types::COMMENT_SCHEMA;
use proxmox_schema::{api, Updater};
use serde::{Deserialize, Serialize};
pub(crate) const SENDMAIL_TYPENAME: &str = "sendmail";
#[api(
properties: {
name: {
schema: ENTITY_NAME_SCHEMA,
},
mailto: {
type: Array,
items: {
schema: EMAIL_SCHEMA,
},
},
comment: {
optional: true,
schema: COMMENT_SCHEMA,
},
},
)]
#[derive(Debug, Serialize, Deserialize, Updater, Default)]
#[serde(rename_all = "kebab-case")]
/// Config for Sendmail notification endpoints
pub struct SendmailConfig {
/// Name of the endpoint
#[updater(skip)]
pub name: String,
/// Mail recipients
pub mailto: Vec<String>,
/// `From` address for the mail
#[serde(skip_serializing_if = "Option::is_none")]
pub from_address: Option<String>,
/// Author of the mail
#[serde(skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
/// Comment
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum DeleteableSendmailProperty {
FromAddress,
Author,
Comment,
}
/// A sendmail notification endpoint.
pub struct SendmailEndpoint {
pub config: SendmailConfig,
}
impl Endpoint for SendmailEndpoint {
fn send(&self, notification: &Notification) -> Result<(), Error> {
let recipients: Vec<&str> = self.config.mailto.iter().map(String::as_str).collect();
// Note: OX has serious problems displaying text mails,
// so we include html as well
let html = format!(
"<html><body><pre>\n{}\n<pre>",
handlebars::html_escape(&notification.body)
);
// proxmox_sys::email::sendmail will set the author to
// "Proxmox Backup Server" if it is not set.
let author = self.config.author.as_deref().or(Some(""));
proxmox_sys::email::sendmail(
&recipients,
&notification.title,
Some(&notification.body),
Some(&html),
self.config.from_address.as_deref(),
author,
)
.map_err(|err| Error::NotifyFailed(self.config.name.clone(), err.into()))
}
fn name(&self) -> &str {
&self.config.name
}
}

View File

@ -200,6 +200,24 @@ impl Bus {
pub fn from_config(config: &Config) -> Result<Self, Error> {
let mut endpoints = HashMap::new();
// Instantiate endpoints
#[cfg(feature = "sendmail")]
{
use endpoints::sendmail::SENDMAIL_TYPENAME;
use endpoints::sendmail::{SendmailConfig, SendmailEndpoint};
endpoints.extend(
parse_endpoints_without_private_config!(
config,
SendmailConfig,
SendmailEndpoint,
SENDMAIL_TYPENAME
)?
.into_iter()
.map(|e| (e.name().into(), e)),
);
}
Ok(Bus { endpoints })
}