diff --git a/workshop-baker/src/error.rs b/workshop-baker/src/error.rs index 12a2c83..de5d5fc 100644 --- a/workshop-baker/src/error.rs +++ b/workshop-baker/src/error.rs @@ -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, diff --git a/workshop-baker/src/finalize/config.rs b/workshop-baker/src/finalize/config.rs index 1e77689..9456a29 100644 --- a/workshop-baker/src/finalize/config.rs +++ b/workshop-baker/src/finalize/config.rs @@ -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) { // 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 diff --git a/workshop-baker/src/finalize/error.rs b/workshop-baker/src/finalize/error.rs index 337694a..3c0cf98 100644 --- a/workshop-baker/src/finalize/error.rs +++ b/workshop-baker/src/finalize/error.rs @@ -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, diff --git a/workshop-baker/src/main.rs b/workshop-baker/src/main.rs index b94e791..7aee4a0 100644 --- a/workshop-baker/src/main.rs +++ b/workshop-baker/src/main.rs @@ -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::() - .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::() + .map(|pe| pe.exit_code()) + .unwrap_or(1), + BareCommands::Bake { .. } => e + .downcast_ref::() + .map(|be| be.exit_code()) + .unwrap_or(1), + BareCommands::Finalize { .. } => e + .downcast_ref::() + .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; diff --git a/workshop-baker/src/prebake/stage/bootstrap.rs b/workshop-baker/src/prebake/stage/bootstrap.rs index 862995b..d4a0d6e 100644 --- a/workshop-baker/src/prebake/stage/bootstrap.rs +++ b/workshop-baker/src/prebake/stage/bootstrap.rs @@ -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()); diff --git a/workshop-baker/src/types.rs b/workshop-baker/src/types.rs index 36c2dc8..bfb0417 100644 --- a/workshop-baker/src/types.rs +++ b/workshop-baker/src/types.rs @@ -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;