feat(baker/finalize): notification system and consolidate status types
- Rename types/build_status.rs to types/buildstatus.rs - Rename StageInfo.sub_stage field to substage for consistency - Move notification logic from finalize to dedicated notify module - Remove notification module from finalize (moved to notify) - Add notify module with event queuing, retry, and priority scheduling - Add exponential backoff for temporary notification failures - Implement plugin filtering by name list during registration - Add fetch_plugins helper for batch plugin download - Update finalize plugin registration to accept optional plugin filter - Fix finalize config template indentation for notification policy - Move all YAML templates to templates/ directory
This commit is contained in:
@@ -156,11 +156,16 @@ notification:
|
||||
sms-notify:
|
||||
to: "+8610012345678"
|
||||
schema: "custom"
|
||||
on_success: "[HBW Notify] Build Succeeded: {{ build.status }} ({{ build.duration }})"
|
||||
on_failure: "[HBW Notify] Build Failed: {{ build.status }} ({{ build.duration }})"
|
||||
on_finish_of:
|
||||
stage: "bake.build"
|
||||
content: "[HBW Notify] Build Progress: {{ build.status }} ({{ build.duration }})"
|
||||
policy:
|
||||
- on_success: "[HBW Notify] Build Succeeded: {{ build.status }} ({{ build.duration }})"
|
||||
to: "+8610012345678"
|
||||
- on_failure: "[HBW Notify] Build Failed: {{ build.status }} ({{ build.duration }})"
|
||||
to: "+8610012345679"
|
||||
|
||||
- on_finish_of:
|
||||
stage: "bake.build"
|
||||
content: "[HBW Notify] Build Progress: {{ build.status }} ({{ build.duration }})"
|
||||
to: "+8610012345677"
|
||||
fallible: true
|
||||
|
||||
# =============================================================================
|
||||
@@ -7,7 +7,7 @@ use crate::ExecutionContext;
|
||||
use crate::prebake::PrebakeConfig;
|
||||
use crate::prebake::security::get_drop_after;
|
||||
use crate::prebake::stage::PrebakeStage;
|
||||
use crate::types::build_status::{write_stage_status, StageInfo, StagePhase, StageResult};
|
||||
use crate::types::buildstatus::{write_stage_status, StageInfo, StagePhase, StageResult};
|
||||
use crate::{Engine, prebake};
|
||||
use std::path::{PathBuf, Path};
|
||||
use chrono::Utc;
|
||||
@@ -71,7 +71,7 @@ pub async fn bake(
|
||||
StageInfo {
|
||||
name: "trivial".to_string(),
|
||||
phase: StagePhase::Bake,
|
||||
sub_stage: "trivial".to_string(),
|
||||
substage: "trivial".to_string(),
|
||||
result: stage_result,
|
||||
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
|
||||
started_at: stage_start,
|
||||
@@ -101,7 +101,7 @@ pub async fn bake(
|
||||
StageInfo {
|
||||
name: func.name.clone(),
|
||||
phase: StagePhase::Bake,
|
||||
sub_stage: func.name.clone(),
|
||||
substage: func.name.clone(),
|
||||
result: stage_result,
|
||||
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
|
||||
started_at: stage_start,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
pub mod notification;
|
||||
pub mod plugin;
|
||||
pub mod stage;
|
||||
pub mod template;
|
||||
@@ -12,35 +11,15 @@ use std::path::PathBuf;
|
||||
use crate::finalize::plugin::fetch::{self, FetchArgument};
|
||||
use crate::cli::Cli;
|
||||
use crate::engine::EventSender;
|
||||
use crate::types::build_status::{read_build_status, BuildStatus, StageResult};
|
||||
use crate::types::build_status::{StageInfo, StagePhase};
|
||||
use crate::types::buildstatus::{read_build_status, BuildStatus, StageResult};
|
||||
use crate::types::buildstatus::{StageInfo, StagePhase};
|
||||
use chrono::Utc;
|
||||
// use crate::finalize::error::PluginError;
|
||||
pub use config::*;
|
||||
pub use error::FinalizeError;
|
||||
use std::path::Path;
|
||||
|
||||
fn _extract_notification_plugins(
|
||||
all_plugins: &plugin::PluginMap,
|
||||
_notification_config: &config::NotificationConfig,
|
||||
) -> Vec<String> {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let notification_names: HashSet<&str> = HashSet::from([
|
||||
"mail",
|
||||
"webhook",
|
||||
"satori",
|
||||
"in-site-notify",
|
||||
]);
|
||||
|
||||
all_plugins
|
||||
.iter()
|
||||
.filter(|(name, _)| notification_names.contains(name.as_str()))
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse(path: &Path) -> Result<FinalizeConfig, FinalizeError> {
|
||||
pub fn parse(path: &Path) -> Result<FinalizeConfig, FinalizeError> {
|
||||
let content = std::fs::read_to_string(path).map_err(FinalizeError::IoError)?;
|
||||
let config: FinalizeConfig = serde_yaml::from_str(&content)?;
|
||||
Ok(config)
|
||||
@@ -86,7 +65,7 @@ pub async fn finalize(
|
||||
|
||||
// Register Plugins
|
||||
log::info!("Registering {} plugins", finalize.plugin.len());
|
||||
let registered_plugins = plugin::register(finalize.plugin)?;
|
||||
let registered_plugins = plugin::register(finalize.plugin, None)?;
|
||||
|
||||
// Early hook
|
||||
let earlyhook_result = stage::hook::hook(
|
||||
@@ -106,43 +85,6 @@ pub async fn finalize(
|
||||
// If prior stages failed, we still send notification but skip artifacts
|
||||
let prior_failed = build_status.has_failures() || earlyhook_result.is_err();
|
||||
|
||||
// Send notification (before late hook, as per standalone flow)
|
||||
if !finalize.notification.groups.is_empty() {
|
||||
let core = notification::NotificationCore::new(registered_plugins.clone());
|
||||
let request = notification::NotificationRequest {
|
||||
pipeline_name: ctx.pipeline_name.clone(),
|
||||
build_id: ctx.task_id.clone(),
|
||||
build_status: build_status.clone(),
|
||||
final_result: final_result.clone(),
|
||||
config: finalize.notification,
|
||||
};
|
||||
if let Err(e) = core.dispatch(request, ctx, event_tx.clone()).await {
|
||||
log::warn!("Notification dispatch failed: {}", e);
|
||||
// Notification failure does not block finalize
|
||||
}
|
||||
} else {
|
||||
log::debug!("No notification groups configured, skipping notification");
|
||||
}
|
||||
|
||||
if prior_failed {
|
||||
log::warn!("Prior stages failed, skipping artifacts and late hook");
|
||||
// Write finalize stage status as failure
|
||||
let _ = crate::types::build_status::write_stage_status(
|
||||
STANDALONE_STATUS_DIR,
|
||||
StageInfo {
|
||||
name: "finalize".to_string(),
|
||||
phase: StagePhase::Finalize,
|
||||
sub_stage: "notify".to_string(),
|
||||
result: StageResult::Failure,
|
||||
duration_ms: 0,
|
||||
started_at: Utc::now(),
|
||||
finished_at: Utc::now(),
|
||||
error_message: Some("Prior stage failed".to_string()),
|
||||
},
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Prepare artifacts
|
||||
// TODO: artifact stage implementation
|
||||
|
||||
@@ -156,12 +98,12 @@ pub async fn finalize(
|
||||
.await?;
|
||||
|
||||
// Write finalize success status
|
||||
let _ = crate::types::build_status::write_stage_status(
|
||||
let _ = crate::types::buildstatus::write_stage_status(
|
||||
STANDALONE_STATUS_DIR,
|
||||
StageInfo {
|
||||
name: "finalize".to_string(),
|
||||
phase: StagePhase::Finalize,
|
||||
sub_stage: "clean".to_string(),
|
||||
substage: "clean".to_string(),
|
||||
result: StageResult::Success,
|
||||
duration_ms: 0,
|
||||
started_at: Utc::now(),
|
||||
|
||||
@@ -3,16 +3,16 @@ use crate::finalize::RawPluginMap;
|
||||
use crate::finalize::plugin::metadata::{PluginMetadata, PluginType};
|
||||
use crate::{EventSender, ExecutionContext, finalize::error::PluginError};
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{PathBuf, Path};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
mod dylib;
|
||||
pub mod fetch;
|
||||
mod internal;
|
||||
mod metadata;
|
||||
mod rhai;
|
||||
mod shell;
|
||||
pub mod fetch;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FinalizePlugin {
|
||||
@@ -99,7 +99,7 @@ fn read_manifest(path: Option<PathBuf>, name: &str) -> Result<PluginMetadata, Pl
|
||||
for ext in FINALIZE_PLUGIN_MANIFEST_EXTENSION {
|
||||
match load_manifest(&path, name, ext) {
|
||||
Ok(content) => break 'load content,
|
||||
Err(PluginError::ManifestNotFound(_)) => {},
|
||||
Err(PluginError::ManifestNotFound(_)) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
@@ -107,26 +107,35 @@ fn read_manifest(path: Option<PathBuf>, name: &str) -> Result<PluginMetadata, Pl
|
||||
};
|
||||
|
||||
log::debug!("[plugin:{}] Parsing manifest content", name);
|
||||
let mut metadata: PluginMetadata = serde_yaml::from_str(manifest_content.as_str()).map_err(|e| PluginError::PluginUnavailable {
|
||||
name: name.to_string(),
|
||||
reason: format!("Failed to parse plugin manifest: {}", e),
|
||||
})?;
|
||||
let mut metadata: PluginMetadata =
|
||||
serde_yaml::from_str(manifest_content.as_str()).map_err(|e| {
|
||||
PluginError::PluginUnavailable {
|
||||
name: name.to_string(),
|
||||
reason: format!("Failed to parse plugin manifest: {}", e),
|
||||
}
|
||||
})?;
|
||||
metadata.fspath = path;
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
fn load_manifest(path: &Path, name: &str, suffix: &str) -> Result<String, PluginError> {
|
||||
let manifest_path = path.join(format!("{}{}", FINALIZE_PLUGIN_MANIFEST,suffix));
|
||||
log::debug!("[plugin:{}] Reading manifest from {}", name, manifest_path.display());
|
||||
let manifest_path = path.join(format!("{}{}", FINALIZE_PLUGIN_MANIFEST, suffix));
|
||||
log::debug!(
|
||||
"[plugin:{}] Reading manifest from {}",
|
||||
name,
|
||||
manifest_path.display()
|
||||
);
|
||||
if !manifest_path.exists() {
|
||||
return Err(PluginError::ManifestNotFound(manifest_path));
|
||||
}
|
||||
|
||||
std::fs::read_to_string(&manifest_path).map_err(|e| {
|
||||
PluginError::PluginUnavailable {
|
||||
name: name.to_string(),
|
||||
reason: format!("Failed to read plugin manifest from {}: {}", manifest_path.display(), e.to_string()),
|
||||
}
|
||||
std::fs::read_to_string(&manifest_path).map_err(|e| PluginError::PluginUnavailable {
|
||||
name: name.to_string(),
|
||||
reason: format!(
|
||||
"Failed to read plugin manifest from {}: {}",
|
||||
manifest_path.display(),
|
||||
e.to_string()
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -202,17 +211,38 @@ fn get_entry(plugin_type: PluginType, name: &str) -> Result<Box<dyn AsyncPluginF
|
||||
///
|
||||
/// Returns a [`PluginMap`] containing all registered plugins, or a [`PluginError`] if
|
||||
/// a plugin manifest cannot be read or parsed.
|
||||
pub fn register(external_plugins: RawPluginMap) -> Result<PluginMap, PluginError> {
|
||||
pub fn register(
|
||||
external_plugins: RawPluginMap,
|
||||
list: Option<&HashSet<String>>,
|
||||
) -> Result<PluginMap, PluginError> {
|
||||
let mut plugins: PluginMap = HashMap::new();
|
||||
let mut count = 0;
|
||||
|
||||
// Register internal plugin
|
||||
for i in internal::get_internal_plugin() {
|
||||
let name = i.name.to_string();
|
||||
match list {
|
||||
Some(list) if !list.contains(&name) => {
|
||||
log::debug!("Plugin {} not in list, skipping", name);
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
log::debug!("Registering internal plugin {}", i.name);
|
||||
plugins.insert(i.name.to_string(), FinalizePlugin::new(i.entry));
|
||||
count += 1;
|
||||
}
|
||||
|
||||
// Register external plugin
|
||||
for (name, data) in external_plugins.into_iter() {
|
||||
match list {
|
||||
Some(list) if !list.contains(&name) => {
|
||||
log::debug!("Plugin {} not in list, skipping", name);
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
log::debug!("Registering external plugin {}", name);
|
||||
let (_source, config) = data.into_iter().next().unwrap_or_else(|| {
|
||||
panic!(
|
||||
@@ -234,8 +264,11 @@ pub fn register(external_plugins: RawPluginMap) -> Result<PluginMap, PluginError
|
||||
.with_metadata(metadata)
|
||||
.with_argument(config.config),
|
||||
);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
log::info!("Registered {} plugins", count);
|
||||
|
||||
Ok(plugins)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
// Code in this module PARTIALLY OR FULLY utilized AI Coding Agent.
|
||||
use crate::finalize::plugin::PluginError;
|
||||
use std::collections::HashSet;
|
||||
use crate::finalize::fetch;
|
||||
use crate::finalize::{
|
||||
PluginConfig,
|
||||
plugin::{PluginError, RawPluginMap},
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
mod types;
|
||||
mod checksum;
|
||||
mod extract;
|
||||
mod http;
|
||||
mod git;
|
||||
mod http;
|
||||
mod types;
|
||||
|
||||
pub use types::FetchArgument;
|
||||
|
||||
@@ -72,3 +77,35 @@ pub async fn fetch_plugin(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch_plugins(
|
||||
plugin_path: &Path,
|
||||
plugin_config: &mut RawPluginMap,
|
||||
list: Option<&HashSet<String>>,
|
||||
) -> Result<(), PluginError> {
|
||||
log::info!("Preparing to download {} plugins", plugin_config.len());
|
||||
for (name, data) in plugin_config.iter_mut() {
|
||||
match list {
|
||||
Some(list) if !list.contains(name) => {
|
||||
log::debug!("Plugin {} not in list, skipping", name);
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
log::debug!("Downloading plugin {}", name);
|
||||
let (source, config) = data.iter_mut().next().ok_or_else(|| {
|
||||
PluginError::PluginUnavailable {
|
||||
name: name.clone(),
|
||||
reason: "no source configured".to_string(),
|
||||
}
|
||||
})?;
|
||||
let fetch_argument = FetchArgument {
|
||||
checksum: config.checksum.clone(),
|
||||
shallow: config.shallow,
|
||||
};
|
||||
fetch::fetch_plugin(&source, &name, &plugin_path, fetch_argument).await?;
|
||||
log::debug!("Storing plugin {} to {}", name, plugin_path.display());
|
||||
config.runtime.fspath = Some(plugin_path.join(name));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod bake;
|
||||
pub mod socket;
|
||||
pub mod types;
|
||||
pub mod constant;
|
||||
pub mod notify;
|
||||
|
||||
// External crates re-exports
|
||||
pub use config;
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
use crate::constant::FINALIZE_STANDALONE_PLUGIN_PATH;
|
||||
use crate::finalize::FinalizeConfig;
|
||||
use crate::finalize::NotificationConfig;
|
||||
use crate::finalize::parse;
|
||||
use crate::finalize::plugin;
|
||||
use crate::finalize::plugin::FinalizePlugin;
|
||||
use crate::finalize::plugin::fetch;
|
||||
use crate::types::buildstatus::StagePhase;
|
||||
use crate::types::buildstatus::StageResult;
|
||||
use crate::{EventSender, ExecutionContext};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod error;
|
||||
use error::NotifyError;
|
||||
|
||||
fn validate(finalize: &FinalizeConfig) -> anyhow::Result<()> {
|
||||
let validate_result = finalize.validate();
|
||||
if let Err(errors) = validate_result {
|
||||
for error in &errors {
|
||||
log::error!("Error: {}", error);
|
||||
}
|
||||
anyhow::bail!(NotifyError::FinalizeValidationError(errors))
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub type NotifyPluginMap = HashMap<String, FinalizePlugin>;
|
||||
|
||||
fn extract_plugin(finalize: &FinalizeConfig) -> HashSet<String> {
|
||||
finalize
|
||||
.notification
|
||||
.groups
|
||||
.iter()
|
||||
.flat_map(|(_, methods)| methods.keys())
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct NotifyEvent {
|
||||
id: Uuid,
|
||||
stage: StagePhase,
|
||||
substage: String,
|
||||
result: StageResult,
|
||||
not_before: DateTime<Utc>,
|
||||
priority: u32,
|
||||
effective_priority: u32,
|
||||
lives: u32,
|
||||
max_lives: u32,
|
||||
target: HashSet<String>, // Target audience
|
||||
}
|
||||
|
||||
impl Hash for NotifyEvent {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NotifyEvent {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for NotifyEvent {}
|
||||
|
||||
impl PartialOrd for NotifyEvent {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(
|
||||
self.effective_priority
|
||||
.cmp(&other.effective_priority)
|
||||
.then(self.not_before.cmp(&other.not_before))
|
||||
.reverse(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for NotifyEvent {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.partial_cmp(other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
type BundledNotifyEvent = Vec<NotifyEvent>;
|
||||
type NotifyEventReceiver = tokio::sync::mpsc::Receiver<BundledNotifyEvent>;
|
||||
pub type NotifyEventSender = tokio::sync::mpsc::Sender<NotifyEvent>;
|
||||
|
||||
const NOTIFY_NOTREADY_DELAY: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
const NOTIFY_NOTREADY_PRIORITY: u32 = 99;
|
||||
const NOTIFY_TEMPORARY_DELAY_FACTOR: f64 = 2.0;
|
||||
const NOTIFY_TEMPORARY_DELAY_TIME: f64 = 5.0;
|
||||
const NOTIFY_TEMPORARY_MAX_TIME: f64 = f64::MAX;
|
||||
|
||||
pub async fn notify(
|
||||
finalize_path: &Path,
|
||||
ctx: &mut ExecutionContext,
|
||||
event_tx: EventSender,
|
||||
notifyevent_rx: &mut NotifyEventReceiver,
|
||||
notifyevent_tx: NotifyEventSender,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut finalize = parse(finalize_path)?;
|
||||
validate(&finalize)?;
|
||||
let plugin_list = extract_plugin(&finalize);
|
||||
|
||||
if plugin_list.is_empty() {
|
||||
log::warn!("No notification method provided, skipping notification");
|
||||
// TODO: send event to event_tx
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if ctx.standalone {
|
||||
fetch::fetch_plugins(
|
||||
&PathBuf::from(FINALIZE_STANDALONE_PLUGIN_PATH),
|
||||
&mut finalize.plugin,
|
||||
Some(&plugin_list),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let notify_plugins = plugin::register(finalize.plugin, Some(&plugin_list))?;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(events) = notifyevent_rx.recv() => {
|
||||
match notify_handler(¬ify_plugins, &finalize.notification, &events){
|
||||
Ok(_) => {}, // Notification succeeded / failed with fallible
|
||||
Err(e) => {
|
||||
log::debug!("Failed to send notification: {:?}", e);
|
||||
match e {
|
||||
NotifyError::PluginNotAvailable(_) => {
|
||||
// Should keep lives, add static delay and give less priority
|
||||
for mut event in events {
|
||||
event.not_before = Utc::now() + NOTIFY_NOTREADY_DELAY;
|
||||
event.effective_priority = event.effective_priority.saturating_add(1);
|
||||
notifyevent_tx.send(event).await?;
|
||||
}
|
||||
},
|
||||
NotifyError::TemporaryError(_) => {
|
||||
// Should cost 1 life, add dynamic (exponential) delay
|
||||
for mut event in events {
|
||||
event.lives -= 1;
|
||||
event.not_before = Utc::now() + notify_delay(&event);
|
||||
notifyevent_tx.send(event).await?;
|
||||
}
|
||||
},
|
||||
NotifyError::PermanentError(_) => {
|
||||
// Unexpected!
|
||||
// Should be dropped
|
||||
log::error!("None of the configured notification plugins works.");
|
||||
log::error!("Message will be dropped.");
|
||||
// TODO: Send audit/metric information
|
||||
}
|
||||
error => {
|
||||
// Unexpected!
|
||||
log::error!("Internal error: notify_handler returned unexpected error: {:?}", error);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_handler(
|
||||
plugins: &NotifyPluginMap,
|
||||
config: &NotificationConfig,
|
||||
event: &Vec<NotifyEvent>,
|
||||
) -> Result<(), NotifyError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
// calculate min(1, k/x)*t as notification grouping period
|
||||
// to avoid excessive notification frequency
|
||||
fn wait_time(group_period: u32, group_watermark: u32, count: u32) -> f32 {
|
||||
if count == 0 {
|
||||
log::warn!("Unexpected notification count: {}", count);
|
||||
return group_period as f32;
|
||||
}
|
||||
f32::min(1.0, group_watermark as f32 / count as f32) * group_period as f32
|
||||
}
|
||||
|
||||
// calculate p^(m-l)*t as notification delay
|
||||
fn notify_delay(event: &NotifyEvent) -> std::time::Duration {
|
||||
let exponent = (event.max_lives - event.lives) as i32;
|
||||
if exponent < 0 {
|
||||
log::warn!("Unexpected lives in notification: lives({}) > max_lives({})", event.lives, event.max_lives);
|
||||
return std::time::Duration::from_secs_f64(NOTIFY_TEMPORARY_MAX_TIME);
|
||||
}
|
||||
let multiplier = NOTIFY_TEMPORARY_DELAY_FACTOR.powi(exponent);
|
||||
let delay = multiplier * NOTIFY_TEMPORARY_DELAY_TIME;
|
||||
if !delay.is_finite() || delay < 0.0 {
|
||||
log::warn!("Unexpected calculated delay: {}, treating as infinite", delay);
|
||||
return std::time::Duration::from_secs_f64(NOTIFY_TEMPORARY_MAX_TIME);
|
||||
}
|
||||
std::time::Duration::from_secs_f64(delay)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum NotifyError{
|
||||
#[error("Finalize config validation failed: {0:?}")]
|
||||
FinalizeValidationError(Vec<String>),
|
||||
|
||||
#[error("Notification plugin not available {0}")]
|
||||
PluginNotAvailable(String),
|
||||
|
||||
#[error("Temporary failure: {0}")]
|
||||
TemporaryError(String),
|
||||
|
||||
#[error("Permanent error: {0}")]
|
||||
PermanentError(String),
|
||||
|
||||
#[error("Notification failed after {0} attempt(s)")]
|
||||
Exhausted(u32),
|
||||
}
|
||||
@@ -32,7 +32,7 @@ use crate::{
|
||||
error::PrebakeError,
|
||||
ExecutionContext,
|
||||
prebake::{security::get_drop_after, stage::PrebakeStage},
|
||||
types::build_status::{write_stage_status, StageInfo, StagePhase, StageResult},
|
||||
types::buildstatus::{write_stage_status, StageInfo, StagePhase, StageResult},
|
||||
constant::STANDALONE_STATUS_DIR,
|
||||
};
|
||||
use std::path::Path;
|
||||
@@ -137,7 +137,7 @@ mut ctx: &mut ExecutionContext,
|
||||
StageInfo {
|
||||
name: "bootstrap".to_string(),
|
||||
phase: StagePhase::Prebake,
|
||||
sub_stage: "bootstrap".to_string(),
|
||||
substage: "bootstrap".to_string(),
|
||||
result: stage_result,
|
||||
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
|
||||
started_at: stage_start,
|
||||
@@ -165,7 +165,7 @@ mut ctx: &mut ExecutionContext,
|
||||
StageInfo {
|
||||
name: "earlyhook".to_string(),
|
||||
phase: StagePhase::Prebake,
|
||||
sub_stage: "earlyhook".to_string(),
|
||||
substage: "earlyhook".to_string(),
|
||||
result: stage_result,
|
||||
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
|
||||
started_at: stage_start,
|
||||
@@ -247,7 +247,7 @@ mut ctx: &mut ExecutionContext,
|
||||
StageInfo {
|
||||
name: "depssystem".to_string(),
|
||||
phase: StagePhase::Prebake,
|
||||
sub_stage: "depssystem".to_string(),
|
||||
substage: "depssystem".to_string(),
|
||||
result: stage_result,
|
||||
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
|
||||
started_at: stage_start,
|
||||
@@ -272,7 +272,7 @@ mut ctx: &mut ExecutionContext,
|
||||
StageInfo {
|
||||
name: "depsuser".to_string(),
|
||||
phase: StagePhase::Prebake,
|
||||
sub_stage: "depsuser".to_string(),
|
||||
substage: "depsuser".to_string(),
|
||||
result: stage_result,
|
||||
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
|
||||
started_at: stage_start,
|
||||
@@ -301,7 +301,7 @@ mut ctx: &mut ExecutionContext,
|
||||
StageInfo {
|
||||
name: "latehook".to_string(),
|
||||
phase: StagePhase::Prebake,
|
||||
sub_stage: "latehook".to_string(),
|
||||
substage: "latehook".to_string(),
|
||||
result: stage_result,
|
||||
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
|
||||
started_at: stage_start,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod architecture;
|
||||
pub mod build_status;
|
||||
pub mod buildstatus;
|
||||
pub mod builderconfig;
|
||||
pub mod cache;
|
||||
pub mod command;
|
||||
|
||||
@@ -41,7 +41,7 @@ impl std::fmt::Display for StageResult {
|
||||
pub struct StageInfo {
|
||||
pub name: String,
|
||||
pub phase: StagePhase,
|
||||
pub sub_stage: String,
|
||||
pub substage: String,
|
||||
pub result: StageResult,
|
||||
pub duration_ms: u64,
|
||||
pub started_at: DateTime<Utc>,
|
||||
@@ -51,7 +51,7 @@ pub struct StageInfo {
|
||||
|
||||
impl StageInfo {
|
||||
pub fn full_name(&self) -> String {
|
||||
format!("{}.{}", self.phase, self.sub_stage)
|
||||
format!("{}.{}", self.phase, self.substage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ mod tests {
|
||||
StageInfo {
|
||||
name: name.to_string(),
|
||||
phase,
|
||||
sub_stage: name.to_string(),
|
||||
substage: name.to_string(),
|
||||
result,
|
||||
duration_ms: 1000,
|
||||
started_at: now,
|
||||
@@ -161,7 +161,7 @@ mod tests {
|
||||
|
||||
let status = read_build_status(&dir).unwrap();
|
||||
assert_eq!(status.stages.len(), 1);
|
||||
assert_eq!(status.stages[0].sub_stage, "bootstrap");
|
||||
assert_eq!(status.stages[0].substage, "bootstrap");
|
||||
assert_eq!(status.stages[0].result, StageResult::Success);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user