refactor(error): extract exit code logic into HasExitCode trait
Introduce a common HasExitCode trait for PrebakeError, BakeError, ExecutionError, and FinalizeError. Refactor main.rs to consolidate CLI command handling and use the trait for unified exit code resolution.
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
use os_info::Info;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Display;
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
// Obsolete?
|
||||
pub trait HasExitCode: Display + Debug + Send + Sync + Sized + 'static {
|
||||
fn exit_code(&self) -> i32;
|
||||
}
|
||||
|
||||
pub const EXITCODE_OK: i32 = 0;
|
||||
pub const EXITCODE_GENERAL_ERROR: i32 = 1;
|
||||
pub const EXITCODE_INVALID_ARGS: i32 = 2;
|
||||
@@ -69,8 +76,8 @@ pub enum PrebakeError {
|
||||
ConfigError(String),
|
||||
}
|
||||
|
||||
impl PrebakeError {
|
||||
pub fn exit_code(&self) -> i32 {
|
||||
impl HasExitCode for PrebakeError {
|
||||
fn exit_code(&self) -> i32 {
|
||||
match self {
|
||||
PrebakeError::PrivDropFailed(_) => EXITCODE_PRIV_DROP_FAILED,
|
||||
PrebakeError::UserNotFound(_) => EXITCODE_PRIV_DROP_FAILED,
|
||||
@@ -149,8 +156,23 @@ pub enum ExecutionError {
|
||||
PackageManagerNotFound,
|
||||
}
|
||||
|
||||
impl ExecutionError {
|
||||
pub fn exit_code(&self) -> i32 {
|
||||
impl HasExitCode for BakeError {
|
||||
fn exit_code(&self) -> i32 {
|
||||
match self {
|
||||
BakeError::ScriptGenerationFailed { .. } => EXITCODE_IO_ERROR,
|
||||
BakeError::TemplateError(_) => EXITCODE_PARSE_ERROR,
|
||||
BakeError::CircularDependency(_) => EXITCODE_PARSE_ERROR,
|
||||
BakeError::UnknownDependency(..) => EXITCODE_PARSE_ERROR,
|
||||
BakeError::IoError(_) => EXITCODE_IO_ERROR,
|
||||
BakeError::YamlParseError(_) => EXITCODE_PARSE_ERROR,
|
||||
BakeError::DecoratorParseError { .. } => EXITCODE_PARSE_ERROR,
|
||||
BakeError::ScriptExecutionError(e) => e.exit_code(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasExitCode for ExecutionError {
|
||||
fn exit_code(&self) -> i32 {
|
||||
match self {
|
||||
ExecutionError::InvalidCommand(_) => EXITCODE_EXECUTION_ERROR,
|
||||
ExecutionError::ExecutionFailed(_) => EXITCODE_EXECUTION_ERROR,
|
||||
|
||||
@@ -249,7 +249,6 @@ pub struct ArtifactDef {
|
||||
/// Compression method: "zstd", "gzip", "none", "dir"
|
||||
#[serde(default)]
|
||||
pub compression: CompressionMethod,
|
||||
|
||||
// Publishing targets
|
||||
// TODO: under heavy refactor
|
||||
}
|
||||
@@ -351,7 +350,11 @@ impl FinalizeConfig {
|
||||
fn validate_notification(&self, errors: &mut Vec<String>) {
|
||||
// Validate templates
|
||||
for (name, tmpl) in &self.notification.templates {
|
||||
if tmpl.use_.is_some() && (tmpl.on_success.is_some() || tmpl.on_failure.is_some() || tmpl.on_finish_of.is_some()) {
|
||||
if tmpl.use_.is_some()
|
||||
&& (tmpl.on_success.is_some()
|
||||
|| tmpl.on_failure.is_some()
|
||||
|| tmpl.on_finish_of.is_some())
|
||||
{
|
||||
errors.push(format!(
|
||||
"Template '{}' cannot combine 'use' with 'on_*' fields (MVP restriction)",
|
||||
name
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::{
|
||||
ExecutionError,
|
||||
error::{
|
||||
EXITCODE_EXECUTION_ERROR, EXITCODE_GENERAL_ERROR, EXITCODE_IO_ERROR, EXITCODE_PARSE_ERROR,
|
||||
HasExitCode,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -38,8 +39,8 @@ pub enum FinalizeError {
|
||||
ExecutionError(#[from] ExecutionError),
|
||||
}
|
||||
|
||||
impl FinalizeError {
|
||||
pub fn exit_code(&self) -> i32 {
|
||||
impl HasExitCode for FinalizeError {
|
||||
fn exit_code(&self) -> i32 {
|
||||
match self {
|
||||
FinalizeError::YamlParseError(_) => EXITCODE_PARSE_ERROR,
|
||||
FinalizeError::ValidateError(_) => EXITCODE_PARSE_ERROR,
|
||||
|
||||
+61
-99
@@ -5,7 +5,7 @@ use workshop_baker::{
|
||||
cli::{BareCommands, Cli, Commands, Parser},
|
||||
daemon,
|
||||
engine::{EventSender, ExecutionContext},
|
||||
error::PrebakeError,
|
||||
error::HasExitCode,
|
||||
finalize::finalize,
|
||||
prebake::prebake,
|
||||
};
|
||||
@@ -21,108 +21,70 @@ async fn main() {
|
||||
Some(Commands::Client { config: _ }) => {
|
||||
unimplemented!("Client mode not yet implemented.")
|
||||
}
|
||||
Some(Commands::Bare { command }) => match command {
|
||||
BareCommands::Prebake { config } => {
|
||||
log::info!("Running in standalone mode.");
|
||||
let mut ctx = ExecutionContext {
|
||||
standalone: true,
|
||||
task_id: format!("{}-{}-init", cli.pipeline, cli.build_id),
|
||||
pipeline_name: cli.pipeline.clone(),
|
||||
username: cli.username.clone(),
|
||||
dry_run: cli.dry_run,
|
||||
privileged: true,
|
||||
..Default::default()
|
||||
};
|
||||
let (tx, event_rx) = tokio::sync::mpsc::channel(256);
|
||||
let event_tx: EventSender = Some(tx);
|
||||
let pipeline_name = cli.pipeline.clone();
|
||||
let build_id = cli.build_id.clone();
|
||||
let receiver_handle =
|
||||
tokio::spawn(workshop_baker::prebake::event::event_receiver(
|
||||
event_rx,
|
||||
pipeline_name,
|
||||
build_id,
|
||||
));
|
||||
let result = prebake(config, &cli, None, &mut ctx, event_tx).await;
|
||||
drop(ctx);
|
||||
let _ = receiver_handle.await;
|
||||
dbg!(&result);
|
||||
if let Err(ref e) = result {
|
||||
log::error!("Prebake stage failed: {}", e);
|
||||
std::process::exit(
|
||||
e.downcast_ref::<PrebakeError>()
|
||||
.map(|pe| pe.exit_code())
|
||||
.unwrap_or(1),
|
||||
);
|
||||
Some(Commands::Bare { command }) => {
|
||||
log::info!("Running in standalone mode.");
|
||||
|
||||
let (stage, privileged) = match command {
|
||||
BareCommands::Prebake { .. } => ("init", true),
|
||||
BareCommands::Bake { .. } => ("bake", false),
|
||||
BareCommands::Finalize { .. } => ("finalize", false),
|
||||
};
|
||||
let mut ctx = ExecutionContext {
|
||||
standalone: true,
|
||||
task_id: format!("{}-{}-{}", cli.pipeline, cli.build_id, stage),
|
||||
pipeline_name: cli.pipeline.clone(),
|
||||
username: cli.username.clone(),
|
||||
dry_run: cli.dry_run,
|
||||
privileged,
|
||||
..Default::default()
|
||||
};
|
||||
let (tx, event_rx) = tokio::sync::mpsc::channel(256);
|
||||
let event_tx: EventSender = Some(tx);
|
||||
let handle = tokio::spawn(workshop_baker::prebake::event::event_receiver(
|
||||
event_rx,
|
||||
cli.pipeline.clone(),
|
||||
cli.build_id.clone(),
|
||||
));
|
||||
|
||||
let result: anyhow::Result<()> = match command {
|
||||
BareCommands::Prebake { config } => {
|
||||
prebake(config, &cli, None, &mut ctx, event_tx).await
|
||||
}
|
||||
}
|
||||
BareCommands::Bake {
|
||||
script,
|
||||
bake_base,
|
||||
prebake: prebake_path,
|
||||
} => {
|
||||
log::info!("Running in standalone mode.");
|
||||
let mut ctx = ExecutionContext {
|
||||
standalone: true,
|
||||
task_id: format!("{}-{}-bake", cli.pipeline, cli.build_id),
|
||||
pipeline_name: cli.pipeline.clone(),
|
||||
username: cli.username.clone(),
|
||||
dry_run: cli.dry_run,
|
||||
privileged: false,
|
||||
..Default::default()
|
||||
};
|
||||
let (tx, event_rx) = tokio::sync::mpsc::channel(256);
|
||||
let event_tx: EventSender = Some(tx);
|
||||
let pipeline_name = cli.pipeline.clone();
|
||||
let build_id = cli.build_id.clone();
|
||||
let receiver_handle =
|
||||
tokio::spawn(workshop_baker::prebake::event::event_receiver(
|
||||
event_rx,
|
||||
pipeline_name,
|
||||
build_id,
|
||||
));
|
||||
let result =
|
||||
bake(script, bake_base, prebake_path, &cli, &mut ctx, event_tx).await;
|
||||
drop(ctx);
|
||||
let _ = receiver_handle.await;
|
||||
dbg!(&result);
|
||||
if result.is_err() {
|
||||
log::error!("Bake stage failed! Exiting...");
|
||||
std::process::exit(1);
|
||||
BareCommands::Bake {
|
||||
script,
|
||||
bake_base,
|
||||
prebake: prebake_path,
|
||||
} => bake(script, bake_base, prebake_path, &cli, &mut ctx, event_tx)
|
||||
.await
|
||||
.map_err(|e| e.into()),
|
||||
BareCommands::Finalize { config } => {
|
||||
finalize(Path::new(config), &cli, &mut ctx, event_tx)
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
BareCommands::Finalize { config } => {
|
||||
log::info!("Running in standalone mode.");
|
||||
let mut ctx = ExecutionContext {
|
||||
standalone: true,
|
||||
task_id: format!("{}-{}-finalize", cli.pipeline, cli.build_id),
|
||||
pipeline_name: cli.pipeline.clone(),
|
||||
username: cli.username.clone(),
|
||||
dry_run: cli.dry_run,
|
||||
privileged: false,
|
||||
..Default::default()
|
||||
};
|
||||
drop(ctx);
|
||||
let _ = handle.await;
|
||||
dbg!(&result);
|
||||
if let Err(ref e) = result {
|
||||
let code = match command {
|
||||
BareCommands::Prebake { .. } => e
|
||||
.downcast_ref::<workshop_baker::error::PrebakeError>()
|
||||
.map(|pe| pe.exit_code())
|
||||
.unwrap_or(1),
|
||||
BareCommands::Bake { .. } => e
|
||||
.downcast_ref::<workshop_baker::error::BakeError>()
|
||||
.map(|be| be.exit_code())
|
||||
.unwrap_or(1),
|
||||
BareCommands::Finalize { .. } => e
|
||||
.downcast_ref::<workshop_baker::finalize::FinalizeError>()
|
||||
.map(|fe| fe.exit_code())
|
||||
.unwrap_or(1),
|
||||
};
|
||||
let (tx, event_rx) = tokio::sync::mpsc::channel(256);
|
||||
let event_tx: EventSender = Some(tx);
|
||||
let pipeline_name = cli.pipeline.clone();
|
||||
let build_id = cli.build_id.clone();
|
||||
let receiver_handle =
|
||||
tokio::spawn(workshop_baker::prebake::event::event_receiver(
|
||||
event_rx,
|
||||
pipeline_name,
|
||||
build_id,
|
||||
));
|
||||
let result =
|
||||
finalize(Path::new(config), &cli, &mut ctx, event_tx).await;
|
||||
drop(ctx);
|
||||
let _ = receiver_handle.await;
|
||||
dbg!(&result);
|
||||
if let Err(ref e) = result {
|
||||
log::error!("Finalize stage failed: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
log::error!("{} stage failed: {}", stage, e);
|
||||
std::process::exit(code);
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
log::info!("Executor will be running in daemon mode.");
|
||||
let _result = daemon::daemon(&cli).await;
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::prebake::config::Bootstrap;
|
||||
use crate::prebake::config::PrebakeConfig;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::{PathBuf,Path};
|
||||
use std::path::{Path, PathBuf};
|
||||
use which::which;
|
||||
|
||||
/// Executes the bootstrap stage of the prebake workflow.
|
||||
@@ -55,15 +55,17 @@ pub async fn bootstrap(
|
||||
|
||||
if let Some(custom_bootstrap) = bootstrap_config.custom {
|
||||
if ctx.dry_run {
|
||||
log::info!("[DRY_RUN] User provided bootstrap script:\n{}", custom_bootstrap);
|
||||
log::info!(
|
||||
"[DRY_RUN] User provided bootstrap script:\n{}",
|
||||
custom_bootstrap
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
// TODO: Parse custom bootstrap URL and download them
|
||||
log::debug!("User provided bootstrap script:\n{}", custom_bootstrap);
|
||||
if ctx.standalone {
|
||||
write_bootstrap_content(&bootstrap_path, custom_bootstrap)?;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Requires PIP(Pipeline Implicit Parameters) to be implemented
|
||||
unimplemented!("Permission check is not implemented, failing...");
|
||||
}
|
||||
@@ -147,7 +149,9 @@ fn generate_bootstrap(
|
||||
// Requires PIP(Pipeline Implicit Parameters) to be implemented
|
||||
// unimplemented!("Permission check is not implemented, failing...");
|
||||
if standalone {
|
||||
log::warn!("Running the whole pipeline with root could harm your pipeline or cause undefined behavior.")
|
||||
log::warn!(
|
||||
"Running the whole pipeline with root could harm your pipeline or cause undefined behavior."
|
||||
)
|
||||
} else {
|
||||
unimplemented!("Permission check is not implemented, failing...");
|
||||
}
|
||||
@@ -221,10 +225,7 @@ fn generate_bootstrap(
|
||||
// TODO: Introduce permission check
|
||||
unimplemented!("Permission check is not implemented, failing...")
|
||||
}
|
||||
script.push_str(&format!(
|
||||
"cat >> /etc/doas.conf << 'EOF'\n{}\nEOF\n",
|
||||
doas
|
||||
));
|
||||
script.push_str(&format!("cat >> /etc/doas.conf << 'EOF'\n{}\nEOF\n", doas));
|
||||
}
|
||||
if let Some(pkg_manager) = pkg_manager {
|
||||
let allowed_commands = format!("/usr/bin/{} *", pkg_manager.binary());
|
||||
|
||||
@@ -3,7 +3,7 @@ pub mod builderconfig;
|
||||
pub mod buildstatus;
|
||||
pub mod cache;
|
||||
pub mod command;
|
||||
pub mod compression;
|
||||
pub mod memsize;
|
||||
pub mod repology;
|
||||
pub mod time;
|
||||
pub mod compression;
|
||||
|
||||
Reference in New Issue
Block a user