Skip to content

Commit 2ab5cd2

Browse files
add alert query mode metadata
persist queryType for alerts: builder, code, or promql defaults old alerts to builder accepts legacy queryType=sql as code reserves old creationType keys treats builder/code as SQL modes for auth and dataset resolution.
1 parent c54b762 commit 2ab5cd2

5 files changed

Lines changed: 101 additions & 12 deletions

File tree

src/alerts/alert_enums.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,26 @@ impl Display for AlertType {
116116
}
117117
}
118118

119+
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Default)]
120+
#[serde(rename_all = "camelCase")]
121+
pub enum AlertQueryType {
122+
#[default]
123+
Builder,
124+
#[serde(alias = "sql")]
125+
Code,
126+
Promql,
127+
}
128+
129+
impl Display for AlertQueryType {
130+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131+
match self {
132+
AlertQueryType::Builder => write!(f, "builder"),
133+
AlertQueryType::Code => write!(f, "code"),
134+
AlertQueryType::Promql => write!(f, "promql"),
135+
}
136+
}
137+
}
138+
119139
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
120140
#[serde(rename_all = "camelCase")]
121141
pub enum AlertOperator {

src/alerts/alert_structs.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ use crate::{
2828
alerts::{
2929
AlertError, CURRENT_ALERTS_VERSION,
3030
alert_enums::{
31-
AlertOperator, AlertState, AlertTask, AlertType, AlertVersion, EvalConfig,
32-
LogicalOperator, NotificationState, Severity, WhereConfigOperator,
31+
AlertOperator, AlertQueryType, AlertState, AlertTask, AlertType, AlertVersion,
32+
EvalConfig, LogicalOperator, NotificationState, Severity, WhereConfigOperator,
3333
},
3434
alert_traits::AlertTrait,
3535
target::{NotificationConfig, TARGETS},
3636
},
3737
metastore::metastore_traits::MetastoreObject,
38+
parseable::PARSEABLE,
3839
query::resolve_stream_names,
3940
storage::object_storage::{alert_json_path, alert_state_json_path, mttr_json_path},
4041
};
@@ -45,6 +46,10 @@ const RESERVED_FIELDS: &[&str] = &[
4546
"severity",
4647
"title",
4748
"query",
49+
"queryType",
50+
"query_type",
51+
"creationType",
52+
"creation_type",
4853
"datasets",
4954
"alertType",
5055
"alert_type",
@@ -282,6 +287,10 @@ pub struct AlertRequest {
282287
pub severity: Severity,
283288
pub title: String,
284289
pub query: String,
290+
#[serde(default)]
291+
pub query_type: AlertQueryType,
292+
#[serde(default)]
293+
pub datasets: Vec<String>,
285294
pub alert_type: String,
286295
pub anomaly_config: Option<AnomalyConfig>,
287296
pub forecast_config: Option<ForecastConfig>,
@@ -326,11 +335,22 @@ impl AlertRequest {
326335
for id in &self.targets {
327336
TARGETS.get_target_by_id(id, &tenant_id).await?;
328337
}
329-
let datasets = resolve_stream_names(&self.query)?;
338+
let datasets = match self.query_type {
339+
AlertQueryType::Builder | AlertQueryType::Code => resolve_stream_names(&self.query)?,
340+
AlertQueryType::Promql => self.datasets,
341+
};
330342

331343
if datasets.len() != 1 {
332344
return Err(AlertError::ValidationFailure(format!(
333-
"Query should include only one dataset. Found: {datasets:?}"
345+
"Alert should include only one dataset. Found: {datasets:?}"
346+
)));
347+
}
348+
if self.query_type == AlertQueryType::Promql
349+
&& !PARSEABLE.check_or_load_stream(&datasets[0], &tenant_id).await
350+
{
351+
return Err(AlertError::ValidationFailure(format!(
352+
"Invalid PromQL metrics stream: {}",
353+
datasets[0]
334354
)));
335355
}
336356

@@ -342,6 +362,7 @@ impl AlertRequest {
342362
severity: self.severity,
343363
title: self.title,
344364
query: self.query,
365+
query_type: self.query_type,
345366
datasets,
346367
alert_type: {
347368
match self.alert_type.as_str() {
@@ -393,6 +414,8 @@ pub struct AlertConfig {
393414
pub severity: Severity,
394415
pub title: String,
395416
pub query: String,
417+
#[serde(default)]
418+
pub query_type: AlertQueryType,
396419
pub datasets: Vec<String>,
397420
pub alert_type: AlertType,
398421
pub threshold_config: ThresholdConfig,
@@ -420,6 +443,7 @@ pub struct AlertConfigResponse {
420443
pub severity: Severity,
421444
pub title: String,
422445
pub query: String,
446+
pub query_type: AlertQueryType,
423447
pub datasets: Vec<String>,
424448
pub alert_type: &'static str,
425449
pub anomaly_config: Option<AnomalyConfig>,
@@ -466,6 +490,7 @@ impl AlertConfig {
466490
severity: self.severity,
467491
title: self.title,
468492
query: self.query,
493+
query_type: self.query_type,
469494
datasets: self.datasets,
470495
alert_type: {
471496
match self.alert_type {

src/alerts/alert_traits.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
use crate::{
2020
alerts::{
21-
AlertConfig, AlertError, AlertState, AlertType, EvalConfig, Severity,
21+
AlertConfig, AlertError, AlertQueryType, AlertState, AlertType, EvalConfig, Severity,
2222
alert_enums::NotificationState,
2323
alert_structs::{Context, ThresholdConfig},
2424
},
@@ -64,6 +64,7 @@ pub trait AlertTrait: Debug + Send + Sync + MetastoreObject {
6464
fn get_severity(&self) -> &Severity;
6565
fn get_title(&self) -> &str;
6666
fn get_query(&self) -> &str;
67+
fn get_query_type(&self) -> AlertQueryType;
6768
fn get_alert_type(&self) -> &AlertType;
6869
fn get_threshold_config(&self) -> &ThresholdConfig;
6970
fn get_eval_config(&self) -> &EvalConfig;

src/alerts/alert_types.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ use ulid::Ulid;
2727

2828
use crate::{
2929
alerts::{
30-
AlertConfig, AlertError, AlertState, AlertType, AlertVersion, EvalConfig, Severity,
31-
ThresholdConfig,
30+
AlertConfig, AlertError, AlertQueryType, AlertState, AlertType, AlertVersion, EvalConfig,
31+
Severity, ThresholdConfig,
3232
alert_enums::NotificationState,
3333
alert_structs::{AlertStateEntry, GroupResult},
3434
alert_traits::{AlertTrait, MessageCreation},
@@ -58,6 +58,8 @@ pub struct ThresholdAlert {
5858
pub severity: Severity,
5959
pub title: String,
6060
pub query: String,
61+
#[serde(default)]
62+
pub query_type: AlertQueryType,
6163
pub alert_type: AlertType,
6264
pub threshold_config: ThresholdConfig,
6365
pub eval_config: EvalConfig,
@@ -89,6 +91,10 @@ impl MetastoreObject for ThresholdAlert {
8991
#[async_trait]
9092
impl AlertTrait for ThresholdAlert {
9193
async fn eval_alert(&self) -> Result<Option<String>, AlertError> {
94+
if self.query_type == AlertQueryType::Promql {
95+
return Err(AlertError::NotPresentInOSS("promql alerts"));
96+
}
97+
9298
let time_range = extract_time_range(&self.eval_config)?;
9399

94100
let tenant = self.tenant_id.as_deref().unwrap_or(DEFAULT_TENANT);
@@ -188,6 +194,10 @@ impl AlertTrait for ThresholdAlert {
188194
return Err(AlertError::InvalidAlertQuery("Empty query".into()));
189195
}
190196

197+
if self.query_type == AlertQueryType::Promql {
198+
return Err(AlertError::NotPresentInOSS("promql alerts"));
199+
}
200+
191201
let tables = resolve_stream_names(&self.query)?;
192202
if tables.is_empty() {
193203
return Err(AlertError::InvalidAlertQuery(
@@ -315,6 +325,10 @@ impl AlertTrait for ThresholdAlert {
315325
&self.query
316326
}
317327

328+
fn get_query_type(&self) -> AlertQueryType {
329+
self.query_type
330+
}
331+
318332
fn get_severity(&self) -> &Severity {
319333
&self.severity
320334
}
@@ -436,6 +450,7 @@ impl From<AlertConfig> for ThresholdAlert {
436450
severity: value.severity,
437451
title: value.title,
438452
query: value.query,
453+
query_type: value.query_type,
439454
alert_type: value.alert_type,
440455
threshold_config: value.threshold_config,
441456
eval_config: value.eval_config,
@@ -461,6 +476,7 @@ impl From<ThresholdAlert> for AlertConfig {
461476
severity: val.severity,
462477
title: val.title,
463478
query: val.query,
479+
query_type: val.query_type,
464480
alert_type: val.alert_type,
465481
threshold_config: val.threshold_config,
466482
eval_config: val.eval_config,

src/alerts/mod.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ pub mod alerts_utils;
4545
pub mod target;
4646

4747
pub use crate::alerts::alert_enums::{
48-
AggregateFunction, AlertOperator, AlertState, AlertTask, AlertType, AlertVersion, EvalConfig,
49-
LogicalOperator, NotificationState, Severity, WhereConfigOperator,
48+
AggregateFunction, AlertOperator, AlertQueryType, AlertState, AlertTask, AlertType,
49+
AlertVersion, EvalConfig, LogicalOperator, NotificationState, Severity, WhereConfigOperator,
5050
};
5151
pub use crate::alerts::alert_structs::{
5252
AlertConfig, AlertInfo, AlertRequest, AlertStateEntry, Alerts, AlertsInfo, AlertsInfoByState,
@@ -61,6 +61,7 @@ use crate::metastore::MetastoreError;
6161
use crate::parseable::{DEFAULT_TENANT, PARSEABLE, StreamNotFound};
6262
use crate::query::{QUERY_SESSION, resolve_stream_names};
6363
use crate::rbac::map::{SessionKey, sessions};
64+
use crate::rbac::{Response, Users, role::Action};
6465
use crate::sse::{SSE_HANDLER, SSEAlertInfo, SSEEvent};
6566
use crate::storage;
6667
use crate::storage::ObjectStorageError;
@@ -102,6 +103,31 @@ pub fn create_default_alerts_manager() -> Alerts {
102103
alerts
103104
}
104105

106+
pub async fn user_auth_for_alert_config(
107+
session: &SessionKey,
108+
alert: &AlertConfig,
109+
) -> Result<(), actix_web::Error> {
110+
match alert.query_type {
111+
AlertQueryType::Builder | AlertQueryType::Code => user_auth_for_query(session, &alert.query).await,
112+
AlertQueryType::Promql => {
113+
let [dataset] = alert.datasets.as_slice() else {
114+
return Err(actix_web::error::ErrorUnauthorized(
115+
"User does not have access to PromQL alert stream",
116+
));
117+
};
118+
119+
if Users.authorize(session.clone(), Action::Query, Some(dataset), None)
120+
!= Response::Authorized
121+
{
122+
return Err(actix_web::error::ErrorUnauthorized(format!(
123+
"User does not have access to stream- {dataset}"
124+
)));
125+
}
126+
Ok(())
127+
}
128+
}
129+
}
130+
105131
impl AlertConfig {
106132
/// Migration function to convert v1 alerts to v2 structure
107133
pub async fn migrate_from_v1(
@@ -125,6 +151,7 @@ impl AlertConfig {
125151
severity: basic_fields.severity,
126152
title: basic_fields.title,
127153
query,
154+
query_type: AlertQueryType::Builder,
128155
datasets,
129156
alert_type: AlertType::Threshold,
130157
threshold_config,
@@ -627,7 +654,7 @@ impl AlertConfig {
627654
let active_session = sessions().get_active_sessions();
628655
let mut broadcast_to = vec![];
629656
for (session, _, _) in active_session {
630-
if user_auth_for_query(&session, &self.query).await.is_ok()
657+
if user_auth_for_alert_config(&session, self).await.is_ok()
631658
&& let SessionKey::SessionId(id) = &session
632659
{
633660
broadcast_to.push(*id);
@@ -1193,7 +1220,7 @@ impl AlertManagerTrait for Alerts {
11931220
let futures: Vec<_> = all_alerts
11941221
.into_iter()
11951222
.map(|alert| async {
1196-
if user_auth_for_query(&session.clone(), &alert.query)
1223+
if user_auth_for_alert_config(&session.clone(), &alert)
11971224
.await
11981225
.is_ok()
11991226
{
@@ -1214,7 +1241,7 @@ impl AlertManagerTrait for Alerts {
12141241
let futures: Vec<_> = all_alerts
12151242
.into_iter()
12161243
.map(|alert| async {
1217-
if user_auth_for_query(&session, &alert.query).await.is_ok() {
1244+
if user_auth_for_alert_config(&session, &alert).await.is_ok() {
12181245
Some(alert)
12191246
} else {
12201247
None

0 commit comments

Comments
 (0)