This commit is contained in:
@@ -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,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
@@ -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,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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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,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(())
|
||||
}
|
||||
|
||||
@@ -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,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::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
|
||||
|
||||
@@ -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())
|
||||
})?;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum NotifyError{
|
||||
pub enum NotifyError {
|
||||
#[error("Finalize config validation failed: {0:?}")]
|
||||
FinalizeValidationError(Vec<String>),
|
||||
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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,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,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
pub mod architecture;
|
||||
pub mod buildstatus;
|
||||
pub mod builderconfig;
|
||||
pub mod buildstatus;
|
||||
pub mod cache;
|
||||
pub mod command;
|
||||
pub mod memsize;
|
||||
|
||||
@@ -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,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"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user