mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 00:09:22 +00:00
169 lines
5.7 KiB
Rust
169 lines
5.7 KiB
Rust
// Copyright 2025 OpenObserve Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use config::meta::destinations::{Destination, DestinationType, Module, Template};
|
|
|
|
use crate::{
|
|
common::{
|
|
infra::config::ALERTS,
|
|
meta::authz::Authz,
|
|
utils::auth::{is_ofga_unsupported, remove_ownership, set_ownership},
|
|
},
|
|
service::db::{self, alerts::destinations::DestinationError, user},
|
|
};
|
|
|
|
pub async fn save(
|
|
name: &str,
|
|
mut destination: Destination,
|
|
create: bool,
|
|
) -> Result<(), DestinationError> {
|
|
// First validate the `destination` according to its `destination_type`
|
|
match &mut destination.module {
|
|
Module::Alert {
|
|
destination_type, ..
|
|
} => match destination_type {
|
|
DestinationType::Email(email) => {
|
|
if email.recipients.is_empty() {
|
|
return Err(DestinationError::EmptyEmail);
|
|
}
|
|
if !config::get_config().smtp.smtp_enabled {
|
|
return Err(DestinationError::SMTPUnavailable);
|
|
}
|
|
let mut lowercase_emails = vec![];
|
|
for email in email.recipients.iter() {
|
|
let email = email.trim().to_lowercase();
|
|
// Check if the email is part of the org
|
|
let res = user::get(Some(&destination.org_id), &email).await;
|
|
if res.is_err() || res.is_ok_and(|usr| usr.is_none()) {
|
|
return Err(DestinationError::UserNotPermitted);
|
|
}
|
|
lowercase_emails.push(email);
|
|
}
|
|
email.recipients = lowercase_emails;
|
|
}
|
|
DestinationType::Http(endpoint) => {
|
|
if endpoint.url.is_empty() {
|
|
return Err(DestinationError::EmptyUrl);
|
|
}
|
|
}
|
|
DestinationType::Sns(aws_sns) => {
|
|
if aws_sns.sns_topic_arn.is_empty() || aws_sns.aws_region.is_empty() {
|
|
return Err(DestinationError::InvalidSns);
|
|
}
|
|
}
|
|
},
|
|
Module::Pipeline { endpoint, .. } => {
|
|
if endpoint.url.is_empty() {
|
|
return Err(DestinationError::EmptyUrl);
|
|
}
|
|
}
|
|
}
|
|
|
|
if !name.is_empty() {
|
|
destination.name = name.to_string();
|
|
}
|
|
destination.name = destination.name.trim().to_string();
|
|
if destination.name.is_empty() {
|
|
return Err(DestinationError::EmptyName);
|
|
}
|
|
if destination.name.contains('/') || is_ofga_unsupported(&destination.name) {
|
|
return Err(DestinationError::InvalidName);
|
|
}
|
|
|
|
match db::alerts::destinations::get(&destination.org_id, &destination.name).await {
|
|
Ok(_) => {
|
|
if create {
|
|
return Err(DestinationError::AlreadyExists);
|
|
}
|
|
}
|
|
Err(_) => {
|
|
if !create {
|
|
return Err(DestinationError::NotFound);
|
|
}
|
|
}
|
|
}
|
|
|
|
let saved = db::alerts::destinations::set(destination).await?;
|
|
if name.is_empty() {
|
|
set_ownership(&saved.org_id, "destinations", Authz::new(&saved.name)).await;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get(org_id: &str, name: &str) -> Result<Destination, DestinationError> {
|
|
db::alerts::destinations::get(org_id, name).await
|
|
}
|
|
|
|
pub async fn get_with_template(
|
|
org_id: &str,
|
|
name: &str,
|
|
) -> Result<(Destination, Template), DestinationError> {
|
|
let dest = get(org_id, name).await?;
|
|
if let Module::Alert { template, .. } = &dest.module {
|
|
let template = db::alerts::templates::get(org_id, template)
|
|
.await
|
|
.map_err(|_| DestinationError::TemplateNotFound)?;
|
|
Ok((dest, template))
|
|
} else {
|
|
Err(DestinationError::TemplateNotFound)
|
|
}
|
|
}
|
|
|
|
pub async fn list(
|
|
org_id: &str,
|
|
module: Option<&str>,
|
|
permitted: Option<Vec<String>>,
|
|
) -> Result<Vec<Destination>, DestinationError> {
|
|
Ok(db::alerts::destinations::list(org_id, module)
|
|
.await?
|
|
.into_iter()
|
|
.filter(|dest| {
|
|
permitted.is_none()
|
|
|| permitted
|
|
.as_ref()
|
|
.unwrap()
|
|
.contains(&format!("destination:{}", dest.name))
|
|
|| permitted
|
|
.as_ref()
|
|
.unwrap()
|
|
.contains(&format!("destination:_all_{}", org_id))
|
|
})
|
|
.collect())
|
|
}
|
|
|
|
pub async fn delete(org_id: &str, name: &str) -> Result<(), DestinationError> {
|
|
let cacher = ALERTS.read().await;
|
|
for (stream_key, (_, alert)) in cacher.iter() {
|
|
if stream_key.starts_with(&format!("{}/", org_id))
|
|
&& alert.destinations.contains(&name.to_string())
|
|
{
|
|
return Err(DestinationError::UsedByAlert(alert.name.to_string()));
|
|
}
|
|
}
|
|
drop(cacher);
|
|
|
|
if let Ok(pls) = db::pipeline::list_by_org(org_id).await {
|
|
for pl in pls {
|
|
if pl.contains_remote_destination(name) {
|
|
return Err(DestinationError::UsedByPipeline(pl.name));
|
|
}
|
|
}
|
|
}
|
|
|
|
db::alerts::destinations::delete(org_id, name).await?;
|
|
remove_ownership(org_id, "destinations", Authz::new(name)).await;
|
|
Ok(())
|
|
}
|