5 use proxmox_notify
::context
::Context
;
7 // Some helpers borrowed and slightly adapted from `proxmox-mail-forward`
9 fn normalize_for_return(s
: Option
<&str>) -> Option
<String
> {
12 s
=> Some(s
.to_string()),
16 fn attempt_file_read
<P
: AsRef
<Path
>>(path
: P
) -> Option
<String
> {
17 match proxmox_sys
::fs
::file_read_optional_string(path
) {
18 Ok(contents
) => contents
,
26 fn lookup_mail_address(content
: &str, user
: &str) -> Option
<String
> {
27 normalize_for_return(content
.lines().find_map(|line
| {
28 let fields
: Vec
<&str> = line
.split('
:'
).collect();
29 #[allow(clippy::get_first)] // to keep expression style consistent
30 match fields
.get(0)?
.trim() == "user" && fields
.get(1)?
.trim() == user
{
31 true => fields
.get(6).copied(),
40 impl Context
for PVEContext
{
41 fn lookup_email_for_user(&self, user
: &str) -> Option
<String
> {
42 let content
= attempt_file_read("/etc/pve/user.cfg");
43 content
.and_then(|content
| lookup_mail_address(&content
, user
))
49 use crate::notify
::lookup_mail_address
;
51 const USER_CONFIG
: &str = "
52 user:root@pam:1:0:::root@example.com:::
53 user:test@pve:1:0:::test@example.com:::
54 user:no-mail@pve:1:0::::::
58 fn test_parse_mail() {
60 lookup_mail_address(USER_CONFIG
, "root@pam"),
61 Some("root@example.com".to_string())
64 lookup_mail_address(USER_CONFIG
, "test@pve"),
65 Some("test@example.com".to_string())
67 assert_eq
!(lookup_mail_address(USER_CONFIG
, "no-mail@pve"), None
);
71 static CONTEXT
: PVEContext
= PVEContext
;
74 proxmox_notify
::context
::set_context(&CONTEXT
)
77 #[perlmod::package(name = "PVE::RS::Notify")]
79 use anyhow
::{bail, Error}
;
81 use serde_json
::Value
as JSONValue
;
84 use proxmox_notify
::endpoints
::gotify
::{
85 DeleteableGotifyProperty
, GotifyConfig
, GotifyConfigUpdater
, GotifyPrivateConfig
,
86 GotifyPrivateConfigUpdater
,
88 use proxmox_notify
::endpoints
::sendmail
::{
89 DeleteableSendmailProperty
, SendmailConfig
, SendmailConfigUpdater
,
91 use proxmox_notify
::filter
::{
92 DeleteableFilterProperty
, FilterConfig
, FilterConfigUpdater
, FilterModeOperator
,
94 use proxmox_notify
::group
::{DeleteableGroupProperty, GroupConfig, GroupConfigUpdater}
;
95 use proxmox_notify
::{api, api::ApiError, Config, Notification, Severity}
;
97 pub struct NotificationConfig
{
98 config
: Mutex
<Config
>,
101 perlmod
::declare_magic
!(Box
<NotificationConfig
> : &NotificationConfig
as "PVE::RS::Notify");
103 /// Support `dclone` so this can be put into the `ccache` of `PVE::Cluster`.
104 #[export(name = "STORABLE_freeze", raw_return)]
106 #[try_from_ref] this: &NotificationConfig,
108 ) -> Result
<Value
, Error
> {
110 bail
!("freezing Notification config not supported!");
113 let mut cloned
= Box
::new(NotificationConfig
{
114 config
: Mutex
::new(this
.config
.lock().unwrap().clone()),
116 let value
= Value
::new_pointer
::<NotificationConfig
>(&mut *cloned
);
117 let _perl
= Box
::leak(cloned
);
121 /// Instead of `thaw` we implement `attach` for `dclone`.
122 #[export(name = "STORABLE_attach", raw_return)]
126 #[raw] serialized: Value,
127 ) -> Result
<Value
, Error
> {
129 bail
!("STORABLE_attach called with cloning=false");
131 let data
= unsafe { Box::from_raw(serialized.pv_raw::<NotificationConfig>()?) }
;
132 Ok(perlmod
::instantiate_magic
!(&class
, MAGIC
=> data
))
135 #[export(raw_return)]
139 raw_private_config
: &[u8],
140 ) -> Result
<Value
, Error
> {
141 let raw_config
= std
::str::from_utf8(raw_config
)?
;
142 let raw_private_config
= std
::str::from_utf8(raw_private_config
)?
;
144 Ok(perlmod
::instantiate_magic
!(&class
, MAGIC
=> Box
::new(
146 config
: Mutex
::new(Config
::new(raw_config
, raw_private_config
)?
)
152 fn write_config(#[try_from_ref] this: &NotificationConfig) -> Result<(String, String), Error> {
153 Ok(this
.config
.lock().unwrap().write()?
)
157 fn digest(#[try_from_ref] this: &NotificationConfig) -> String {
158 let config
= this
.config
.lock().unwrap();
159 hex
::encode(config
.digest())
162 #[export(serialize_error)]
164 #[try_from_ref] this: &NotificationConfig,
169 properties
: Option
<JSONValue
>,
170 ) -> Result
<(), ApiError
> {
171 let config
= this
.config
.lock().unwrap();
173 let notification
= Notification
{
180 api
::common
::send(&config
, channel
, ¬ification
)
183 #[export(serialize_error)]
185 #[try_from_ref] this: &NotificationConfig,
187 ) -> Result
<(), ApiError
> {
188 let config
= this
.config
.lock().unwrap();
189 api
::common
::test_target(&config
, target
)
192 #[export(serialize_error)]
193 fn get_groups(#[try_from_ref] this: &NotificationConfig) -> Result<Vec<GroupConfig>, ApiError> {
194 let config
= this
.config
.lock().unwrap();
195 api
::group
::get_groups(&config
)
198 #[export(serialize_error)]
200 #[try_from_ref] this: &NotificationConfig,
202 ) -> Result
<GroupConfig
, ApiError
> {
203 let config
= this
.config
.lock().unwrap();
204 api
::group
::get_group(&config
, id
)
207 #[export(serialize_error)]
209 #[try_from_ref] this: &NotificationConfig,
211 endpoints
: Vec
<String
>,
212 comment
: Option
<String
>,
213 filter
: Option
<String
>,
214 ) -> Result
<(), ApiError
> {
215 let mut config
= this
.config
.lock().unwrap();
216 api
::group
::add_group(
227 #[export(serialize_error)]
229 #[try_from_ref] this: &NotificationConfig,
231 endpoints
: Option
<Vec
<String
>>,
232 comment
: Option
<String
>,
233 filter
: Option
<String
>,
234 delete
: Option
<Vec
<DeleteableGroupProperty
>>,
235 digest
: Option
<&str>,
236 ) -> Result
<(), ApiError
> {
237 let mut config
= this
.config
.lock().unwrap();
238 let digest
= digest
.map(hex
::decode
).transpose().map_err(|e
| {
239 ApiError
::internal_server_error(format
!("invalid digest: {e}"), Some(Box
::new(e
)))
242 api
::group
::update_group(
245 &GroupConfigUpdater
{
255 #[export(serialize_error)]
256 fn delete_group(#[try_from_ref] this: &NotificationConfig, name: &str) -> Result<(), ApiError> {
257 let mut config
= this
.config
.lock().unwrap();
258 api
::group
::delete_group(&mut config
, name
)
261 #[export(serialize_error)]
262 fn get_sendmail_endpoints(
263 #[try_from_ref] this: &NotificationConfig,
264 ) -> Result
<Vec
<SendmailConfig
>, ApiError
> {
265 let config
= this
.config
.lock().unwrap();
266 api
::sendmail
::get_endpoints(&config
)
269 #[export(serialize_error)]
270 fn get_sendmail_endpoint(
271 #[try_from_ref] this: &NotificationConfig,
273 ) -> Result
<SendmailConfig
, ApiError
> {
274 let config
= this
.config
.lock().unwrap();
275 api
::sendmail
::get_endpoint(&config
, id
)
278 #[export(serialize_error)]
279 #[allow(clippy::too_many_arguments)]
280 fn add_sendmail_endpoint(
281 #[try_from_ref] this: &NotificationConfig,
283 mailto
: Option
<Vec
<String
>>,
284 mailto_user
: Option
<Vec
<String
>>,
285 from_address
: Option
<String
>,
286 author
: Option
<String
>,
287 comment
: Option
<String
>,
288 filter
: Option
<String
>,
289 ) -> Result
<(), ApiError
> {
290 let mut config
= this
.config
.lock().unwrap();
292 api
::sendmail
::add_endpoint(
306 #[export(serialize_error)]
307 #[allow(clippy::too_many_arguments)]
308 fn update_sendmail_endpoint(
309 #[try_from_ref] this: &NotificationConfig,
311 mailto
: Option
<Vec
<String
>>,
312 mailto_user
: Option
<Vec
<String
>>,
313 from_address
: Option
<String
>,
314 author
: Option
<String
>,
315 comment
: Option
<String
>,
316 filter
: Option
<String
>,
317 delete
: Option
<Vec
<DeleteableSendmailProperty
>>,
318 digest
: Option
<&str>,
319 ) -> Result
<(), ApiError
> {
320 let mut config
= this
.config
.lock().unwrap();
321 let digest
= digest
.map(hex
::decode
).transpose().map_err(|e
| {
322 ApiError
::internal_server_error(format
!("invalid digest: {e}"), Some(Box
::new(e
)))
325 api
::sendmail
::update_endpoint(
328 &SendmailConfigUpdater
{
341 #[export(serialize_error)]
342 fn delete_sendmail_endpoint(
343 #[try_from_ref] this: &NotificationConfig,
345 ) -> Result
<(), ApiError
> {
346 let mut config
= this
.config
.lock().unwrap();
347 api
::sendmail
::delete_endpoint(&mut config
, name
)
350 #[export(serialize_error)]
351 fn get_gotify_endpoints(
352 #[try_from_ref] this: &NotificationConfig,
353 ) -> Result
<Vec
<GotifyConfig
>, ApiError
> {
354 let config
= this
.config
.lock().unwrap();
355 api
::gotify
::get_endpoints(&config
)
358 #[export(serialize_error)]
359 fn get_gotify_endpoint(
360 #[try_from_ref] this: &NotificationConfig,
362 ) -> Result
<GotifyConfig
, ApiError
> {
363 let config
= this
.config
.lock().unwrap();
364 api
::gotify
::get_endpoint(&config
, id
)
367 #[export(serialize_error)]
368 fn add_gotify_endpoint(
369 #[try_from_ref] this: &NotificationConfig,
373 comment
: Option
<String
>,
374 filter
: Option
<String
>,
375 ) -> Result
<(), ApiError
> {
376 let mut config
= this
.config
.lock().unwrap();
377 api
::gotify
::add_endpoint(
385 &GotifyPrivateConfig { name, token }
,
389 #[export(serialize_error)]
390 #[allow(clippy::too_many_arguments)]
391 fn update_gotify_endpoint(
392 #[try_from_ref] this: &NotificationConfig,
394 server
: Option
<String
>,
395 token
: Option
<String
>,
396 comment
: Option
<String
>,
397 filter
: Option
<String
>,
398 delete
: Option
<Vec
<DeleteableGotifyProperty
>>,
399 digest
: Option
<&str>,
400 ) -> Result
<(), ApiError
> {
401 let mut config
= this
.config
.lock().unwrap();
402 let digest
= digest
.map(hex
::decode
).transpose().map_err(|e
| {
403 ApiError
::internal_server_error(format
!("invalid digest: {e}"), Some(Box
::new(e
)))
406 api
::gotify
::update_endpoint(
409 &GotifyConfigUpdater
{
414 &GotifyPrivateConfigUpdater { token }
,
420 #[export(serialize_error)]
421 fn delete_gotify_endpoint(
422 #[try_from_ref] this: &NotificationConfig,
424 ) -> Result
<(), ApiError
> {
425 let mut config
= this
.config
.lock().unwrap();
426 api
::gotify
::delete_gotify_endpoint(&mut config
, name
)
429 #[export(serialize_error)]
431 #[try_from_ref] this: &NotificationConfig,
432 ) -> Result
<Vec
<FilterConfig
>, ApiError
> {
433 let config
= this
.config
.lock().unwrap();
434 api
::filter
::get_filters(&config
)
437 #[export(serialize_error)]
439 #[try_from_ref] this: &NotificationConfig,
441 ) -> Result
<FilterConfig
, ApiError
> {
442 let config
= this
.config
.lock().unwrap();
443 api
::filter
::get_filter(&config
, id
)
446 #[export(serialize_error)]
447 #[allow(clippy::too_many_arguments)]
449 #[try_from_ref] this: &NotificationConfig,
451 min_severity
: Option
<Severity
>,
452 mode
: Option
<FilterModeOperator
>,
453 invert_match
: Option
<bool
>,
454 comment
: Option
<String
>,
455 ) -> Result
<(), ApiError
> {
456 let mut config
= this
.config
.lock().unwrap();
457 api
::filter
::add_filter(
469 #[export(serialize_error)]
470 #[allow(clippy::too_many_arguments)]
472 #[try_from_ref] this: &NotificationConfig,
474 min_severity
: Option
<Severity
>,
475 mode
: Option
<FilterModeOperator
>,
476 invert_match
: Option
<bool
>,
477 comment
: Option
<String
>,
478 delete
: Option
<Vec
<DeleteableFilterProperty
>>,
479 digest
: Option
<&str>,
480 ) -> Result
<(), ApiError
> {
481 let mut config
= this
.config
.lock().unwrap();
482 let digest
= digest
.map(hex
::decode
).transpose().map_err(|e
| {
483 ApiError
::internal_server_error(format
!("invalid digest: {e}"), Some(Box
::new(e
)))
486 api
::filter
::update_filter(
489 &FilterConfigUpdater
{
500 #[export(serialize_error)]
502 #[try_from_ref] this: &NotificationConfig,
504 ) -> Result
<(), ApiError
> {
505 let mut config
= this
.config
.lock().unwrap();
506 api
::filter
::delete_filter(&mut config
, name
)