summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/db.rs191
-rw-r--r--src/main.rs6
-rw-r--r--src/updater.rs51
3 files changed, 173 insertions, 75 deletions
diff --git a/src/db.rs b/src/db.rs
index c914769..fc00621 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -2,9 +2,18 @@ use chrono::{DateTime, Utc};
use rusqlite::Connection;
use rusqlite::OptionalExtension;
+#[derive(Debug, PartialEq)]
+pub struct Image {
+ pub id: i64,
+ pub registry: String,
+ pub image: String,
+ pub expires_at: DateTime<Utc>,
+}
+
pub trait Db {
- fn get_image(&self, registry: &str, image: &str) -> Option<(i64, DateTime<Utc>)>;
- fn insert_image(&self, registry: &str, image: &str, expires_at: &DateTime<Utc>) -> i64;
+ fn get_image(&self, registry: &str, image: &str) -> Option<Image>;
+ fn insert_image(&self, image: &mut Image);
+ fn get_expired_images(&self, now: &DateTime<Utc>) -> Vec<Image>;
fn get_tags_sorted(&self, image_id: i64) -> Vec<String>;
fn delete_tag(&self, image_id: i64, tag: &str);
fn insert_tag(&self, image_id: i64, tag: &str, fetched_at: &DateTime<Utc>);
@@ -108,23 +117,43 @@ impl SqliteDb {
}
impl Db for SqliteDb {
- fn get_image(&self, registry: &str, image: &str) -> Option<(i64, DateTime<Utc>)> {
+ fn get_image(&self, registry: &str, image: &str) -> Option<Image> {
let _timer = crate::metrics::get().db_query_duration.start_timer();
return self.conn.query_row("
- SELECT id, expires_at FROM images
+ SELECT id, registry, image, expires_at FROM images
WHERE registry = ?1 AND image = ?2
- ", (registry, image), |row| Ok((
- row.get::<_, i64>(0)?,
- row.get::<_, DateTime<Utc>>(1)?,
- ))).optional().unwrap();
+ ", (registry, image), |row| Ok(Image {
+ id: row.get(0)?,
+ registry: row.get(1)?,
+ image: row.get(2)?,
+ expires_at: row.get(3)?,
+ })).optional().unwrap();
}
- fn insert_image(&self, registry: &str, image: &str, expires_at: &DateTime<Utc>) -> i64 {
+ fn insert_image(&self, image: &mut Image) {
let _timer = crate::metrics::get().db_query_duration.start_timer();
self.conn.execute("
INSERT INTO images(registry, image, expires_at) VALUES (?1, ?2, ?3)
- ", (registry, image, expires_at)).unwrap();
- return self.conn.last_insert_rowid();
+ ", (&image.registry, &image.image, &image.expires_at)).unwrap();
+ image.id = self.conn.last_insert_rowid();
+ }
+
+ fn get_expired_images(&self, now: &DateTime<Utc>) -> Vec<Image> {
+ let _timer = crate::metrics::get().db_query_duration.start_timer();
+ let mut stmt = self.conn.prepare("
+ SELECT id, registry, image, expires_at FROM images WHERE expires_at <= ?1
+ ").unwrap();
+ let mut rows = stmt.query((now,)).unwrap();
+ let mut images = vec![];
+ while let Some(row) = rows.next().unwrap() {
+ images.push(Image {
+ id: row.get(0).unwrap(),
+ registry: row.get(1).unwrap(),
+ image: row.get(2).unwrap(),
+ expires_at: row.get(3).unwrap(),
+ });
+ }
+ return images;
}
fn get_tags_sorted(&self, image_id: i64) -> Vec<String> {
@@ -172,7 +201,7 @@ impl Db for SqliteDb {
#[cfg(test)]
pub struct StubDb {
next_id: std::cell::RefCell<i64>,
- images: std::cell::RefCell<Vec<(String, String, i64, DateTime<Utc>)>>,
+ images: std::cell::RefCell<Vec<Image>>,
tags: std::cell::RefCell<Vec<(i64, String, Option<String>, DateTime<Utc>)>>,
}
@@ -189,20 +218,45 @@ impl Default for StubDb {
#[cfg(test)]
impl Db for StubDb {
- fn get_image(&self, registry: &str, image: &str) -> Option<(i64, DateTime<Utc>)> {
- for (r, i, id, expires_at) in self.images.borrow().iter() {
- if r == registry && i == image {
- return Some((*id, *expires_at));
+ fn get_image(&self, registry: &str, image: &str) -> Option<Image> {
+ for img in self.images.borrow().iter() {
+ if img.registry == registry && img.image == image {
+ return Some(Image {
+ id: img.id,
+ registry: img.registry.clone(),
+ image: img.image.clone(),
+ expires_at: img.expires_at,
+ });
}
}
return None;
}
- fn insert_image(&self, registry: &str, image: &str, expires_at: &DateTime<Utc>) -> i64 {
+ fn insert_image(&self, image: &mut Image) {
let id = *self.next_id.borrow();
*self.next_id.borrow_mut() += 1;
- self.images.borrow_mut().push((registry.to_string(), image.to_string(), id, expires_at.clone()));
- return id;
+ image.id = id;
+ self.images.borrow_mut().push(Image {
+ id,
+ registry: image.registry.clone(),
+ image: image.image.clone(),
+ expires_at: image.expires_at,
+ });
+ }
+
+ fn get_expired_images(&self, now: &DateTime<Utc>) -> Vec<Image> {
+ let mut result = vec![];
+ for img in self.images.borrow().iter() {
+ if img.expires_at <= *now {
+ result.push(Image {
+ id: img.id,
+ registry: img.registry.clone(),
+ image: img.image.clone(),
+ expires_at: img.expires_at,
+ });
+ }
+ }
+ return result;
}
fn get_tags_sorted(&self, image_id: i64) -> Vec<String> {
@@ -225,9 +279,9 @@ impl Db for StubDb {
}
fn set_expires_at(&self, image_id: i64, expires_at: &DateTime<Utc>) {
- for (_, _, id, ea) in self.images.borrow_mut().iter_mut() {
- if *id == image_id {
- *ea = expires_at.clone();
+ for img in self.images.borrow_mut().iter_mut() {
+ if img.id == image_id {
+ img.expires_at = *expires_at;
return;
}
}
@@ -260,9 +314,11 @@ 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);
- assert!(id2 > id1);
+ let mut img1 = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now };
+ let mut img2 = Image { id: 0, registry: "docker.io".into(), image: "redis".into(), expires_at: now };
+ db.insert_image(&mut img1);
+ db.insert_image(&mut img2);
+ assert!(img2.id > img1.id);
}
fn test_get_image_returns_none_for_unknown(db: &dyn Db) {
@@ -271,57 +327,78 @@ 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 (got_id, got_time) = db.get_image("docker.io", "nginx").unwrap();
- assert_eq!(got_id, id);
- assert_eq!(got_time, now);
+ let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now };
+ db.insert_image(&mut img);
+ let got = db.get_image("docker.io", "nginx").unwrap();
+ assert_eq!(got.id, img.id);
+ assert_eq!(got.expires_at, now);
}
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);
- assert_eq!(db.get_tags_sorted(id), Vec::<String>::new());
+ let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now };
+ db.insert_image(&mut img);
+ assert_eq!(db.get_tags_sorted(img.id), Vec::<String>::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);
- assert_eq!(db.get_tags_sorted(id), vec!["1.0", "2.0", "latest"]);
+ let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now };
+ db.insert_image(&mut img);
+ db.insert_tag(img.id, "2.0", &now);
+ db.insert_tag(img.id, "1.0", &now);
+ db.insert_tag(img.id, "latest", &now);
+ assert_eq!(db.get_tags_sorted(img.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);
- db.delete_tag(id, "1.0");
- assert_eq!(db.get_tags_sorted(id), vec!["2.0"]);
+ let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now };
+ db.insert_image(&mut img);
+ db.insert_tag(img.id, "1.0", &now);
+ db.insert_tag(img.id, "2.0", &now);
+ db.delete_tag(img.id, "1.0");
+ assert_eq!(db.get_tags_sorted(img.id), vec!["2.0"]);
}
fn test_set_expires_at(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.set_expires_at(id, &t2);
- assert_eq!(db.get_image("docker.io", "nginx").unwrap().1, t2);
+ let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: t1 };
+ db.insert_image(&mut img);
+ db.set_expires_at(img.id, &t2);
+ assert_eq!(db.get_image("docker.io", "nginx").unwrap().expires_at, 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);
- assert_eq!(db.get_tag_digest(id, "1.0"), None);
+ let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now };
+ db.insert_image(&mut img);
+ db.insert_tag(img.id, "1.0", &now);
+ assert_eq!(db.get_tag_digest(img.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);
- assert_eq!(db.get_tag_digest(id, "1.0"), Some("sha256:abc".to_string()));
+ let mut img = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: now };
+ db.insert_image(&mut img);
+ db.insert_tag(img.id, "1.0", &now);
+ db.update_tag_digest(img.id, "1.0", "sha256:abc", &now);
+ assert_eq!(db.get_tag_digest(img.id, "1.0"), Some("sha256:abc".to_string()));
+ }
+
+ fn test_get_expired_images(db: &dyn Db) {
+ let now = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
+ let past = now - chrono::Duration::days(1);
+ let future = now + chrono::Duration::days(1);
+ let mut expired = Image { id: 0, registry: "docker.io".into(), image: "nginx".into(), expires_at: past };
+ let mut fresh = Image { id: 0, registry: "docker.io".into(), image: "redis".into(), expires_at: future };
+ db.insert_image(&mut expired);
+ db.insert_image(&mut fresh);
+ let result = db.get_expired_images(&now);
+ assert_eq!(result.len(), 1);
+ assert_eq!(result[0].id, expired.id);
+ assert_eq!(result[0].image, "nginx");
}
#[test]
@@ -431,4 +508,16 @@ mod tests {
let dir = tempfile::tempdir().unwrap();
test_update_tag_digest(&SqliteDb::new(&dir.path().join("db.sqlite")));
}
+
+ #[test]
+ fn conformance_stub_get_expired_images() {
+ test_get_expired_images(&StubDb::default());
+ }
+
+ #[test]
+ fn conformance_sqlite_get_expired_images() {
+ crate::metrics::init();
+ let dir = tempfile::tempdir().unwrap();
+ test_get_expired_images(&SqliteDb::new(&dir.path().join("db.sqlite")));
+ }
}
diff --git a/src/main.rs b/src/main.rs
index 6eb2cb7..24a1b01 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -331,9 +331,9 @@ fn run_tool(db: &dyn Db, reg: &dyn Registries, repos: &Vec<Repository>, mut infi
.unwrap_or("registry.hub.docker.com");
let image_name = &file.content[img.image.clone()];
- if let Some((_, expires_at)) = db.get_image(registry, image_name) {
- if expires_at < min_expiry {
- min_expiry = expires_at;
+ if let Some(img) = db.get_image(registry, image_name) {
+ if img.expires_at < min_expiry {
+ min_expiry = img.expires_at;
}
}
}
diff --git a/src/updater.rs b/src/updater.rs
index e407a2f..9f6c7df 100644
--- a/src/updater.rs
+++ b/src/updater.rs
@@ -1,6 +1,6 @@
use crate::version::{VersionPattern, CompareOutcome};
use crate::docker::DockerRef;
-use crate::db::Db;
+use crate::db::{Db, Image};
use crate::registry::Registries;
use crate::metrics;
use chrono::{DateTime, Utc};
@@ -34,11 +34,16 @@ pub fn update_images(now: &DateTime<Utc>, db: &dyn Db, reg: &dyn Registries, fil
let image_name = &file.content[img.image.clone()];
let (image_id, expires_at) = match db.get_image(registry, image_name) {
- Some(x) => x,
+ Some(img) => (img.id, img.expires_at),
None => {
- let epoch = DateTime::<Utc>::UNIX_EPOCH;
- let id = db.insert_image(registry, image_name, &epoch);
- (id, epoch)
+ let mut new_image = Image {
+ id: 0,
+ registry: registry.to_string(),
+ image: image_name.to_string(),
+ expires_at: DateTime::<Utc>::UNIX_EPOCH,
+ };
+ db.insert_image(&mut new_image);
+ (new_image.id, new_image.expires_at)
},
};
@@ -230,9 +235,10 @@ mod tests {
let reg = StubRegistry::default();
let past_expiry = now - chrono::Duration::days(1);
- let id = db.insert_image("registry.hub.docker.com", "nginx", &past_expiry);
- db.insert_tag(id, "1.20", &past_expiry);
- db.insert_tag(id, "1.21", &past_expiry);
+ let mut img = Image { id: 0, registry: "registry.hub.docker.com".into(), image: "nginx".into(), expires_at: past_expiry };
+ db.insert_image(&mut img);
+ db.insert_tag(img.id, "1.20", &past_expiry);
+ db.insert_tag(img.id, "1.21", &past_expiry);
reg.add_tag("registry.hub.docker.com", "nginx", "1.21", "sha256:0000000000000000000000000000000000000000000000000000000000000001");
reg.add_tag("registry.hub.docker.com", "nginx", "1.22", "sha256:0000000000000000000000000000000000000000000000000000000000000002");
@@ -244,9 +250,9 @@ mod tests {
let mut outcomes = vec![];
update_images(&now, &db, &reg, &files, &mut outcomes);
- assert_eq!(db.get_tags_sorted(id), vec!["1.21", "1.22"]);
- let (_, expires_at) = db.get_image("registry.hub.docker.com", "nginx").unwrap();
- assert!(expires_at > now);
+ assert_eq!(db.get_tags_sorted(img.id), vec!["1.21", "1.22"]);
+ let got = db.get_image("registry.hub.docker.com", "nginx").unwrap();
+ assert!(got.expires_at > now);
}
#[test]
@@ -257,8 +263,9 @@ mod tests {
let reg = StubRegistry::default();
let future_expiry = now + chrono::Duration::minutes(30);
- let id = db.insert_image("registry.hub.docker.com", "nginx", &future_expiry);
- db.insert_tag(id, "1.21", &now);
+ let mut img = Image { id: 0, registry: "registry.hub.docker.com".into(), image: "nginx".into(), expires_at: future_expiry };
+ db.insert_image(&mut img);
+ db.insert_tag(img.id, "1.21", &now);
let files = vec![FileInput {
path: "/test".into(),
@@ -364,9 +371,10 @@ mod tests {
let reg = StubRegistry::default();
let future_expiry = now + chrono::Duration::minutes(30);
- let id = db.insert_image("registry.hub.docker.com", "nginx", &future_expiry);
- db.insert_tag(id, "1.21", &now);
- db.update_tag_digest(id, "1.21", "sha256:0000000000000000000000000000000000000000000000000000000000000003", &now);
+ let mut img = Image { id: 0, registry: "registry.hub.docker.com".into(), image: "nginx".into(), expires_at: future_expiry };
+ db.insert_image(&mut img);
+ db.insert_tag(img.id, "1.21", &now);
+ db.update_tag_digest(img.id, "1.21", "sha256:0000000000000000000000000000000000000000000000000000000000000003", &now);
let files = vec![FileInput {
path: "/test".into(),
@@ -486,8 +494,8 @@ mod tests {
assert!(outcomes[0].is_err());
assert!(outcomes[0].as_ref().unwrap_err().contains("image not found"));
- let (_, expires_at) = db.get_image("registry.hub.docker.com", "nginx").unwrap();
- assert!(expires_at > now);
+ let got = db.get_image("registry.hub.docker.com", "nginx").unwrap();
+ assert!(got.expires_at > now);
}
#[test]
@@ -596,9 +604,10 @@ mod tests {
let reg = StubRegistry::default();
let past_expiry = now - chrono::Duration::days(1);
- let id = db.insert_image("registry.hub.docker.com", "nginx", &past_expiry);
- db.insert_tag(id, "1.21", &past_expiry);
- db.update_tag_digest(id, "1.21", "sha256:0000000000000000000000000000000000000000000000000000000000000003", &past_expiry);
+ let mut img = Image { id: 0, registry: "registry.hub.docker.com".into(), image: "nginx".into(), expires_at: past_expiry };
+ db.insert_image(&mut img);
+ db.insert_tag(img.id, "1.21", &past_expiry);
+ db.update_tag_digest(img.id, "1.21", "sha256:0000000000000000000000000000000000000000000000000000000000000003", &past_expiry);
reg.add_tag("registry.hub.docker.com", "nginx", "1.21", "sha256:0000000000000000000000000000000000000000000000000000000000000005");