proxmox/proxmox-notify/src/api/gotify.rs
Lukas Wagner 9bea76c6b9 notify: add built-in config and 'origin' parameter
This allows us to define a (modifiable) builtin-config, which is
at the moment hardcoded in PVEContext

The 'origin' parameter indicates whether a config entry was created by
a user, builtin or a modified builtin.

These changes require context to be set for tests, so we set
PVEContext by default if in a test context. There might be a nicer
solution for that, but for now this should work.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
2023-11-17 08:31:36 +01:00

290 lines
8.7 KiB
Rust

use proxmox_http_error::HttpError;
use crate::api::http_err;
use crate::endpoints::gotify::{
DeleteableGotifyProperty, GotifyConfig, GotifyConfigUpdater, GotifyPrivateConfig,
GotifyPrivateConfigUpdater, GOTIFY_TYPENAME,
};
use crate::Config;
/// Get a list of all gotify endpoints.
///
/// The caller is responsible for any needed permission checks.
/// Returns a list of all gotify endpoints or a `HttpError` if the config is
/// erroneous (`500 Internal server error`).
pub fn get_endpoints(config: &Config) -> Result<Vec<GotifyConfig>, HttpError> {
config
.config
.convert_to_typed_array(GOTIFY_TYPENAME)
.map_err(|e| http_err!(NOT_FOUND, "Could not fetch endpoints: {e}"))
}
/// Get gotify endpoint with given `name`
///
/// The caller is responsible for any needed permission checks.
/// Returns the endpoint or a `HttpError` if the endpoint was not found (`404 Not found`).
pub fn get_endpoint(config: &Config, name: &str) -> Result<GotifyConfig, HttpError> {
config
.config
.lookup(GOTIFY_TYPENAME, name)
.map_err(|_| http_err!(NOT_FOUND, "endpoint '{name}' not found"))
}
/// Add a new gotify endpoint.
///
/// The caller is responsible for any needed permission checks.
/// The caller also responsible for locking the configuration files.
/// Returns a `HttpError` if:
/// - an entity with the same name already exists (`400 Bad request`)
/// - the configuration could not be saved (`500 Internal server error`)
///
/// Panics if the names of the private config and the public config do not match.
pub fn add_endpoint(
config: &mut Config,
endpoint_config: &GotifyConfig,
private_endpoint_config: &GotifyPrivateConfig,
) -> Result<(), HttpError> {
if endpoint_config.name != private_endpoint_config.name {
// Programming error by the user of the crate, thus we panic
panic!("name for endpoint config and private config must be identical");
}
super::ensure_unique(config, &endpoint_config.name)?;
set_private_config_entry(config, private_endpoint_config)?;
config
.config
.set_data(&endpoint_config.name, GOTIFY_TYPENAME, endpoint_config)
.map_err(|e| {
http_err!(
INTERNAL_SERVER_ERROR,
"could not save endpoint '{}': {e}",
endpoint_config.name
)
})
}
/// Update existing gotify endpoint
///
/// The caller is responsible for any needed permission checks.
/// The caller also responsible for locking the configuration files.
/// Returns a `HttpError` if:
/// - an entity with the same name already exists (`400 Bad request`)
/// - the configuration could not be saved (`500 Internal server error`)
pub fn update_endpoint(
config: &mut Config,
name: &str,
endpoint_config_updater: &GotifyConfigUpdater,
private_endpoint_config_updater: &GotifyPrivateConfigUpdater,
delete: Option<&[DeleteableGotifyProperty]>,
digest: Option<&[u8]>,
) -> Result<(), HttpError> {
super::verify_digest(config, digest)?;
let mut endpoint = get_endpoint(config, name)?;
if let Some(delete) = delete {
for deleteable_property in delete {
match deleteable_property {
DeleteableGotifyProperty::Comment => endpoint.comment = None,
DeleteableGotifyProperty::Disable => endpoint.disable = None,
}
}
}
if let Some(server) = &endpoint_config_updater.server {
endpoint.server = server.into();
}
if let Some(token) = &private_endpoint_config_updater.token {
set_private_config_entry(
config,
&GotifyPrivateConfig {
name: name.into(),
token: token.into(),
},
)?;
}
if let Some(comment) = &endpoint_config_updater.comment {
endpoint.comment = Some(comment.into());
}
if let Some(disable) = &endpoint_config_updater.disable {
endpoint.disable = Some(*disable);
}
config
.config
.set_data(name, GOTIFY_TYPENAME, &endpoint)
.map_err(|e| {
http_err!(
INTERNAL_SERVER_ERROR,
"could not save endpoint '{name}': {e}"
)
})
}
/// Delete existing gotify endpoint
///
/// The caller is responsible for any needed permission checks.
/// The caller also responsible for locking the configuration files.
/// Returns a `HttpError` if:
/// - the entity does not exist (`404 Not found`)
/// - the endpoint is still referenced by another entity (`400 Bad request`)
pub fn delete_gotify_endpoint(config: &mut Config, name: &str) -> Result<(), HttpError> {
// Check if the endpoint exists
let _ = get_endpoint(config, name)?;
super::ensure_unused(config, name)?;
remove_private_config_entry(config, name)?;
config.config.sections.remove(name);
Ok(())
}
fn set_private_config_entry(
config: &mut Config,
private_config: &GotifyPrivateConfig,
) -> Result<(), HttpError> {
config
.private_config
.set_data(&private_config.name, GOTIFY_TYPENAME, private_config)
.map_err(|e| {
http_err!(
INTERNAL_SERVER_ERROR,
"could not save private config for endpoint '{}': {e}",
private_config.name
)
})
}
fn remove_private_config_entry(config: &mut Config, name: &str) -> Result<(), HttpError> {
config.private_config.sections.remove(name);
Ok(())
}
#[cfg(all(feature = "pve-context", test))]
mod tests {
use super::*;
use crate::api::test_helpers::empty_config;
pub fn add_default_gotify_endpoint(config: &mut Config) -> Result<(), HttpError> {
add_endpoint(
config,
&GotifyConfig {
name: "gotify-endpoint".into(),
server: "localhost".into(),
comment: Some("comment".into()),
..Default::default()
},
&GotifyPrivateConfig {
name: "gotify-endpoint".into(),
token: "supersecrettoken".into(),
},
)?;
assert!(get_endpoint(config, "gotify-endpoint").is_ok());
Ok(())
}
#[test]
fn test_update_not_existing_returns_error() -> Result<(), HttpError> {
let mut config = empty_config();
assert!(update_endpoint(
&mut config,
"test",
&Default::default(),
&Default::default(),
None,
None
)
.is_err());
Ok(())
}
#[test]
fn test_update_invalid_digest_returns_error() -> Result<(), HttpError> {
let mut config = empty_config();
add_default_gotify_endpoint(&mut config)?;
assert!(update_endpoint(
&mut config,
"gotify-endpoint",
&Default::default(),
&Default::default(),
None,
Some(&[0; 32])
)
.is_err());
Ok(())
}
#[test]
fn test_gotify_update() -> Result<(), HttpError> {
let mut config = empty_config();
add_default_gotify_endpoint(&mut config)?;
let digest = config.digest;
update_endpoint(
&mut config,
"gotify-endpoint",
&GotifyConfigUpdater {
server: Some("newhost".into()),
comment: Some("newcomment".into()),
..Default::default()
},
&GotifyPrivateConfigUpdater {
token: Some("changedtoken".into()),
},
None,
Some(&digest),
)?;
let endpoint = get_endpoint(&config, "gotify-endpoint")?;
assert_eq!(endpoint.server, "newhost".to_string());
let token = config
.private_config
.lookup::<GotifyPrivateConfig>(GOTIFY_TYPENAME, "gotify-endpoint")
.unwrap()
.token;
assert_eq!(token, "changedtoken".to_string());
assert_eq!(endpoint.comment, Some("newcomment".to_string()));
// Test property deletion
update_endpoint(
&mut config,
"gotify-endpoint",
&Default::default(),
&Default::default(),
Some(&[DeleteableGotifyProperty::Comment]),
None,
)?;
let endpoint = get_endpoint(&config, "gotify-endpoint")?;
assert_eq!(endpoint.comment, None);
Ok(())
}
#[test]
fn test_gotify_endpoint_delete() -> Result<(), HttpError> {
let mut config = empty_config();
add_default_gotify_endpoint(&mut config)?;
delete_gotify_endpoint(&mut config, "gotify-endpoint")?;
assert!(delete_gotify_endpoint(&mut config, "gotify-endpoint").is_err());
assert_eq!(get_endpoints(&config)?.len(), 0);
Ok(())
}
}