fix: expand empty packages to all available patches + refresh_listener UPSERT
Some checks failed
CI Pipeline / Rust Format Check (push) Failing after 4s
CI Pipeline / Clippy Lints (push) Successful in 46s
CI Pipeline / Rust Unit Tests (push) Successful in 1m1s
CI Pipeline / Security Audit (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 12s
CI Pipeline / Build .deb & Release (push) Has been skipped
Some checks failed
CI Pipeline / Rust Format Check (push) Failing after 4s
CI Pipeline / Clippy Lints (push) Successful in 46s
CI Pipeline / Rust Unit Tests (push) Successful in 1m1s
CI Pipeline / Security Audit (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 12s
CI Pipeline / Build .deb & Release (push) Has been skipped
BUG-15: Empty patch_selection sent to agent as-is, causing apply nothing instead of apply all available patches per SPEC. When packages is empty, now query host_patch_data and expand to full package list. BUG-16: refresh_listener used INSERT instead of UPSERT for host_patch_data, causing duplicate key constraint errors.
This commit is contained in:
@ -388,9 +388,49 @@ async fn execute_host_job(
|
||||
},
|
||||
};
|
||||
|
||||
let packages: Vec<String> =
|
||||
let mut packages: Vec<String> =
|
||||
serde_json::from_value(patch_sel.patch_selection).unwrap_or_default();
|
||||
|
||||
// ── 2b. Expand empty packages to all available patches ─────────────────
|
||||
// Per SPEC: "empty = all available patches". The agent treats an empty
|
||||
// list as "apply nothing", so we must expand it here.
|
||||
if packages.is_empty() {
|
||||
match sqlx::query_scalar::<_, serde_json::Value>(
|
||||
r#"
|
||||
SELECT available_patches
|
||||
FROM host_patch_data
|
||||
WHERE host_id = $1
|
||||
ORDER BY polled_at DESC
|
||||
LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.bind(host_id)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
{
|
||||
Ok(Some(val)) => {
|
||||
if let Ok(patches) = serde_json::from_value::<Vec<serde_json::Value>>(val) {
|
||||
for p in &patches {
|
||||
if let Some(name) = p.get("name").and_then(|n| n.as_str()) {
|
||||
packages.push(name.to_string());
|
||||
}
|
||||
}
|
||||
tracing::info!(
|
||||
%pjh_id,
|
||||
count = packages.len(),
|
||||
"execute_host_job: expanded empty packages to all available patches"
|
||||
);
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
tracing::warn!(%pjh_id, "execute_host_job: no patch data for host, sending empty packages");
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!(%pjh_id, error = %e, "execute_host_job: failed to fetch patch data for expansion");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Load mTLS certs ───────────────────────────────────────────────────
|
||||
let certs = match load_agent_certs(&config.security) {
|
||||
Ok(c) => c,
|
||||
|
||||
@ -181,6 +181,12 @@ async fn refresh_host(
|
||||
INSERT INTO host_patch_data
|
||||
(host_id, available_patches, installed_packages, patch_count, cve_count)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (host_id) DO UPDATE SET
|
||||
available_patches = EXCLUDED.available_patches,
|
||||
installed_packages = EXCLUDED.installed_packages,
|
||||
patch_count = EXCLUDED.patch_count,
|
||||
cve_count = EXCLUDED.cve_count,
|
||||
polled_at = NOW()
|
||||
"#,
|
||||
)
|
||||
.bind(host.id)
|
||||
|
||||
Reference in New Issue
Block a user