2658d0ebfa
Add new `build_status` types module for tracking pipeline stage execution status across prebake, bake, and finalize phases. Introduce `NotificationCore` dispatcher that processes notification methods by priority groups with fallback on failure. Status files written to `/tmp/.hbwstatus` in JSON lines format.
159 lines
5.1 KiB
Rust
159 lines
5.1 KiB
Rust
use crate::bake::parser::Function;
|
|
use crate::cli::Cli;
|
|
use crate::constant::{DEFAULT_WORKSPACE, STANDALONE_STATUS_DIR, TRIVIAL_SCRIPT_NAME};
|
|
use crate::engine::EventSender;
|
|
use crate::error::BakeError;
|
|
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::{Engine, prebake};
|
|
use std::path::{PathBuf, Path};
|
|
use chrono::Utc;
|
|
|
|
mod builder;
|
|
mod decorator;
|
|
pub mod parser;
|
|
mod schedule;
|
|
|
|
pub async fn bake(
|
|
script_path: &Path,
|
|
bake_base_path: &Path,
|
|
prebake_path: &Path,
|
|
cli: &Cli,
|
|
ctx: &mut ExecutionContext,
|
|
event_tx: EventSender,
|
|
) -> Result<(), BakeError> {
|
|
let script_content = std::fs::read_to_string(script_path)?;
|
|
let prebake_content = std::fs::read_to_string(prebake_path)?;
|
|
let prebake = prebake::parse(prebake_content.as_str()).map_err(|e| {
|
|
log::error!("Failed to parse prebake.yml: {}", e);
|
|
log::error!("This is unexpected!");
|
|
BakeError::YamlParseError(e)
|
|
})?;
|
|
let workspace = get_workspace(&prebake);
|
|
let functions = parser::parse_script(&script_content)?;
|
|
|
|
let has_pipeline = functions.pipeline_functions.iter().any(|f| {
|
|
f.decorators
|
|
.iter()
|
|
.any(|d| matches!(d, decorator::Decorator::Pipeline))
|
|
});
|
|
let trivial = false;
|
|
let use_template = true;
|
|
|
|
let engine = Engine::new();
|
|
if let Some(env) = &prebake.envvars {
|
|
if let Some(prebake_env) = &env.bake {
|
|
ctx.env_vars.extend(prebake_env.clone());
|
|
}
|
|
}
|
|
|
|
if !has_pipeline || trivial {
|
|
let stage_start = Utc::now();
|
|
let function = Function{
|
|
name: TRIVIAL_SCRIPT_NAME.to_string(),
|
|
decorators: vec![],
|
|
body: script_content,
|
|
};
|
|
let script = builder::build_script(
|
|
&function,
|
|
bake_base_path,
|
|
&workspace,
|
|
None,
|
|
use_template,
|
|
)?;
|
|
let result = engine.execute_script(&script, &ctx, &event_tx).await;
|
|
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure };
|
|
let _ = write_stage_status(
|
|
STANDALONE_STATUS_DIR,
|
|
StageInfo {
|
|
name: "trivial".to_string(),
|
|
phase: StagePhase::Bake,
|
|
sub_stage: "trivial".to_string(),
|
|
result: stage_result,
|
|
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
|
|
started_at: stage_start,
|
|
finished_at: Utc::now(),
|
|
error_message: result.as_ref().err().map(|e| e.to_string()),
|
|
},
|
|
);
|
|
result?;
|
|
} else {
|
|
let sorted_functions = schedule::sort_function(functions.pipeline_functions)?;
|
|
let remaining = functions.remaining_code;
|
|
for func in sorted_functions {
|
|
let stage_start = Utc::now();
|
|
let mut modified_func = func.clone();
|
|
modified_func.body = remaining.clone() + &func.body;
|
|
let script = builder::build_script(
|
|
&modified_func,
|
|
bake_base_path,
|
|
&workspace,
|
|
None,
|
|
use_template,
|
|
)?;
|
|
let result = engine.execute_script(&script, &ctx, &event_tx).await;
|
|
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure };
|
|
let _ = write_stage_status(
|
|
STANDALONE_STATUS_DIR,
|
|
StageInfo {
|
|
name: func.name.clone(),
|
|
phase: StagePhase::Bake,
|
|
sub_stage: func.name.clone(),
|
|
result: stage_result,
|
|
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64,
|
|
started_at: stage_start,
|
|
finished_at: Utc::now(),
|
|
error_message: result.as_ref().err().map(|e| e.to_string()),
|
|
},
|
|
);
|
|
result?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn get_workspace(prebake_config: &PrebakeConfig) -> PathBuf {
|
|
prebake_config
|
|
.bootstrap
|
|
.as_ref()
|
|
.and_then(|b| b.workspace.as_ref())
|
|
.map(|ws| {
|
|
if ws.fallback {
|
|
PathBuf::from(DEFAULT_WORKSPACE)
|
|
} else {
|
|
PathBuf::from(ws.path.clone())
|
|
}
|
|
})
|
|
.unwrap_or_else(|| PathBuf::from(DEFAULT_WORKSPACE))
|
|
}
|
|
|
|
fn privileged(prebake_config: &PrebakeConfig) -> bool {
|
|
if let Some(bootstrap) = &prebake_config.bootstrap {
|
|
if bootstrap.user == "root" {
|
|
return true;
|
|
}
|
|
}
|
|
let drop_after = get_drop_after(&prebake_config.security).unwrap_or_default();
|
|
PrebakeStage::Never == drop_after
|
|
}
|
|
|
|
|
|
type TaskResult = Result<(), BakeError>;
|
|
|
|
type TaskFn = Box<dyn FnOnce() -> TaskResult + Send>;
|
|
|
|
// TODO: Learn closure and continue
|
|
// async fn run<F>(f:F) -> TaskFn {
|
|
// let engine = Engine::new();
|
|
// engine.execute_script
|
|
// }
|
|
|
|
// fn with_timeout<F>(f: F, duration: i32) -> TaskFn {
|
|
|
|
|
|
// }
|