Link image findings to their workloads and suggest a fixing image update
Container findings now name the workloads that run the image and link into the Kubernetes view, which highlights them. An update check asks the registry for newer tags of the same variant, scans the newest one and records which of the open findings are gone in it. The image row then shows the candidate tag, how many findings it fixes and how many remain, marks those CVEs in the expanded list, and offers to roll every workload over to it. The check runs as a job, nightly for all running images or on demand for one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@ -418,6 +418,7 @@ use domain::ports::ClusterGateway;
|
||||
#[derive(Default)]
|
||||
pub struct MemCluster {
|
||||
pub actions: Mutex<Vec<String>>,
|
||||
pub fail: std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
pub fn sample_overview() -> ClusterOverview {
|
||||
@ -469,6 +470,9 @@ pub fn sample_overview() -> ClusterOverview {
|
||||
#[async_trait]
|
||||
impl ClusterGateway for MemCluster {
|
||||
async fn overview(&self) -> Result<ClusterOverview, DomainError> {
|
||||
if self.fail.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
return Err(DomainError::Unavailable("cluster unreachable".into()));
|
||||
}
|
||||
Ok(sample_overview())
|
||||
}
|
||||
async fn restart(&self, w: &WorkloadRef) -> Result<(), DomainError> {
|
||||
@ -964,3 +968,56 @@ pub fn strategy(name: &str, target_id: Uuid) -> BackupStrategy {
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
use domain::image::ImageUpdate;
|
||||
use domain::ports::{ImageRegistry, ImageUpdateRepository};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MemRegistry {
|
||||
pub tags: Mutex<Vec<String>>,
|
||||
pub fail: std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ImageRegistry for MemRegistry {
|
||||
async fn tags(&self, _image: &str) -> Result<Vec<String>, DomainError> {
|
||||
if self.fail.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
return Err(DomainError::Unavailable("registry unreachable".into()));
|
||||
}
|
||||
Ok(self.tags.lock().unwrap().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MemImageUpdates(pub Mutex<Vec<ImageUpdate>>);
|
||||
|
||||
#[async_trait]
|
||||
impl ImageUpdateRepository for MemImageUpdates {
|
||||
async fn upsert(&self, update: &ImageUpdate) -> Result<(), DomainError> {
|
||||
let mut v = self.0.lock().unwrap();
|
||||
v.retain(|u| u.image != update.image);
|
||||
v.push(update.clone());
|
||||
Ok(())
|
||||
}
|
||||
async fn get(&self, image: &str) -> Result<Option<ImageUpdate>, DomainError> {
|
||||
Ok(self
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|u| u.image == image)
|
||||
.cloned())
|
||||
}
|
||||
async fn list(&self) -> Result<Vec<ImageUpdate>, DomainError> {
|
||||
Ok(self.0.lock().unwrap().clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings service on in-memory stores, for services that only need it as a dependency.
|
||||
pub fn settings_service() -> Arc<crate::SettingsService> {
|
||||
Arc::new(crate::SettingsService::new(
|
||||
Arc::new(MemSettings::default()),
|
||||
Arc::new(FakeCipher),
|
||||
Arc::new(MemMailer::default()),
|
||||
))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user