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