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
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
components: clippy, rustfmt
- name: Cache cargo dependencies
uses: actions/cache@v4
@@ -43,8 +43,14 @@ jobs:
restore-keys: |
${{ runner.os }}-cargo-
- name: Check formatting
run: cargo fmt -- --check
- name: Check code (cargo check)
run: cargo check --all-features
- name: Run Clippy lints
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::path::Path;
+22 -17
View File
@@ -1,16 +1,16 @@
use crate::ExecutionContext;
use crate::bake::parser::Function;
use crate::cli::Cli;
use crate::constant::{DEFAULT_WORKSPACE, STANDALONE_STATUS_DIR, TRIVIAL_SCRIPT_NAME};
use crate::engine::EventSender;
use crate::error::BakeError;
use crate::ExecutionContext;
use crate::prebake::PrebakeConfig;
use crate::prebake::security::get_drop_after;
use crate::prebake::stage::PrebakeStage;
use crate::types::buildstatus::{write_stage_status, StageInfo, StagePhase, StageResult};
use crate::types::buildstatus::{StageInfo, StagePhase, StageResult, write_stage_status};
use crate::{Engine, prebake};
use std::path::{PathBuf, Path};
use chrono::Utc;
use std::path::{Path, PathBuf};
mod builder;
mod decorator;
@@ -52,20 +52,19 @@ pub async fn bake(
if !has_pipeline || trivial {
let stage_start = Utc::now();
let function = Function{
let function = Function {
name: TRIVIAL_SCRIPT_NAME.to_string(),
decorators: vec![],
body: script_content,
};
let script = builder::build_script(
&function,
bake_base_path,
&workspace,
None,
use_template,
)?;
let script =
builder::build_script(&function, bake_base_path, &workspace, None, use_template)?;
let result = engine.execute_script(&script, &ctx, &event_tx).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure };
let stage_result = if result.is_ok() {
StageResult::Success
} else {
StageResult::Failure
};
let _ = write_stage_status(
STANDALONE_STATUS_DIR,
StageInfo {
@@ -73,7 +72,9 @@ pub async fn bake(
phase: StagePhase::Bake,
substage: "trivial".to_string(),
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,
finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()),
@@ -95,7 +96,11 @@ pub async fn bake(
use_template,
)?;
let result = engine.execute_script(&script, &ctx, &event_tx).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure };
let stage_result = if result.is_ok() {
StageResult::Success
} else {
StageResult::Failure
};
let _ = write_stage_status(
STANDALONE_STATUS_DIR,
StageInfo {
@@ -103,7 +108,9 @@ pub async fn bake(
phase: StagePhase::Bake,
substage: func.name.clone(),
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,
finished_at: Utc::now(),
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
}
type TaskResult = Result<(), BakeError>;
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 {
// }
+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 lazy_static::lazy_static;
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
if is_pipeline {
if !has_after {
if let Some(ref last) = last_pipeline {
ts.add_dependency(last, function.name.clone());
}
if !has_after && let Some(ref last) = last_pipeline {
ts.add_dependency(last, 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_json::Value;
// use std::path::PathBuf;
// use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::net::{UnixStream};
use tokio::net::UnixStream;
// use tokio::process::{Child, Command};
// use tokio::signal;
// use tokio::sync::RwLock;
+21 -17
View File
@@ -8,8 +8,8 @@ use tokio::time::timeout;
use super::pm::PackageManager;
use super::types::{
CgroupManager, EventSender, ExecutionContext, ExecutionError, ExecutionEvent, ExecutionResult,
ResourceLimits, DeltaExecutionContext, StreamType
CgroupManager, DeltaExecutionContext, EventSender, ExecutionContext, ExecutionError,
ExecutionEvent, ExecutionResult, ResourceLimits, StreamType,
};
impl super::types::Engine {
@@ -68,7 +68,7 @@ impl super::types::Engine {
};
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());
@@ -94,8 +94,8 @@ impl super::types::Engine {
cmd: &str,
ctx: &ExecutionContext,
event_tx: &EventSender,
delta_ctx: &DeltaExecutionContext
) -> Result<ExecutionResult,ExecutionError> {
delta_ctx: &DeltaExecutionContext,
) -> Result<ExecutionResult, ExecutionError> {
let mut local_ctx = ctx.clone();
if let Some(working_dir) = &delta_ctx.working_dir {
log::debug!("Updating working directory to {}", working_dir.display());
@@ -222,7 +222,7 @@ impl super::types::Engine {
return Err(ExecutionError::IoError(format!(
"Failed to wait for process: {}",
e
)))
)));
}
Err(_) => {
log::warn!(
@@ -267,21 +267,25 @@ impl super::types::Engine {
while let Some(data) = stdout_rx.recv().await {
stdout_buf.push_str(&data);
if let Some(tx) = event_tx {
let _ = tx.send(ExecutionEvent::OutputChunk {
task_id: task_id.clone(),
stream: StreamType::Stdout,
data: data.clone(),
}).await;
let _ = tx
.send(ExecutionEvent::OutputChunk {
task_id: task_id.clone(),
stream: StreamType::Stdout,
data: data.clone(),
})
.await;
}
}
while let Some(data) = stderr_rx.recv().await {
stderr_buf.push_str(&data);
if let Some(tx) = event_tx {
let _ = tx.send(ExecutionEvent::OutputChunk {
task_id: task_id.clone(),
stream: StreamType::Stderr,
data: data.clone(),
}).await;
let _ = tx
.send(ExecutionEvent::OutputChunk {
task_id: task_id.clone(),
stream: StreamType::Stderr,
data: data.clone(),
})
.await;
}
}
@@ -328,7 +332,7 @@ impl super::types::Engine {
package: &[String],
pkgman: &dyn PackageManager,
ctx: &ExecutionContext,
event_tx: &EventSender
event_tx: &EventSender,
) -> Result<ExecutionResult, ExecutionError> {
let cmds = pkgman.install(package);
let mut combined_result = ExecutionResult {
+16 -10
View File
@@ -127,7 +127,7 @@ impl PackageManager for Pacman {
));
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]
}
@@ -446,18 +446,22 @@ mod tests {
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert!(recv_args
.join(" ")
.contains("pacman-key --recv-keys ABCDEF123456"));
assert!(
recv_args
.join(" ")
.contains("pacman-key --recv-keys ABCDEF123456")
);
let lsign_cmd = cmds[2].as_std();
let lsign_args: Vec<String> = lsign_cmd
.get_args()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert!(lsign_args
.join(" ")
.contains("pacman-key --lsign-key ABCDEF123456"));
assert!(
lsign_args
.join(" ")
.contains("pacman-key --lsign-key ABCDEF123456")
);
}
#[test]
@@ -493,9 +497,11 @@ mod tests {
.get_args()
.map(|a| a.to_string_lossy().into_owned())
.collect();
assert!(install_args
.join(" ")
.contains("pacman -Sy gnupg --needed --noconfirm"));
assert!(
install_args
.join(" ")
.contains("pacman -Sy gnupg --needed --noconfirm")
);
}
#[test]
+1 -4
View File
@@ -81,10 +81,7 @@ lazy_static::lazy_static! {
};
}
pub async fn resolve(
packages: &[String],
target_pm: &str,
) -> Vec<String> {
pub async fn resolve(packages: &[String], target_pm: &str) -> Vec<String> {
let mut result = Vec::new();
for pkg in packages {
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;
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)]
pub struct ResourceUsage {
+1 -3
View File
@@ -34,9 +34,7 @@ pub trait UserPackageManager {
}
pub fn all_managers() -> Vec<Box<dyn UserPackageManager>> {
vec![
Box::new(custom::Custom),
]
vec![Box::new(custom::Custom)]
}
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 serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::Engine;
use crate::engine::types::DeltaExecutionContext;
@@ -55,7 +55,7 @@ impl UserPackageManager for Custom {
log::error!("Custom command {} failed: {:?}", index, e);
return Err(DependencyError::CustomFailed(e));
}
};
}
Ok(())
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ pub const EXITCODE_PRIV_DROP_FAILED: i32 = 201;
#[derive(Error, Debug)]
pub enum BakeError {
#[error("Failed to generate staged script {script}: {reason}")]
ScriptGenerationFailed{script: String, reason: String},
ScriptGenerationFailed { script: String, reason: String },
#[error("Template error: {0}")]
TemplateError(#[from] minijinja::Error),
+5 -6
View File
@@ -5,15 +5,15 @@ pub mod plugin;
pub mod stage;
pub mod template;
use crate::cli::Cli;
use crate::constant::FINALIZE_STANDALONE_PLUGIN_PATH;
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::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 chrono::Utc;
use std::path::PathBuf;
// use crate::finalize::error::PluginError;
pub use config::*;
pub use error::FinalizeError;
@@ -77,8 +77,7 @@ pub async fn finalize(
.await;
// Read build status from prior stages
let build_status = read_build_status(STANDALONE_STATUS_DIR)
.unwrap_or_default();
let build_status = read_build_status(STANDALONE_STATUS_DIR).unwrap_or_default();
let final_result = build_status.final_result();
// Determine if we should continue with artifacts/latehook
+10 -6
View File
@@ -406,9 +406,11 @@ plugin:
let result = config.validate();
assert!(result.is_err());
let errors = result.unwrap_err();
assert!(errors
.iter()
.any(|e| e.contains("show_file_list") && e.contains("no URL")));
assert!(
errors
.iter()
.any(|e| e.contains("show_file_list") && e.contains("no URL"))
);
}
#[test]
@@ -444,8 +446,10 @@ plugin:
let result = config.validate();
assert!(result.is_err());
let errors = result.unwrap_err();
assert!(errors
.iter()
.any(|e| e.contains("show_file_list") && e.contains("2 URL definitions")));
assert!(
errors
.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 thiserror::Error;
use crate::{ExecutionError, error::{
EXITCODE_EXECUTION_ERROR, EXITCODE_GENERAL_ERROR, EXITCODE_IO_ERROR, EXITCODE_PARSE_ERROR,
}};
use crate::{
ExecutionError,
error::{
EXITCODE_EXECUTION_ERROR, EXITCODE_GENERAL_ERROR, EXITCODE_IO_ERROR, EXITCODE_PARSE_ERROR,
},
};
#[derive(Error, Debug)]
pub enum FinalizeError {
@@ -60,16 +63,13 @@ pub enum PluginError {
GeneralError(String),
#[error("Plugin {name} is not available: {reason}")]
PluginUnavailable{
name: String,
reason: String,
},
PluginUnavailable { name: String, reason: String },
#[error("Plugin execution failed: {0}")]
ExecutionError(#[from] ExecutionError),
#[error("Failed to fetch plugin({name}:{url}): {reason}")]
FetchError{
FetchError {
name: String,
url: String,
reason: String,
+22 -8
View File
@@ -10,25 +10,39 @@ pub async fn event_receiver(
ExecutionEvent::TaskStarted { task_id, timestamp } => {
log::debug!(
"[{}] [{}] [{}] Task started: {}",
pipeline_name, build_id, timestamp, task_id
pipeline_name,
build_id,
timestamp,
task_id
);
}
ExecutionEvent::OutputChunk { task_id, stream, data } => {
match stream {
StreamType::Stdout => print!("[{}] [{}] [stdout] {}", pipeline_name, task_id, data),
StreamType::Stderr => eprint!("[{}] [{}] [stderr] {}", pipeline_name, task_id, data),
ExecutionEvent::OutputChunk {
task_id,
stream,
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 } => {
log::debug!(
"[{}] [{}] 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 } => {
log::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::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
use async_trait::async_trait;
+8 -7
View File
@@ -1,10 +1,10 @@
// Code in this module PARTIALLY OR FULLY utilized AI Coding Agent.
use std::collections::HashSet;
use crate::finalize::fetch;
use crate::finalize::{
PluginConfig,
plugin::{PluginError, RawPluginMap},
};
use std::collections::HashSet;
use std::path::Path;
mod checksum;
@@ -93,12 +93,13 @@ pub async fn fetch_plugins(
_ => {}
}
log::debug!("Downloading plugin {}", name);
let (source, config) = data.iter_mut().next().ok_or_else(|| {
PluginError::PluginUnavailable {
name: name.clone(),
reason: "no source configured".to_string(),
}
})?;
let (source, config) =
data.iter_mut()
.next()
.ok_or_else(|| PluginError::PluginUnavailable {
name: name.clone(),
reason: "no source configured".to_string(),
})?;
let fetch_argument = FetchArgument {
checksum: config.checksum.clone(),
shallow: config.shallow,
@@ -1,6 +1,6 @@
// Code in this module PARTIALLY OR FULLY utilized AI Coding Agent.
use std::path::PathBuf;
use crate::finalize::plugin::PluginError;
use std::path::PathBuf;
use tokio::task;
#[derive(Debug, Clone, Copy)]
@@ -12,14 +12,18 @@ pub enum CompressionType {
impl CompressionType {
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())
.unwrap_or("")
.to_lowercase();
if name.ends_with(".tar.gz") || name.ends_with(".tgz") {
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)
} else if name.ends_with(".tar") {
Ok(CompressionType::None)
@@ -38,51 +42,50 @@ pub async fn extract_tar(
compression: CompressionType,
) -> Result<(), PluginError> {
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))
)?;
})?;
}
tokio::fs::create_dir_all(destdir).await.map_err(|e|
PluginError::GeneralError(format!("Failed to create directory: {}", e))
)?;
tokio::fs::create_dir_all(destdir)
.await
.map_err(|e| PluginError::GeneralError(format!("Failed to create directory: {}", e)))?;
let archive = archive.clone();
let destdir = destdir.clone();
task::spawn_blocking(move || {
let file = std::fs::File::open(&archive).map_err(|e|
PluginError::GeneralError(format!("Failed to open archive: {}", e))
)?;
let file = std::fs::File::open(&archive)
.map_err(|e| PluginError::GeneralError(format!("Failed to open archive: {}", e)))?;
match compression {
CompressionType::Gzip => {
let decoder = flate2::read::GzDecoder::new(file);
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))
)?;
})?;
}
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))
)?;
})?;
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))
)?;
})?;
}
CompressionType::None => {
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))
)?;
})?;
}
};
Ok::<(), PluginError>(())
})
.await
.map_err(|e| PluginError::GeneralError(format!("Task join error: {}", e)))??;
Ok(())
}
+10 -18
View File
@@ -1,7 +1,7 @@
// Code in this module PARTIALLY OR FULLY utilized AI Coding Agent.
use crate::finalize::plugin::PluginError;
use std::path::Path;
use tokio::process::Command;
use crate::finalize::plugin::PluginError;
#[derive(PartialEq)]
pub enum GitVersion {
@@ -106,24 +106,16 @@ pub async fn fetch_git_plugin(
GitVersion::Mixed(tag, commit) => {
log::debug!("Fetching git plugin: {}@{}/{}", name, tag, commit);
let shallow_flag = shallow.unwrap_or(false);
let result = git_clone_and_checkout(
&baseurl,
&tag,
&basedir.join(name),
name,
shallow_flag,
)
.await;
let result =
git_clone_and_checkout(&baseurl, &tag, &basedir.join(name), name, shallow_flag)
.await;
if result.is_err() {
log::warn!("Failed to clone with tag, trying commit hash. URL: {}", path);
return git_clone_and_checkout(
&baseurl,
&commit,
&basedir.join(name),
name,
false,
)
.await;
log::warn!(
"Failed to clone with tag, trying commit hash. URL: {}",
path
);
return git_clone_and_checkout(&baseurl, &commit, &basedir.join(name), name, false)
.await;
}
result
}
@@ -1,14 +1,10 @@
// 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::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(
url: &str,
dest: &Path,
name: &str,
) -> Result<(), PluginError> {
pub async fn download_to_file(url: &str, dest: &Path, name: &str) -> Result<(), PluginError> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(300))
.build()
@@ -33,28 +29,42 @@ pub async fn download_to_file(
});
}
let bytes = response.bytes().await.map_err(|e| PluginError::FetchError {
name: name.to_string(),
url: url.to_string(),
reason: format!("Failed to read response body: {}", e),
})?;
let bytes = response
.bytes()
.await
.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 {
name: name.to_string(),
url: url.to_string(),
reason: format!("Failed to write file: {}", e),
})?;
tokio::fs::write(dest, &bytes)
.await
.map_err(|e| PluginError::FetchError {
name: name.to_string(),
url: url.to_string(),
reason: format!("Failed to write file: {}", e),
})?;
Ok(())
}
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 lower = file_part.to_lowercase();
if lower.ends_with(".tar.gz") || lower.ends_with(".tgz") {
".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"
} else if lower.ends_with(".tar") {
".tar"
@@ -82,19 +92,17 @@ pub async fn fetch_http_plugin(
PluginError::GeneralError(format!("Failed to read downloaded file: {}", e))
})?;
let checksum_type = detect_checksum_type(checksum)
.map_err(|e| PluginError::GeneralError(e))?;
let checksum_type =
detect_checksum_type(checksum).map_err(|e| PluginError::GeneralError(e))?;
if let Some(ct) = checksum_type {
verify_checksum(&bytes, checksum, ct).map_err(|e| {
PluginError::GeneralError(e)
})?;
verify_checksum(&bytes, checksum, ct).map_err(|e| PluginError::GeneralError(e))?;
}
}
}
let compression = CompressionType::from_path(&temp_file)
.map_err(|e| PluginError::GeneralError(e))?;
let compression =
CompressionType::from_path(&temp_file).map_err(|e| PluginError::GeneralError(e))?;
let destdir = basedir.join(name);
extract_tar(&temp_file, &destdir, compression).await?;
@@ -1,9 +1,9 @@
use crate::finalize::plugin::AsyncPluginFn;
pub mod dummy;
mod insitenotify;
mod mail;
mod satori;
mod webhook;
pub mod dummy;
pub struct InternalPlugin {
pub name: &'static str,
pub entry: Box<dyn AsyncPluginFn>,
@@ -1,5 +1,8 @@
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;
pub struct InsiteNotify;
@@ -1,11 +1,11 @@
// 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::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
use async_trait::async_trait;
use std::time::Duration;
use lettre::message::{MultiPart, SinglePart, header};
use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
use lettre::message::{header, MultiPart, SinglePart};
use std::time::Duration;
use tokio::time::timeout;
mod config;
@@ -29,20 +29,29 @@ impl AsyncPluginFn for Mail {
.map_err(|e| PluginError::GeneralError(format!("Invalid mail config: {}", e)))?;
if let Err(errors) = config.validate() {
return Err(PluginError::GeneralError(
format!("Mail config validation failed: {:?}", errors)
));
return Err(PluginError::GeneralError(format!(
"Mail config validation failed: {:?}",
errors
)));
}
let template_data = prepare_template_data(ctx);
let (subject_template, body_template_html, body_template_text) = match config.schema {
Schema::Default => {
let subject = config.subject.as_deref()
let subject = config
.subject
.as_deref()
.unwrap_or(DefaultTemplates::SUBJECT);
let body_html = config.body.as_deref()
let body_html = config
.body
.as_deref()
.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 => {
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)))?;
let body_html = render_template(&body_template_html, &template_data)
.map_err(|e| PluginError::GeneralError(format!("Failed to render body_html: {}", e)))?;
let body_text = render_template(&body_template_text, &template_data)
.map_err(|e| PluginError::GeneralError(format!("Failed to render body_text(?): {}", e)))?;
let body_text = render_template(&body_template_text, &template_data).map_err(|e| {
PluginError::GeneralError(format!("Failed to render body_text(?): {}", e))
})?;
let email = build_email(&config, subject, body_html, body_text)?;
@@ -71,43 +81,48 @@ fn build_email(
body_text: String,
) -> Result<Message, PluginError> {
let from_str = config.from.to_string();
let from: lettre::message::Mailbox = from_str.parse()
.map_err(|e| PluginError::GeneralError(format!("Invalid from address '{}': {}", from_str, e)))?;
let from: lettre::message::Mailbox = from_str.parse().map_err(|e| {
PluginError::GeneralError(format!("Invalid from address '{}': {}", from_str, e))
})?;
let to_addrs: Vec<String> = config.to.to_vec();
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()
.from(from)
.subject(subject);
let mut message_builder = Message::builder().from(from).subject(subject);
for addr in &to_addrs {
let mailbox: lettre::message::Mailbox = addr.parse()
.map_err(|e| PluginError::GeneralError(format!("Invalid to address '{}': {}", addr, e)))?;
let mailbox: lettre::message::Mailbox = addr.parse().map_err(|e| {
PluginError::GeneralError(format!("Invalid to address '{}': {}", addr, e))
})?;
message_builder = message_builder.to(mailbox);
}
if let Some(cc) = &config.cc {
for addr in cc.to_vec() {
let mailbox: lettre::message::Mailbox = addr.parse()
.map_err(|e| PluginError::GeneralError(format!("Invalid cc address '{}': {}", addr, e)))?;
let mailbox: lettre::message::Mailbox = addr.parse().map_err(|e| {
PluginError::GeneralError(format!("Invalid cc address '{}': {}", addr, e))
})?;
message_builder = message_builder.cc(mailbox);
}
}
if let Some(bcc) = &config.bcc {
for addr in bcc.to_vec() {
let mailbox: lettre::message::Mailbox = addr.parse()
.map_err(|e| PluginError::GeneralError(format!("Invalid bcc address '{}': {}", addr, e)))?;
let mailbox: lettre::message::Mailbox = addr.parse().map_err(|e| {
PluginError::GeneralError(format!("Invalid bcc address '{}': {}", addr, e))
})?;
message_builder = message_builder.bcc(mailbox);
}
}
if let Some(reply_to) = &config.reply_to {
let mailbox: lettre::message::Mailbox = reply_to.parse()
.map_err(|e| PluginError::GeneralError(format!("Invalid reply-to address '{}': {}", reply_to, e)))?;
let mailbox: lettre::message::Mailbox = reply_to.parse().map_err(|e| {
PluginError::GeneralError(format!("Invalid reply-to address '{}': {}", reply_to, e))
})?;
message_builder = message_builder.reply_to(mailbox);
}
@@ -117,23 +132,20 @@ fn build_email(
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_PLAIN)
.body(body_text)
.body(body_text),
)
.singlepart(
SinglePart::builder()
.header(header::ContentType::TEXT_HTML)
.body(body_html)
)
.body(body_html),
),
)
.map_err(|e| PluginError::GeneralError(format!("Failed to build email: {}", e)))?;
Ok(message)
}
async fn send_email(
config: &MailConfig,
email: Message,
) -> Result<(), PluginError> {
async fn send_email(config: &MailConfig, email: Message) -> Result<(), PluginError> {
let connect_timeout = if config.smtp.connect_timeout_ms == 0 {
None
} else {
@@ -162,10 +174,13 @@ async fn send_email(
match config.smtp.auth.auth_type {
AuthType::Password => {
log::debug!("Using password auth");
transport_builder = transport_builder.credentials((
config.smtp.auth.username.clone(),
config.smtp.auth.password.clone()
).into());
transport_builder = transport_builder.credentials(
(
config.smtp.auth.username.clone(),
config.smtp.auth.password.clone(),
)
.into(),
);
}
AuthType::None => {
log::debug!("Using no auth");
@@ -1,5 +1,5 @@
use crate::finalize::plugin::PluginMetadata;
use crate::finalize::plugin::PluginError;
use crate::finalize::plugin::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
use async_trait::async_trait;
@@ -1,5 +1,5 @@
use crate::finalize::plugin::PluginMetadata;
use crate::finalize::plugin::PluginError;
use crate::finalize::plugin::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
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::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
use async_trait::async_trait;
+107 -38
View File
@@ -1,5 +1,5 @@
use crate::constant::FINALIZE_SHELL_ENVPREFIX;
use crate::Engine;
use crate::constant::FINALIZE_SHELL_ENVPREFIX;
use crate::finalize::plugin::PluginError;
use crate::finalize::plugin::PluginMetadata;
use crate::{EventSender, ExecutionContext, finalize::plugin::AsyncPluginFn};
@@ -8,7 +8,6 @@ use std::collections::HashMap;
pub struct Shell;
/// Parses a YAML value used as a mapping key into a string.
///
/// 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);
/// // 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();
match argument {
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) => {
envvars.insert(prefix.to_string(), value.to_string());
},
}
serde_yaml::Value::Tagged(tagged_value) => {
let subenvvars = convert_argument(prefix, tagged_value.value, in_sequence);
envvars.extend(subenvvars);
}
serde_yaml::Value::Sequence(seq) => {
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;
}
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);
if item.is_mapping() {
has_mapping = true;
}
else {
array.push_str(subenvvars.get(new_prefix.as_str()).map(|v| v.as_str()).unwrap_or_default());
} else {
array.push_str(
subenvvars
.get(new_prefix.as_str())
.map(|v| v.as_str())
.unwrap_or_default(),
);
}
_envvars.extend(subenvvars);
}
if has_mapping {
envvars.extend(_envvars);
}
else {
} else {
array.push_str(")");
envvars.insert(prefix.to_string(), array);
}
@@ -148,7 +157,13 @@ impl AsyncPluginFn for Shell {
let envvars = convert_argument(FINALIZE_SHELL_ENVPREFIX, argument, false);
let mut local_ctx = ctx.clone();
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(())
}
}
@@ -205,27 +220,36 @@ mod tests {
#[test]
fn test_convert_argument_nested_mapping() {
let yaml = serde_yaml::from_str("
let yaml = serde_yaml::from_str(
"
from:
host: smtp.example.com
port: 587
").unwrap();
",
)
.unwrap();
let result = convert_argument("CFG", yaml, false);
// 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()));
}
#[test]
fn test_convert_argument_different_types() {
let yaml = serde_yaml::from_str("
let yaml = serde_yaml::from_str(
"
enabled: true
count: 42
ratio: 3.14
name: test
empty: ~
").unwrap();
",
)
.unwrap();
let result = convert_argument("VAR", yaml, false);
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 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]
fn test_convert_argument_sequence_with_mapping() {
let yaml = serde_yaml::from_str("
let yaml = serde_yaml::from_str(
"
env:
- name: KEY1
value: val1
- name: KEY2
value: val2
").unwrap();
",
)
.unwrap();
let result = convert_argument("CFG", yaml, false);
assert_eq!(result.get("CFG_ENV_0_NAME"), Some(&"KEY1".to_string()));
@@ -263,13 +293,16 @@ env:
#[test]
fn test_convert_argument_nested_sequence() {
let yaml = serde_yaml::from_str("
let yaml = serde_yaml::from_str(
"
matrix:
- - a
- b
- - c
- d
").unwrap();
",
)
.unwrap();
let result = convert_argument("CFG", yaml, false);
// Nested sequences are not supported (line 50-52 logs warning and skips)
@@ -297,10 +330,13 @@ matrix:
#[test]
fn test_convert_argument_null_value() {
let yaml = serde_yaml::from_str("
let yaml = serde_yaml::from_str(
"
host: docker.io
token: ~
").unwrap();
",
)
.unwrap();
let result = convert_argument("CFG", yaml, false);
// Keys are uppercased: CFG_HOST
@@ -311,10 +347,13 @@ token: ~
#[test]
fn test_convert_argument_number_keys() {
let yaml = serde_yaml::from_str("
let yaml = serde_yaml::from_str(
"
0: first
1: second
").unwrap();
",
)
.unwrap();
let result = convert_argument("CFG", yaml, false);
// Number keys are preserved but prefixed: CFG_0, CFG_1
@@ -324,7 +363,8 @@ token: ~
#[test]
fn test_convert_argument_complex_nested() {
let yaml = serde_yaml::from_str("
let yaml = serde_yaml::from_str(
"
plugin:
docker-push:
host: docker.io
@@ -335,38 +375,67 @@ plugin:
tags:
- latest
- v1.0
").unwrap();
",
)
.unwrap();
let result = convert_argument("CFG", yaml, false);
// All keys uppercased for shell environment variables
assert_eq!(result.get("CFG_PLUGIN_DOCKER-PUSH_HOST"), Some(&"docker.io".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()));
assert_eq!(
result.get("CFG_PLUGIN_DOCKER-PUSH_HOST"),
Some(&"docker.io".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]
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
path: /workspace/output.tar.gz
token: 'Bearer abc123'
").unwrap();
",
)
.unwrap();
let result = convert_argument("CFG", yaml, false);
// 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!(result.get("CFG_PATH"), Some(&"/workspace/output.tar.gz".to_string()));
assert_eq!(
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()));
}
#[test]
fn test_convert_argument_quoted_strings_preserved() {
let yaml = serde_yaml::from_str("
let yaml = serde_yaml::from_str(
"
msg: 'Hello World'
cmd: \"echo 'test'\"
").unwrap();
",
)
.unwrap();
let result = convert_argument("CFG", yaml, false);
// Keys uppercased: CFG_MSG, CFG_CMD
+3 -1
View File
@@ -45,7 +45,9 @@ pub async fn hook(
hook_cmd.argument.clone(),
&plugin_ctx,
&event_tx,
).await.map_err(|e| {
)
.await
.map_err(|e| {
log::error!("Plugin hook {} failed: {:?}", index, e);
ExecutionError::ExecutionFailed(e.to_string())
})?;
+3 -3
View File
@@ -1,17 +1,17 @@
// Library crate for workshop-executor
// Selective exports: only modules used by external callers or tests
pub mod bake;
pub mod constant;
pub mod daemon;
pub mod engine;
pub mod error;
pub mod finalize;
pub mod monitor;
pub mod notify;
pub mod prebake;
pub mod bake;
pub mod socket;
pub mod types;
pub mod constant;
pub mod notify;
// External crates re-exports
pub use config;
+22 -15
View File
@@ -1,14 +1,13 @@
use std::path::Path;
use env_logger;
use workshop_baker::{
bake::bake,
cli::{Cli, Commands, Parser},
daemon,
engine::{EventSender, ExecutionContext},
error::PrebakeError,
prebake::prebake,
bake::bake,
finalize::finalize,
prebake::prebake,
};
#[tokio::main]
@@ -22,7 +21,7 @@ async fn main() {
Some(Commands::Monitor { group: _ }) => {
unimplemented!("executor-monitor should be moved to daemon instead.")
}
Some(Commands::Client { config:_ }) => {
Some(Commands::Client { config: _ }) => {
unimplemented!("Client mode not yet implemented.")
}
Some(Commands::Prebake { config }) => {
@@ -40,9 +39,11 @@ async fn main() {
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 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;
@@ -52,12 +53,14 @@ async fn main() {
std::process::exit(
e.downcast_ref::<PrebakeError>()
.map(|pe| pe.exit_code())
.unwrap_or(1)
.unwrap_or(1),
);
}
}
Some(Commands::Bake {
script, bake_base, prebake
script,
bake_base,
prebake,
}) => {
log::info!("Running in standalone mode.");
let mut ctx = ExecutionContext {
@@ -73,9 +76,11 @@ async fn main() {
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 receiver_handle = tokio::spawn(workshop_baker::prebake::event::event_receiver(
event_rx,
pipeline_name,
build_id,
));
let result = bake(script, bake_base, prebake, &cli, &mut ctx, event_tx).await;
drop(ctx);
let _ = receiver_handle.await;
@@ -100,9 +105,11 @@ async fn main() {
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 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;
+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 {
let exponent = (event.max_lives - event.lives) as i32;
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);
}
let multiplier = NOTIFY_TEMPORARY_DELAY_FACTOR.powi(exponent);
let delay = multiplier * NOTIFY_TEMPORARY_DELAY_TIME;
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);
}
std::time::Duration::from_secs_f64(delay)
+1 -1
View File
@@ -1,7 +1,7 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum NotifyError{
pub enum NotifyError {
#[error("Finalize config validation failed: {0:?}")]
FinalizeValidationError(Vec<String>),
+47 -16
View File
@@ -27,16 +27,16 @@ pub mod security;
pub mod stage;
use crate::{
ExecutionContext,
cli::Cli,
constant::STANDALONE_STATUS_DIR,
engine::EventSender,
error::PrebakeError,
ExecutionContext,
prebake::{security::get_drop_after, stage::PrebakeStage},
types::buildstatus::{write_stage_status, StageInfo, StagePhase, StageResult},
constant::STANDALONE_STATUS_DIR,
types::buildstatus::{StageInfo, StagePhase, StageResult, write_stage_status},
};
use std::path::Path;
use chrono::Utc;
use std::path::Path;
pub use config::PrebakeConfig;
pub use security::prebake_drop_privilege;
@@ -92,7 +92,7 @@ pub async fn prebake(
prebake_path: &Path,
cli: &Cli,
stage: Option<PrebakeStage>,
mut ctx: &mut ExecutionContext,
mut ctx: &mut ExecutionContext,
event_tx: EventSender,
) -> anyhow::Result<()> {
// Initialize
@@ -131,7 +131,11 @@ mut ctx: &mut ExecutionContext,
ctx.task_id = format!("{}-{}-bootstrap", cli.pipeline, cli.build_id);
let osinfo = os_info::get();
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(
STANDALONE_STATUS_DIR,
StageInfo {
@@ -139,7 +143,9 @@ mut ctx: &mut ExecutionContext,
phase: StagePhase::Prebake,
substage: "bootstrap".to_string(),
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,
finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()),
@@ -159,7 +165,11 @@ mut ctx: &mut ExecutionContext,
None => None,
};
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(
STANDALONE_STATUS_DIR,
StageInfo {
@@ -167,7 +177,9 @@ mut ctx: &mut ExecutionContext,
phase: StagePhase::Prebake,
substage: "earlyhook".to_string(),
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,
finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()),
@@ -240,8 +252,13 @@ mut ctx: &mut ExecutionContext,
log::info!("Running PBStage: DepsSystem");
prebake_drop_privilege(PrebakeStage::DepsSystem, dropafter, target_user, &mut ctx)?;
ctx.task_id = format!("{}-{}-depssystem", cli.pipeline, cli.build_id);
let result = stage::depssystem::depssystem(&system_config, &ctx, event_tx.clone()).await;
let stage_result = if result.is_ok() { StageResult::Success } else { StageResult::Failure };
let result =
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(
STANDALONE_STATUS_DIR,
StageInfo {
@@ -249,7 +266,9 @@ mut ctx: &mut ExecutionContext,
phase: StagePhase::Prebake,
substage: "depssystem".to_string(),
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,
finished_at: Utc::now(),
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)?;
ctx.task_id = format!("{}-{}-depsuser", cli.pipeline, cli.build_id);
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(
STANDALONE_STATUS_DIR,
StageInfo {
@@ -274,7 +297,9 @@ mut ctx: &mut ExecutionContext,
phase: StagePhase::Prebake,
substage: "depsuser".to_string(),
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,
finished_at: Utc::now(),
error_message: result.as_ref().err().map(|e| e.to_string()),
@@ -295,7 +320,11 @@ mut ctx: &mut ExecutionContext,
None => None,
};
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(
STANDALONE_STATUS_DIR,
StageInfo {
@@ -303,7 +332,9 @@ mut ctx: &mut ExecutionContext,
phase: StagePhase::Prebake,
substage: "latehook".to_string(),
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,
finished_at: Utc::now(),
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 } => {
log::debug!(
"[{}] [{}] [{}] Task started: {}",
pipeline_name, build_id, timestamp, task_id
pipeline_name,
build_id,
timestamp,
task_id
);
}
ExecutionEvent::OutputChunk { task_id, stream, data } => {
match stream {
StreamType::Stdout => print!("[{}] [{}] [stdout] {}", pipeline_name, task_id, data),
StreamType::Stderr => eprint!("[{}] [{}] [stderr] {}", pipeline_name, task_id, data),
ExecutionEvent::OutputChunk {
task_id,
stream,
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 } => {
log::debug!(
"[{}] [{}] 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 } => {
log::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;
pub mod bootstrap;
pub mod environment;
pub mod hook;
pub mod depssystem;
pub mod depsuser;
pub mod environment;
pub mod hook;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum PrebakeStage {
@@ -62,7 +62,10 @@ pub async fn bootstrap(
log::debug!("Generated bootstrap script:\n{}", script);
let mut file = File::create(&bootstrap_path)?;
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 {
// Requires PIP(Pipeline Implicit Parameters) to be implemented
unimplemented!("Not supporting custom bootstrap.sh at this time");
@@ -41,13 +41,12 @@ pub async fn depssystem(
log::info!("Changing mirror to: {}", mirror);
let cmds = package_manager.change_mirror(mirror, Some(&osinfo));
for cmd in cmds {
log::debug!("Executing: {:?}", &cmd);
engine
.execute(cmd, ctx, &event_tx)
.await
.map_err(|e| DependencyError::StageFailed {
stage: "MirrorChange",
source: e,
log::debug!("Executing: {:?}", &cmd);
engine.execute(cmd, ctx, &event_tx).await.map_err(|e| {
DependencyError::StageFailed {
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) {
Some(pm) => pm,
None => {
log::warn!("Unknown user package manager: {}, skipping sysdep collection", name);
log::warn!(
"Unknown user package manager: {}, skipping sysdep collection",
name
);
continue;
}
};
@@ -1,4 +1,4 @@
use crate::prebake::config::{PrebakeConfig};
use crate::prebake::config::PrebakeConfig;
use crate::types::builderconfig::BuilderType;
use config::Config;
+1 -1
View File
@@ -12,8 +12,8 @@
//! Each hook command supports custom working directories, environment variables,
//! and timeout configurations.
use crate::engine::types::DeltaExecutionContext;
use crate::ExecutionContext;
use crate::engine::types::DeltaExecutionContext;
use crate::engine::{Engine, EventSender, ExecutionError};
use crate::types::command::CustomCommand;
use std::path::PathBuf;
+1 -1
View File
@@ -1,6 +1,6 @@
pub mod architecture;
pub mod buildstatus;
pub mod builderconfig;
pub mod buildstatus;
pub mod cache;
pub mod command;
pub mod memsize;
+2 -2
View File
@@ -153,11 +153,11 @@ pub fn parse_mem_size(s: &str) -> Result<MemSize, String> {
"k" | "K" => 1000,
"m" | "M" => 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,
"mi" | "mI" | "Mi" | "MI" => 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)),
};
+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;
@@ -6,8 +6,8 @@
use std::collections::HashMap;
use std::time::Duration;
use workshop_baker::engine::{ExecutionContext, ExecutionEvent};
use workshop_baker::finalize::plugin::PluginMap;
use workshop_baker::finalize::stage::hook::hook;
use workshop_baker::finalize::plugin::{PluginMap};
use workshop_baker::types::command::CustomCommand;
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);
(Some(tx), rx)
}
@@ -146,7 +149,10 @@ async fn test_multiple_hooks_execute_sequentially() {
];
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]
@@ -5,9 +5,9 @@
use std::collections::HashMap;
use std::time::Duration;
use workshop_baker::cli::Cli;
use workshop_baker::engine::{ExecutionContext, ExecutionEvent};
use workshop_baker::finalize::finalize;
use workshop_baker::cli::Cli;
fn create_test_context() -> 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);
(Some(tx), rx)
}
@@ -76,7 +79,11 @@ hooks:
let _ = consumer_handle.await;
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]
@@ -94,16 +101,19 @@ version: "0.0.1"
let mut ctx = create_test_context();
let (event_tx, mut event_rx) = create_test_event_channel();
let consumer_handle = tokio::spawn(async move {
while let Some(_event) = event_rx.recv().await {}
});
let consumer_handle =
tokio::spawn(async move { while let Some(_event) = event_rx.recv().await {} });
let result = finalize(&finalize_path, &cli, &mut ctx, event_tx).await;
let _ = consumer_handle.await;
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]
@@ -130,9 +140,8 @@ hooks:
let mut ctx = create_test_context();
let (event_tx, mut event_rx) = create_test_event_channel();
let consumer_handle = tokio::spawn(async move {
while let Some(_event) = event_rx.recv().await {}
});
let consumer_handle =
tokio::spawn(async move { while let Some(_event) = event_rx.recv().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
// 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"
);
}