style: apply rustfmt and clippy fixes across codebase
CI / Check & Lint (push) Failing after 23m46s

This commit is contained in:
Catty Steve
2026-04-23 18:34:26 +08:00
parent 9d3d61ba66
commit 36112c671d
47 changed files with 532 additions and 326 deletions
+7 -1
View File
@@ -30,7 +30,7 @@ jobs:
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
components: clippy components: clippy, rustfmt
- name: Cache cargo dependencies - name: Cache cargo dependencies
uses: actions/cache@v4 uses: actions/cache@v4
@@ -43,8 +43,14 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-cargo- ${{ runner.os }}-cargo-
- name: Check formatting
run: cargo fmt -- --check
- name: Check code (cargo check) - name: Check code (cargo check)
run: cargo check --all-features run: cargo check --all-features
- name: Run Clippy lints - name: Run Clippy lints
run: cargo clippy --all-features -- -D warnings run: cargo clippy --all-features -- -D warnings
- name: Run tests
run: cargo test --all-features
+1 -1
View File
@@ -1,4 +1,4 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{Criterion, black_box, criterion_group, criterion_main};
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
+22 -17
View File
@@ -1,16 +1,16 @@
use crate::ExecutionContext;
use crate::bake::parser::Function; use crate::bake::parser::Function;
use crate::cli::Cli; use crate::cli::Cli;
use crate::constant::{DEFAULT_WORKSPACE, STANDALONE_STATUS_DIR, TRIVIAL_SCRIPT_NAME}; use crate::constant::{DEFAULT_WORKSPACE, STANDALONE_STATUS_DIR, TRIVIAL_SCRIPT_NAME};
use crate::engine::EventSender; use crate::engine::EventSender;
use crate::error::BakeError; use crate::error::BakeError;
use crate::ExecutionContext;
use crate::prebake::PrebakeConfig; use crate::prebake::PrebakeConfig;
use crate::prebake::security::get_drop_after; use crate::prebake::security::get_drop_after;
use crate::prebake::stage::PrebakeStage; use crate::prebake::stage::PrebakeStage;
use crate::types::buildstatus::{write_stage_status, StageInfo, StagePhase, StageResult}; use crate::types::buildstatus::{StageInfo, StagePhase, StageResult, write_stage_status};
use crate::{Engine, prebake}; use crate::{Engine, prebake};
use std::path::{PathBuf, Path};
use chrono::Utc; use chrono::Utc;
use std::path::{Path, PathBuf};
mod builder; mod builder;
mod decorator; mod decorator;
@@ -52,20 +52,19 @@ pub async fn bake(
if !has_pipeline || trivial { if !has_pipeline || trivial {
let stage_start = Utc::now(); let stage_start = Utc::now();
let function = Function{ let function = Function {
name: TRIVIAL_SCRIPT_NAME.to_string(), name: TRIVIAL_SCRIPT_NAME.to_string(),
decorators: vec![], decorators: vec![],
body: script_content, body: script_content,
}; };
let script = builder::build_script( let script =
&function, builder::build_script(&function, bake_base_path, &workspace, None, use_template)?;
bake_base_path,
&workspace,
None,
use_template,
)?;
let result = engine.execute_script(&script, &ctx, &event_tx).await; let result = engine.execute_script(&script, &ctx, &event_tx).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure }; let stage_result = if result.is_ok() {
StageResult::Success
} else {
StageResult::Failure
};
let _ = write_stage_status( let _ = write_stage_status(
STANDALONE_STATUS_DIR, STANDALONE_STATUS_DIR,
StageInfo { StageInfo {
@@ -73,7 +72,9 @@ pub async fn bake(
phase: StagePhase::Bake, phase: StagePhase::Bake,
substage: "trivial".to_string(), substage: "trivial".to_string(),
result: stage_result, result: stage_result,
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, duration_ms: Utc::now()
.signed_duration_since(stage_start)
.num_milliseconds() as u64,
started_at: stage_start, started_at: stage_start,
finished_at: Utc::now(), finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()), error_message: result.as_ref().err().map(|e| e.to_string()),
@@ -95,7 +96,11 @@ pub async fn bake(
use_template, use_template,
)?; )?;
let result = engine.execute_script(&script, &ctx, &event_tx).await; let result = engine.execute_script(&script, &ctx, &event_tx).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure }; let stage_result = if result.is_ok() {
StageResult::Success
} else {
StageResult::Failure
};
let _ = write_stage_status( let _ = write_stage_status(
STANDALONE_STATUS_DIR, STANDALONE_STATUS_DIR,
StageInfo { StageInfo {
@@ -103,7 +108,9 @@ pub async fn bake(
phase: StagePhase::Bake, phase: StagePhase::Bake,
substage: func.name.clone(), substage: func.name.clone(),
result: stage_result, result: stage_result,
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, duration_ms: Utc::now()
.signed_duration_since(stage_start)
.num_milliseconds() as u64,
started_at: stage_start, started_at: stage_start,
finished_at: Utc::now(), finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()), error_message: result.as_ref().err().map(|e| e.to_string()),
@@ -141,7 +148,6 @@ fn privileged(prebake_config: &PrebakeConfig) -> bool {
PrebakeStage::Never == drop_after PrebakeStage::Never == drop_after
} }
type TaskResult = Result<(), BakeError>; type TaskResult = Result<(), BakeError>;
type TaskFn = Box<dyn FnOnce() -> TaskResult + Send>; type TaskFn = Box<dyn FnOnce() -> TaskResult + Send>;
@@ -154,5 +160,4 @@ type TaskFn = Box<dyn FnOnce() -> TaskResult + Send>;
// fn with_timeout<F>(f: F, duration: i32) -> TaskFn { // fn with_timeout<F>(f: F, duration: i32) -> TaskFn {
// } // }
+1 -1
View File
@@ -1,4 +1,4 @@
use super::decorator::{parse_decorator, Decorator}; use super::decorator::{Decorator, parse_decorator};
use crate::bake::BakeError; use crate::bake::BakeError;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
+2 -4
View File
@@ -28,10 +28,8 @@ pub fn sort_function(functions: Vec<Function>) -> Result<Vec<Function>, BakeErro
// Add implicit @after for pipeline functions that don't have explicit @after // Add implicit @after for pipeline functions that don't have explicit @after
if is_pipeline { if is_pipeline {
if !has_after { if !has_after && let Some(ref last) = last_pipeline {
if let Some(ref last) = last_pipeline { ts.add_dependency(last, function.name.clone());
ts.add_dependency(last, function.name.clone());
}
} }
last_pipeline = Some(function.name.clone()); last_pipeline = Some(function.name.clone());
} }
+2 -2
View File
@@ -1,10 +1,10 @@
use crate::cli::{Cli}; use crate::cli::Cli;
// use serde::Serialize; // use serde::Serialize;
use serde_json::Value; use serde_json::Value;
// use std::path::PathBuf; // use std::path::PathBuf;
// use std::sync::Arc; // use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::net::{UnixStream}; use tokio::net::UnixStream;
// use tokio::process::{Child, Command}; // use tokio::process::{Child, Command};
// use tokio::signal; // use tokio::signal;
// use tokio::sync::RwLock; // use tokio::sync::RwLock;
+21 -17
View File
@@ -8,8 +8,8 @@ use tokio::time::timeout;
use super::pm::PackageManager; use super::pm::PackageManager;
use super::types::{ use super::types::{
CgroupManager, EventSender, ExecutionContext, ExecutionError, ExecutionEvent, ExecutionResult, CgroupManager, DeltaExecutionContext, EventSender, ExecutionContext, ExecutionError,
ResourceLimits, DeltaExecutionContext, StreamType ExecutionEvent, ExecutionResult, ResourceLimits, StreamType,
}; };
impl super::types::Engine { impl super::types::Engine {
@@ -68,7 +68,7 @@ impl super::types::Engine {
}; };
if !abs_path.exists() { if !abs_path.exists() {
return Err(ExecutionError::ScriptNotFound(abs_path)) return Err(ExecutionError::ScriptNotFound(abs_path));
} }
let mut command = Command::new(ctx.shell.as_str()); let mut command = Command::new(ctx.shell.as_str());
@@ -94,8 +94,8 @@ impl super::types::Engine {
cmd: &str, cmd: &str,
ctx: &ExecutionContext, ctx: &ExecutionContext,
event_tx: &EventSender, event_tx: &EventSender,
delta_ctx: &DeltaExecutionContext delta_ctx: &DeltaExecutionContext,
) -> Result<ExecutionResult,ExecutionError> { ) -> Result<ExecutionResult, ExecutionError> {
let mut local_ctx = ctx.clone(); let mut local_ctx = ctx.clone();
if let Some(working_dir) = &delta_ctx.working_dir { if let Some(working_dir) = &delta_ctx.working_dir {
log::debug!("Updating working directory to {}", working_dir.display()); log::debug!("Updating working directory to {}", working_dir.display());
@@ -222,7 +222,7 @@ impl super::types::Engine {
return Err(ExecutionError::IoError(format!( return Err(ExecutionError::IoError(format!(
"Failed to wait for process: {}", "Failed to wait for process: {}",
e e
))) )));
} }
Err(_) => { Err(_) => {
log::warn!( log::warn!(
@@ -267,21 +267,25 @@ impl super::types::Engine {
while let Some(data) = stdout_rx.recv().await { while let Some(data) = stdout_rx.recv().await {
stdout_buf.push_str(&data); stdout_buf.push_str(&data);
if let Some(tx) = event_tx { if let Some(tx) = event_tx {
let _ = tx.send(ExecutionEvent::OutputChunk { let _ = tx
task_id: task_id.clone(), .send(ExecutionEvent::OutputChunk {
stream: StreamType::Stdout, task_id: task_id.clone(),
data: data.clone(), stream: StreamType::Stdout,
}).await; data: data.clone(),
})
.await;
} }
} }
while let Some(data) = stderr_rx.recv().await { while let Some(data) = stderr_rx.recv().await {
stderr_buf.push_str(&data); stderr_buf.push_str(&data);
if let Some(tx) = event_tx { if let Some(tx) = event_tx {
let _ = tx.send(ExecutionEvent::OutputChunk { let _ = tx
task_id: task_id.clone(), .send(ExecutionEvent::OutputChunk {
stream: StreamType::Stderr, task_id: task_id.clone(),
data: data.clone(), stream: StreamType::Stderr,
}).await; data: data.clone(),
})
.await;
} }
} }
@@ -328,7 +332,7 @@ impl super::types::Engine {
package: &[String], package: &[String],
pkgman: &dyn PackageManager, pkgman: &dyn PackageManager,
ctx: &ExecutionContext, ctx: &ExecutionContext,
event_tx: &EventSender event_tx: &EventSender,
) -> Result<ExecutionResult, ExecutionError> { ) -> Result<ExecutionResult, ExecutionError> {
let cmds = pkgman.install(package); let cmds = pkgman.install(package);
let mut combined_result = ExecutionResult { let mut combined_result = ExecutionResult {
+16 -10
View File
@@ -127,7 +127,7 @@ impl PackageManager for Pacman {
)); ));
let mut keyring_cmd = Command::new("sh"); let mut keyring_cmd = Command::new("sh");
keyring_cmd.arg("-c").arg("pacman-key --init"); keyring_cmd.arg("-c").arg("pacman-key --init");
vec![cmd, keyring_cmd] vec![cmd, keyring_cmd]
} }
@@ -446,18 +446,22 @@ mod tests {
.map(|a| a.to_string_lossy().into_owned()) .map(|a| a.to_string_lossy().into_owned())
.collect(); .collect();
assert!(recv_args assert!(
.join(" ") recv_args
.contains("pacman-key --recv-keys ABCDEF123456")); .join(" ")
.contains("pacman-key --recv-keys ABCDEF123456")
);
let lsign_cmd = cmds[2].as_std(); let lsign_cmd = cmds[2].as_std();
let lsign_args: Vec<String> = lsign_cmd let lsign_args: Vec<String> = lsign_cmd
.get_args() .get_args()
.map(|a| a.to_string_lossy().into_owned()) .map(|a| a.to_string_lossy().into_owned())
.collect(); .collect();
assert!(lsign_args assert!(
.join(" ") lsign_args
.contains("pacman-key --lsign-key ABCDEF123456")); .join(" ")
.contains("pacman-key --lsign-key ABCDEF123456")
);
} }
#[test] #[test]
@@ -493,9 +497,11 @@ mod tests {
.get_args() .get_args()
.map(|a| a.to_string_lossy().into_owned()) .map(|a| a.to_string_lossy().into_owned())
.collect(); .collect();
assert!(install_args assert!(
.join(" ") install_args
.contains("pacman -Sy gnupg --needed --noconfirm")); .join(" ")
.contains("pacman -Sy gnupg --needed --noconfirm")
);
} }
#[test] #[test]
+1 -4
View File
@@ -81,10 +81,7 @@ lazy_static::lazy_static! {
}; };
} }
pub async fn resolve( pub async fn resolve(packages: &[String], target_pm: &str) -> Vec<String> {
packages: &[String],
target_pm: &str,
) -> Vec<String> {
let mut result = Vec::new(); let mut result = Vec::new();
for pkg in packages { for pkg in packages {
if let Some(mappings) = MAPPING.get(pkg.as_str()) { if let Some(mappings) = MAPPING.get(pkg.as_str()) {
+1 -1
View File
@@ -5,7 +5,7 @@ use std::time::Duration;
use tokio::sync::mpsc; use tokio::sync::mpsc;
pub use super::cgroups::CgroupManager; pub use super::cgroups::CgroupManager;
pub use crate::error::{ExecutionError, EXITCODE_EXECUTION_ERROR, EXITCODE_IO_ERROR}; pub use crate::error::{EXITCODE_EXECUTION_ERROR, EXITCODE_IO_ERROR, ExecutionError};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceUsage { pub struct ResourceUsage {
+1 -3
View File
@@ -34,9 +34,7 @@ pub trait UserPackageManager {
} }
pub fn all_managers() -> Vec<Box<dyn UserPackageManager>> { pub fn all_managers() -> Vec<Box<dyn UserPackageManager>> {
vec![ vec![Box::new(custom::Custom)]
Box::new(custom::Custom),
]
} }
pub fn select(manager: &str) -> Option<Box<dyn UserPackageManager>> { pub fn select(manager: &str) -> Option<Box<dyn UserPackageManager>> {
+2 -2
View File
@@ -1,6 +1,6 @@
use std::path::PathBuf;
use async_trait::async_trait; use async_trait::async_trait;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::Engine; use crate::Engine;
use crate::engine::types::DeltaExecutionContext; use crate::engine::types::DeltaExecutionContext;
@@ -55,7 +55,7 @@ impl UserPackageManager for Custom {
log::error!("Custom command {} failed: {:?}", index, e); log::error!("Custom command {} failed: {:?}", index, e);
return Err(DependencyError::CustomFailed(e)); return Err(DependencyError::CustomFailed(e));
} }
}; }
Ok(()) Ok(())
} }
} }
+1 -1
View File
@@ -14,7 +14,7 @@ pub const EXITCODE_PRIV_DROP_FAILED: i32 = 201;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum BakeError { pub enum BakeError {
#[error("Failed to generate staged script {script}: {reason}")] #[error("Failed to generate staged script {script}: {reason}")]
ScriptGenerationFailed{script: String, reason: String}, ScriptGenerationFailed { script: String, reason: String },
#[error("Template error: {0}")] #[error("Template error: {0}")]
TemplateError(#[from] minijinja::Error), TemplateError(#[from] minijinja::Error),
+5 -6
View File
@@ -5,15 +5,15 @@ pub mod plugin;
pub mod stage; pub mod stage;
pub mod template; pub mod template;
use crate::cli::Cli;
use crate::constant::FINALIZE_STANDALONE_PLUGIN_PATH; use crate::constant::FINALIZE_STANDALONE_PLUGIN_PATH;
use crate::constant::STANDALONE_STATUS_DIR; use crate::constant::STANDALONE_STATUS_DIR;
use std::path::PathBuf;
use crate::finalize::plugin::fetch::{self, FetchArgument};
use crate::cli::Cli;
use crate::engine::EventSender; use crate::engine::EventSender;
use crate::types::buildstatus::{read_build_status, BuildStatus, StageResult}; use crate::finalize::plugin::fetch::{self, FetchArgument};
use crate::types::buildstatus::{BuildStatus, StageResult, read_build_status};
use crate::types::buildstatus::{StageInfo, StagePhase}; use crate::types::buildstatus::{StageInfo, StagePhase};
use chrono::Utc; use chrono::Utc;
use std::path::PathBuf;
// use crate::finalize::error::PluginError; // use crate::finalize::error::PluginError;
pub use config::*; pub use config::*;
pub use error::FinalizeError; pub use error::FinalizeError;
@@ -77,8 +77,7 @@ pub async fn finalize(
.await; .await;
// Read build status from prior stages // Read build status from prior stages
let build_status = read_build_status(STANDALONE_STATUS_DIR) let build_status = read_build_status(STANDALONE_STATUS_DIR).unwrap_or_default();
.unwrap_or_default();
let final_result = build_status.final_result(); let final_result = build_status.final_result();
// Determine if we should continue with artifacts/latehook // Determine if we should continue with artifacts/latehook
+10 -6
View File
@@ -406,9 +406,11 @@ plugin:
let result = config.validate(); let result = config.validate();
assert!(result.is_err()); assert!(result.is_err());
let errors = result.unwrap_err(); let errors = result.unwrap_err();
assert!(errors assert!(
.iter() errors
.any(|e| e.contains("show_file_list") && e.contains("no URL"))); .iter()
.any(|e| e.contains("show_file_list") && e.contains("no URL"))
);
} }
#[test] #[test]
@@ -444,8 +446,10 @@ plugin:
let result = config.validate(); let result = config.validate();
assert!(result.is_err()); assert!(result.is_err());
let errors = result.unwrap_err(); let errors = result.unwrap_err();
assert!(errors assert!(
.iter() errors
.any(|e| e.contains("show_file_list") && e.contains("2 URL definitions"))); .iter()
.any(|e| e.contains("show_file_list") && e.contains("2 URL definitions"))
);
} }
} }
+8 -8
View File
@@ -1,9 +1,12 @@
use std::path::PathBuf; use std::path::PathBuf;
use thiserror::Error; use thiserror::Error;
use crate::{ExecutionError, error::{ use crate::{
EXITCODE_EXECUTION_ERROR, EXITCODE_GENERAL_ERROR, EXITCODE_IO_ERROR, EXITCODE_PARSE_ERROR, ExecutionError,
}}; error::{
EXITCODE_EXECUTION_ERROR, EXITCODE_GENERAL_ERROR, EXITCODE_IO_ERROR, EXITCODE_PARSE_ERROR,
},
};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum FinalizeError { pub enum FinalizeError {
@@ -60,16 +63,13 @@ pub enum PluginError {
GeneralError(String), GeneralError(String),
#[error("Plugin {name} is not available: {reason}")] #[error("Plugin {name} is not available: {reason}")]
PluginUnavailable{ PluginUnavailable { name: String, reason: String },
name: String,
reason: String,
},
#[error("Plugin execution failed: {0}")] #[error("Plugin execution failed: {0}")]
ExecutionError(#[from] ExecutionError), ExecutionError(#[from] ExecutionError),
#[error("Failed to fetch plugin({name}:{url}): {reason}")] #[error("Failed to fetch plugin({name}:{url}): {reason}")]
FetchError{ FetchError {
name: String, name: String,
url: String, url: String,
reason: String, reason: String,
+22 -8
View File
@@ -10,25 +10,39 @@ pub async fn event_receiver(
ExecutionEvent::TaskStarted { task_id, timestamp } => { ExecutionEvent::TaskStarted { task_id, timestamp } => {
log::debug!( log::debug!(
"[{}] [{}] [{}] Task started: {}", "[{}] [{}] [{}] Task started: {}",
pipeline_name, build_id, timestamp, task_id pipeline_name,
build_id,
timestamp,
task_id
); );
} }
ExecutionEvent::OutputChunk { task_id, stream, data } => { ExecutionEvent::OutputChunk {
match stream { task_id,
StreamType::Stdout => print!("[{}] [{}] [stdout] {}", pipeline_name, task_id, data), stream,
StreamType::Stderr => eprint!("[{}] [{}] [stderr] {}", pipeline_name, task_id, data), data,
} => match stream {
StreamType::Stdout => print!("[{}] [{}] [stdout] {}", pipeline_name, task_id, data),
StreamType::Stderr => {
eprint!("[{}] [{}] [stderr] {}", pipeline_name, task_id, data)
} }
} },
ExecutionEvent::TaskCompleted { task_id, result } => { ExecutionEvent::TaskCompleted { task_id, result } => {
log::debug!( log::debug!(
"[{}] [{}] Task completed: {} (exit_code={}, duration={:?})", "[{}] [{}] Task completed: {} (exit_code={}, duration={:?})",
pipeline_name, build_id, task_id, result.exit_code, result.duration pipeline_name,
build_id,
task_id,
result.exit_code,
result.duration
); );
} }
ExecutionEvent::TaskFailed { task_id, error } => { ExecutionEvent::TaskFailed { task_id, error } => {
log::error!( log::error!(
"[{}] [{}] Task failed: {} (error={})", "[{}] [{}] Task failed: {} (error={})",
pipeline_name, build_id, task_id, error pipeline_name,
build_id,
task_id,
error
); );
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::finalize::plugin::PluginMetadata;
use crate::finalize::plugin::PluginError; use crate::finalize::plugin::PluginError;
use crate::finalize::plugin::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn}; use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
use async_trait::async_trait; use async_trait::async_trait;
+8 -7
View File
@@ -1,10 +1,10 @@
// Code in this module PARTIALLY OR FULLY utilized AI Coding Agent. // Code in this module PARTIALLY OR FULLY utilized AI Coding Agent.
use std::collections::HashSet;
use crate::finalize::fetch; use crate::finalize::fetch;
use crate::finalize::{ use crate::finalize::{
PluginConfig, PluginConfig,
plugin::{PluginError, RawPluginMap}, plugin::{PluginError, RawPluginMap},
}; };
use std::collections::HashSet;
use std::path::Path; use std::path::Path;
mod checksum; mod checksum;
@@ -93,12 +93,13 @@ pub async fn fetch_plugins(
_ => {} _ => {}
} }
log::debug!("Downloading plugin {}", name); log::debug!("Downloading plugin {}", name);
let (source, config) = data.iter_mut().next().ok_or_else(|| { let (source, config) =
PluginError::PluginUnavailable { data.iter_mut()
name: name.clone(), .next()
reason: "no source configured".to_string(), .ok_or_else(|| PluginError::PluginUnavailable {
} name: name.clone(),
})?; reason: "no source configured".to_string(),
})?;
let fetch_argument = FetchArgument { let fetch_argument = FetchArgument {
checksum: config.checksum.clone(), checksum: config.checksum.clone(),
shallow: config.shallow, shallow: config.shallow,
@@ -1,6 +1,6 @@
// Code in this module PARTIALLY OR FULLY utilized AI Coding Agent. // Code in this module PARTIALLY OR FULLY utilized AI Coding Agent.
use std::path::PathBuf;
use crate::finalize::plugin::PluginError; use crate::finalize::plugin::PluginError;
use std::path::PathBuf;
use tokio::task; use tokio::task;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@@ -12,14 +12,18 @@ pub enum CompressionType {
impl CompressionType { impl CompressionType {
pub fn from_path(path: &std::path::Path) -> Result<Self, String> { pub fn from_path(path: &std::path::Path) -> Result<Self, String> {
let name = path.file_name() let name = path
.file_name()
.and_then(|n| n.to_str()) .and_then(|n| n.to_str())
.unwrap_or("") .unwrap_or("")
.to_lowercase(); .to_lowercase();
if name.ends_with(".tar.gz") || name.ends_with(".tgz") { if name.ends_with(".tar.gz") || name.ends_with(".tgz") {
Ok(CompressionType::Gzip) Ok(CompressionType::Gzip)
} else if name.ends_with(".tar.zst") || name.ends_with(".tar.zstd") || name.ends_with(".tzst") { } else if name.ends_with(".tar.zst")
|| name.ends_with(".tar.zstd")
|| name.ends_with(".tzst")
{
Ok(CompressionType::Zstd) Ok(CompressionType::Zstd)
} else if name.ends_with(".tar") { } else if name.ends_with(".tar") {
Ok(CompressionType::None) Ok(CompressionType::None)
@@ -38,51 +42,50 @@ pub async fn extract_tar(
compression: CompressionType, compression: CompressionType,
) -> Result<(), PluginError> { ) -> Result<(), PluginError> {
if destdir.exists() { if destdir.exists() {
tokio::fs::remove_dir_all(destdir).await.map_err(|e| tokio::fs::remove_dir_all(destdir).await.map_err(|e| {
PluginError::GeneralError(format!("Failed to remove existing directory: {}", e)) PluginError::GeneralError(format!("Failed to remove existing directory: {}", e))
)?; })?;
} }
tokio::fs::create_dir_all(destdir).await.map_err(|e| tokio::fs::create_dir_all(destdir)
PluginError::GeneralError(format!("Failed to create directory: {}", e)) .await
)?; .map_err(|e| PluginError::GeneralError(format!("Failed to create directory: {}", e)))?;
let archive = archive.clone(); let archive = archive.clone();
let destdir = destdir.clone(); let destdir = destdir.clone();
task::spawn_blocking(move || { task::spawn_blocking(move || {
let file = std::fs::File::open(&archive).map_err(|e| let file = std::fs::File::open(&archive)
PluginError::GeneralError(format!("Failed to open archive: {}", e)) .map_err(|e| PluginError::GeneralError(format!("Failed to open archive: {}", e)))?;
)?;
match compression { match compression {
CompressionType::Gzip => { CompressionType::Gzip => {
let decoder = flate2::read::GzDecoder::new(file); let decoder = flate2::read::GzDecoder::new(file);
let mut archive = tar::Archive::new(decoder); let mut archive = tar::Archive::new(decoder);
archive.unpack(&destdir).map_err(|e| archive.unpack(&destdir).map_err(|e| {
PluginError::GeneralError(format!("Failed to unpack archive: {}", e)) PluginError::GeneralError(format!("Failed to unpack archive: {}", e))
)?; })?;
} }
CompressionType::Zstd => { CompressionType::Zstd => {
let decoder = zstd::stream::read::Decoder::new(file).map_err(|e| let decoder = zstd::stream::read::Decoder::new(file).map_err(|e| {
PluginError::GeneralError(format!("Failed to create zstd decoder: {}", e)) PluginError::GeneralError(format!("Failed to create zstd decoder: {}", e))
)?; })?;
let mut archive = tar::Archive::new(decoder); let mut archive = tar::Archive::new(decoder);
archive.unpack(&destdir).map_err(|e| archive.unpack(&destdir).map_err(|e| {
PluginError::GeneralError(format!("Failed to unpack archive: {}", e)) PluginError::GeneralError(format!("Failed to unpack archive: {}", e))
)?; })?;
} }
CompressionType::None => { CompressionType::None => {
let mut archive = tar::Archive::new(file); let mut archive = tar::Archive::new(file);
archive.unpack(&destdir).map_err(|e| archive.unpack(&destdir).map_err(|e| {
PluginError::GeneralError(format!("Failed to unpack archive: {}", e)) PluginError::GeneralError(format!("Failed to unpack archive: {}", e))
)?; })?;
} }
}; };
Ok::<(), PluginError>(()) Ok::<(), PluginError>(())
}) })
.await .await
.map_err(|e| PluginError::GeneralError(format!("Task join error: {}", e)))??; .map_err(|e| PluginError::GeneralError(format!("Task join error: {}", e)))??;
Ok(()) Ok(())
} }
+10 -18
View File
@@ -1,7 +1,7 @@
// Code in this module PARTIALLY OR FULLY utilized AI Coding Agent. // Code in this module PARTIALLY OR FULLY utilized AI Coding Agent.
use crate::finalize::plugin::PluginError;
use std::path::Path; use std::path::Path;
use tokio::process::Command; use tokio::process::Command;
use crate::finalize::plugin::PluginError;
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum GitVersion { pub enum GitVersion {
@@ -106,24 +106,16 @@ pub async fn fetch_git_plugin(
GitVersion::Mixed(tag, commit) => { GitVersion::Mixed(tag, commit) => {
log::debug!("Fetching git plugin: {}@{}/{}", name, tag, commit); log::debug!("Fetching git plugin: {}@{}/{}", name, tag, commit);
let shallow_flag = shallow.unwrap_or(false); let shallow_flag = shallow.unwrap_or(false);
let result = git_clone_and_checkout( let result =
&baseurl, git_clone_and_checkout(&baseurl, &tag, &basedir.join(name), name, shallow_flag)
&tag, .await;
&basedir.join(name),
name,
shallow_flag,
)
.await;
if result.is_err() { if result.is_err() {
log::warn!("Failed to clone with tag, trying commit hash. URL: {}", path); log::warn!(
return git_clone_and_checkout( "Failed to clone with tag, trying commit hash. URL: {}",
&baseurl, path
&commit, );
&basedir.join(name), return git_clone_and_checkout(&baseurl, &commit, &basedir.join(name), name, false)
name, .await;
false,
)
.await;
} }
result result
} }
@@ -1,14 +1,10 @@
// Code in this module PARTIALLY OR FULLY utilized AI Coding Agent. // Code in this module PARTIALLY OR FULLY utilized AI Coding Agent.
use std::path::Path;
use crate::finalize::plugin::PluginError;
use super::checksum::{detect_checksum_type, verify_checksum}; use super::checksum::{detect_checksum_type, verify_checksum};
use super::extract::{extract_tar, CompressionType}; use super::extract::{CompressionType, extract_tar};
use crate::finalize::plugin::PluginError;
use std::path::Path;
pub async fn download_to_file( pub async fn download_to_file(url: &str, dest: &Path, name: &str) -> Result<(), PluginError> {
url: &str,
dest: &Path,
name: &str,
) -> Result<(), PluginError> {
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(300)) .timeout(std::time::Duration::from_secs(300))
.build() .build()
@@ -33,28 +29,42 @@ pub async fn download_to_file(
}); });
} }
let bytes = response.bytes().await.map_err(|e| PluginError::FetchError { let bytes = response
name: name.to_string(), .bytes()
url: url.to_string(), .await
reason: format!("Failed to read response body: {}", e), .map_err(|e| PluginError::FetchError {
})?; name: name.to_string(),
url: url.to_string(),
reason: format!("Failed to read response body: {}", e),
})?;
tokio::fs::write(dest, &bytes).await.map_err(|e| PluginError::FetchError { tokio::fs::write(dest, &bytes)
name: name.to_string(), .await
url: url.to_string(), .map_err(|e| PluginError::FetchError {
reason: format!("Failed to write file: {}", e), name: name.to_string(),
})?; url: url.to_string(),
reason: format!("Failed to write file: {}", e),
})?;
Ok(()) Ok(())
} }
fn extract_archive_extension(url: &str) -> &str { fn extract_archive_extension(url: &str) -> &str {
let path_part = url.split('?').next().unwrap_or(url).split('#').next().unwrap_or(url); let path_part = url
.split('?')
.next()
.unwrap_or(url)
.split('#')
.next()
.unwrap_or(url);
let file_part = path_part.rsplit('/').next().unwrap_or(""); let file_part = path_part.rsplit('/').next().unwrap_or("");
let lower = file_part.to_lowercase(); let lower = file_part.to_lowercase();
if lower.ends_with(".tar.gz") || lower.ends_with(".tgz") { if lower.ends_with(".tar.gz") || lower.ends_with(".tgz") {
".tar.gz" ".tar.gz"
} else if lower.ends_with(".tar.zst") || lower.ends_with(".tar.zstd") || lower.ends_with(".tzst") { } else if lower.ends_with(".tar.zst")
|| lower.ends_with(".tar.zstd")
|| lower.ends_with(".tzst")
{
".tar.zst" ".tar.zst"
} else if lower.ends_with(".tar") { } else if lower.ends_with(".tar") {
".tar" ".tar"
@@ -82,19 +92,17 @@ pub async fn fetch_http_plugin(
PluginError::GeneralError(format!("Failed to read downloaded file: {}", e)) PluginError::GeneralError(format!("Failed to read downloaded file: {}", e))
})?; })?;
let checksum_type = detect_checksum_type(checksum) let checksum_type =
.map_err(|e| PluginError::GeneralError(e))?; detect_checksum_type(checksum).map_err(|e| PluginError::GeneralError(e))?;
if let Some(ct) = checksum_type { if let Some(ct) = checksum_type {
verify_checksum(&bytes, checksum, ct).map_err(|e| { verify_checksum(&bytes, checksum, ct).map_err(|e| PluginError::GeneralError(e))?;
PluginError::GeneralError(e)
})?;
} }
} }
} }
let compression = CompressionType::from_path(&temp_file) let compression =
.map_err(|e| PluginError::GeneralError(e))?; CompressionType::from_path(&temp_file).map_err(|e| PluginError::GeneralError(e))?;
let destdir = basedir.join(name); let destdir = basedir.join(name);
extract_tar(&temp_file, &destdir, compression).await?; extract_tar(&temp_file, &destdir, compression).await?;
@@ -1,9 +1,9 @@
use crate::finalize::plugin::AsyncPluginFn; use crate::finalize::plugin::AsyncPluginFn;
pub mod dummy;
mod insitenotify; mod insitenotify;
mod mail; mod mail;
mod satori; mod satori;
mod webhook; mod webhook;
pub mod dummy;
pub struct InternalPlugin { pub struct InternalPlugin {
pub name: &'static str, pub name: &'static str,
pub entry: Box<dyn AsyncPluginFn>, pub entry: Box<dyn AsyncPluginFn>,
@@ -1,5 +1,8 @@
use crate::finalize::plugin::PluginMetadata; use crate::finalize::plugin::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::{error::PluginError, plugin::AsyncPluginFn}}; use crate::{
EventSender, ExecutionContext,
finalize::{error::PluginError, plugin::AsyncPluginFn},
};
use async_trait::async_trait; use async_trait::async_trait;
pub struct InsiteNotify; pub struct InsiteNotify;
@@ -1,11 +1,11 @@
// Code in this module PARTIALLY OR FULLY utilized AI Coding Agent. // Code in this module PARTIALLY OR FULLY utilized AI Coding Agent.
use crate::finalize::plugin::PluginMetadata;
use crate::finalize::plugin::PluginError; use crate::finalize::plugin::PluginError;
use crate::finalize::plugin::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn}; use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
use async_trait::async_trait; use async_trait::async_trait;
use std::time::Duration; use lettre::message::{MultiPart, SinglePart, header};
use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor}; use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
use lettre::message::{header, MultiPart, SinglePart}; use std::time::Duration;
use tokio::time::timeout; use tokio::time::timeout;
mod config; mod config;
@@ -29,20 +29,29 @@ impl AsyncPluginFn for Mail {
.map_err(|e| PluginError::GeneralError(format!("Invalid mail config: {}", e)))?; .map_err(|e| PluginError::GeneralError(format!("Invalid mail config: {}", e)))?;
if let Err(errors) = config.validate() { if let Err(errors) = config.validate() {
return Err(PluginError::GeneralError( return Err(PluginError::GeneralError(format!(
format!("Mail config validation failed: {:?}", errors) "Mail config validation failed: {:?}",
)); errors
)));
} }
let template_data = prepare_template_data(ctx); let template_data = prepare_template_data(ctx);
let (subject_template, body_template_html, body_template_text) = match config.schema { let (subject_template, body_template_html, body_template_text) = match config.schema {
Schema::Default => { Schema::Default => {
let subject = config.subject.as_deref() let subject = config
.subject
.as_deref()
.unwrap_or(DefaultTemplates::SUBJECT); .unwrap_or(DefaultTemplates::SUBJECT);
let body_html = config.body.as_deref() let body_html = config
.body
.as_deref()
.unwrap_or(DefaultTemplates::BODY_HTML); .unwrap_or(DefaultTemplates::BODY_HTML);
(subject.to_string(), body_html.to_string(), DefaultTemplates::BODY_TEXT.to_string()) (
subject.to_string(),
body_html.to_string(),
DefaultTemplates::BODY_TEXT.to_string(),
)
} }
Schema::Custom => { Schema::Custom => {
let subject = config.subject.clone().unwrap(); let subject = config.subject.clone().unwrap();
@@ -55,8 +64,9 @@ impl AsyncPluginFn for Mail {
.map_err(|e| PluginError::GeneralError(format!("Failed to render subject: {}", e)))?; .map_err(|e| PluginError::GeneralError(format!("Failed to render subject: {}", e)))?;
let body_html = render_template(&body_template_html, &template_data) let body_html = render_template(&body_template_html, &template_data)
.map_err(|e| PluginError::GeneralError(format!("Failed to render body_html: {}", e)))?; .map_err(|e| PluginError::GeneralError(format!("Failed to render body_html: {}", e)))?;
let body_text = render_template(&body_template_text, &template_data) let body_text = render_template(&body_template_text, &template_data).map_err(|e| {
.map_err(|e| PluginError::GeneralError(format!("Failed to render body_text(?): {}", e)))?; PluginError::GeneralError(format!("Failed to render body_text(?): {}", e))
})?;
let email = build_email(&config, subject, body_html, body_text)?; let email = build_email(&config, subject, body_html, body_text)?;
@@ -71,43 +81,48 @@ fn build_email(
body_text: String, body_text: String,
) -> Result<Message, PluginError> { ) -> Result<Message, PluginError> {
let from_str = config.from.to_string(); let from_str = config.from.to_string();
let from: lettre::message::Mailbox = from_str.parse() let from: lettre::message::Mailbox = from_str.parse().map_err(|e| {
.map_err(|e| PluginError::GeneralError(format!("Invalid from address '{}': {}", from_str, e)))?; PluginError::GeneralError(format!("Invalid from address '{}': {}", from_str, e))
})?;
let to_addrs: Vec<String> = config.to.to_vec(); let to_addrs: Vec<String> = config.to.to_vec();
if to_addrs.is_empty() { if to_addrs.is_empty() {
return Err(PluginError::GeneralError("No recipients specified".to_string())); return Err(PluginError::GeneralError(
"No recipients specified".to_string(),
));
} }
let mut message_builder = Message::builder() let mut message_builder = Message::builder().from(from).subject(subject);
.from(from)
.subject(subject);
for addr in &to_addrs { for addr in &to_addrs {
let mailbox: lettre::message::Mailbox = addr.parse() let mailbox: lettre::message::Mailbox = addr.parse().map_err(|e| {
.map_err(|e| PluginError::GeneralError(format!("Invalid to address '{}': {}", addr, e)))?; PluginError::GeneralError(format!("Invalid to address '{}': {}", addr, e))
})?;
message_builder = message_builder.to(mailbox); message_builder = message_builder.to(mailbox);
} }
if let Some(cc) = &config.cc { if let Some(cc) = &config.cc {
for addr in cc.to_vec() { for addr in cc.to_vec() {
let mailbox: lettre::message::Mailbox = addr.parse() let mailbox: lettre::message::Mailbox = addr.parse().map_err(|e| {
.map_err(|e| PluginError::GeneralError(format!("Invalid cc address '{}': {}", addr, e)))?; PluginError::GeneralError(format!("Invalid cc address '{}': {}", addr, e))
})?;
message_builder = message_builder.cc(mailbox); message_builder = message_builder.cc(mailbox);
} }
} }
if let Some(bcc) = &config.bcc { if let Some(bcc) = &config.bcc {
for addr in bcc.to_vec() { for addr in bcc.to_vec() {
let mailbox: lettre::message::Mailbox = addr.parse() let mailbox: lettre::message::Mailbox = addr.parse().map_err(|e| {
.map_err(|e| PluginError::GeneralError(format!("Invalid bcc address '{}': {}", addr, e)))?; PluginError::GeneralError(format!("Invalid bcc address '{}': {}", addr, e))
})?;
message_builder = message_builder.bcc(mailbox); message_builder = message_builder.bcc(mailbox);
} }
} }
if let Some(reply_to) = &config.reply_to { if let Some(reply_to) = &config.reply_to {
let mailbox: lettre::message::Mailbox = reply_to.parse() let mailbox: lettre::message::Mailbox = reply_to.parse().map_err(|e| {
.map_err(|e| PluginError::GeneralError(format!("Invalid reply-to address '{}': {}", reply_to, e)))?; PluginError::GeneralError(format!("Invalid reply-to address '{}': {}", reply_to, e))
})?;
message_builder = message_builder.reply_to(mailbox); message_builder = message_builder.reply_to(mailbox);
} }
@@ -117,23 +132,20 @@ fn build_email(
.singlepart( .singlepart(
SinglePart::builder() SinglePart::builder()
.header(header::ContentType::TEXT_PLAIN) .header(header::ContentType::TEXT_PLAIN)
.body(body_text) .body(body_text),
) )
.singlepart( .singlepart(
SinglePart::builder() SinglePart::builder()
.header(header::ContentType::TEXT_HTML) .header(header::ContentType::TEXT_HTML)
.body(body_html) .body(body_html),
) ),
) )
.map_err(|e| PluginError::GeneralError(format!("Failed to build email: {}", e)))?; .map_err(|e| PluginError::GeneralError(format!("Failed to build email: {}", e)))?;
Ok(message) Ok(message)
} }
async fn send_email( async fn send_email(config: &MailConfig, email: Message) -> Result<(), PluginError> {
config: &MailConfig,
email: Message,
) -> Result<(), PluginError> {
let connect_timeout = if config.smtp.connect_timeout_ms == 0 { let connect_timeout = if config.smtp.connect_timeout_ms == 0 {
None None
} else { } else {
@@ -162,10 +174,13 @@ async fn send_email(
match config.smtp.auth.auth_type { match config.smtp.auth.auth_type {
AuthType::Password => { AuthType::Password => {
log::debug!("Using password auth"); log::debug!("Using password auth");
transport_builder = transport_builder.credentials(( transport_builder = transport_builder.credentials(
config.smtp.auth.username.clone(), (
config.smtp.auth.password.clone() config.smtp.auth.username.clone(),
).into()); config.smtp.auth.password.clone(),
)
.into(),
);
} }
AuthType::None => { AuthType::None => {
log::debug!("Using no auth"); log::debug!("Using no auth");
@@ -1,5 +1,5 @@
use crate::finalize::plugin::PluginMetadata;
use crate::finalize::plugin::PluginError; use crate::finalize::plugin::PluginError;
use crate::finalize::plugin::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn}; use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
use async_trait::async_trait; use async_trait::async_trait;
@@ -1,5 +1,5 @@
use crate::finalize::plugin::PluginMetadata;
use crate::finalize::plugin::PluginError; use crate::finalize::plugin::PluginError;
use crate::finalize::plugin::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn}; use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
use async_trait::async_trait; use async_trait::async_trait;
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::finalize::plugin::PluginMetadata;
use crate::finalize::plugin::PluginError; use crate::finalize::plugin::PluginError;
use crate::finalize::plugin::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn}; use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
use async_trait::async_trait; use async_trait::async_trait;
+107 -38
View File
@@ -1,5 +1,5 @@
use crate::constant::FINALIZE_SHELL_ENVPREFIX;
use crate::Engine; use crate::Engine;
use crate::constant::FINALIZE_SHELL_ENVPREFIX;
use crate::finalize::plugin::PluginError; use crate::finalize::plugin::PluginError;
use crate::finalize::plugin::PluginMetadata; use crate::finalize::plugin::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn}; use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
@@ -8,7 +8,6 @@ use std::collections::HashMap;
pub struct Shell; pub struct Shell;
/// Parses a YAML value used as a mapping key into a string. /// Parses a YAML value used as a mapping key into a string.
/// ///
/// Since YAML keys can be strings, numbers, or booleans, this function /// Since YAML keys can be strings, numbers, or booleans, this function
@@ -72,7 +71,11 @@ fn parse_key(key: serde_yaml::Value) -> String {
/// let result = convert_argument("PLUGIN", yaml, false); /// let result = convert_argument("PLUGIN", yaml, false);
/// // Yields: {"PLUGIN_HOST" => "\"docker.io\"", "PLUGIN_USER" => "\"admin\""} /// // Yields: {"PLUGIN_HOST" => "\"docker.io\"", "PLUGIN_USER" => "\"admin\""}
/// ``` /// ```
fn convert_argument(prefix: &str, argument: serde_yaml::Value, in_sequence: bool) -> HashMap<String, String> { fn convert_argument(
prefix: &str,
argument: serde_yaml::Value,
in_sequence: bool,
) -> HashMap<String, String> {
let mut envvars: HashMap<String, String> = HashMap::new(); let mut envvars: HashMap<String, String> = HashMap::new();
match argument { match argument {
serde_yaml::Value::Mapping(mapping) => { serde_yaml::Value::Mapping(mapping) => {
@@ -95,14 +98,17 @@ fn convert_argument(prefix: &str, argument: serde_yaml::Value, in_sequence: bool
} }
serde_yaml::Value::Number(value) => { serde_yaml::Value::Number(value) => {
envvars.insert(prefix.to_string(), value.to_string()); envvars.insert(prefix.to_string(), value.to_string());
}, }
serde_yaml::Value::Tagged(tagged_value) => { serde_yaml::Value::Tagged(tagged_value) => {
let subenvvars = convert_argument(prefix, tagged_value.value, in_sequence); let subenvvars = convert_argument(prefix, tagged_value.value, in_sequence);
envvars.extend(subenvvars); envvars.extend(subenvvars);
} }
serde_yaml::Value::Sequence(seq) => { serde_yaml::Value::Sequence(seq) => {
if in_sequence { if in_sequence {
log::warn!("Nested sequence in sequence is not supported, skipping: {}", prefix); log::warn!(
"Nested sequence in sequence is not supported, skipping: {}",
prefix
);
return envvars; return envvars;
} }
let mut array = String::new(); let mut array = String::new();
@@ -117,16 +123,19 @@ fn convert_argument(prefix: &str, argument: serde_yaml::Value, in_sequence: bool
let subenvvars = convert_argument(new_prefix.as_str(), item.clone(), true); let subenvvars = convert_argument(new_prefix.as_str(), item.clone(), true);
if item.is_mapping() { if item.is_mapping() {
has_mapping = true; has_mapping = true;
} } else {
else { array.push_str(
array.push_str(subenvvars.get(new_prefix.as_str()).map(|v| v.as_str()).unwrap_or_default()); subenvvars
.get(new_prefix.as_str())
.map(|v| v.as_str())
.unwrap_or_default(),
);
} }
_envvars.extend(subenvvars); _envvars.extend(subenvvars);
} }
if has_mapping { if has_mapping {
envvars.extend(_envvars); envvars.extend(_envvars);
} } else {
else {
array.push_str(")"); array.push_str(")");
envvars.insert(prefix.to_string(), array); envvars.insert(prefix.to_string(), array);
} }
@@ -148,7 +157,13 @@ impl AsyncPluginFn for Shell {
let envvars = convert_argument(FINALIZE_SHELL_ENVPREFIX, argument, false); let envvars = convert_argument(FINALIZE_SHELL_ENVPREFIX, argument, false);
let mut local_ctx = ctx.clone(); let mut local_ctx = ctx.clone();
local_ctx.env_vars.extend(envvars); local_ctx.env_vars.extend(envvars);
engine.execute_script(&metadata.fspath.join(&metadata.entrypoint), &local_ctx, event_tx).await?; engine
.execute_script(
&metadata.fspath.join(&metadata.entrypoint),
&local_ctx,
event_tx,
)
.await?;
Ok(()) Ok(())
} }
} }
@@ -205,27 +220,36 @@ mod tests {
#[test] #[test]
fn test_convert_argument_nested_mapping() { fn test_convert_argument_nested_mapping() {
let yaml = serde_yaml::from_str(" let yaml = serde_yaml::from_str(
"
from: from:
host: smtp.example.com host: smtp.example.com
port: 587 port: 587
").unwrap(); ",
)
.unwrap();
let result = convert_argument("CFG", yaml, false); let result = convert_argument("CFG", yaml, false);
// Nested keys use uppercase format: CFG_FROM_HOST // Nested keys use uppercase format: CFG_FROM_HOST
assert_eq!(result.get("CFG_FROM_HOST"), Some(&"smtp.example.com".to_string())); assert_eq!(
result.get("CFG_FROM_HOST"),
Some(&"smtp.example.com".to_string())
);
assert_eq!(result.get("CFG_FROM_PORT"), Some(&"587".to_string())); assert_eq!(result.get("CFG_FROM_PORT"), Some(&"587".to_string()));
} }
#[test] #[test]
fn test_convert_argument_different_types() { fn test_convert_argument_different_types() {
let yaml = serde_yaml::from_str(" let yaml = serde_yaml::from_str(
"
enabled: true enabled: true
count: 42 count: 42
ratio: 3.14 ratio: 3.14
name: test name: test
empty: ~ empty: ~
").unwrap(); ",
)
.unwrap();
let result = convert_argument("VAR", yaml, false); let result = convert_argument("VAR", yaml, false);
assert_eq!(result.get("VAR_ENABLED"), Some(&"true".to_string())); assert_eq!(result.get("VAR_ENABLED"), Some(&"true".to_string()));
@@ -240,18 +264,24 @@ empty: ~
let yaml = serde_yaml::from_str("tags:\n - latest\n - v1.0\n - stable").unwrap(); let yaml = serde_yaml::from_str("tags:\n - latest\n - v1.0\n - stable").unwrap();
let result = convert_argument("CFG", yaml, false); let result = convert_argument("CFG", yaml, false);
assert_eq!(result.get("CFG_TAGS"), Some(&"(latest v1.0 stable)".to_string())); assert_eq!(
result.get("CFG_TAGS"),
Some(&"(latest v1.0 stable)".to_string())
);
} }
#[test] #[test]
fn test_convert_argument_sequence_with_mapping() { fn test_convert_argument_sequence_with_mapping() {
let yaml = serde_yaml::from_str(" let yaml = serde_yaml::from_str(
"
env: env:
- name: KEY1 - name: KEY1
value: val1 value: val1
- name: KEY2 - name: KEY2
value: val2 value: val2
").unwrap(); ",
)
.unwrap();
let result = convert_argument("CFG", yaml, false); let result = convert_argument("CFG", yaml, false);
assert_eq!(result.get("CFG_ENV_0_NAME"), Some(&"KEY1".to_string())); assert_eq!(result.get("CFG_ENV_0_NAME"), Some(&"KEY1".to_string()));
@@ -263,13 +293,16 @@ env:
#[test] #[test]
fn test_convert_argument_nested_sequence() { fn test_convert_argument_nested_sequence() {
let yaml = serde_yaml::from_str(" let yaml = serde_yaml::from_str(
"
matrix: matrix:
- - a - - a
- b - b
- - c - - c
- d - d
").unwrap(); ",
)
.unwrap();
let result = convert_argument("CFG", yaml, false); let result = convert_argument("CFG", yaml, false);
// Nested sequences are not supported (line 50-52 logs warning and skips) // Nested sequences are not supported (line 50-52 logs warning and skips)
@@ -297,10 +330,13 @@ matrix:
#[test] #[test]
fn test_convert_argument_null_value() { fn test_convert_argument_null_value() {
let yaml = serde_yaml::from_str(" let yaml = serde_yaml::from_str(
"
host: docker.io host: docker.io
token: ~ token: ~
").unwrap(); ",
)
.unwrap();
let result = convert_argument("CFG", yaml, false); let result = convert_argument("CFG", yaml, false);
// Keys are uppercased: CFG_HOST // Keys are uppercased: CFG_HOST
@@ -311,10 +347,13 @@ token: ~
#[test] #[test]
fn test_convert_argument_number_keys() { fn test_convert_argument_number_keys() {
let yaml = serde_yaml::from_str(" let yaml = serde_yaml::from_str(
"
0: first 0: first
1: second 1: second
").unwrap(); ",
)
.unwrap();
let result = convert_argument("CFG", yaml, false); let result = convert_argument("CFG", yaml, false);
// Number keys are preserved but prefixed: CFG_0, CFG_1 // Number keys are preserved but prefixed: CFG_0, CFG_1
@@ -324,7 +363,8 @@ token: ~
#[test] #[test]
fn test_convert_argument_complex_nested() { fn test_convert_argument_complex_nested() {
let yaml = serde_yaml::from_str(" let yaml = serde_yaml::from_str(
"
plugin: plugin:
docker-push: docker-push:
host: docker.io host: docker.io
@@ -335,38 +375,67 @@ plugin:
tags: tags:
- latest - latest
- v1.0 - v1.0
").unwrap(); ",
)
.unwrap();
let result = convert_argument("CFG", yaml, false); let result = convert_argument("CFG", yaml, false);
// All keys uppercased for shell environment variables // All keys uppercased for shell environment variables
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_HOST"), Some(&"docker.io".to_string())); assert_eq!(
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_IMAGE"), Some(&"user/app:tag".to_string())); result.get("CFG_PLUGIN_DOCKER-PUSH_HOST"),
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_OPTIONS_FORCE"), Some(&"true".to_string())); Some(&"docker.io".to_string())
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_OPTIONS_RETRIES"), Some(&"3".to_string())); );
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_TAGS"), Some(&"(latest v1.0)".to_string())); assert_eq!(
result.get("CFG_PLUGIN_DOCKER-PUSH_IMAGE"),
Some(&"user/app:tag".to_string())
);
assert_eq!(
result.get("CFG_PLUGIN_DOCKER-PUSH_OPTIONS_FORCE"),
Some(&"true".to_string())
);
assert_eq!(
result.get("CFG_PLUGIN_DOCKER-PUSH_OPTIONS_RETRIES"),
Some(&"3".to_string())
);
assert_eq!(
result.get("CFG_PLUGIN_DOCKER-PUSH_TAGS"),
Some(&"(latest v1.0)".to_string())
);
} }
#[test] #[test]
fn test_convert_argument_special_chars_in_values() { fn test_convert_argument_special_chars_in_values() {
let yaml = serde_yaml::from_str(" let yaml = serde_yaml::from_str(
"
url: https://example.com/path?query=value url: https://example.com/path?query=value
path: /workspace/output.tar.gz path: /workspace/output.tar.gz
token: 'Bearer abc123' token: 'Bearer abc123'
").unwrap(); ",
)
.unwrap();
let result = convert_argument("CFG", yaml, false); let result = convert_argument("CFG", yaml, false);
// Keys uppercased: CFG_URL, CFG_PATH, CFG_TOKEN // Keys uppercased: CFG_URL, CFG_PATH, CFG_TOKEN
assert_eq!(result.get("CFG_URL"), Some(&"https://example.com/path?query=value".to_string())); assert_eq!(
assert_eq!(result.get("CFG_PATH"), Some(&"/workspace/output.tar.gz".to_string())); result.get("CFG_URL"),
Some(&"https://example.com/path?query=value".to_string())
);
assert_eq!(
result.get("CFG_PATH"),
Some(&"/workspace/output.tar.gz".to_string())
);
assert_eq!(result.get("CFG_TOKEN"), Some(&"Bearer abc123".to_string())); assert_eq!(result.get("CFG_TOKEN"), Some(&"Bearer abc123".to_string()));
} }
#[test] #[test]
fn test_convert_argument_quoted_strings_preserved() { fn test_convert_argument_quoted_strings_preserved() {
let yaml = serde_yaml::from_str(" let yaml = serde_yaml::from_str(
"
msg: 'Hello World' msg: 'Hello World'
cmd: \"echo 'test'\" cmd: \"echo 'test'\"
").unwrap(); ",
)
.unwrap();
let result = convert_argument("CFG", yaml, false); let result = convert_argument("CFG", yaml, false);
// Keys uppercased: CFG_MSG, CFG_CMD // Keys uppercased: CFG_MSG, CFG_CMD
+3 -1
View File
@@ -45,7 +45,9 @@ pub async fn hook(
hook_cmd.argument.clone(), hook_cmd.argument.clone(),
&plugin_ctx, &plugin_ctx,
&event_tx, &event_tx,
).await.map_err(|e| { )
.await
.map_err(|e| {
log::error!("Plugin hook {} failed: {:?}", index, e); log::error!("Plugin hook {} failed: {:?}", index, e);
ExecutionError::ExecutionFailed(e.to_string()) ExecutionError::ExecutionFailed(e.to_string())
})?; })?;
+3 -3
View File
@@ -1,17 +1,17 @@
// Library crate for workshop-executor // Library crate for workshop-executor
// Selective exports: only modules used by external callers or tests // Selective exports: only modules used by external callers or tests
pub mod bake;
pub mod constant;
pub mod daemon; pub mod daemon;
pub mod engine; pub mod engine;
pub mod error; pub mod error;
pub mod finalize; pub mod finalize;
pub mod monitor; pub mod monitor;
pub mod notify;
pub mod prebake; pub mod prebake;
pub mod bake;
pub mod socket; pub mod socket;
pub mod types; pub mod types;
pub mod constant;
pub mod notify;
// External crates re-exports // External crates re-exports
pub use config; pub use config;
+22 -15
View File
@@ -1,14 +1,13 @@
use std::path::Path; use std::path::Path;
use env_logger;
use workshop_baker::{ use workshop_baker::{
bake::bake,
cli::{Cli, Commands, Parser}, cli::{Cli, Commands, Parser},
daemon, daemon,
engine::{EventSender, ExecutionContext}, engine::{EventSender, ExecutionContext},
error::PrebakeError, error::PrebakeError,
prebake::prebake,
bake::bake,
finalize::finalize, finalize::finalize,
prebake::prebake,
}; };
#[tokio::main] #[tokio::main]
@@ -22,7 +21,7 @@ async fn main() {
Some(Commands::Monitor { group: _ }) => { Some(Commands::Monitor { group: _ }) => {
unimplemented!("executor-monitor should be moved to daemon instead.") unimplemented!("executor-monitor should be moved to daemon instead.")
} }
Some(Commands::Client { config:_ }) => { Some(Commands::Client { config: _ }) => {
unimplemented!("Client mode not yet implemented.") unimplemented!("Client mode not yet implemented.")
} }
Some(Commands::Prebake { config }) => { Some(Commands::Prebake { config }) => {
@@ -40,9 +39,11 @@ async fn main() {
let event_tx: EventSender = Some(tx); let event_tx: EventSender = Some(tx);
let pipeline_name = cli.pipeline.clone(); let pipeline_name = cli.pipeline.clone();
let build_id = cli.build_id.clone(); let build_id = cli.build_id.clone();
let receiver_handle = tokio::spawn( let receiver_handle = tokio::spawn(workshop_baker::prebake::event::event_receiver(
workshop_baker::prebake::event::event_receiver(event_rx, pipeline_name, build_id) event_rx,
); pipeline_name,
build_id,
));
let result = prebake(config, &cli, None, &mut ctx, event_tx).await; let result = prebake(config, &cli, None, &mut ctx, event_tx).await;
drop(ctx); drop(ctx);
let _ = receiver_handle.await; let _ = receiver_handle.await;
@@ -52,12 +53,14 @@ async fn main() {
std::process::exit( std::process::exit(
e.downcast_ref::<PrebakeError>() e.downcast_ref::<PrebakeError>()
.map(|pe| pe.exit_code()) .map(|pe| pe.exit_code())
.unwrap_or(1) .unwrap_or(1),
); );
} }
} }
Some(Commands::Bake { Some(Commands::Bake {
script, bake_base, prebake script,
bake_base,
prebake,
}) => { }) => {
log::info!("Running in standalone mode."); log::info!("Running in standalone mode.");
let mut ctx = ExecutionContext { let mut ctx = ExecutionContext {
@@ -73,9 +76,11 @@ async fn main() {
let event_tx: EventSender = Some(tx); let event_tx: EventSender = Some(tx);
let pipeline_name = cli.pipeline.clone(); let pipeline_name = cli.pipeline.clone();
let build_id = cli.build_id.clone(); let build_id = cli.build_id.clone();
let receiver_handle = tokio::spawn( let receiver_handle = tokio::spawn(workshop_baker::prebake::event::event_receiver(
workshop_baker::prebake::event::event_receiver(event_rx, pipeline_name, build_id) event_rx,
); pipeline_name,
build_id,
));
let result = bake(script, bake_base, prebake, &cli, &mut ctx, event_tx).await; let result = bake(script, bake_base, prebake, &cli, &mut ctx, event_tx).await;
drop(ctx); drop(ctx);
let _ = receiver_handle.await; let _ = receiver_handle.await;
@@ -100,9 +105,11 @@ async fn main() {
let event_tx: EventSender = Some(tx); let event_tx: EventSender = Some(tx);
let pipeline_name = cli.pipeline.clone(); let pipeline_name = cli.pipeline.clone();
let build_id = cli.build_id.clone(); let build_id = cli.build_id.clone();
let receiver_handle = tokio::spawn( let receiver_handle = tokio::spawn(workshop_baker::prebake::event::event_receiver(
workshop_baker::prebake::event::event_receiver(event_rx, pipeline_name, build_id) event_rx,
); pipeline_name,
build_id,
));
let result = finalize(Path::new(config), &cli, &mut ctx, event_tx).await; let result = finalize(Path::new(config), &cli, &mut ctx, event_tx).await;
drop(ctx); drop(ctx);
let _ = receiver_handle.await; let _ = receiver_handle.await;
+9 -2
View File
@@ -190,13 +190,20 @@ fn wait_time(group_period: u32, group_watermark: u32, count: u32) -> f32 {
fn notify_delay(event: &NotifyEvent) -> std::time::Duration { fn notify_delay(event: &NotifyEvent) -> std::time::Duration {
let exponent = (event.max_lives - event.lives) as i32; let exponent = (event.max_lives - event.lives) as i32;
if exponent < 0 { if exponent < 0 {
log::warn!("Unexpected lives in notification: lives({}) > max_lives({})", event.lives, event.max_lives); 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); return std::time::Duration::from_secs_f64(NOTIFY_TEMPORARY_MAX_TIME);
} }
let multiplier = NOTIFY_TEMPORARY_DELAY_FACTOR.powi(exponent); let multiplier = NOTIFY_TEMPORARY_DELAY_FACTOR.powi(exponent);
let delay = multiplier * NOTIFY_TEMPORARY_DELAY_TIME; let delay = multiplier * NOTIFY_TEMPORARY_DELAY_TIME;
if !delay.is_finite() || delay < 0.0 { if !delay.is_finite() || delay < 0.0 {
log::warn!("Unexpected calculated delay: {}, treating as infinite", delay); log::warn!(
"Unexpected calculated delay: {}, treating as infinite",
delay
);
return std::time::Duration::from_secs_f64(NOTIFY_TEMPORARY_MAX_TIME); return std::time::Duration::from_secs_f64(NOTIFY_TEMPORARY_MAX_TIME);
} }
std::time::Duration::from_secs_f64(delay) std::time::Duration::from_secs_f64(delay)
+1 -1
View File
@@ -1,7 +1,7 @@
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum NotifyError{ pub enum NotifyError {
#[error("Finalize config validation failed: {0:?}")] #[error("Finalize config validation failed: {0:?}")]
FinalizeValidationError(Vec<String>), FinalizeValidationError(Vec<String>),
+47 -16
View File
@@ -27,16 +27,16 @@ pub mod security;
pub mod stage; pub mod stage;
use crate::{ use crate::{
ExecutionContext,
cli::Cli, cli::Cli,
constant::STANDALONE_STATUS_DIR,
engine::EventSender, engine::EventSender,
error::PrebakeError, error::PrebakeError,
ExecutionContext,
prebake::{security::get_drop_after, stage::PrebakeStage}, prebake::{security::get_drop_after, stage::PrebakeStage},
types::buildstatus::{write_stage_status, StageInfo, StagePhase, StageResult}, types::buildstatus::{StageInfo, StagePhase, StageResult, write_stage_status},
constant::STANDALONE_STATUS_DIR,
}; };
use std::path::Path;
use chrono::Utc; use chrono::Utc;
use std::path::Path;
pub use config::PrebakeConfig; pub use config::PrebakeConfig;
pub use security::prebake_drop_privilege; pub use security::prebake_drop_privilege;
@@ -92,7 +92,7 @@ pub async fn prebake(
prebake_path: &Path, prebake_path: &Path,
cli: &Cli, cli: &Cli,
stage: Option<PrebakeStage>, stage: Option<PrebakeStage>,
mut ctx: &mut ExecutionContext, mut ctx: &mut ExecutionContext,
event_tx: EventSender, event_tx: EventSender,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
// Initialize // Initialize
@@ -131,7 +131,11 @@ mut ctx: &mut ExecutionContext,
ctx.task_id = format!("{}-{}-bootstrap", cli.pipeline, cli.build_id); ctx.task_id = format!("{}-{}-bootstrap", cli.pipeline, cli.build_id);
let osinfo = os_info::get(); let osinfo = os_info::get();
let result = stage::bootstrap::bootstrap(&prebake, &osinfo, &ctx, &event_tx).await; let result = stage::bootstrap::bootstrap(&prebake, &osinfo, &ctx, &event_tx).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure }; let stage_result = if result.is_ok() {
StageResult::Success
} else {
StageResult::Failure
};
let _ = write_stage_status( let _ = write_stage_status(
STANDALONE_STATUS_DIR, STANDALONE_STATUS_DIR,
StageInfo { StageInfo {
@@ -139,7 +143,9 @@ mut ctx: &mut ExecutionContext,
phase: StagePhase::Prebake, phase: StagePhase::Prebake,
substage: "bootstrap".to_string(), substage: "bootstrap".to_string(),
result: stage_result, result: stage_result,
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, duration_ms: Utc::now()
.signed_duration_since(stage_start)
.num_milliseconds() as u64,
started_at: stage_start, started_at: stage_start,
finished_at: Utc::now(), finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()), error_message: result.as_ref().err().map(|e| e.to_string()),
@@ -159,7 +165,11 @@ mut ctx: &mut ExecutionContext,
None => None, None => None,
}; };
let result = stage::hook::hook(earlyhook, &ctx, event_tx.clone()).await; let result = stage::hook::hook(earlyhook, &ctx, event_tx.clone()).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure }; let stage_result = if result.is_ok() {
StageResult::Success
} else {
StageResult::Failure
};
let _ = write_stage_status( let _ = write_stage_status(
STANDALONE_STATUS_DIR, STANDALONE_STATUS_DIR,
StageInfo { StageInfo {
@@ -167,7 +177,9 @@ mut ctx: &mut ExecutionContext,
phase: StagePhase::Prebake, phase: StagePhase::Prebake,
substage: "earlyhook".to_string(), substage: "earlyhook".to_string(),
result: stage_result, result: stage_result,
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, duration_ms: Utc::now()
.signed_duration_since(stage_start)
.num_milliseconds() as u64,
started_at: stage_start, started_at: stage_start,
finished_at: Utc::now(), finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()), error_message: result.as_ref().err().map(|e| e.to_string()),
@@ -240,8 +252,13 @@ mut ctx: &mut ExecutionContext,
log::info!("Running PBStage: DepsSystem"); log::info!("Running PBStage: DepsSystem");
prebake_drop_privilege(PrebakeStage::DepsSystem, dropafter, target_user, &mut ctx)?; prebake_drop_privilege(PrebakeStage::DepsSystem, dropafter, target_user, &mut ctx)?;
ctx.task_id = format!("{}-{}-depssystem", cli.pipeline, cli.build_id); ctx.task_id = format!("{}-{}-depssystem", cli.pipeline, cli.build_id);
let result = stage::depssystem::depssystem(&system_config, &ctx, event_tx.clone()).await; let result =
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure }; stage::depssystem::depssystem(&system_config, &ctx, event_tx.clone()).await;
let stage_result = if result.is_ok() {
StageResult::Success
} else {
StageResult::Failure
};
let _ = write_stage_status( let _ = write_stage_status(
STANDALONE_STATUS_DIR, STANDALONE_STATUS_DIR,
StageInfo { StageInfo {
@@ -249,7 +266,9 @@ mut ctx: &mut ExecutionContext,
phase: StagePhase::Prebake, phase: StagePhase::Prebake,
substage: "depssystem".to_string(), substage: "depssystem".to_string(),
result: stage_result, result: stage_result,
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, duration_ms: Utc::now()
.signed_duration_since(stage_start)
.num_milliseconds() as u64,
started_at: stage_start, started_at: stage_start,
finished_at: Utc::now(), finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()), error_message: result.as_ref().err().map(|e| e.to_string()),
@@ -266,7 +285,11 @@ mut ctx: &mut ExecutionContext,
prebake_drop_privilege(PrebakeStage::DepsUser, dropafter, target_user, &mut ctx)?; prebake_drop_privilege(PrebakeStage::DepsUser, dropafter, target_user, &mut ctx)?;
ctx.task_id = format!("{}-{}-depsuser", cli.pipeline, cli.build_id); ctx.task_id = format!("{}-{}-depsuser", cli.pipeline, cli.build_id);
let result = stage::depsuser::depsuser(&user, &ctx, event_tx.clone(), &endpoint).await; let result = stage::depsuser::depsuser(&user, &ctx, event_tx.clone(), &endpoint).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure }; let stage_result = if result.is_ok() {
StageResult::Success
} else {
StageResult::Failure
};
let _ = write_stage_status( let _ = write_stage_status(
STANDALONE_STATUS_DIR, STANDALONE_STATUS_DIR,
StageInfo { StageInfo {
@@ -274,7 +297,9 @@ mut ctx: &mut ExecutionContext,
phase: StagePhase::Prebake, phase: StagePhase::Prebake,
substage: "depsuser".to_string(), substage: "depsuser".to_string(),
result: stage_result, result: stage_result,
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, duration_ms: Utc::now()
.signed_duration_since(stage_start)
.num_milliseconds() as u64,
started_at: stage_start, started_at: stage_start,
finished_at: Utc::now(), finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()), error_message: result.as_ref().err().map(|e| e.to_string()),
@@ -295,7 +320,11 @@ mut ctx: &mut ExecutionContext,
None => None, None => None,
}; };
let result = stage::hook::hook(latehook, &ctx, event_tx.clone()).await; let result = stage::hook::hook(latehook, &ctx, event_tx.clone()).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure }; let stage_result = if result.is_ok() {
StageResult::Success
} else {
StageResult::Failure
};
let _ = write_stage_status( let _ = write_stage_status(
STANDALONE_STATUS_DIR, STANDALONE_STATUS_DIR,
StageInfo { StageInfo {
@@ -303,7 +332,9 @@ mut ctx: &mut ExecutionContext,
phase: StagePhase::Prebake, phase: StagePhase::Prebake,
substage: "latehook".to_string(), substage: "latehook".to_string(),
result: stage_result, result: stage_result,
duration_ms: Utc::now().signed_duration_since(stage_start).num_milliseconds() as u64, duration_ms: Utc::now()
.signed_duration_since(stage_start)
.num_milliseconds() as u64,
started_at: stage_start, started_at: stage_start,
finished_at: Utc::now(), finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()), error_message: result.as_ref().err().map(|e| e.to_string()),
+22 -8
View File
@@ -10,25 +10,39 @@ pub async fn event_receiver(
ExecutionEvent::TaskStarted { task_id, timestamp } => { ExecutionEvent::TaskStarted { task_id, timestamp } => {
log::debug!( log::debug!(
"[{}] [{}] [{}] Task started: {}", "[{}] [{}] [{}] Task started: {}",
pipeline_name, build_id, timestamp, task_id pipeline_name,
build_id,
timestamp,
task_id
); );
} }
ExecutionEvent::OutputChunk { task_id, stream, data } => { ExecutionEvent::OutputChunk {
match stream { task_id,
StreamType::Stdout => print!("[{}] [{}] [stdout] {}", pipeline_name, task_id, data), stream,
StreamType::Stderr => eprint!("[{}] [{}] [stderr] {}", pipeline_name, task_id, data), data,
} => match stream {
StreamType::Stdout => print!("[{}] [{}] [stdout] {}", pipeline_name, task_id, data),
StreamType::Stderr => {
eprint!("[{}] [{}] [stderr] {}", pipeline_name, task_id, data)
} }
} },
ExecutionEvent::TaskCompleted { task_id, result } => { ExecutionEvent::TaskCompleted { task_id, result } => {
log::debug!( log::debug!(
"[{}] [{}] Task completed: {} (exit_code={}, duration={:?})", "[{}] [{}] Task completed: {} (exit_code={}, duration={:?})",
pipeline_name, build_id, task_id, result.exit_code, result.duration pipeline_name,
build_id,
task_id,
result.exit_code,
result.duration
); );
} }
ExecutionEvent::TaskFailed { task_id, error } => { ExecutionEvent::TaskFailed { task_id, error } => {
log::error!( log::error!(
"[{}] [{}] Task failed: {} (error={})", "[{}] [{}] Task failed: {} (error={})",
pipeline_name, build_id, task_id, error pipeline_name,
build_id,
task_id,
error
); );
} }
} }
+2 -2
View File
@@ -1,10 +1,10 @@
use std::str::FromStr; use std::str::FromStr;
pub mod bootstrap; pub mod bootstrap;
pub mod environment;
pub mod hook;
pub mod depssystem; pub mod depssystem;
pub mod depsuser; pub mod depsuser;
pub mod environment;
pub mod hook;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum PrebakeStage { pub enum PrebakeStage {
@@ -62,7 +62,10 @@ pub async fn bootstrap(
log::debug!("Generated bootstrap script:\n{}", script); log::debug!("Generated bootstrap script:\n{}", script);
let mut file = File::create(&bootstrap_path)?; let mut file = File::create(&bootstrap_path)?;
file.write_all(script.as_bytes())?; file.write_all(script.as_bytes())?;
std::fs::set_permissions(&bootstrap_path, std::os::unix::fs::PermissionsExt::from_mode(0o755))?; std::fs::set_permissions(
&bootstrap_path,
std::os::unix::fs::PermissionsExt::from_mode(0o755),
)?;
} else { } else {
// Requires PIP(Pipeline Implicit Parameters) to be implemented // Requires PIP(Pipeline Implicit Parameters) to be implemented
unimplemented!("Not supporting custom bootstrap.sh at this time"); unimplemented!("Not supporting custom bootstrap.sh at this time");
@@ -41,13 +41,12 @@ pub async fn depssystem(
log::info!("Changing mirror to: {}", mirror); log::info!("Changing mirror to: {}", mirror);
let cmds = package_manager.change_mirror(mirror, Some(&osinfo)); let cmds = package_manager.change_mirror(mirror, Some(&osinfo));
for cmd in cmds { for cmd in cmds {
log::debug!("Executing: {:?}", &cmd); log::debug!("Executing: {:?}", &cmd);
engine engine.execute(cmd, ctx, &event_tx).await.map_err(|e| {
.execute(cmd, ctx, &event_tx) DependencyError::StageFailed {
.await stage: "MirrorChange",
.map_err(|e| DependencyError::StageFailed { source: e,
stage: "MirrorChange", }
source: e,
})?; })?;
} }
} }
+4 -1
View File
@@ -32,7 +32,10 @@ pub async fn collect_upm_sysdeps(
let package_manager = match upm::select(name) { let package_manager = match upm::select(name) {
Some(pm) => pm, Some(pm) => pm,
None => { None => {
log::warn!("Unknown user package manager: {}, skipping sysdep collection", name); log::warn!(
"Unknown user package manager: {}, skipping sysdep collection",
name
);
continue; continue;
} }
}; };
@@ -1,4 +1,4 @@
use crate::prebake::config::{PrebakeConfig}; use crate::prebake::config::PrebakeConfig;
use crate::types::builderconfig::BuilderType; use crate::types::builderconfig::BuilderType;
use config::Config; use config::Config;
+1 -1
View File
@@ -12,8 +12,8 @@
//! Each hook command supports custom working directories, environment variables, //! Each hook command supports custom working directories, environment variables,
//! and timeout configurations. //! and timeout configurations.
use crate::engine::types::DeltaExecutionContext;
use crate::ExecutionContext; use crate::ExecutionContext;
use crate::engine::types::DeltaExecutionContext;
use crate::engine::{Engine, EventSender, ExecutionError}; use crate::engine::{Engine, EventSender, ExecutionError};
use crate::types::command::CustomCommand; use crate::types::command::CustomCommand;
use std::path::PathBuf; use std::path::PathBuf;
+1 -1
View File
@@ -1,6 +1,6 @@
pub mod architecture; pub mod architecture;
pub mod buildstatus;
pub mod builderconfig; pub mod builderconfig;
pub mod buildstatus;
pub mod cache; pub mod cache;
pub mod command; pub mod command;
pub mod memsize; pub mod memsize;
+2 -2
View File
@@ -153,11 +153,11 @@ pub fn parse_mem_size(s: &str) -> Result<MemSize, String> {
"k" | "K" => 1000, "k" | "K" => 1000,
"m" | "M" => 1000 * 1000, "m" | "M" => 1000 * 1000,
"g" | "G" => 1000 * 1000 * 1000, "g" | "G" => 1000 * 1000 * 1000,
"t" | "T" => 1000 as u64 * 1000 * 1000 * 1000, "t" | "T" => 1000_u64 * 1000 * 1000 * 1000,
"ki" | "kI" | "Ki" | "KI" => 1024, "ki" | "kI" | "Ki" | "KI" => 1024,
"mi" | "mI" | "Mi" | "MI" => 1024 * 1024, "mi" | "mI" | "Mi" | "MI" => 1024 * 1024,
"gi" | "gI" | "Gi" | "GI" => 1024 * 1024 * 1024, "gi" | "gI" | "Gi" | "GI" => 1024 * 1024 * 1024,
"ti" | "tI" | "Ti" | "TI" => 1024 as u64 * 1024 * 1024 * 1024, "ti" | "tI" | "Ti" | "TI" => 1024_u64 * 1024 * 1024 * 1024,
_ => return Err(format!("unknown unit: {}", unit_raw)), _ => return Err(format!("unknown unit: {}", unit_raw)),
}; };
+1 -1
View File
@@ -1,4 +1,4 @@
use serde::{de::Visitor, Deserializer}; use serde::{Deserializer, de::Visitor};
pub const INT_MAX_MS: u64 = i32::MAX as u64; pub const INT_MAX_MS: u64 = i32::MAX as u64;
@@ -6,8 +6,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
use workshop_baker::engine::{ExecutionContext, ExecutionEvent}; use workshop_baker::engine::{ExecutionContext, ExecutionEvent};
use workshop_baker::finalize::plugin::PluginMap;
use workshop_baker::finalize::stage::hook::hook; use workshop_baker::finalize::stage::hook::hook;
use workshop_baker::finalize::plugin::{PluginMap};
use workshop_baker::types::command::CustomCommand; use workshop_baker::types::command::CustomCommand;
fn create_test_context() -> ExecutionContext { fn create_test_context() -> ExecutionContext {
@@ -26,7 +26,10 @@ fn create_test_context() -> ExecutionContext {
} }
} }
fn create_test_event_channel() -> (Option<tokio::sync::mpsc::Sender<ExecutionEvent>>, tokio::sync::mpsc::Receiver<ExecutionEvent>) { fn create_test_event_channel() -> (
Option<tokio::sync::mpsc::Sender<ExecutionEvent>>,
tokio::sync::mpsc::Receiver<ExecutionEvent>,
) {
let (tx, rx) = tokio::sync::mpsc::channel(64); let (tx, rx) = tokio::sync::mpsc::channel(64);
(Some(tx), rx) (Some(tx), rx)
} }
@@ -146,7 +149,10 @@ async fn test_multiple_hooks_execute_sequentially() {
]; ];
let result = hook(Some(&cmds), &ctx, event_tx, &plugins).await; let result = hook(Some(&cmds), &ctx, event_tx, &plugins).await;
assert!(result.is_ok(), "Multiple hooks should execute sequentially and succeed"); assert!(
result.is_ok(),
"Multiple hooks should execute sequentially and succeed"
);
} }
#[tokio::test] #[tokio::test]
@@ -5,9 +5,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
use workshop_baker::cli::Cli;
use workshop_baker::engine::{ExecutionContext, ExecutionEvent}; use workshop_baker::engine::{ExecutionContext, ExecutionEvent};
use workshop_baker::finalize::finalize; use workshop_baker::finalize::finalize;
use workshop_baker::cli::Cli;
fn create_test_context() -> ExecutionContext { fn create_test_context() -> ExecutionContext {
ExecutionContext { ExecutionContext {
@@ -35,7 +35,10 @@ fn create_test_cli() -> Cli {
} }
} }
fn create_test_event_channel() -> (Option<tokio::sync::mpsc::Sender<ExecutionEvent>>, tokio::sync::mpsc::Receiver<ExecutionEvent>) { fn create_test_event_channel() -> (
Option<tokio::sync::mpsc::Sender<ExecutionEvent>>,
tokio::sync::mpsc::Receiver<ExecutionEvent>,
) {
let (tx, rx) = tokio::sync::mpsc::channel(64); let (tx, rx) = tokio::sync::mpsc::channel(64);
(Some(tx), rx) (Some(tx), rx)
} }
@@ -76,7 +79,11 @@ hooks:
let _ = consumer_handle.await; let _ = consumer_handle.await;
std::fs::remove_file(&finalize_path).ok(); std::fs::remove_file(&finalize_path).ok();
assert!(result.is_ok(), "Full finalize pipeline should succeed: {:?}", result); assert!(
result.is_ok(),
"Full finalize pipeline should succeed: {:?}",
result
);
} }
#[tokio::test] #[tokio::test]
@@ -94,16 +101,19 @@ version: "0.0.1"
let mut ctx = create_test_context(); let mut ctx = create_test_context();
let (event_tx, mut event_rx) = create_test_event_channel(); let (event_tx, mut event_rx) = create_test_event_channel();
let consumer_handle = tokio::spawn(async move { let consumer_handle =
while let Some(_event) = event_rx.recv().await {} tokio::spawn(async move { while let Some(_event) = event_rx.recv().await {} });
});
let result = finalize(&finalize_path, &cli, &mut ctx, event_tx).await; let result = finalize(&finalize_path, &cli, &mut ctx, event_tx).await;
let _ = consumer_handle.await; let _ = consumer_handle.await;
std::fs::remove_file(&finalize_path).ok(); std::fs::remove_file(&finalize_path).ok();
assert!(result.is_ok(), "Finalize with empty hooks should succeed: {:?}", result); assert!(
result.is_ok(),
"Finalize with empty hooks should succeed: {:?}",
result
);
} }
#[tokio::test] #[tokio::test]
@@ -130,9 +140,8 @@ hooks:
let mut ctx = create_test_context(); let mut ctx = create_test_context();
let (event_tx, mut event_rx) = create_test_event_channel(); let (event_tx, mut event_rx) = create_test_event_channel();
let consumer_handle = tokio::spawn(async move { let consumer_handle =
while let Some(_event) = event_rx.recv().await {} tokio::spawn(async move { while let Some(_event) = event_rx.recv().await {} });
});
let result = finalize(&finalize_path, &cli, &mut ctx, event_tx).await; let result = finalize(&finalize_path, &cli, &mut ctx, event_tx).await;
@@ -141,5 +150,8 @@ hooks:
// With the notification subsystem, prior stage failures cause finalize to // With the notification subsystem, prior stage failures cause finalize to
// send a failure notification and then return Ok (skipping artifacts/latehook) // send a failure notification and then return Ok (skipping artifacts/latehook)
assert!(result.is_ok(), "Finalize with failing early hook should return Ok after sending failure notification"); assert!(
result.is_ok(),
"Finalize with failing early hook should return Ok after sending failure notification"
);
} }