diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 194 | ||||
| -rw-r--r-- | src/metrics.rs | 4 |
2 files changed, 132 insertions, 66 deletions
diff --git a/src/main.rs b/src/main.rs index 3811e4a..97bc034 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,7 +38,7 @@ Options: #[derive(Debug)] enum AuthMethod { Basic, - Bearer{realm: String, service: String, scope: String}, + Bearer { realm: String, service: String, scope: String }, } impl AuthMethod { @@ -51,7 +51,7 @@ impl AuthMethod { } if &header_str[parse.scheme.clone()] == "Bearer" { - return Some(AuthMethod::Bearer{ + return Some(AuthMethod::Bearer { realm: header_str[parse.realm.unwrap()].to_string(), scope: header_str[parse.scope.unwrap()].to_string(), service: header_str[parse.service.unwrap()].to_string(), @@ -147,7 +147,7 @@ impl AuthState{ return Ok(()); }, - None => Err(format!("Server {} provided us with a challenge, but we didn't understand it", self.info.host)), + None => return Err(format!("Server {} provided us with a challenge, but we didn't understand it", self.info.host)), } } } @@ -251,79 +251,103 @@ fn update_images(db: &Connection, file: &str, auth: &mut Auth, img: DockerRef, e metrics::get().images_checked.with_label_values(&[registry]).inc(); + let now = chrono::offset::Utc::now(); + let cache_max_age = chrono::Duration::hours(24); + let query_result = { let _timer = metrics::get().db_query_duration.start_timer(); db.query_one(" - SELECT last_checked FROM images + SELECT id, last_checked FROM images WHERE registry = ?1 AND image = ?2 - ", (registry, image), |row| row.get::<_, chrono::DateTime<chrono::Utc>>(0)).optional() + ", (registry, image), |row| Ok(( + row.get::<_, i64>(0)?, + row.get::<_, chrono::DateTime<chrono::Utc>>(1)?, + ))).optional() }; - let _last_checked = match query_result { + let (image_id, last_checked) = match query_result { Ok(Some(x)) => x, Ok(None) => { - let time = chrono::offset::Utc::now(); { let _timer = metrics::get().db_query_duration.start_timer(); db.execute(" INSERT INTO images(registry, image, last_checked) VALUES (?1, ?2, ?3) - ", (registry, &image, time)).unwrap(); + ", (registry, &image, now)).unwrap(); } - time + (db.last_insert_rowid(), now) }, Err(_x) => return Err("Database Failure".to_string()), }; + let cache_stale = now - last_checked > cache_max_age; if let Some(ref tag_str) = tag { - let mut current = VersionPattern::parse(&tag_str); + if cache_stale { + let mut url = format!("/v2/{}/tags/list", &image); + loop { + let mut response = match perform_registry_request(registry, &url, "application/vnd.oci.image.index.v1+json", auth) { + Ok(r) => r, + Err(RegistryError::NotFound) => { + println!("Warning: image not found, skipping: {}", image); + return Ok(()); + }, + Err(RegistryError::Other(msg)) => return Err(msg), + }; - let mut new_tag = None; + let content_type = response.headers()["Content-Type"].to_str().unwrap(); + if !content_type.starts_with("application/json") { + return Err(format!("Unexpected Content-Type: {}", content_type)); + } - let mut url = format!("/v2/{}/tags/list", &image); - loop { - let mut response = match perform_registry_request(registry, &url, "application/vnd.oci.image.index.v1+json", auth) { - Ok(r) => r, - Err(RegistryError::NotFound) => { - println!("Warning: image not found, skipping: {}", image); - return Ok(()); - }, - Err(RegistryError::Other(msg)) => return Err(msg), - }; + let body = response.body_mut().read_to_string().unwrap(); + let body = body.parse::<tinyjson::JsonValue>().unwrap(); - let content_type = response.headers()["Content-Type"].to_str().unwrap(); - if !content_type.starts_with("application/json") { - return Err(format!("Unexpected Content-Type: {}", content_type)); - } + for it in body["tags"].get::<Vec<tinyjson::JsonValue>>().unwrap().iter() { + let candidate_str = it.get::<String>().unwrap(); - let body = response.body_mut().read_to_string().unwrap(); - let body = body.parse::<tinyjson::JsonValue>().unwrap(); + let _timer = metrics::get().db_query_duration.start_timer(); + db.execute(" + INSERT OR IGNORE INTO tags(image_id, tag, fetched_at) VALUES (?1, ?2, ?3) + ", (image_id, candidate_str, now)).unwrap(); + } - for it in body["tags"].get::<Vec<tinyjson::JsonValue>>().unwrap().iter() { - let candidate_str = it.get::<String>().unwrap(); + if let Some(link_header) = response.headers().get("link") { + let link_str = link_header.to_str().unwrap(); + let link = extract_next_page(&link_str).unwrap(); + url = link_str[link.next_uri.unwrap().clone()].to_string(); + } else { + break; + } + } - // db.execute(" - // INSERT INTO known_images(registry, image, tag, discovered) VALUES (?1, ?2, ?3, ?4) - // ", (registry, &file[img.image.clone()], candidate_str, chrono::offset::Utc::now())).unwrap(); + let _timer = metrics::get().db_query_duration.start_timer(); + db.execute("UPDATE images SET last_checked = ?1 WHERE id = ?2", (now, image_id)).unwrap(); + } - let candidate = VersionPattern::parse(candidate_str); - match current.compare(&candidate) { - CompareOutcome::Higher => { - new_tag = Some(candidate_str.clone()); - current = candidate; - }, - CompareOutcome::Lower => {}, - CompareOutcome::Incompatible => {}, - CompareOutcome::Identical => {}, - } + let tags: Vec<String> = { + let _timer = metrics::get().db_query_duration.start_timer(); + let mut stmt = db.prepare("SELECT tag FROM tags WHERE image_id = ?1").unwrap(); + let mut rows = stmt.query((image_id,)).unwrap(); + let mut tags = vec![]; + while let Some(row) = rows.next().unwrap() { + tags.push(row.get(0).unwrap()); } + tags + }; - if let Some(link_header) = response.headers().get("link") { - let link_str = link_header.to_str().unwrap(); - let link = extract_next_page(&link_str).unwrap(); - url = link_str[link.next_uri.unwrap().clone()].to_string(); - } else { - break; + let mut current = VersionPattern::parse(&tag_str); + let mut new_tag = None; + + for candidate_str in tags { + let candidate = VersionPattern::parse(&candidate_str); + match current.compare(&candidate) { + CompareOutcome::Higher => { + new_tag = Some(candidate_str); + current = candidate; + }, + CompareOutcome::Lower => {}, + CompareOutcome::Incompatible => {}, + CompareOutcome::Identical => {}, } } @@ -338,21 +362,44 @@ fn update_images(db: &Connection, file: &str, auth: &mut Auth, img: DockerRef, e } if let Some(ref digest) = img.digest { - // Find the digest for the selected tag - let url = format!("/v2/{}/manifests/{}", &file[img.image.clone()], tag.as_deref().unwrap_or("latest")); - let response = match perform_registry_request(registry, &url, "application/vnd.oci.image.manifest.v1+json,application/vnd.oci.image.index.v1+json", auth) { - Ok(r) => r, - Err(RegistryError::NotFound) => { - println!("Warning: image not found, skipping: {}", image); - return Ok(()); - }, - Err(RegistryError::Other(msg)) => return Err(msg), - }; + let tag_for_digest = tag.as_deref().unwrap_or("latest"); + + let mut digest_string = None; + if !cache_stale { + let _timer = metrics::get().db_query_duration.start_timer(); + digest_string = db.query_one(" + SELECT digest FROM tags WHERE image_id = ?1 AND tag = ?2 AND digest IS NOT NULL + ", (image_id, tag_for_digest), |row| row.get::<_, String>(0)).optional().unwrap(); + } - let digest_string = response.headers()["docker-content-digest"].to_str().unwrap().to_string(); + if digest_string.is_none() { + let url = format!("/v2/{}/manifests/{}", &file[img.image.clone()], tag_for_digest); + let response = match perform_registry_request(registry, &url, "application/vnd.oci.image.manifest.v1+json,application/vnd.oci.image.index.v1+json", auth) { + Ok(r) => r, + Err(RegistryError::NotFound) => { + println!("Warning: image not found, skipping: {}", image); + return Ok(()); + }, + Err(RegistryError::Other(msg)) => return Err(msg), + }; + + let fetched = response.headers()["docker-content-digest"].to_str().unwrap().to_string(); + + { + let _timer = metrics::get().db_query_duration.start_timer(); + db.execute(" + INSERT INTO tags(image_id, tag, digest, fetched_at) VALUES (?1, ?2, ?3, ?4) + ON CONFLICT(image_id, tag) DO UPDATE SET digest = ?3, fetched_at = ?4 + ", (image_id, tag_for_digest, &fetched, now)).unwrap(); + } + + digest_string = Some(fetched); + } + + let digest_string = digest_string.unwrap(); if file[digest.clone()] != digest_string { - edits.push(FilePatch{ + edits.push(FilePatch { position: img.digest.unwrap(), content: digest_string, }); @@ -441,7 +488,7 @@ fn is_dockerfile(path: &std::path::Path) -> bool { return name == "Dockerfile" || name == "dockerfile"; } } - return false + return false; } fn is_yaml(path: &std::path::Path) -> bool { @@ -450,7 +497,7 @@ fn is_yaml(path: &std::path::Path) -> bool { return ext == "yaml"; } } - return false + return false; } fn run_tool(db: &Connection, auth: &mut Auth, repos: &Vec<Repository>, mut infile_paths: Vec<std::path::PathBuf>, overwrite: bool) { @@ -526,7 +573,7 @@ fn run_tool(db: &Connection, auth: &mut Auth, repos: &Vec<Repository>, mut infil infile_paths.push(repo.dest.clone()); } - let mut inpaths = vec!(); + let mut inpaths = vec![]; for infile_path in infile_paths { if infile_path.is_dir() { let mut unsearched = vec![infile_path.clone()]; @@ -807,6 +854,25 @@ fn main() { db.execute("INSERT INTO migrations(id) VALUES (?1)", (5, )).unwrap(); } + + if newest_migration < 6 { + db.execute(" + CREATE TABLE tags ( + id INTEGER PRIMARY KEY, + image_id INTEGER NOT NULL REFERENCES images(id), + tag TEXT NOT NULL, + digest TEXT, + fetched_at DATETIME NOT NULL + ) + ", ()).unwrap(); + + db.execute(" + CREATE UNIQUE INDEX tags__image_id_tag + ON tags(image_id, tag) + ", ()).unwrap(); + + db.execute("INSERT INTO migrations(id) VALUES (?1)", (6, )).unwrap(); + } } let mut repos = vec![]; @@ -962,7 +1028,7 @@ fn main() { _ => panic!("Invalid config json"), } - repos.push(Repository{ + repos.push(Repository { url: k, key: Some(workdir.join(path.parent().unwrap().join(std::path::PathBuf::from(key_name)))), host: Some(workdir.join(path.parent().unwrap().join(std::path::PathBuf::from(host_name)))), diff --git a/src/metrics.rs b/src/metrics.rs index a6c8286..95a4782 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -99,7 +99,7 @@ impl Metrics { ).unwrap(); registry.register(Box::new(db_query_duration.clone())).unwrap(); - Self { + return Self { state, images_checked, images_updated, @@ -111,7 +111,7 @@ impl Metrics { git_operations, db_query_duration, registry, - } + }; } } |
