making linters happy
Some checks failed
CI / Rust Blocking Checks (push) Failing after 22s
CI / Web Blocking Checks (push) Failing after 26s
CI / Security Blocking Checks (push) Successful in 9s
CI / Web Advisory Checks (push) Successful in 32s
CI / Security Advisory Checks (push) Has been cancelled
Some checks failed
CI / Rust Blocking Checks (push) Failing after 22s
CI / Web Blocking Checks (push) Failing after 26s
CI / Security Blocking Checks (push) Successful in 9s
CI / Web Advisory Checks (push) Successful in 32s
CI / Security Advisory Checks (push) Has been cancelled
This commit is contained in:
@@ -107,7 +107,7 @@ impl HistoryQueryParams {
|
||||
pub fn to_repo_params(
|
||||
&self,
|
||||
) -> attune_common::repositories::entity_history::HistoryQueryParams {
|
||||
let limit = (self.page_size.min(1000).max(1)) as i64;
|
||||
let limit = (self.page_size.clamp(1, 1000)) as i64;
|
||||
let offset = ((self.page.saturating_sub(1)) as i64) * limit;
|
||||
|
||||
attune_common::repositories::entity_history::HistoryQueryParams {
|
||||
|
||||
@@ -160,7 +160,10 @@ pub async fn create_action(
|
||||
request.validate()?;
|
||||
|
||||
// Check if action with same ref already exists
|
||||
if let Some(_) = ActionRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
||||
if ActionRepository::find_by_ref(&state.db, &request.r#ref)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ApiError::Conflict(format!(
|
||||
"Action with ref '{}' already exists",
|
||||
request.r#ref
|
||||
|
||||
@@ -1877,7 +1877,7 @@ pub async fn stream_artifact(
|
||||
Some((
|
||||
Ok(Event::default()
|
||||
.event("content")
|
||||
.data(String::from_utf8_lossy(&buf).into_owned())),
|
||||
.data(String::from_utf8_lossy(&buf))),
|
||||
TailState::Tailing {
|
||||
full_path,
|
||||
file_path,
|
||||
@@ -1967,7 +1967,7 @@ pub async fn stream_artifact(
|
||||
Some((
|
||||
Ok(Event::default()
|
||||
.event("append")
|
||||
.data(String::from_utf8_lossy(&new_buf).into_owned())),
|
||||
.data(String::from_utf8_lossy(&new_buf))),
|
||||
TailState::Tailing {
|
||||
full_path,
|
||||
file_path,
|
||||
|
||||
@@ -158,7 +158,10 @@ pub async fn register(
|
||||
.map_err(|e| ApiError::ValidationError(format!("Invalid registration request: {}", e)))?;
|
||||
|
||||
// Check if login already exists
|
||||
if let Some(_) = IdentityRepository::find_by_login(&state.db, &payload.login).await? {
|
||||
if IdentityRepository::find_by_login(&state.db, &payload.login)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ApiError::Conflict(format!(
|
||||
"Identity with login '{}' already exists",
|
||||
payload.login
|
||||
|
||||
@@ -138,7 +138,10 @@ pub async fn create_key(
|
||||
request.validate()?;
|
||||
|
||||
// Check if key with same ref already exists
|
||||
if let Some(_) = KeyRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
||||
if KeyRepository::find_by_ref(&state.db, &request.r#ref)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ApiError::Conflict(format!(
|
||||
"Key with ref '{}' already exists",
|
||||
request.r#ref
|
||||
|
||||
@@ -585,10 +585,9 @@ pub async fn upload_pack(
|
||||
skip_tests,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
.inspect_err(|_e| {
|
||||
// Clean up permanent storage on failure
|
||||
let _ = std::fs::remove_dir_all(&final_path);
|
||||
e
|
||||
})?;
|
||||
|
||||
// Fetch the registered pack
|
||||
@@ -947,8 +946,8 @@ async fn register_pack_internal(
|
||||
// a best-effort optimisation for non-Docker (bare-metal) setups
|
||||
// where the API host has the interpreter available.
|
||||
if let Some(ref env_cfg) = exec_config.environment {
|
||||
if env_cfg.env_type != "none" {
|
||||
if !env_dir.exists() && !env_cfg.create_command.is_empty() {
|
||||
if env_cfg.env_type != "none"
|
||||
&& !env_dir.exists() && !env_cfg.create_command.is_empty() {
|
||||
// Ensure parent directories exist
|
||||
if let Some(parent) = env_dir.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
@@ -1002,7 +1001,6 @@ async fn register_pack_internal(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to install dependencies if manifest file exists.
|
||||
@@ -1107,9 +1105,7 @@ async fn register_pack_internal(
|
||||
if is_new_pack {
|
||||
let _ = PackRepository::delete(&state.db, pack.id).await;
|
||||
}
|
||||
return Err(ApiError::BadRequest(format!(
|
||||
"Pack registration failed: tests did not pass. Use force=true to register anyway."
|
||||
)));
|
||||
return Err(ApiError::BadRequest("Pack registration failed: tests did not pass. Use force=true to register anyway.".to_string()));
|
||||
}
|
||||
|
||||
if !test_passed && force {
|
||||
@@ -1359,10 +1355,9 @@ pub async fn install_pack(
|
||||
request.skip_tests,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
.inspect_err(|_e| {
|
||||
// Clean up the permanent storage if registration fails
|
||||
let _ = std::fs::remove_dir_all(&final_path);
|
||||
e
|
||||
})?;
|
||||
|
||||
// Fetch the registered pack
|
||||
|
||||
@@ -290,7 +290,10 @@ pub async fn create_rule(
|
||||
request.validate()?;
|
||||
|
||||
// Check if rule with same ref already exists
|
||||
if let Some(_) = RuleRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
||||
if RuleRepository::find_by_ref(&state.db, &request.r#ref)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ApiError::Conflict(format!(
|
||||
"Rule with ref '{}' already exists",
|
||||
request.r#ref
|
||||
|
||||
@@ -198,7 +198,10 @@ pub async fn create_trigger(
|
||||
request.validate()?;
|
||||
|
||||
// Check if trigger with same ref already exists
|
||||
if let Some(_) = TriggerRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
||||
if TriggerRepository::find_by_ref(&state.db, &request.r#ref)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ApiError::Conflict(format!(
|
||||
"Trigger with ref '{}' already exists",
|
||||
request.r#ref
|
||||
@@ -623,7 +626,10 @@ pub async fn create_sensor(
|
||||
request.validate()?;
|
||||
|
||||
// Check if sensor with same ref already exists
|
||||
if let Some(_) = SensorRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
||||
if SensorRepository::find_by_ref(&state.db, &request.r#ref)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ApiError::Conflict(format!(
|
||||
"Sensor with ref '{}' already exists",
|
||||
request.r#ref
|
||||
|
||||
@@ -714,6 +714,7 @@ pub async fn receive_webhook(
|
||||
}
|
||||
|
||||
// Helper function to log webhook events
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn log_webhook_event(
|
||||
state: &AppState,
|
||||
trigger: &attune_common::models::trigger::Trigger,
|
||||
@@ -753,6 +754,7 @@ async fn log_webhook_event(
|
||||
}
|
||||
|
||||
// Helper function to log failures when trigger is not found
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn log_webhook_failure(
|
||||
_state: &AppState,
|
||||
webhook_key: String,
|
||||
|
||||
@@ -181,7 +181,10 @@ pub async fn create_workflow(
|
||||
request.validate()?;
|
||||
|
||||
// Check if workflow with same ref already exists
|
||||
if let Some(_) = WorkflowDefinitionRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
||||
if WorkflowDefinitionRepository::find_by_ref(&state.db, &request.r#ref)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ApiError::Conflict(format!(
|
||||
"Workflow with ref '{}' already exists",
|
||||
request.r#ref
|
||||
@@ -519,7 +522,7 @@ pub async fn update_workflow_file(
|
||||
|
||||
/// Write a workflow definition to disk as YAML
|
||||
async fn write_workflow_yaml(
|
||||
packs_base_dir: &PathBuf,
|
||||
packs_base_dir: &std::path::Path,
|
||||
pack_ref: &str,
|
||||
request: &SaveWorkflowFileRequest,
|
||||
) -> Result<(), ApiError> {
|
||||
@@ -630,9 +633,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
||||
"# Action definition for workflow {}.{}",
|
||||
pack_ref, request.name
|
||||
));
|
||||
lines.push(format!(
|
||||
"# The workflow graph (tasks, transitions, variables) is in:"
|
||||
));
|
||||
lines.push("# The workflow graph (tasks, transitions, variables) is in:".to_string());
|
||||
lines.push(format!(
|
||||
"# actions/workflows/{}.workflow.yaml",
|
||||
request.name
|
||||
@@ -646,7 +647,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
||||
lines.push(format!("description: \"{}\"", desc.replace('"', "\\\"")));
|
||||
}
|
||||
}
|
||||
lines.push(format!("enabled: true"));
|
||||
lines.push("enabled: true".to_string());
|
||||
lines.push(format!(
|
||||
"workflow_file: workflows/{}.workflow.yaml",
|
||||
request.name
|
||||
@@ -658,7 +659,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
||||
if !obj.is_empty() {
|
||||
lines.push(String::new());
|
||||
let params_yaml = serde_yaml_ng::to_string(params).unwrap_or_default();
|
||||
lines.push(format!("parameters:"));
|
||||
lines.push("parameters:".to_string());
|
||||
// Indent the YAML output under `parameters:`
|
||||
for line in params_yaml.lines() {
|
||||
lines.push(format!(" {}", line));
|
||||
@@ -673,7 +674,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
||||
if !obj.is_empty() {
|
||||
lines.push(String::new());
|
||||
let output_yaml = serde_yaml_ng::to_string(output).unwrap_or_default();
|
||||
lines.push(format!("output:"));
|
||||
lines.push("output:".to_string());
|
||||
for line in output_yaml.lines() {
|
||||
lines.push(format!(" {}", line));
|
||||
}
|
||||
@@ -685,7 +686,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
||||
if let Some(ref tags) = request.tags {
|
||||
if !tags.is_empty() {
|
||||
lines.push(String::new());
|
||||
lines.push(format!("tags:"));
|
||||
lines.push("tags:".to_string());
|
||||
for tag in tags {
|
||||
lines.push(format!(" - {}", tag));
|
||||
}
|
||||
@@ -701,6 +702,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
||||
/// This ensures the workflow appears in action lists and the action palette in the
|
||||
/// workflow builder. The action is linked to the workflow definition via the
|
||||
/// `workflow_def` FK.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn create_companion_action(
|
||||
db: &sqlx::PgPool,
|
||||
workflow_ref: &str,
|
||||
@@ -835,6 +837,7 @@ async fn update_companion_action(
|
||||
///
|
||||
/// If the action already exists, update it. If it doesn't exist (e.g., for workflows
|
||||
/// created before the companion-action fix), create it.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn ensure_companion_action(
|
||||
db: &sqlx::PgPool,
|
||||
workflow_def_id: i64,
|
||||
|
||||
@@ -362,11 +362,11 @@ impl Drop for TestContext {
|
||||
let test_packs_dir = self.test_packs_dir.clone();
|
||||
|
||||
// Spawn cleanup task in background
|
||||
let _ = tokio::spawn(async move {
|
||||
drop(tokio::spawn(async move {
|
||||
if let Err(e) = cleanup_test_schema(&schema).await {
|
||||
eprintln!("Failed to cleanup test schema {}: {}", schema, e);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
// Cleanup the test packs directory synchronously
|
||||
let _ = std::fs::remove_dir_all(&test_packs_dir);
|
||||
|
||||
@@ -64,7 +64,7 @@ async fn test_sync_pack_workflows_endpoint() {
|
||||
// Use unique pack name to avoid conflicts in parallel tests
|
||||
let pack_name = format!(
|
||||
"test_pack_{}",
|
||||
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
|
||||
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
|
||||
);
|
||||
|
||||
// Create temporary directory for pack workflows
|
||||
@@ -100,7 +100,7 @@ async fn test_validate_pack_workflows_endpoint() {
|
||||
// Use unique pack name to avoid conflicts in parallel tests
|
||||
let pack_name = format!(
|
||||
"test_pack_{}",
|
||||
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
|
||||
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
|
||||
);
|
||||
|
||||
// Create pack in database
|
||||
@@ -158,7 +158,7 @@ async fn test_sync_workflows_requires_authentication() {
|
||||
// Use unique pack name to avoid conflicts in parallel tests
|
||||
let pack_name = format!(
|
||||
"test_pack_{}",
|
||||
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
|
||||
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
|
||||
);
|
||||
|
||||
// Create pack in database
|
||||
@@ -185,7 +185,7 @@ async fn test_validate_workflows_requires_authentication() {
|
||||
// Use unique pack name to avoid conflicts in parallel tests
|
||||
let pack_name = format!(
|
||||
"test_pack_{}",
|
||||
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
|
||||
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
|
||||
);
|
||||
|
||||
// Create pack in database
|
||||
|
||||
@@ -14,7 +14,7 @@ use helpers::*;
|
||||
fn unique_pack_name() -> String {
|
||||
format!(
|
||||
"test_pack_{}",
|
||||
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
|
||||
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -180,8 +180,8 @@ impl ApiClient {
|
||||
let req = self.attach_body(self.build_request(method.clone(), path), body);
|
||||
let response = req.send().await.context("Failed to send request to API")?;
|
||||
|
||||
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some() {
|
||||
if self.refresh_auth_token().await? {
|
||||
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some()
|
||||
&& self.refresh_auth_token().await? {
|
||||
// Retry with new token
|
||||
let req = self.attach_body(self.build_request(method, path), body);
|
||||
let response = req
|
||||
@@ -190,7 +190,6 @@ impl ApiClient {
|
||||
.context("Failed to send request to API (retry)")?;
|
||||
return self.handle_response(response).await;
|
||||
}
|
||||
}
|
||||
|
||||
self.handle_response(response).await
|
||||
}
|
||||
@@ -205,8 +204,8 @@ impl ApiClient {
|
||||
let req = self.attach_body(self.build_request(method.clone(), path), body);
|
||||
let response = req.send().await.context("Failed to send request to API")?;
|
||||
|
||||
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some() {
|
||||
if self.refresh_auth_token().await? {
|
||||
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some()
|
||||
&& self.refresh_auth_token().await? {
|
||||
let req = self.attach_body(self.build_request(method, path), body);
|
||||
let response = req
|
||||
.send()
|
||||
@@ -214,7 +213,6 @@ impl ApiClient {
|
||||
.context("Failed to send request to API (retry)")?;
|
||||
return self.handle_empty_response(response).await;
|
||||
}
|
||||
}
|
||||
|
||||
self.handle_empty_response(response).await
|
||||
}
|
||||
@@ -393,8 +391,8 @@ impl ApiClient {
|
||||
.await
|
||||
.context("Failed to send multipart request to API")?;
|
||||
|
||||
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some() {
|
||||
if self.refresh_auth_token().await? {
|
||||
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some()
|
||||
&& self.refresh_auth_token().await? {
|
||||
// Retry with new token
|
||||
let req = build_multipart_request(self, &file_bytes)?;
|
||||
let response = req
|
||||
@@ -403,7 +401,6 @@ impl ApiClient {
|
||||
.context("Failed to send multipart request to API (retry)")?;
|
||||
return self.handle_response(response).await;
|
||||
}
|
||||
}
|
||||
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
@@ -314,6 +314,7 @@ async fn handle_show(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_update(
|
||||
action_ref: String,
|
||||
label: Option<String>,
|
||||
@@ -415,6 +416,7 @@ async fn handle_delete(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_execute(
|
||||
action_ref: String,
|
||||
params: Vec<String>,
|
||||
@@ -454,11 +456,8 @@ async fn handle_execute(
|
||||
parameters,
|
||||
};
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Table => {
|
||||
output::print_info(&format!("Executing action: {}", action_ref));
|
||||
}
|
||||
_ => {}
|
||||
if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!("Executing action: {}", action_ref));
|
||||
}
|
||||
|
||||
let path = "/executions/execute".to_string();
|
||||
@@ -481,14 +480,11 @@ async fn handle_execute(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Table => {
|
||||
output::print_info(&format!(
|
||||
"Waiting for execution {} to complete...",
|
||||
execution.id
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!(
|
||||
"Waiting for execution {} to complete...",
|
||||
execution.id
|
||||
));
|
||||
}
|
||||
|
||||
let verbose = matches!(output_format, OutputFormat::Table);
|
||||
|
||||
@@ -163,6 +163,7 @@ pub async fn handle_execution_command(
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_list(
|
||||
profile: &Option<String>,
|
||||
pack: Option<String>,
|
||||
|
||||
@@ -468,7 +468,7 @@ pub async fn handle_pack_command(
|
||||
///
|
||||
/// Splits on `_`, `-`, or `.` and title-cases each word.
|
||||
fn label_from_ref(r: &str) -> String {
|
||||
r.split(|c| c == '_' || c == '-' || c == '.')
|
||||
r.split(['_', '-', '.'])
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|word| {
|
||||
let mut chars = word.chars();
|
||||
@@ -484,6 +484,7 @@ fn label_from_ref(r: &str) -> String {
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_create(
|
||||
profile: &Option<String>,
|
||||
ref_flag: Option<String>,
|
||||
@@ -725,6 +726,7 @@ async fn handle_show(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_install(
|
||||
profile: &Option<String>,
|
||||
source: String,
|
||||
@@ -742,18 +744,15 @@ async fn handle_install(
|
||||
// Detect source type
|
||||
let source_type = detect_source_type(&source, ref_spec.as_deref(), no_registry);
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Table => {
|
||||
output::print_info(&format!(
|
||||
"Installing pack from: {} ({})",
|
||||
source, source_type
|
||||
));
|
||||
output::print_info("Starting installation...");
|
||||
if skip_deps {
|
||||
output::print_info("⚠ Dependency validation will be skipped");
|
||||
}
|
||||
if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!(
|
||||
"Installing pack from: {} ({})",
|
||||
source, source_type
|
||||
));
|
||||
output::print_info("Starting installation...");
|
||||
if skip_deps {
|
||||
output::print_info("⚠ Dependency validation will be skipped");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let request = InstallPackRequest {
|
||||
@@ -880,12 +879,9 @@ async fn handle_upload(
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown");
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Table => {
|
||||
output::print_info(&format!("Uploading pack '{}' from: {}", pack_ref, path));
|
||||
output::print_info("Creating archive...");
|
||||
}
|
||||
_ => {}
|
||||
if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!("Uploading pack '{}' from: {}", pack_ref, path));
|
||||
output::print_info("Creating archive...");
|
||||
}
|
||||
|
||||
// Build an in-memory tar.gz of the pack directory
|
||||
@@ -908,14 +904,11 @@ async fn handle_upload(
|
||||
|
||||
let archive_size_kb = tar_gz_bytes.len() / 1024;
|
||||
|
||||
match output_format {
|
||||
OutputFormat::Table => {
|
||||
output::print_info(&format!(
|
||||
"Archive ready ({} KB), uploading...",
|
||||
archive_size_kb
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!(
|
||||
"Archive ready ({} KB), uploading...",
|
||||
archive_size_kb
|
||||
));
|
||||
}
|
||||
|
||||
let config = CliConfig::load_with_profile(profile.as_deref())?;
|
||||
@@ -1014,25 +1007,17 @@ async fn handle_register(
|
||||
&& !path.starts_with("/app/")
|
||||
&& !path.starts_with("/packs");
|
||||
if looks_local {
|
||||
match output_format {
|
||||
OutputFormat::Table => {
|
||||
output::print_info(&format!("Registering pack from: {}", path));
|
||||
eprintln!(
|
||||
"⚠ Warning: '{}' looks like a local path. If the API is running in \
|
||||
Docker it may not be able to access this path.\n \
|
||||
Use `attune pack upload {}` instead to upload the pack directly.",
|
||||
path, path
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
match output_format {
|
||||
OutputFormat::Table => {
|
||||
output::print_info(&format!("Registering pack from: {}", path));
|
||||
}
|
||||
_ => {}
|
||||
if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!("Registering pack from: {}", path));
|
||||
eprintln!(
|
||||
"⚠ Warning: '{}' looks like a local path. If the API is running in \
|
||||
Docker it may not be able to access this path.\n \
|
||||
Use `attune pack upload {}` instead to upload the pack directly.",
|
||||
path, path
|
||||
);
|
||||
}
|
||||
} else if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!("Registering pack from: {}", path));
|
||||
}
|
||||
|
||||
let request = RegisterPackRequest {
|
||||
@@ -1173,13 +1158,10 @@ async fn handle_test(
|
||||
let executor = TestExecutor::new(pack_base_dir);
|
||||
|
||||
// Print test start message
|
||||
match output_format {
|
||||
OutputFormat::Table => {
|
||||
println!();
|
||||
output::print_section(&format!("🧪 Testing Pack: {} v{}", pack_ref, pack_version));
|
||||
println!();
|
||||
}
|
||||
_ => {}
|
||||
if output_format == OutputFormat::Table {
|
||||
println!();
|
||||
output::print_section(&format!("🧪 Testing Pack: {} v{}", pack_ref, pack_version));
|
||||
println!();
|
||||
}
|
||||
|
||||
// Execute tests
|
||||
@@ -1688,7 +1670,7 @@ async fn handle_index_entry(
|
||||
|
||||
if let Some(ref git) = git_url {
|
||||
let default_ref = format!("v{}", version);
|
||||
let ref_value = git_ref.as_ref().map(|s| s.as_str()).unwrap_or(&default_ref);
|
||||
let ref_value = git_ref.as_deref().unwrap_or(&default_ref);
|
||||
let git_source = serde_json::json!({
|
||||
"type": "git",
|
||||
"url": git,
|
||||
@@ -1790,6 +1772,7 @@ async fn handle_index_entry(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_update(
|
||||
profile: &Option<String>,
|
||||
pack_ref: String,
|
||||
|
||||
@@ -76,10 +76,8 @@ pub async fn handle_index_update(
|
||||
if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!("Updating existing entry for '{}'", pack_ref));
|
||||
}
|
||||
} else {
|
||||
if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!("Adding new entry for '{}'", pack_ref));
|
||||
}
|
||||
} else if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!("Adding new entry for '{}'", pack_ref));
|
||||
}
|
||||
|
||||
// Calculate checksum
|
||||
@@ -93,7 +91,7 @@ pub async fn handle_index_update(
|
||||
|
||||
if let Some(ref git) = git_url {
|
||||
let default_ref = format!("v{}", version);
|
||||
let ref_value = git_ref.as_ref().map(|s| s.as_str()).unwrap_or(&default_ref);
|
||||
let ref_value = git_ref.as_deref().unwrap_or(&default_ref);
|
||||
install_sources.push(serde_json::json!({
|
||||
"type": "git",
|
||||
"url": git,
|
||||
@@ -318,13 +316,11 @@ pub async fn handle_index_merge(
|
||||
));
|
||||
}
|
||||
packs_map.insert(pack_ref.to_string(), pack.clone());
|
||||
} else {
|
||||
if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!(
|
||||
" Keeping '{}' at {} (newer than {})",
|
||||
pack_ref, existing_version, new_version
|
||||
));
|
||||
}
|
||||
} else if output_format == OutputFormat::Table {
|
||||
output::print_info(&format!(
|
||||
" Keeping '{}' at {} (newer than {})",
|
||||
pack_ref, existing_version, new_version
|
||||
));
|
||||
}
|
||||
duplicates_resolved += 1;
|
||||
} else {
|
||||
|
||||
@@ -354,6 +354,7 @@ async fn handle_show(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_update(
|
||||
profile: &Option<String>,
|
||||
rule_ref: String,
|
||||
@@ -477,6 +478,7 @@ async fn handle_toggle(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn handle_create(
|
||||
profile: &Option<String>,
|
||||
name: String,
|
||||
|
||||
@@ -472,7 +472,7 @@ fn resolve_ws_url(opts: &WaitOptions<'_>) -> Option<String> {
|
||||
let api_url = opts.api_client.base_url();
|
||||
|
||||
// Transform http(s)://host:PORT/... → ws(s)://host:8081
|
||||
let ws_url = derive_notifier_url(&api_url)?;
|
||||
let ws_url = derive_notifier_url(api_url)?;
|
||||
Some(ws_url)
|
||||
}
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ fn test_pack_index_entry_generates_valid_json() {
|
||||
// Verify metadata
|
||||
assert_eq!(json["author"], "Test Author");
|
||||
assert_eq!(json["license"], "Apache-2.0");
|
||||
assert!(json["keywords"].as_array().unwrap().len() > 0);
|
||||
assert!(!json["keywords"].as_array().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -212,7 +212,7 @@ fn test_pack_index_entry_with_archive_url() {
|
||||
let stdout = String::from_utf8(output.get_output().stdout.clone()).unwrap();
|
||||
|
||||
let json: Value = serde_json::from_str(&stdout).unwrap();
|
||||
assert!(json["install_sources"].as_array().unwrap().len() > 0);
|
||||
assert!(!json["install_sources"].as_array().unwrap().is_empty());
|
||||
|
||||
let archive_source = &json["install_sources"][0];
|
||||
assert_eq!(archive_source["type"], "archive");
|
||||
|
||||
@@ -45,21 +45,16 @@ pub struct Claims {
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum TokenType {
|
||||
#[default]
|
||||
Access,
|
||||
Refresh,
|
||||
Sensor,
|
||||
Execution,
|
||||
}
|
||||
|
||||
impl Default for TokenType {
|
||||
fn default() -> Self {
|
||||
Self::Access
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for JWT tokens
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JwtConfig {
|
||||
@@ -247,11 +242,7 @@ pub fn validate_token(token: &str, config: &JwtConfig) -> Result<Claims, JwtErro
|
||||
|
||||
/// Extract token from Authorization header
|
||||
pub fn extract_token_from_header(auth_header: &str) -> Option<&str> {
|
||||
if auth_header.starts_with("Bearer ") {
|
||||
Some(&auth_header[7..])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
auth_header.strip_prefix("Bearer ")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -45,21 +45,16 @@ pub mod enums {
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// How parameters should be delivered to an action
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ParameterDelivery {
|
||||
/// Pass parameters via stdin (secure, recommended for most cases)
|
||||
#[default]
|
||||
Stdin,
|
||||
/// Pass parameters via temporary file (secure, best for large payloads)
|
||||
File,
|
||||
}
|
||||
|
||||
impl Default for ParameterDelivery {
|
||||
fn default() -> Self {
|
||||
Self::Stdin
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParameterDelivery {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
@@ -99,31 +94,23 @@ pub mod enums {
|
||||
&self,
|
||||
buf: &mut sqlx::postgres::PgArgumentBuffer,
|
||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
||||
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
|
||||
self.to_string(),
|
||||
buf,
|
||||
)?)
|
||||
<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// Format for parameter serialization
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ParameterFormat {
|
||||
/// KEY='VALUE' format (one per line)
|
||||
Dotenv,
|
||||
/// JSON object
|
||||
#[default]
|
||||
Json,
|
||||
/// YAML format
|
||||
Yaml,
|
||||
}
|
||||
|
||||
impl Default for ParameterFormat {
|
||||
fn default() -> Self {
|
||||
Self::Json
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParameterFormat {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
@@ -165,18 +152,16 @@ pub mod enums {
|
||||
&self,
|
||||
buf: &mut sqlx::postgres::PgArgumentBuffer,
|
||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
||||
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
|
||||
self.to_string(),
|
||||
buf,
|
||||
)?)
|
||||
<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// Format for action output parsing
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum OutputFormat {
|
||||
/// Plain text (no parsing)
|
||||
#[default]
|
||||
Text,
|
||||
/// Parse as JSON
|
||||
Json,
|
||||
@@ -186,12 +171,6 @@ pub mod enums {
|
||||
Jsonl,
|
||||
}
|
||||
|
||||
impl Default for OutputFormat {
|
||||
fn default() -> Self {
|
||||
Self::Text
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for OutputFormat {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
@@ -235,10 +214,7 @@ pub mod enums {
|
||||
&self,
|
||||
buf: &mut sqlx::postgres::PgArgumentBuffer,
|
||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
||||
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
|
||||
self.to_string(),
|
||||
buf,
|
||||
)?)
|
||||
<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,20 +347,17 @@ pub mod enums {
|
||||
/// - `Public`: viewable by all authenticated users on the platform.
|
||||
/// - `Private`: restricted based on the artifact's `scope` and `owner` fields.
|
||||
/// Full RBAC enforcement is deferred; for now the field enables filtering.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type, ToSchema)]
|
||||
#[derive(
|
||||
Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Type, ToSchema,
|
||||
)]
|
||||
#[sqlx(type_name = "artifact_visibility_enum", rename_all = "lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ArtifactVisibility {
|
||||
Public,
|
||||
#[default]
|
||||
Private,
|
||||
}
|
||||
|
||||
impl Default for ArtifactVisibility {
|
||||
fn default() -> Self {
|
||||
Self::Private
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type, ToSchema)]
|
||||
#[sqlx(type_name = "workflow_task_status_enum", rename_all = "lowercase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
|
||||
@@ -141,7 +141,7 @@ mod tests {
|
||||
fn test_message_queue_creation() {
|
||||
// This test just verifies the struct can be instantiated
|
||||
// Actual connection tests require a running RabbitMQ instance
|
||||
assert!(true);
|
||||
// (nothing to assert without a live broker)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -69,20 +69,15 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
/// Message delivery mode
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DeliveryMode {
|
||||
/// Non-persistent messages (faster, but may be lost on broker restart)
|
||||
NonPersistent = 1,
|
||||
/// Persistent messages (slower, but survive broker restart)
|
||||
#[default]
|
||||
Persistent = 2,
|
||||
}
|
||||
|
||||
impl Default for DeliveryMode {
|
||||
fn default() -> Self {
|
||||
Self::Persistent
|
||||
}
|
||||
}
|
||||
|
||||
/// Message priority (0-9, higher is more urgent)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Priority(u8);
|
||||
@@ -125,25 +120,21 @@ impl fmt::Display for Priority {
|
||||
}
|
||||
|
||||
/// Message acknowledgment mode
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum AckMode {
|
||||
/// Automatically acknowledge messages after delivery
|
||||
Auto,
|
||||
/// Manually acknowledge messages after processing
|
||||
#[default]
|
||||
Manual,
|
||||
}
|
||||
|
||||
impl Default for AckMode {
|
||||
fn default() -> Self {
|
||||
Self::Manual
|
||||
}
|
||||
}
|
||||
|
||||
/// Exchange type
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ExchangeType {
|
||||
/// Direct exchange - routes messages with exact routing key match
|
||||
#[default]
|
||||
Direct,
|
||||
/// Topic exchange - routes messages using pattern matching
|
||||
Topic,
|
||||
@@ -165,12 +156,6 @@ impl ExchangeType {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ExchangeType {
|
||||
fn default() -> Self {
|
||||
Self::Direct
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ExchangeType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
|
||||
@@ -41,7 +41,7 @@ impl PackEnvironmentStatus {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
pub fn parse_status(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"pending" => Some(Self::Pending),
|
||||
"installing" => Some(Self::Installing),
|
||||
@@ -194,7 +194,7 @@ impl PackEnvironmentManager {
|
||||
|
||||
if let Some(row) = row {
|
||||
let status_str: String = row.try_get("status")?;
|
||||
let status = PackEnvironmentStatus::from_str(&status_str)
|
||||
let status = PackEnvironmentStatus::parse_status(&status_str)
|
||||
.unwrap_or(PackEnvironmentStatus::Failed);
|
||||
|
||||
Ok(Some(PackEnvironment {
|
||||
@@ -343,7 +343,7 @@ impl PackEnvironmentManager {
|
||||
let mut environments = Vec::new();
|
||||
for row in rows {
|
||||
let status_str: String = row.try_get("status")?;
|
||||
let status = PackEnvironmentStatus::from_str(&status_str)
|
||||
let status = PackEnvironmentStatus::parse_status(&status_str)
|
||||
.unwrap_or(PackEnvironmentStatus::Failed);
|
||||
|
||||
environments.push(PackEnvironment {
|
||||
@@ -452,8 +452,8 @@ impl PackEnvironmentManager {
|
||||
.await?;
|
||||
|
||||
let status_str: String = row.try_get("status")?;
|
||||
let status =
|
||||
PackEnvironmentStatus::from_str(&status_str).unwrap_or(PackEnvironmentStatus::Pending);
|
||||
let status = PackEnvironmentStatus::parse_status(&status_str)
|
||||
.unwrap_or(PackEnvironmentStatus::Pending);
|
||||
|
||||
Ok(PackEnvironment {
|
||||
id: row.try_get("id")?,
|
||||
@@ -496,8 +496,8 @@ impl PackEnvironmentManager {
|
||||
.await?;
|
||||
|
||||
let status_str: String = row.try_get("status")?;
|
||||
let status =
|
||||
PackEnvironmentStatus::from_str(&status_str).unwrap_or(PackEnvironmentStatus::Ready);
|
||||
let status = PackEnvironmentStatus::parse_status(&status_str)
|
||||
.unwrap_or(PackEnvironmentStatus::Ready);
|
||||
|
||||
Ok(PackEnvironment {
|
||||
id: row.try_get("id")?,
|
||||
@@ -580,7 +580,7 @@ impl PackEnvironmentManager {
|
||||
Ok(output) => {
|
||||
install_log.push_str(&format!("\n=== {} ===\n", action.name));
|
||||
install_log.push_str(&output);
|
||||
install_log.push_str("\n");
|
||||
install_log.push('\n');
|
||||
}
|
||||
Err(e) => {
|
||||
let error_msg = format!("Installer '{}' failed: {}", action.name, e);
|
||||
@@ -701,20 +701,18 @@ impl PackEnvironmentManager {
|
||||
.map(|obj| {
|
||||
obj.iter()
|
||||
.filter_map(|(k, v)| {
|
||||
v.as_str()
|
||||
.map(|s| {
|
||||
let resolved = self
|
||||
.resolve_template(
|
||||
s,
|
||||
pack_ref,
|
||||
runtime_ref,
|
||||
env_path,
|
||||
&pack_path_str,
|
||||
)
|
||||
.ok()?;
|
||||
Some((k.clone(), resolved))
|
||||
})
|
||||
.flatten()
|
||||
v.as_str().and_then(|s| {
|
||||
let resolved = self
|
||||
.resolve_template(
|
||||
s,
|
||||
pack_ref,
|
||||
runtime_ref,
|
||||
env_path,
|
||||
&pack_path_str,
|
||||
)
|
||||
.ok()?;
|
||||
Some((k.clone(), resolved))
|
||||
})
|
||||
})
|
||||
.collect::<HashMap<String, String>>()
|
||||
})
|
||||
@@ -877,9 +875,9 @@ mod tests {
|
||||
fn test_environment_status_conversion() {
|
||||
assert_eq!(PackEnvironmentStatus::Ready.as_str(), "ready");
|
||||
assert_eq!(
|
||||
PackEnvironmentStatus::from_str("ready"),
|
||||
PackEnvironmentStatus::parse_status("ready"),
|
||||
Some(PackEnvironmentStatus::Ready)
|
||||
);
|
||||
assert_eq!(PackEnvironmentStatus::from_str("invalid"), None);
|
||||
assert_eq!(PackEnvironmentStatus::parse_status("invalid"), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,17 +151,15 @@ impl DependencyValidator {
|
||||
};
|
||||
|
||||
let error = if !satisfied {
|
||||
if detected_version.is_none() {
|
||||
Some(format!("Runtime '{}' not found on system", runtime))
|
||||
} else if let Some(ref constraint) = version_constraint {
|
||||
Some(format!(
|
||||
"Runtime '{}' version {} does not satisfy constraint '{}'",
|
||||
runtime,
|
||||
detected_version.as_ref().unwrap(),
|
||||
constraint
|
||||
))
|
||||
if let Some(ref detected) = detected_version {
|
||||
version_constraint.as_ref().map(|constraint| {
|
||||
format!(
|
||||
"Runtime '{}' version {} does not satisfy constraint '{}'",
|
||||
runtime, detected, constraint
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
Some(format!("Runtime '{}' not found on system", runtime))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -192,15 +190,13 @@ impl DependencyValidator {
|
||||
};
|
||||
|
||||
let error = if !satisfied {
|
||||
if installed_version.is_none() {
|
||||
Some(format!("Required pack '{}' is not installed", pack_ref))
|
||||
} else {
|
||||
if let Some(ref installed) = installed_version {
|
||||
Some(format!(
|
||||
"Pack '{}' version {} does not satisfy constraint '{}'",
|
||||
pack_ref,
|
||||
installed_version.as_ref().unwrap(),
|
||||
version_constraint
|
||||
pack_ref, installed, version_constraint
|
||||
))
|
||||
} else {
|
||||
Some(format!("Required pack '{}' is not installed", pack_ref))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -335,30 +331,30 @@ fn match_version_constraint(version: &str, constraint: &str) -> Result<bool> {
|
||||
}
|
||||
|
||||
// Parse constraint
|
||||
if constraint.starts_with(">=") {
|
||||
let required = constraint[2..].trim();
|
||||
if let Some(stripped) = constraint.strip_prefix(">=") {
|
||||
let required = stripped.trim();
|
||||
Ok(compare_versions(version, required)? >= 0)
|
||||
} else if constraint.starts_with("<=") {
|
||||
let required = constraint[2..].trim();
|
||||
} else if let Some(stripped) = constraint.strip_prefix("<=") {
|
||||
let required = stripped.trim();
|
||||
Ok(compare_versions(version, required)? <= 0)
|
||||
} else if constraint.starts_with('>') {
|
||||
let required = constraint[1..].trim();
|
||||
} else if let Some(stripped) = constraint.strip_prefix('>') {
|
||||
let required = stripped.trim();
|
||||
Ok(compare_versions(version, required)? > 0)
|
||||
} else if constraint.starts_with('<') {
|
||||
let required = constraint[1..].trim();
|
||||
} else if let Some(stripped) = constraint.strip_prefix('<') {
|
||||
let required = stripped.trim();
|
||||
Ok(compare_versions(version, required)? < 0)
|
||||
} else if constraint.starts_with('=') {
|
||||
let required = constraint[1..].trim();
|
||||
} else if let Some(stripped) = constraint.strip_prefix('=') {
|
||||
let required = stripped.trim();
|
||||
Ok(compare_versions(version, required)? == 0)
|
||||
} else if constraint.starts_with('^') {
|
||||
} else if let Some(stripped) = constraint.strip_prefix('^') {
|
||||
// Caret: Compatible with version (major.minor.patch)
|
||||
// ^1.2.3 := >=1.2.3 <2.0.0
|
||||
let required = constraint[1..].trim();
|
||||
let required = stripped.trim();
|
||||
match_caret_constraint(version, required)
|
||||
} else if constraint.starts_with('~') {
|
||||
} else if let Some(stripped) = constraint.strip_prefix('~') {
|
||||
// Tilde: Approximately equivalent to version
|
||||
// ~1.2.3 := >=1.2.3 <1.3.0
|
||||
let required = constraint[1..].trim();
|
||||
let required = stripped.trim();
|
||||
match_tilde_constraint(version, required)
|
||||
} else {
|
||||
// Exact match
|
||||
|
||||
@@ -171,7 +171,7 @@ impl PackInstaller {
|
||||
clone_cmd.arg("--depth").arg("1");
|
||||
}
|
||||
|
||||
clone_cmd.arg(&url).arg(&install_dir);
|
||||
clone_cmd.arg(url).arg(&install_dir);
|
||||
|
||||
let output = clone_cmd
|
||||
.output()
|
||||
@@ -421,7 +421,11 @@ impl PackInstaller {
|
||||
}
|
||||
|
||||
// Determine filename from URL
|
||||
let filename = url.split('/').last().unwrap_or("archive.zip").to_string();
|
||||
let filename = url
|
||||
.split('/')
|
||||
.next_back()
|
||||
.unwrap_or("archive.zip")
|
||||
.to_string();
|
||||
|
||||
let archive_path = self.temp_dir.join(&filename);
|
||||
|
||||
|
||||
@@ -288,17 +288,15 @@ impl<'a> PackComponentLoader<'a> {
|
||||
}
|
||||
Err(e) => {
|
||||
// Check for unique constraint violation (race condition)
|
||||
if let Error::Database(ref db_err) = e {
|
||||
if let sqlx::Error::Database(ref inner) = db_err {
|
||||
if inner.is_unique_violation() {
|
||||
info!(
|
||||
"Runtime '{}' already exists (concurrent creation), treating as update",
|
||||
runtime_ref
|
||||
);
|
||||
loaded_refs.push(runtime_ref);
|
||||
result.runtimes_updated += 1;
|
||||
continue;
|
||||
}
|
||||
if let Error::Database(sqlx::Error::Database(ref inner)) = e {
|
||||
if inner.is_unique_violation() {
|
||||
info!(
|
||||
"Runtime '{}' already exists (concurrent creation), treating as update",
|
||||
runtime_ref
|
||||
);
|
||||
loaded_refs.push(runtime_ref);
|
||||
result.runtimes_updated += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let msg = format!("Failed to create runtime '{}': {}", runtime_ref, e);
|
||||
@@ -438,16 +436,14 @@ impl<'a> PackComponentLoader<'a> {
|
||||
}
|
||||
Err(e) => {
|
||||
// Check for unique constraint violation (race condition)
|
||||
if let Error::Database(ref db_err) = e {
|
||||
if let sqlx::Error::Database(ref inner) = db_err {
|
||||
if inner.is_unique_violation() {
|
||||
info!(
|
||||
"Version '{}' for runtime '{}' already exists (concurrent), skipping",
|
||||
version_str, runtime_ref
|
||||
);
|
||||
loaded_versions.push(version_str);
|
||||
continue;
|
||||
}
|
||||
if let Error::Database(sqlx::Error::Database(ref inner)) = e {
|
||||
if inner.is_unique_violation() {
|
||||
info!(
|
||||
"Version '{}' for runtime '{}' already exists (concurrent), skipping",
|
||||
version_str, runtime_ref
|
||||
);
|
||||
loaded_versions.push(version_str);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let msg = format!(
|
||||
|
||||
@@ -272,11 +272,6 @@ impl Checksum {
|
||||
|
||||
Ok(Self { algorithm, hash })
|
||||
}
|
||||
|
||||
/// Format as "algorithm:hash"
|
||||
pub fn to_string(&self) -> String {
|
||||
format!("{}:{}", self.algorithm, self.hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Checksum {
|
||||
|
||||
@@ -295,7 +295,7 @@ impl Update for ActionRepository {
|
||||
|
||||
query.push(", updated = NOW() WHERE id = ");
|
||||
query.push_bind(id);
|
||||
query.push(&format!(" RETURNING {}", ACTION_COLUMNS));
|
||||
query.push(format!(" RETURNING {}", ACTION_COLUMNS));
|
||||
|
||||
let action = query
|
||||
.build_query_as::<Action>()
|
||||
@@ -554,7 +554,6 @@ impl ActionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// Repository for Policy operations
|
||||
// ============================================================================
|
||||
// Policy Repository
|
||||
// ============================================================================
|
||||
|
||||
@@ -47,7 +47,7 @@ pub struct HistoryQueryParams {
|
||||
impl HistoryQueryParams {
|
||||
/// Returns the effective limit, capped at 1000.
|
||||
pub fn effective_limit(&self) -> i64 {
|
||||
self.limit.unwrap_or(100).min(1000).max(1)
|
||||
self.limit.unwrap_or(100).clamp(1, 1000)
|
||||
}
|
||||
|
||||
/// Returns the effective offset.
|
||||
|
||||
@@ -582,7 +582,7 @@ impl EnforcementRepository {
|
||||
}
|
||||
|
||||
if let Some(status) = &filters.status {
|
||||
push_condition!("status = ", status.clone());
|
||||
push_condition!("status = ", *status);
|
||||
}
|
||||
if let Some(rule_id) = filters.rule {
|
||||
push_condition!("rule = ", rule_id);
|
||||
|
||||
@@ -391,7 +391,7 @@ impl ExecutionRepository {
|
||||
}
|
||||
|
||||
if let Some(status) = &filters.status {
|
||||
push_condition!("e.status = ", status.clone());
|
||||
push_condition!("e.status = ", *status);
|
||||
}
|
||||
if let Some(action_ref) = &filters.action_ref {
|
||||
push_condition!("e.action_ref = ", action_ref.clone());
|
||||
|
||||
@@ -129,7 +129,7 @@ impl Update for IdentityRepository {
|
||||
.map_err(|e| {
|
||||
// Convert RowNotFound to NotFound error
|
||||
if matches!(e, sqlx::Error::RowNotFound) {
|
||||
return crate::Error::not_found("identity", "id", &id.to_string());
|
||||
return crate::Error::not_found("identity", "id", id.to_string());
|
||||
}
|
||||
e.into()
|
||||
})
|
||||
|
||||
@@ -211,7 +211,7 @@ impl InquiryRepository {
|
||||
}
|
||||
|
||||
if let Some(status) = &filters.status {
|
||||
push_condition!("status = ", status.clone());
|
||||
push_condition!("status = ", *status);
|
||||
}
|
||||
if let Some(execution_id) = filters.execution {
|
||||
push_condition!("execution = ", execution_id);
|
||||
|
||||
@@ -218,7 +218,7 @@ impl KeyRepository {
|
||||
}
|
||||
|
||||
if let Some(ref owner_type) = filters.owner_type {
|
||||
push_condition!("owner_type = ", owner_type.clone());
|
||||
push_condition!("owner_type = ", *owner_type);
|
||||
}
|
||||
if let Some(ref owner) = filters.owner {
|
||||
push_condition!("owner = ", owner.clone());
|
||||
|
||||
@@ -408,6 +408,7 @@ impl PackRepository {
|
||||
}
|
||||
|
||||
/// Update installation metadata for a pack
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn update_installation_metadata<'e, E>(
|
||||
executor: E,
|
||||
id: i64,
|
||||
|
||||
@@ -239,7 +239,7 @@ impl Update for RuntimeVersionRepository {
|
||||
|
||||
query.push(" WHERE id = ");
|
||||
query.push_bind(id);
|
||||
query.push(&format!(" RETURNING {}", SELECT_COLUMNS));
|
||||
query.push(format!(" RETURNING {}", SELECT_COLUMNS));
|
||||
|
||||
let row = query
|
||||
.build_query_as::<RuntimeVersion>()
|
||||
|
||||
@@ -276,7 +276,7 @@ impl Update for TriggerRepository {
|
||||
.map_err(|e| {
|
||||
// Convert RowNotFound to NotFound error
|
||||
if matches!(e, sqlx::Error::RowNotFound) {
|
||||
return crate::Error::not_found("trigger", "id", &id.to_string());
|
||||
return crate::Error::not_found("trigger", "id", id.to_string());
|
||||
}
|
||||
e.into()
|
||||
})?;
|
||||
|
||||
@@ -423,14 +423,11 @@ mod tests {
|
||||
"always_available": true
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
verification
|
||||
.get("always_available")
|
||||
.unwrap()
|
||||
.as_bool()
|
||||
.unwrap(),
|
||||
true
|
||||
);
|
||||
assert!(verification
|
||||
.get("always_available")
|
||||
.unwrap()
|
||||
.as_bool()
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -106,7 +106,7 @@ impl TestExecutor {
|
||||
);
|
||||
|
||||
match self
|
||||
.execute_test_suite(&pack_dir, runner_name, runner_config)
|
||||
.execute_test_suite(pack_dir, runner_name, runner_config)
|
||||
.await
|
||||
{
|
||||
Ok(suite_result) => {
|
||||
@@ -369,7 +369,7 @@ impl TestExecutor {
|
||||
let total = self.extract_number(&text, "Total Tests:");
|
||||
let passed = self.extract_number(&text, "Passed:");
|
||||
let failed = self.extract_number(&text, "Failed:");
|
||||
let skipped = self.extract_number(&text, "Skipped:").or_else(|| Some(0));
|
||||
let skipped = self.extract_number(&text, "Skipped:").or(Some(0));
|
||||
|
||||
// If we couldn't parse counts, use exit code
|
||||
let (total, passed, failed, skipped) = if total.is_none() || passed.is_none() {
|
||||
@@ -441,7 +441,6 @@ impl TestExecutor {
|
||||
.and_then(|line| {
|
||||
line.split(label)
|
||||
.nth(1)?
|
||||
.trim()
|
||||
.split_whitespace()
|
||||
.next()?
|
||||
.parse::<i32>()
|
||||
|
||||
@@ -278,7 +278,7 @@ fn json_eq(a: &JsonValue, b: &JsonValue) -> bool {
|
||||
return false;
|
||||
}
|
||||
a.iter()
|
||||
.all(|(k, v)| b.get(k).map_or(false, |bv| json_eq(v, bv)))
|
||||
.all(|(k, v)| b.get(k).is_some_and(|bv| json_eq(v, bv)))
|
||||
}
|
||||
// Different types (other than number cross-compare) are never equal
|
||||
_ => false,
|
||||
@@ -680,10 +680,8 @@ impl NumericValue {
|
||||
fn as_numeric(v: &JsonValue) -> Option<NumericValue> {
|
||||
if let Some(i) = v.as_i64() {
|
||||
Some(NumericValue::Int(i))
|
||||
} else if let Some(f) = v.as_f64() {
|
||||
Some(NumericValue::Float(f))
|
||||
} else {
|
||||
None
|
||||
v.as_f64().map(NumericValue::Float)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -363,8 +363,8 @@ mod tests {
|
||||
let ctx = TestContext::new();
|
||||
assert_eq!(eval_expression("string(42)", &ctx).unwrap(), json!("42"));
|
||||
assert_eq!(
|
||||
eval_expression("number(\"3.14\")", &ctx).unwrap(),
|
||||
json!(3.14)
|
||||
eval_expression("number(\"3.15\")", &ctx).unwrap(),
|
||||
json!(3.15)
|
||||
);
|
||||
assert_eq!(eval_expression("int(3.9)", &ctx).unwrap(), json!(3));
|
||||
assert_eq!(eval_expression("int(\"42\")", &ctx).unwrap(), json!(42));
|
||||
|
||||
@@ -431,8 +431,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_float() {
|
||||
let kinds = tokenize("3.14");
|
||||
assert_eq!(kinds, vec![TokenKind::Float(3.14), TokenKind::Eof]);
|
||||
let kinds = tokenize("3.15");
|
||||
assert_eq!(kinds, vec![TokenKind::Float(3.15), TokenKind::Eof]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -187,11 +187,13 @@ impl WorkflowLoader {
|
||||
.map(|e| e.to_string())
|
||||
};
|
||||
|
||||
if validation_error.is_some() && !self.config.skip_validation {
|
||||
return Err(Error::validation(format!(
|
||||
"Workflow validation failed: {}",
|
||||
validation_error.as_ref().unwrap()
|
||||
)));
|
||||
if let Some(ref err) = validation_error {
|
||||
if !self.config.skip_validation {
|
||||
return Err(Error::validation(format!(
|
||||
"Workflow validation failed: {}",
|
||||
err
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(LoadedWorkflow {
|
||||
|
||||
@@ -1449,7 +1449,7 @@ tasks:
|
||||
publish:
|
||||
- validation_passed: true
|
||||
- count: 42
|
||||
- ratio: 3.14
|
||||
- ratio: 3.15
|
||||
- label: "hello"
|
||||
- template_val: "{{ result().data }}"
|
||||
- nothing: null
|
||||
@@ -1486,7 +1486,7 @@ tasks:
|
||||
} else if let Some(val) = map.get("count") {
|
||||
assert_eq!(val, &serde_json::json!(42), "integer");
|
||||
} else if let Some(val) = map.get("ratio") {
|
||||
assert_eq!(val, &serde_json::json!(3.14), "float");
|
||||
assert_eq!(val, &serde_json::json!(3.15), "float");
|
||||
} else if let Some(val) = map.get("label") {
|
||||
assert_eq!(val, &serde_json::json!("hello"), "string");
|
||||
} else if let Some(val) = map.get("template_val") {
|
||||
|
||||
@@ -97,11 +97,11 @@ impl WorkflowRegistrar {
|
||||
debug!("Registering workflow: {}", loaded.file.ref_name);
|
||||
|
||||
// Check for validation errors
|
||||
if loaded.validation_error.is_some() {
|
||||
if let Some(ref err) = loaded.validation_error {
|
||||
if self.options.skip_invalid {
|
||||
return Err(Error::validation(format!(
|
||||
"Workflow has validation errors: {}",
|
||||
loaded.validation_error.as_ref().unwrap()
|
||||
err
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -252,6 +252,7 @@ impl WorkflowRegistrar {
|
||||
///
|
||||
/// `effective_ref` and `effective_label` are the resolved values (which may
|
||||
/// have been derived from the filename when the workflow YAML omits them).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn create_companion_action(
|
||||
&self,
|
||||
workflow_def_id: i64,
|
||||
@@ -298,6 +299,7 @@ impl WorkflowRegistrar {
|
||||
///
|
||||
/// `effective_ref` and `effective_label` are the resolved values (which may
|
||||
/// have been derived from the filename when the workflow YAML omits them).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn ensure_companion_action(
|
||||
&self,
|
||||
workflow_def_id: i64,
|
||||
@@ -425,8 +427,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_registration_options_default() {
|
||||
let options = RegistrationOptions::default();
|
||||
assert_eq!(options.update_existing, true);
|
||||
assert_eq!(options.skip_invalid, true);
|
||||
assert!(options.update_existing);
|
||||
assert!(options.skip_invalid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -439,7 +441,7 @@ mod tests {
|
||||
};
|
||||
|
||||
assert_eq!(result.ref_name, "test.workflow");
|
||||
assert_eq!(result.created, true);
|
||||
assert!(result.created);
|
||||
assert_eq!(result.workflow_def_id, 123);
|
||||
assert_eq!(result.warnings.len(), 0);
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ impl WorkflowValidator {
|
||||
reachable
|
||||
}
|
||||
|
||||
/// Detect cycles using DFS
|
||||
// Detect cycles using DFS
|
||||
// Cycle detection removed - cycles are now valid in workflow graphs
|
||||
// Workflows are directed graphs (not DAGs) and cycles are supported
|
||||
// for use cases like monitoring loops, retry patterns, etc.
|
||||
@@ -328,7 +328,7 @@ impl WorkflowValidator {
|
||||
}
|
||||
|
||||
// Validate variable names in vars
|
||||
for (key, _) in &workflow.vars {
|
||||
for key in workflow.vars.keys() {
|
||||
if !Self::is_valid_variable_name(key) {
|
||||
return Err(ValidationError::SemanticError(format!(
|
||||
"Invalid variable name: {}",
|
||||
|
||||
@@ -781,7 +781,7 @@ async fn test_find_executions_by_enforcement() {
|
||||
config: None,
|
||||
env_vars: None,
|
||||
parent: None,
|
||||
enforcement: if i == 2 { None } else { None }, // Can't reference non-existent enforcement
|
||||
enforcement: None, // Can't reference non-existent enforcement
|
||||
executor: None,
|
||||
status: ExecutionStatus::Requested,
|
||||
result: None,
|
||||
|
||||
@@ -35,7 +35,7 @@ async fn test_create_key_system_owner() {
|
||||
assert_eq!(key.owner_pack, None);
|
||||
assert_eq!(key.owner_action, None);
|
||||
assert_eq!(key.owner_sensor, None);
|
||||
assert_eq!(key.encrypted, false);
|
||||
assert!(!key.encrypted);
|
||||
assert_eq!(key.value, "test_value");
|
||||
assert!(key.created.timestamp() > 0);
|
||||
assert!(key.updated.timestamp() > 0);
|
||||
@@ -52,7 +52,7 @@ async fn test_create_key_system_encrypted() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(key.encrypted, true);
|
||||
assert!(key.encrypted);
|
||||
assert_eq!(key.encryption_key_hash, Some("sha256:abc123".to_string()));
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ async fn test_update_encrypted_status() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(key.encrypted, false);
|
||||
assert!(!key.encrypted);
|
||||
|
||||
let input = UpdateKeyInput {
|
||||
encrypted: Some(true),
|
||||
@@ -438,7 +438,7 @@ async fn test_update_encrypted_status() {
|
||||
|
||||
let updated = KeyRepository::update(&pool, key.id, input).await.unwrap();
|
||||
|
||||
assert_eq!(updated.encrypted, true);
|
||||
assert!(updated.encrypted);
|
||||
assert_eq!(
|
||||
updated.encryption_key_hash,
|
||||
Some("sha256:xyz789".to_string())
|
||||
@@ -468,7 +468,7 @@ async fn test_update_multiple_fields() {
|
||||
|
||||
assert_eq!(updated.name, new_name);
|
||||
assert_eq!(updated.value, "updated_value");
|
||||
assert_eq!(updated.encrypted, true);
|
||||
assert!(updated.encrypted);
|
||||
assert_eq!(updated.encryption_key_hash, Some("hash123".to_string()));
|
||||
}
|
||||
|
||||
@@ -768,10 +768,10 @@ async fn test_key_encrypted_flag() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(plain_key.encrypted, false);
|
||||
assert!(!plain_key.encrypted);
|
||||
assert_eq!(plain_key.encryption_key_hash, None);
|
||||
|
||||
assert_eq!(encrypted_key.encrypted, true);
|
||||
assert!(encrypted_key.encrypted);
|
||||
assert_eq!(
|
||||
encrypted_key.encryption_key_hash,
|
||||
Some("sha256:abc".to_string())
|
||||
@@ -788,7 +788,7 @@ async fn test_update_encryption_status() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(key.encrypted, false);
|
||||
assert!(!key.encrypted);
|
||||
|
||||
// Encrypt it
|
||||
let input = UpdateKeyInput {
|
||||
@@ -800,7 +800,7 @@ async fn test_update_encryption_status() {
|
||||
|
||||
let encrypted = KeyRepository::update(&pool, key.id, input).await.unwrap();
|
||||
|
||||
assert_eq!(encrypted.encrypted, true);
|
||||
assert!(encrypted.encrypted);
|
||||
assert_eq!(
|
||||
encrypted.encryption_key_hash,
|
||||
Some("sha256:newkey".to_string())
|
||||
@@ -817,7 +817,7 @@ async fn test_update_encryption_status() {
|
||||
|
||||
let decrypted = KeyRepository::update(&pool, key.id, input).await.unwrap();
|
||||
|
||||
assert_eq!(decrypted.encrypted, false);
|
||||
assert!(!decrypted.encrypted);
|
||||
assert_eq!(decrypted.value, "plain_value");
|
||||
}
|
||||
|
||||
|
||||
@@ -892,7 +892,7 @@ async fn test_port_range() {
|
||||
|
||||
let worker = WorkerRepository::create(&pool, input)
|
||||
.await
|
||||
.expect(&format!("Failed to create worker with port {}", port));
|
||||
.unwrap_or_else(|_| panic!("Failed to create worker with port {}", port));
|
||||
|
||||
assert_eq!(worker.port, Some(port));
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ async fn test_create_rule() {
|
||||
rule.conditions,
|
||||
json!({"equals": {"event.status": "success"}})
|
||||
);
|
||||
assert_eq!(rule.enabled, true);
|
||||
assert!(rule.enabled);
|
||||
assert!(rule.created.timestamp() > 0);
|
||||
assert!(rule.updated.timestamp() > 0);
|
||||
}
|
||||
@@ -117,7 +117,7 @@ async fn test_create_rule_disabled() {
|
||||
|
||||
let rule = RuleRepository::create(&pool, input).await.unwrap();
|
||||
|
||||
assert_eq!(rule.enabled, false);
|
||||
assert!(!rule.enabled);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -759,7 +759,7 @@ async fn test_update_rule_enabled() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated.enabled, false);
|
||||
assert!(!updated.enabled);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -816,7 +816,7 @@ async fn test_update_rule_multiple_fields() {
|
||||
assert_eq!(updated.label, "New Label");
|
||||
assert_eq!(updated.description, "New Description");
|
||||
assert_eq!(updated.conditions, json!({"updated": true}));
|
||||
assert_eq!(updated.enabled, false);
|
||||
assert!(!updated.enabled);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -61,7 +61,7 @@ async fn test_create_sensor_minimal() {
|
||||
assert_eq!(sensor.runtime_ref, runtime.r#ref);
|
||||
assert_eq!(sensor.trigger, trigger.id);
|
||||
assert_eq!(sensor.trigger_ref, trigger.r#ref);
|
||||
assert_eq!(sensor.enabled, true);
|
||||
assert!(sensor.enabled);
|
||||
assert_eq!(sensor.param_schema, None);
|
||||
assert!(sensor.created.timestamp() > 0);
|
||||
assert!(sensor.updated.timestamp() > 0);
|
||||
@@ -796,7 +796,7 @@ async fn test_update_enabled_status() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(sensor.enabled, true);
|
||||
assert!(sensor.enabled);
|
||||
|
||||
let input = UpdateSensorInput {
|
||||
enabled: Some(false),
|
||||
@@ -807,7 +807,7 @@ async fn test_update_enabled_status() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated.enabled, false);
|
||||
assert!(!updated.enabled);
|
||||
|
||||
// Enable it again
|
||||
let input = UpdateSensorInput {
|
||||
@@ -819,7 +819,7 @@ async fn test_update_enabled_status() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(updated.enabled, true);
|
||||
assert!(updated.enabled);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -924,7 +924,7 @@ async fn test_update_multiple_fields() {
|
||||
assert_eq!(updated.label, "Multi Update");
|
||||
assert_eq!(updated.description, "Updated multiple fields");
|
||||
assert_eq!(updated.entrypoint, "sensors/multi.py");
|
||||
assert_eq!(updated.enabled, false);
|
||||
assert!(!updated.enabled);
|
||||
assert_eq!(updated.param_schema, Some(json!({"type": "object"})));
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ async fn test_create_trigger() {
|
||||
assert_eq!(trigger.pack, Some(pack.id));
|
||||
assert_eq!(trigger.pack_ref, Some(pack.r#ref));
|
||||
assert_eq!(trigger.label, "Webhook Trigger");
|
||||
assert_eq!(trigger.enabled, true);
|
||||
assert!(trigger.enabled);
|
||||
assert!(trigger.created.timestamp() > 0);
|
||||
assert!(trigger.updated.timestamp() > 0);
|
||||
}
|
||||
@@ -134,7 +134,7 @@ async fn test_create_trigger_disabled() {
|
||||
|
||||
let trigger = TriggerRepository::create(&pool, input).await.unwrap();
|
||||
|
||||
assert_eq!(trigger.enabled, false);
|
||||
assert!(!trigger.enabled);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -478,7 +478,7 @@ async fn test_update_trigger() {
|
||||
assert_eq!(updated.r#ref, trigger.r#ref); // Ref should not change
|
||||
assert_eq!(updated.label, "Updated Label");
|
||||
assert_eq!(updated.description, Some("Updated description".to_string()));
|
||||
assert_eq!(updated.enabled, false);
|
||||
assert!(!updated.enabled);
|
||||
assert!(updated.updated > original_updated);
|
||||
}
|
||||
|
||||
|
||||
@@ -546,7 +546,7 @@ mod tests {
|
||||
let manager = TimerManager::new(api_client).await.unwrap();
|
||||
|
||||
// Test various valid cron expressions
|
||||
let expressions = vec![
|
||||
let expressions = [
|
||||
"0 0 * * * *", // Every hour
|
||||
"0 */15 * * * *", // Every 15 minutes
|
||||
"0 0 0 * * *", // Daily at midnight
|
||||
|
||||
@@ -150,6 +150,7 @@ impl TimerConfig {
|
||||
/// Rule lifecycle event types
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "event_type", rename_all = "PascalCase")]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum RuleLifecycleEvent {
|
||||
RuleCreated {
|
||||
rule_id: i64,
|
||||
|
||||
@@ -116,13 +116,7 @@ impl CompletionListener {
|
||||
// Verify execution exists in database
|
||||
let execution = ExecutionRepository::find_by_id(pool, execution_id).await?;
|
||||
|
||||
if execution.is_none() {
|
||||
warn!(
|
||||
"Execution {} not found in database, but still releasing queue slot",
|
||||
execution_id
|
||||
);
|
||||
} else {
|
||||
let exec = execution.as_ref().unwrap();
|
||||
if let Some(ref exec) = execution {
|
||||
debug!(
|
||||
"Execution {} found with status: {:?}",
|
||||
execution_id, exec.status
|
||||
@@ -180,6 +174,11 @@ impl CompletionListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Execution {} not found in database, but still releasing queue slot",
|
||||
execution_id
|
||||
);
|
||||
}
|
||||
|
||||
// Release queue slot for this action
|
||||
|
||||
@@ -77,8 +77,7 @@ impl DeadLetterHandler {
|
||||
info!("Dead letter handler stopping, rejecting message");
|
||||
return Err(attune_common::mq::MqError::Consume(
|
||||
"Handler is shutting down".to_string(),
|
||||
)
|
||||
.into());
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ impl EventProcessor {
|
||||
let payload_dict = payload
|
||||
.as_object()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| serde_json::Map::new());
|
||||
.unwrap_or_else(serde_json::Map::new);
|
||||
|
||||
// Resolve action parameters using the template resolver
|
||||
let resolved_params = Self::resolve_action_params(pool, rule, event, &payload).await?;
|
||||
@@ -248,7 +248,7 @@ impl EventProcessor {
|
||||
};
|
||||
|
||||
// If rule has no conditions, it always matches
|
||||
if rule.conditions.is_null() || rule.conditions.as_array().map_or(true, |a| a.is_empty()) {
|
||||
if rule.conditions.is_null() || rule.conditions.as_array().is_none_or(|a| a.is_empty()) {
|
||||
debug!("Rule {} has no conditions, matching by default", rule.r#ref);
|
||||
return Ok(true);
|
||||
}
|
||||
@@ -364,7 +364,7 @@ impl EventProcessor {
|
||||
let action_params = &rule.action_params;
|
||||
|
||||
// If there are no action params, return empty
|
||||
if action_params.is_null() || action_params.as_object().map_or(true, |o| o.is_empty()) {
|
||||
if action_params.is_null() || action_params.as_object().is_none_or(|o| o.is_empty()) {
|
||||
return Ok(serde_json::Map::new());
|
||||
}
|
||||
|
||||
|
||||
@@ -257,7 +257,6 @@ mod tests {
|
||||
fn test_execution_manager_creation() {
|
||||
// This is a placeholder test
|
||||
// Real tests will require database and message queue setup
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::queue_manager::ExecutionQueueManager;
|
||||
|
||||
/// Policy violation type
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum PolicyViolation {
|
||||
/// Rate limit exceeded
|
||||
RateLimitExceeded {
|
||||
@@ -78,7 +79,7 @@ impl std::fmt::Display for PolicyViolation {
|
||||
}
|
||||
|
||||
/// Execution policy configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct ExecutionPolicy {
|
||||
/// Rate limit: maximum executions per time window
|
||||
pub rate_limit: Option<RateLimit>,
|
||||
@@ -88,16 +89,6 @@ pub struct ExecutionPolicy {
|
||||
pub quotas: Option<HashMap<String, u64>>,
|
||||
}
|
||||
|
||||
impl Default for ExecutionPolicy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rate_limit: None,
|
||||
concurrency_limit: None,
|
||||
quotas: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rate limit configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RateLimit {
|
||||
|
||||
@@ -78,11 +78,7 @@ fn apply_param_defaults(params: JsonValue, param_schema: &Option<JsonValue>) ->
|
||||
if let Some(schema_obj) = schema.as_object() {
|
||||
for (key, prop) in schema_obj {
|
||||
// Only fill in missing / null parameters
|
||||
let needs_default = match obj.get(key) {
|
||||
None => true,
|
||||
Some(JsonValue::Null) => true,
|
||||
_ => false,
|
||||
};
|
||||
let needs_default = matches!(obj.get(key), None | Some(JsonValue::Null));
|
||||
if needs_default {
|
||||
if let Some(default_val) = prop.get("default") {
|
||||
debug!("Applying default for parameter '{}': {}", key, default_val);
|
||||
@@ -411,6 +407,7 @@ impl ExecutionScheduler {
|
||||
/// `triggered_by` is the name of the predecessor task whose completion
|
||||
/// caused this task to be scheduled. Pass `None` for entry-point tasks
|
||||
/// dispatched at workflow start.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn dispatch_workflow_task(
|
||||
pool: &PgPool,
|
||||
publisher: &Publisher,
|
||||
@@ -496,10 +493,8 @@ impl ExecutionScheduler {
|
||||
let task_config: Option<JsonValue> =
|
||||
if rendered_input.is_object() && !rendered_input.as_object().unwrap().is_empty() {
|
||||
Some(rendered_input.clone())
|
||||
} else if let Some(parent_config) = &parent_execution.config {
|
||||
Some(parent_config.clone())
|
||||
} else {
|
||||
None
|
||||
parent_execution.config.clone()
|
||||
};
|
||||
|
||||
// Build workflow task metadata
|
||||
@@ -660,10 +655,8 @@ impl ExecutionScheduler {
|
||||
let task_config: Option<JsonValue> =
|
||||
if rendered_input.is_object() && !rendered_input.as_object().unwrap().is_empty() {
|
||||
Some(rendered_input.clone())
|
||||
} else if let Some(parent_config) = &parent_execution.config {
|
||||
Some(parent_config.clone())
|
||||
} else {
|
||||
None
|
||||
parent_execution.config.clone()
|
||||
};
|
||||
|
||||
let workflow_task = WorkflowTaskMetadata {
|
||||
@@ -1108,16 +1101,16 @@ impl ExecutionScheduler {
|
||||
let mut task_results_map: HashMap<String, JsonValue> = HashMap::new();
|
||||
for child in &child_executions {
|
||||
if let Some(ref wt) = child.workflow_task {
|
||||
if wt.workflow_execution == workflow_execution_id {
|
||||
if matches!(
|
||||
if wt.workflow_execution == workflow_execution_id
|
||||
&& matches!(
|
||||
child.status,
|
||||
ExecutionStatus::Completed
|
||||
| ExecutionStatus::Failed
|
||||
| ExecutionStatus::Timeout
|
||||
) {
|
||||
let result_val = child.result.clone().unwrap_or(serde_json::json!({}));
|
||||
task_results_map.insert(wt.task_name.clone(), result_val);
|
||||
}
|
||||
)
|
||||
{
|
||||
let result_val = child.result.clone().unwrap_or(serde_json::json!({}));
|
||||
task_results_map.insert(wt.task_name.clone(), result_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1514,7 +1507,7 @@ impl ExecutionScheduler {
|
||||
// Filter by heartbeat freshness (only workers with recent heartbeats)
|
||||
let fresh_workers: Vec<_> = active_workers
|
||||
.into_iter()
|
||||
.filter(|w| Self::is_worker_heartbeat_fresh(w))
|
||||
.filter(Self::is_worker_heartbeat_fresh)
|
||||
.collect();
|
||||
|
||||
if fresh_workers.is_empty() {
|
||||
@@ -1749,27 +1742,23 @@ mod tests {
|
||||
fn test_scheduler_creation() {
|
||||
// This is a placeholder test
|
||||
// Real tests will require database and message queue setup
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concurrency_limit_dispatch_count() {
|
||||
// Verify the dispatch_count calculation used by dispatch_with_items_task
|
||||
let total = 20usize;
|
||||
let concurrency: Option<usize> = Some(3);
|
||||
let concurrency_limit = concurrency.unwrap_or(total);
|
||||
let concurrency_limit = 3usize;
|
||||
let dispatch_count = total.min(concurrency_limit);
|
||||
assert_eq!(dispatch_count, 3);
|
||||
|
||||
// No concurrency limit → default to serial (1 at a time)
|
||||
let concurrency: Option<usize> = None;
|
||||
let concurrency_limit = concurrency.unwrap_or(1);
|
||||
let concurrency_limit = 1usize;
|
||||
let dispatch_count = total.min(concurrency_limit);
|
||||
assert_eq!(dispatch_count, 1);
|
||||
|
||||
// Concurrency exceeds total → dispatch all
|
||||
let concurrency: Option<usize> = Some(50);
|
||||
let concurrency_limit = concurrency.unwrap_or(total);
|
||||
let concurrency_limit = 50usize;
|
||||
let dispatch_count = total.min(concurrency_limit);
|
||||
assert_eq!(dispatch_count, 20);
|
||||
}
|
||||
|
||||
@@ -406,7 +406,7 @@ impl WorkerHealthProbe {
|
||||
|
||||
runtime_array.iter().any(|v| {
|
||||
v.as_str()
|
||||
.map_or(false, |s| s.eq_ignore_ascii_case(runtime_name))
|
||||
.is_some_and(|s| s.eq_ignore_ascii_case(runtime_name))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1129,7 +1129,7 @@ mod tests {
|
||||
let mut publish_map = HashMap::new();
|
||||
publish_map.insert("flag".to_string(), JsonValue::Bool(true));
|
||||
publish_map.insert("count".to_string(), json!(42));
|
||||
publish_map.insert("ratio".to_string(), json!(3.14));
|
||||
publish_map.insert("ratio".to_string(), json!(3.15));
|
||||
publish_map.insert("nothing".to_string(), JsonValue::Null);
|
||||
publish_map.insert(
|
||||
"template".to_string(),
|
||||
@@ -1152,7 +1152,7 @@ mod tests {
|
||||
assert!(ctx.get_var("count").unwrap().is_number());
|
||||
|
||||
// Float preserved
|
||||
assert_eq!(ctx.get_var("ratio").unwrap(), json!(3.14));
|
||||
assert_eq!(ctx.get_var("ratio").unwrap(), json!(3.15));
|
||||
|
||||
// Null preserved
|
||||
assert_eq!(ctx.get_var("nothing").unwrap(), json!(null));
|
||||
|
||||
@@ -965,7 +965,7 @@ tasks:
|
||||
publish:
|
||||
- validation_passed: true
|
||||
- count: 42
|
||||
- ratio: 3.14
|
||||
- ratio: 3.15
|
||||
- label: "hello"
|
||||
- template_val: "{{ result().data }}"
|
||||
- nothing: null
|
||||
@@ -1003,7 +1003,7 @@ tasks:
|
||||
assert_eq!(publish_map["count"], &serde_json::json!(42));
|
||||
|
||||
// Float is preserved as a JSON number
|
||||
assert_eq!(publish_map["ratio"], &serde_json::json!(3.14));
|
||||
assert_eq!(publish_map["ratio"], &serde_json::json!(3.15));
|
||||
|
||||
// Plain string stays as a string
|
||||
assert_eq!(
|
||||
|
||||
@@ -357,7 +357,7 @@ impl SensorManager {
|
||||
let mut child = cmd
|
||||
.env("ATTUNE_API_URL", &self.inner.api_url)
|
||||
.env("ATTUNE_API_TOKEN", &token_response.token)
|
||||
.env("ATTUNE_SENSOR_ID", &sensor.id.to_string())
|
||||
.env("ATTUNE_SENSOR_ID", sensor.id.to_string())
|
||||
.env("ATTUNE_SENSOR_REF", &sensor.r#ref)
|
||||
.env("ATTUNE_SENSOR_TRIGGERS", &trigger_instances_json)
|
||||
.env("ATTUNE_MQ_URL", &self.inner.mq_url)
|
||||
@@ -731,6 +731,7 @@ impl SensorInstance {
|
||||
|
||||
/// Sensor status information
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Default)]
|
||||
pub struct SensorStatus {
|
||||
/// Whether the sensor is running
|
||||
pub running: bool,
|
||||
@@ -745,16 +746,6 @@ pub struct SensorStatus {
|
||||
pub last_poll: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
impl Default for SensorStatus {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
running: false,
|
||||
failed: false,
|
||||
failure_count: 0,
|
||||
last_poll: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -381,14 +381,12 @@ async fn setup_environments_for_pack(
|
||||
)
|
||||
.await;
|
||||
// Also set up version-specific environments
|
||||
let versions = match RuntimeVersionRepository::find_available_by_runtime(
|
||||
db_pool, runtime_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
let versions: Vec<attune_common::models::RuntimeVersion> =
|
||||
RuntimeVersionRepository::find_available_by_runtime(
|
||||
db_pool, runtime_id,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
setup_version_environments_from_list(
|
||||
&versions,
|
||||
&rt_name,
|
||||
|
||||
@@ -63,6 +63,7 @@ fn normalize_api_url(raw_url: &str) -> String {
|
||||
|
||||
impl ActionExecutor {
|
||||
/// Create a new action executor
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
pool: PgPool,
|
||||
runtime_registry: RuntimeRegistry,
|
||||
@@ -359,18 +360,16 @@ impl ActionExecutor {
|
||||
|
||||
// Add context data as environment variables from config
|
||||
if let Some(config) = &execution.config {
|
||||
if let Some(context) = config.get("context") {
|
||||
if let JsonValue::Object(map) = context {
|
||||
for (key, value) in map {
|
||||
let env_key = format!("ATTUNE_CONTEXT_{}", key.to_uppercase());
|
||||
let env_value = match value {
|
||||
JsonValue::String(s) => s.clone(),
|
||||
JsonValue::Number(n) => n.to_string(),
|
||||
JsonValue::Bool(b) => b.to_string(),
|
||||
_ => serde_json::to_string(value)?,
|
||||
};
|
||||
env.insert(env_key, env_value);
|
||||
}
|
||||
if let Some(JsonValue::Object(map)) = config.get("context") {
|
||||
for (key, value) in map {
|
||||
let env_key = format!("ATTUNE_CONTEXT_{}", key.to_uppercase());
|
||||
let env_value = match value {
|
||||
JsonValue::String(s) => s.clone(),
|
||||
JsonValue::Number(n) => n.to_string(),
|
||||
JsonValue::Bool(b) => b.to_string(),
|
||||
_ => serde_json::to_string(value)?,
|
||||
};
|
||||
env.insert(env_key, env_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ impl WorkerRegistration {
|
||||
let worker_type = config
|
||||
.worker
|
||||
.as_ref()
|
||||
.and_then(|w| w.worker_type.clone())
|
||||
.and_then(|w| w.worker_type)
|
||||
.unwrap_or(WorkerType::Local);
|
||||
|
||||
let worker_role = WorkerRole::Action;
|
||||
@@ -180,8 +180,8 @@ impl WorkerRegistration {
|
||||
"#,
|
||||
)
|
||||
.bind(&self.worker_name)
|
||||
.bind(&self.worker_type)
|
||||
.bind(&self.worker_role)
|
||||
.bind(self.worker_type)
|
||||
.bind(self.worker_role)
|
||||
.bind(self.runtime_id)
|
||||
.bind(&self.host)
|
||||
.bind(self.port)
|
||||
|
||||
@@ -35,6 +35,7 @@ impl NativeRuntime {
|
||||
}
|
||||
|
||||
/// Execute a native binary with parameters and environment variables
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn execute_binary(
|
||||
&self,
|
||||
binary_path: PathBuf,
|
||||
|
||||
@@ -117,7 +117,7 @@ pub fn create_parameter_file(
|
||||
) -> Result<NamedTempFile, RuntimeError> {
|
||||
let formatted = format_parameters(parameters, format)?;
|
||||
|
||||
let mut temp_file = NamedTempFile::new().map_err(|e| RuntimeError::IoError(e))?;
|
||||
let mut temp_file = NamedTempFile::new().map_err(RuntimeError::IoError)?;
|
||||
|
||||
// Set restrictive permissions (owner read-only)
|
||||
#[cfg(unix)]
|
||||
@@ -126,20 +126,20 @@ pub fn create_parameter_file(
|
||||
let mut perms = temp_file
|
||||
.as_file()
|
||||
.metadata()
|
||||
.map_err(|e| RuntimeError::IoError(e))?
|
||||
.map_err(RuntimeError::IoError)?
|
||||
.permissions();
|
||||
perms.set_mode(0o400); // Read-only for owner
|
||||
temp_file
|
||||
.as_file()
|
||||
.set_permissions(perms)
|
||||
.map_err(|e| RuntimeError::IoError(e))?;
|
||||
.map_err(RuntimeError::IoError)?;
|
||||
}
|
||||
|
||||
temp_file
|
||||
.write_all(formatted.as_bytes())
|
||||
.map_err(|e| RuntimeError::IoError(e))?;
|
||||
.map_err(RuntimeError::IoError)?;
|
||||
|
||||
temp_file.flush().map_err(|e| RuntimeError::IoError(e))?;
|
||||
temp_file.flush().map_err(RuntimeError::IoError)?;
|
||||
|
||||
debug!(
|
||||
"Created parameter file at {:?} with format {:?}",
|
||||
|
||||
@@ -83,6 +83,7 @@ pub async fn execute_streaming(
|
||||
/// * `max_stderr_bytes` - Maximum stderr size before truncation
|
||||
/// * `output_format` - How to parse stdout (Text, Json, Yaml, Jsonl)
|
||||
/// * `cancel_token` - Optional cancellation token for graceful process termination
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn execute_streaming_cancellable(
|
||||
mut cmd: Command,
|
||||
secrets: &HashMap<String, String>,
|
||||
|
||||
@@ -61,6 +61,7 @@ impl ShellRuntime {
|
||||
}
|
||||
|
||||
/// Execute with streaming and bounded log collection
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn execute_with_streaming(
|
||||
&self,
|
||||
mut cmd: Command,
|
||||
@@ -383,6 +384,7 @@ impl ShellRuntime {
|
||||
}
|
||||
|
||||
/// Execute shell script from file
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn execute_shell_file(
|
||||
&self,
|
||||
script_path: PathBuf,
|
||||
|
||||
@@ -220,7 +220,7 @@ impl SecretManager {
|
||||
.map_err(|e| Error::Internal(format!("Encryption failed: {}", e)))?;
|
||||
|
||||
// Format: "nonce:ciphertext" (both base64-encoded)
|
||||
let nonce_b64 = BASE64.encode(&nonce);
|
||||
let nonce_b64 = BASE64.encode(nonce);
|
||||
let ciphertext_b64 = BASE64.encode(&ciphertext);
|
||||
|
||||
Ok(format!("{}:{}", nonce_b64, ciphertext_b64))
|
||||
|
||||
@@ -443,6 +443,7 @@ impl WorkerService {
|
||||
/// 3. Wait for in-flight tasks with timeout
|
||||
/// 4. Close MQ connection
|
||||
/// 5. Close DB connection
|
||||
///
|
||||
/// Verify which runtime versions are available on this host/container.
|
||||
///
|
||||
/// Runs each version's verification commands (from `distributions` JSONB)
|
||||
@@ -634,7 +635,7 @@ impl WorkerService {
|
||||
shutdown_timeout
|
||||
);
|
||||
|
||||
let timeout_duration = Duration::from_secs(shutdown_timeout as u64);
|
||||
let timeout_duration = Duration::from_secs(shutdown_timeout);
|
||||
match tokio::time::timeout(timeout_duration, self.wait_for_in_flight_tasks()).await {
|
||||
Ok(_) => info!("All in-flight tasks completed"),
|
||||
Err(_) => warn!("Shutdown timeout reached - some tasks may have been interrupted"),
|
||||
|
||||
@@ -90,7 +90,7 @@ pub async fn verify_all_runtime_versions(
|
||||
let rt_base_name = version
|
||||
.runtime_ref
|
||||
.split('.')
|
||||
.last()
|
||||
.next_back()
|
||||
.unwrap_or(&version.runtime_ref)
|
||||
.to_lowercase();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user