notifications: add type for sync notification template data

This commit adds a separate type for the data passed to this type of
notification template. Also we make sure that we do not expose any
non-primitive types to the template renderer, any data needed in the
template is mapped into the new dedicated template data type.

This ensures that any changes in types defined in other places do not
leak into the template rendering process by accident.
These changes are also preparation for allowing user-overrides for
notification templates.

This commit also tries to unify the style and naming of template
variables.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
This commit is contained in:
Lukas Wagner 2025-03-28 11:22:38 +01:00 committed by Thomas Lamprecht
parent 1b9e3cfd18
commit 1599b424cd
6 changed files with 91 additions and 37 deletions

View File

@ -25,7 +25,8 @@ mod template_data;
use template_data::{
AcmeErrTemplateData, CommonData, GcErrTemplateData, GcOkTemplateData,
PackageUpdatesTemplateData, PruneErrTemplateData, PruneOkTemplateData,
PackageUpdatesTemplateData, PruneErrTemplateData, PruneOkTemplateData, SyncErrTemplateData,
SyncOkTemplateData,
};
/// Initialize the notification system by setting context in proxmox_notify
@ -320,21 +321,6 @@ pub fn send_prune_status(
}
pub fn send_sync_status(job: &SyncJobConfig, result: &Result<(), Error>) -> Result<(), Error> {
let (fqdn, port) = get_server_url();
let mut data = json!({
"job": job,
"fqdn": fqdn,
"port": port,
});
let (template, severity) = match result {
Ok(()) => ("sync-ok", Severity::Info),
Err(err) => {
data["error"] = err.to_string().into();
("sync-err", Severity::Error)
}
};
let metadata = HashMap::from([
("job-id".into(), job.id.clone()),
("datastore".into(), job.store.clone()),
@ -342,7 +328,39 @@ pub fn send_sync_status(job: &SyncJobConfig, result: &Result<(), Error>) -> Resu
("type".into(), "sync".into()),
]);
let notification = Notification::from_template(severity, template, data, metadata);
let notification = match result {
Ok(()) => {
let template_data = SyncOkTemplateData {
common: CommonData::new(),
datastore: job.store.clone(),
job_id: job.id.clone(),
remote: job.remote.clone(),
remote_datastore: job.remote_store.clone(),
};
Notification::from_template(
Severity::Info,
"sync-ok",
serde_json::to_value(template_data)?,
metadata,
)
}
Err(err) => {
let template_data = SyncErrTemplateData {
common: CommonData::new(),
datastore: job.store.clone(),
job_id: job.id.clone(),
remote: job.remote.clone(),
remote_datastore: job.remote_store.clone(),
error: format!("{err:#}"),
};
Notification::from_template(
Severity::Error,
"sync-err",
serde_json::to_value(template_data)?,
metadata,
)
}
};
let (email, notify, mode) = lookup_datastore_notify_settings(&job.store);
match mode {

View File

@ -211,3 +211,39 @@ pub struct PruneErrTemplateData {
/// The error that occured during the prune job.
pub error: String,
}
/// Template data for the sync-ok template.
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct SyncOkTemplateData {
/// Common properties.
#[serde(flatten)]
pub common: CommonData,
/// The datastore.
pub datastore: String,
/// The ID of the job.
pub job_id: String,
/// The remote.
pub remote: Option<String>,
/// The remote datastore we synced to/from.
pub remote_datastore: String,
}
/// Template data for the sync-err template.
#[derive(Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct SyncErrTemplateData {
/// Common properties.
#[serde(flatten)]
pub common: CommonData,
/// The datastore.
pub datastore: String,
/// The ID of the job.
pub job_id: String,
/// The remote.
pub remote: Option<String>,
/// The remote datastore we synced to/from.
pub remote_datastore: String,
/// The error that occurred during the sync job.
pub error: String,
}

View File

@ -1,14 +1,14 @@
Job ID: {{job.id}}
Datastore: {{job.store}}
{{#if job.remote~}}
Remote: {{job.remote}}
Remote Store: {{job.remote-store}}
Job ID: {{job-id}}
Datastore: {{datastore}}
{{#if remote~}}
Remote: {{remote}}
Remote Store: {{remote-datastore}}
{{else~}}
Local Source Store: {{job.remote-store}}
Local Source Store: {{remote-datastore}}
{{/if}}
Synchronization failed: {{error}}
Please visit the web interface for further details:
<https://{{fqdn}}:{{port}}/#pbsServerAdministration:tasks>
<{{base-url}}/#pbsServerAdministration:tasks>

View File

@ -1,5 +1,5 @@
{{#if job.remote~}}
Sync remote '{{ job.remote }}' datastore '{{ job.remote-store }}' failed
{{#if remote~}}
Sync remote '{{remote}}' datastore '{{remote-datastore}}' failed
{{else~}}
Sync local datastore '{{ job.remote-store }}' failed
Sync local datastore '{{remote-datastore}}' failed
{{/if}}

View File

@ -1,14 +1,14 @@
Job ID: {{job.id}}
Datastore: {{job.store}}
{{#if job.remote~}}
Remote: {{job.remote}}
Remote Store: {{job.remote-store}}
Job ID: {{job-id}}
Datastore: {{datastore}}
{{#if remote~}}
Remote: {{remote}}
Remote Store: {{remote-datastore}}
{{else~}}
Local Source Store: {{job.remote-store}}
Local Source Store: {{remote-datastore}}
{{/if}}
Synchronization successful.
Please visit the web interface for further details:
<https://{{fqdn}}:{{port}}/#DataStore-{{job.store}}>
<{{base-url}}/#DataStore-{{datastore}}>

View File

@ -1,5 +1,5 @@
{{#if job.remote~}}
Sync remote '{{ job.remote }}' datastore '{{ job.remote-store }}' successful
{{#if remote~}}
Sync remote '{{remote}}' datastore '{{remote-datastore}}' successful
{{else~}}
Sync local datastore '{{ job.remote-store }}' successful
Sync local datastore '{{remote-datastore}}' successful
{{/if}}