Private
Public Access
1
0

fix: resolve CI failures (fmt, clippy, tests)

- Fix rustfmt formatting in cache.rs, patches.rs, system.rs, routes.rs, main.rs
- Add Default impl for PackageCacheState (clippy new_without_default)
- Change apply_with_cache_retry generic bound from Fn to FnMut
- Add mut to refresh_fn parameter for FnMut compatibility
- Replace bool comparison with ! operator (clippy bool_comparison)
- Update todo.md with completed status
This commit is contained in:
2026-05-27 15:04:25 -05:00
parent 135c91d256
commit cc67edab12
6 changed files with 137 additions and 72 deletions

View File

@ -126,7 +126,12 @@ pub async fn apply_patches(
// MANDATORY: Refresh package cache before applying patches
let _ = job_manager_clone
.update_job(&job_id_clone, JobStatus::Running, Some(0), Some("Refreshing package index...".to_string()))
.update_job(
&job_id_clone,
JobStatus::Running,
Some(0),
Some("Refreshing package index...".to_string()),
)
.await;
let _ = job_manager_clone
.add_job_log(&job_id_clone, "Refreshing package cache...".to_string())
@ -135,10 +140,18 @@ pub async fn apply_patches(
match backend_clone.refresh_package_cache(&cache_state_clone) {
Ok(_) => {
let _ = job_manager_clone
.add_job_log(&job_id_clone, "Package cache refreshed successfully".to_string())
.add_job_log(
&job_id_clone,
"Package cache refreshed successfully".to_string(),
)
.await;
let _ = job_manager_clone
.update_job(&job_id_clone, JobStatus::Running, Some(10), Some("Cache refreshed, applying patches...".to_string()))
.update_job(
&job_id_clone,
JobStatus::Running,
Some(10),
Some("Cache refreshed, applying patches...".to_string()),
)
.await;
}
Err(e) => {
@ -194,17 +207,25 @@ pub async fn apply_patches(
// 404/fetch error: refresh cache and retry once
info!(job_id = %job_id_clone, "Patch apply failed with fetch error, refreshing cache and retrying");
let _ = job_manager_clone
.add_job_log(&job_id_clone, "Fetch error detected, refreshing cache and retrying...".to_string())
.add_job_log(
&job_id_clone,
"Fetch error detected, refreshing cache and retrying..."
.to_string(),
)
.await;
match backend_clone.refresh_package_cache(&cache_state_clone) {
Ok(_) => {
let _ = job_manager_clone
.add_job_log(&job_id_clone, "Cache refreshed, retrying patch apply...".to_string())
.add_job_log(
&job_id_clone,
"Cache refreshed, retrying patch apply...".to_string(),
)
.await;
}
Err(refresh_err) => {
let err_msg = format!("Cache refresh on retry failed: {}", refresh_err);
let err_msg =
format!("Cache refresh on retry failed: {}", refresh_err);
let _ = job_manager_clone.fail_job(&job_id_clone, err_msg).await;
error!(job_id = %job_id_clone, error = %refresh_err, "Cache refresh on retry failed");
return;
@ -228,29 +249,40 @@ pub async fn apply_patches(
),
)
.await;
match backend_clone.reboot_system(request.reboot_delay_seconds) {
match backend_clone.reboot_system(request.reboot_delay_seconds)
{
Ok(_) => {
let _ = job_manager_clone
.add_job_log(&job_id_clone, "Reboot command executed".to_string())
.add_job_log(
&job_id_clone,
"Reboot command executed".to_string(),
)
.await;
}
Err(e) => {
let _ = job_manager_clone
.add_job_log(&job_id_clone, format!("Reboot failed: {}", e))
.add_job_log(
&job_id_clone,
format!("Reboot failed: {}", e),
)
.await;
}
}
}
}
Err(retry_err) => {
let _ = job_manager_clone.fail_job(&job_id_clone, retry_err.to_string()).await;
let _ = job_manager_clone
.fail_job(&job_id_clone, retry_err.to_string())
.await;
error!(job_id = %job_id_clone, error = %retry_err, "Patch application failed after retry");
}
}
}
Err(e) => {
// Non-fetch error: fail immediately
let _ = job_manager_clone.fail_job(&job_id_clone, e.to_string()).await;
let _ = job_manager_clone
.fail_job(&job_id_clone, e.to_string())
.await;
error!(job_id = %job_id_clone, error = %e, "Patch application failed");
}
}

View File

@ -42,11 +42,11 @@ pub struct SystemInfoData {
/// Health check response data
#[derive(Debug, Serialize)]
pub struct HealthData {
pub status: String, // "healthy" or "degraded"
pub status: String, // "healthy" or "degraded"
pub uptime_seconds: u64,
pub version: String,
pub last_cache_update: Option<String>, // RFC3339 timestamp
pub cache_status: String, // "fresh", "stale", "unknown", "failed"
pub last_cache_update: Option<String>, // RFC3339 timestamp
pub cache_status: String, // "fresh", "stale", "unknown", "failed"
}
/// Service status response data
@ -138,15 +138,27 @@ pub async fn health_check(
match backend.refresh_package_cache(&cache_state) {
Ok(_) => {
let updated = cache_state.status();
("healthy".to_string(), "fresh".to_string(), updated.last_update.map(|dt| dt.to_rfc3339()))
(
"healthy".to_string(),
"fresh".to_string(),
updated.last_update.map(|dt| dt.to_rfc3339()),
)
}
Err(e) => {
error!("Health check cache refresh failed: {}", e);
("degraded".to_string(), "failed".to_string(), cache_status_val.last_update.map(|dt| dt.to_rfc3339()))
(
"degraded".to_string(),
"failed".to_string(),
cache_status_val.last_update.map(|dt| dt.to_rfc3339()),
)
}
}
} else {
("healthy".to_string(), "fresh".to_string(), cache_status_val.last_update.map(|dt| dt.to_rfc3339()))
(
"healthy".to_string(),
"fresh".to_string(),
cache_status_val.last_update.map(|dt| dt.to_rfc3339()),
)
};
let response = ApiResponse::success(HealthData {

View File

@ -26,21 +26,24 @@ pub fn configure_api_routes(
) {
info!("Configuring API v1 routes");
cfg.app_data(job_manager).app_data(backend).app_data(cache_state).service(
web::scope("/api/v1")
// VULN-005: Default handler for unsupported methods returns 405 instead of 404
.default_service(web::route().to(method_not_allowed))
// Package Management Endpoints
.configure(packages::configure_routes)
// Patch Management Endpoints
.configure(patches::configure_routes)
// System Management Endpoints
.configure(system::configure_routes)
// Job Management Endpoints
.configure(jobs::configure_routes)
// WebSocket Endpoint
.configure(websocket::configure_routes),
);
cfg.app_data(job_manager)
.app_data(backend)
.app_data(cache_state)
.service(
web::scope("/api/v1")
// VULN-005: Default handler for unsupported methods returns 405 instead of 404
.default_service(web::route().to(method_not_allowed))
// Package Management Endpoints
.configure(packages::configure_routes)
// Patch Management Endpoints
.configure(patches::configure_routes)
// System Management Endpoints
.configure(system::configure_routes)
// Job Management Endpoints
.configure(jobs::configure_routes)
// WebSocket Endpoint
.configure(websocket::configure_routes),
);
}
/// Health check route (outside API scope for load balancer checks)

View File

@ -166,7 +166,12 @@ async fn main() -> Result<()> {
// Configure API routes
app = app.configure(|cfg| {
configure_api_routes(cfg, job_manager_data.clone(), backend_data.clone(), cache_state.clone());
configure_api_routes(
cfg,
job_manager_data.clone(),
backend_data.clone(),
cache_state.clone(),
);
});
// Configure health route (outside API scope)

View File

@ -21,7 +21,7 @@ const CACHE_REFRESH_TIMEOUT_SECS: u64 = 120;
/// Persistent cache state (written to cache.json)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheStateFile {
pub last_cache_update: Option<String>, // RFC3339
pub last_cache_update: Option<String>, // RFC3339
pub last_update_success: bool,
}
@ -44,6 +44,12 @@ struct CacheStateInner {
last_update_error: Option<String>,
}
impl Default for PackageCacheState {
fn default() -> Self {
Self::new()
}
}
impl PackageCacheState {
pub fn new() -> Self {
// Try to load from state file on startup
@ -83,8 +89,7 @@ impl PackageCacheState {
Some(t) => {
let threshold = Duration::from_secs(STALE_THRESHOLD_SECS);
Utc::now() - t
> chrono::Duration::from_std(threshold)
.unwrap_or(chrono::TimeDelta::MAX)
> chrono::Duration::from_std(threshold).unwrap_or(chrono::TimeDelta::MAX)
}
}
}
@ -151,12 +156,9 @@ pub fn is_fetch_error(error: &anyhow::Error) -> bool {
/// Execute a patch apply with automatic cache refresh retry on 404/fetch errors.
/// Hardcoded 1 retry after cache refresh.
pub fn apply_with_cache_retry<F>(
refresh_fn: F,
apply_fn: impl Fn() -> Result<()>,
) -> Result<()>
pub fn apply_with_cache_retry<F>(mut refresh_fn: F, apply_fn: impl Fn() -> Result<()>) -> Result<()>
where
F: Fn() -> Result<()>,
F: FnMut() -> Result<()>,
{
match apply_fn() {
Ok(()) => Ok(()),
@ -226,7 +228,7 @@ mod tests {
let status = state.status();
// Fresh state should have no last_update (unless state file exists)
// Just verify it doesn't panic
assert!(status.last_update_success == false || status.last_update.is_some());
assert!(!status.last_update_success || status.last_update.is_some());
}
#[test]
@ -259,19 +261,14 @@ mod tests {
#[test]
fn test_apply_with_cache_retry_success() {
let result = apply_with_cache_retry(
|| Ok(()),
|| Ok(()),
);
let result = apply_with_cache_retry(|| Ok(()), || Ok(()));
assert!(result.is_ok());
}
#[test]
fn test_apply_with_cache_retry_non_fetch_error() {
let result: Result<()> = apply_with_cache_retry(
|| Ok(()),
|| Err(anyhow::anyhow!("Permission denied")),
);
let result: Result<()> =
apply_with_cache_retry(|| Ok(()), || Err(anyhow::anyhow!("Permission denied")));
assert!(result.is_err());
let err = result.unwrap_err();
assert!(!is_fetch_error(&err));