From f634dab8509ce012c99dcba26392000424e49c82 Mon Sep 17 00:00:00 2001 From: Jesper Jensen Date: Sat, 7 Feb 2026 20:40:44 +0100 Subject: Start to peel apart the update loop I also moved over to passing borrows of the time. This should have been a separate commit, but i forgot. --- src/db.rs | 68 ++++++++++++++++++++++++++++++------------------------------- src/main.rs | 49 +++++++++++++++++++++++++++++++------------- 2 files changed, 69 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/db.rs b/src/db.rs index 457921a..ae6073f 100644 --- a/src/db.rs +++ b/src/db.rs @@ -4,13 +4,13 @@ use rusqlite::OptionalExtension; pub trait Db { fn get_image(&self, registry: &str, image: &str) -> Option<(i64, DateTime)>; - fn insert_image(&self, registry: &str, image: &str, last_checked: DateTime) -> i64; + fn insert_image(&self, registry: &str, image: &str, last_checked: &DateTime) -> i64; fn get_tags_sorted(&self, image_id: i64) -> Vec; fn delete_tag(&self, image_id: i64, tag: &str); - fn insert_tag(&self, image_id: i64, tag: &str, fetched_at: DateTime); - fn update_last_checked(&self, image_id: i64, last_checked: DateTime); + fn insert_tag(&self, image_id: i64, tag: &str, fetched_at: &DateTime); + fn update_last_checked(&self, image_id: i64, last_checked: &DateTime); fn get_tag_digest(&self, image_id: i64, tag: &str) -> Option; - fn update_tag_digest(&self, image_id: i64, tag: &str, digest: &str, fetched_at: DateTime); + fn update_tag_digest(&self, image_id: i64, tag: &str, digest: &str, fetched_at: &DateTime); } pub struct SqliteDb { @@ -113,7 +113,7 @@ impl Db for SqliteDb { ))).optional().unwrap(); } - fn insert_image(&self, registry: &str, image: &str, last_checked: DateTime) -> i64 { + fn insert_image(&self, registry: &str, image: &str, last_checked: &DateTime) -> i64 { let _timer = crate::metrics::get().db_query_duration.start_timer(); self.conn.execute(" INSERT INTO images(registry, image, last_checked) VALUES (?1, ?2, ?3) @@ -138,13 +138,13 @@ impl Db for SqliteDb { (image_id, tag)).unwrap(); } - fn insert_tag(&self, image_id: i64, tag: &str, fetched_at: DateTime) { + fn insert_tag(&self, image_id: i64, tag: &str, fetched_at: &DateTime) { let _timer = crate::metrics::get().db_query_duration.start_timer(); self.conn.execute("INSERT INTO tags(image_id, tag, fetched_at) VALUES (?1, ?2, ?3)", (image_id, tag, fetched_at)).unwrap(); } - fn update_last_checked(&self, image_id: i64, last_checked: DateTime) { + fn update_last_checked(&self, image_id: i64, last_checked: &DateTime) { let _timer = crate::metrics::get().db_query_duration.start_timer(); self.conn.execute("UPDATE images SET last_checked = ?1 WHERE id = ?2", (last_checked, image_id)).unwrap(); } @@ -156,7 +156,7 @@ impl Db for SqliteDb { ", (image_id, tag), |row| row.get::<_, String>(0)).optional().unwrap(); } - fn update_tag_digest(&self, image_id: i64, tag: &str, digest: &str, fetched_at: DateTime) { + fn update_tag_digest(&self, image_id: i64, tag: &str, digest: &str, fetched_at: &DateTime) { let _timer = crate::metrics::get().db_query_duration.start_timer(); self.conn.execute("UPDATE tags SET digest = ?1, fetched_at = ?2 WHERE image_id = ?3 AND tag = ?4", (digest, fetched_at, image_id, tag)).unwrap(); @@ -192,10 +192,10 @@ impl Db for StubDb { return None; } - fn insert_image(&self, registry: &str, image: &str, last_checked: DateTime) -> i64 { + fn insert_image(&self, registry: &str, image: &str, last_checked: &DateTime) -> i64 { let id = *self.next_id.borrow(); *self.next_id.borrow_mut() += 1; - self.images.borrow_mut().push((registry.to_string(), image.to_string(), id, last_checked)); + self.images.borrow_mut().push((registry.to_string(), image.to_string(), id, last_checked.clone())); return id; } @@ -214,14 +214,14 @@ impl Db for StubDb { self.tags.borrow_mut().retain(|(id, t, _, _)| !(*id == image_id && t == tag)); } - fn insert_tag(&self, image_id: i64, tag: &str, fetched_at: DateTime) { - self.tags.borrow_mut().push((image_id, tag.to_string(), None, fetched_at)); + fn insert_tag(&self, image_id: i64, tag: &str, fetched_at: &DateTime) { + self.tags.borrow_mut().push((image_id, tag.to_string(), None, fetched_at.clone())); } - fn update_last_checked(&self, image_id: i64, last_checked: DateTime) { + fn update_last_checked(&self, image_id: i64, last_checked: &DateTime) { for (_, _, id, lc) in self.images.borrow_mut().iter_mut() { if *id == image_id { - *lc = last_checked; + *lc = last_checked.clone(); return; } } @@ -236,11 +236,11 @@ impl Db for StubDb { return None; } - fn update_tag_digest(&self, image_id: i64, tag: &str, digest: &str, fetched_at: DateTime) { + fn update_tag_digest(&self, image_id: i64, tag: &str, digest: &str, fetched_at: &DateTime) { for (id, t, d, fa) in self.tags.borrow_mut().iter_mut() { if *id == image_id && t == tag { *d = Some(digest.to_string()); - *fa = fetched_at; + *fa = fetched_at.clone(); return; } } @@ -254,8 +254,8 @@ mod tests { fn test_insert_image_returns_incrementing_ids(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); - let id1 = db.insert_image("docker.io", "nginx", now); - let id2 = db.insert_image("docker.io", "redis", now); + let id1 = db.insert_image("docker.io", "nginx", &now); + let id2 = db.insert_image("docker.io", "redis", &now); assert!(id2 > id1); } @@ -265,7 +265,7 @@ mod tests { fn test_get_image_returns_inserted(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); - let id = db.insert_image("docker.io", "nginx", now); + let id = db.insert_image("docker.io", "nginx", &now); let (got_id, got_time) = db.get_image("docker.io", "nginx").unwrap(); assert_eq!(got_id, id); assert_eq!(got_time, now); @@ -273,24 +273,24 @@ mod tests { fn test_get_tags_returns_empty_for_no_tags(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); - let id = db.insert_image("docker.io", "nginx", now); + let id = db.insert_image("docker.io", "nginx", &now); assert_eq!(db.get_tags_sorted(id), Vec::::new()); } fn test_get_tags_returns_sorted(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); - let id = db.insert_image("docker.io", "nginx", now); - db.insert_tag(id, "2.0", now); - db.insert_tag(id, "1.0", now); - db.insert_tag(id, "latest", now); + let id = db.insert_image("docker.io", "nginx", &now); + db.insert_tag(id, "2.0", &now); + db.insert_tag(id, "1.0", &now); + db.insert_tag(id, "latest", &now); assert_eq!(db.get_tags_sorted(id), vec!["1.0", "2.0", "latest"]); } fn test_delete_tag_removes_tag(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); - let id = db.insert_image("docker.io", "nginx", now); - db.insert_tag(id, "1.0", now); - db.insert_tag(id, "2.0", now); + let id = db.insert_image("docker.io", "nginx", &now); + db.insert_tag(id, "1.0", &now); + db.insert_tag(id, "2.0", &now); db.delete_tag(id, "1.0"); assert_eq!(db.get_tags_sorted(id), vec!["2.0"]); } @@ -298,23 +298,23 @@ mod tests { fn test_update_last_checked(db: &dyn Db) { let t1 = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); let t2 = Utc.with_ymd_and_hms(2000, 1, 1, 1, 0, 0).unwrap(); - let id = db.insert_image("docker.io", "nginx", t1); - db.update_last_checked(id, t2); + let id = db.insert_image("docker.io", "nginx", &t1); + db.update_last_checked(id, &t2); assert_eq!(db.get_image("docker.io", "nginx").unwrap().1, t2); } fn test_get_tag_digest_returns_none_when_unset(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); - let id = db.insert_image("docker.io", "nginx", now); - db.insert_tag(id, "1.0", now); + let id = db.insert_image("docker.io", "nginx", &now); + db.insert_tag(id, "1.0", &now); assert_eq!(db.get_tag_digest(id, "1.0"), None); } fn test_update_tag_digest(db: &dyn Db) { let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap(); - let id = db.insert_image("docker.io", "nginx", now); - db.insert_tag(id, "1.0", now); - db.update_tag_digest(id, "1.0", "sha256:abc", now); + let id = db.insert_image("docker.io", "nginx", &now); + db.insert_tag(id, "1.0", &now); + db.update_tag_digest(id, "1.0", "sha256:abc", &now); assert_eq!(db.get_tag_digest(id, "1.0"), Some("sha256:abc".to_string())); } diff --git a/src/main.rs b/src/main.rs index ed3c9d5..680bc9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,32 +52,52 @@ struct FileInput { images: Vec>, } -fn update_images(db: &dyn Db, reg: &dyn Registry, files: &[FileInput], outcomes: &mut Vec, String>>) { +fn update_images(now: &chrono::DateTime, db: &dyn Db, reg: &dyn Registry, files: &[FileInput], outcomes: &mut Vec, String>>) { for file in files { let mut patches = vec![]; let mut error: Option = None; + let mut imgs: Vec = vec![]; + let mut image_ids: Vec = vec![]; + let mut last_checkeds: Vec> = vec![]; + for image in &file.images { - println!("Checking image {}", &file.content[image.clone()]); let img = DockerRef::parse(&file.content, image); - let registry = img.registry.as_ref().map(|x| &file.content[x.clone()]).unwrap_or("registry.hub.docker.com"); - let mut tag = img.tag.as_ref().map(|x| file.content[x.clone()].to_string()); + let registry = img.registry.as_ref() + .map(|x| &file.content[x.clone()]) + .unwrap_or("registry.hub.docker.com"); let image_name = &file.content[img.image.clone()]; - metrics::get().images_checked.with_label_values(&[registry]).inc(); - - let now = chrono::offset::Utc::now(); - let cache_max_age = chrono::Duration::hours(24); - let (image_id, last_checked) = match db.get_image(registry, image_name) { Some(x) => x, None => { - let id = db.insert_image(registry, image_name, now); - (id, now) + let epoch = chrono::DateTime::::UNIX_EPOCH; + let id = db.insert_image(registry, image_name, &epoch); + (id, epoch) }, }; - let cache_stale = now - last_checked > cache_max_age; + imgs.push(img); + image_ids.push(image_id); + last_checkeds.push(last_checked); + } + + let cache_max_age = chrono::Duration::hours(24); + + for i in 0..imgs.len() { + let img = &imgs[i]; + let registry = img.registry.as_ref() + .map(|x| &file.content[x.clone()]) + .unwrap_or("registry.hub.docker.com"); + let mut tag = img.tag.as_ref().map(|x| file.content[x.clone()].to_string()); + let image_name = &file.content[img.image.clone()]; + let image_id = image_ids[i]; + let last_checked = last_checkeds[i]; + + println!("Checking image {}", &file.content[file.images[i].clone()]); + metrics::get().images_checked.with_label_values(&[registry]).inc(); + + let cache_stale = *now - last_checked > cache_max_age; let cached_tags: Vec = db.get_tags_sorted(image_id); @@ -143,7 +163,7 @@ fn update_images(db: &dyn Db, reg: &dyn Registry, files: &[FileInput], outcomes: if let Some(new_tag) = new_tag { tag = Some(new_tag.clone()); patches.push(FilePatch { - position: img.tag.unwrap(), + position: img.tag.clone().unwrap(), content: new_tag, }); metrics::get().images_updated.with_label_values(&[registry]).inc(); @@ -393,8 +413,9 @@ fn run_tool(db: &dyn Db, reg: &dyn Registry, repos: &Vec, mut infile file_inputs.push(FileInput { path, content, images }); } + let now = chrono::offset::Utc::now(); let mut outcomes: Vec, String>> = Vec::with_capacity(file_inputs.len()); - update_images(db, reg, &file_inputs, &mut outcomes); + update_images(&now, db, reg, &file_inputs, &mut outcomes); for i in 0..file_inputs.len() { let file = &file_inputs[i]; -- cgit v1.2.3